Sign in to follow this  
Followers 0
windghost91

Continous Movement for SDL 2 Joysticks?

39 posts in this topic

So, I am trying to implement controller motion in SDL 2 for continuous movement when I push on a controller direction. At this point in time, my code only works when I push on the controller once. When I get to the maximum movement, the movement on the screen stops. I can mitigate this by getting to the end of the range of motion more slowly when pushing on the stick, but that does not solve the issue.

Here is my code:

//more code before
 else if (e.type == SDL_JOYAXISMOTION) //handles joystick movement with collisions
           {
                if (e.jaxis.which == 0)
                {
                    if (e.jaxis.axis == 0)
                    {
                        if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                        {
                            if(base.x == 0) //base is of type SDL_Rect
                                base.x++;
                            base.x--;
                        }

                        else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                        {
                            if (base.x == 1024 - base.w)
                                base.x--;
                            base.x++;
                        }
                    }

                    else if (e.jaxis.axis == 1)
                    {
                        if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                        {
                            if (base.y == 0)
                                base.y++;
                            base.y--;
                        }

                        else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                        {
                            if (base.y == 768 - base.h)
                                base.y--;
                            base.y++;
                        }
                    }
                }
           }
//more code after

Thoughts? How can I get the controller to move the SDL_Rect as long as the joystick is held down?

0

Share this post


Link to post
Share on other sites

Posted (edited)

Your idea of SDL_JOYAXISMOTION is incorrect. Its wiki page says "An SDL_JOYAXISMOTION event occurs whenever a user moves an axis on the joystick."

(see https://wiki.libsdl.org/SDL_JoyAxisEvent )

 

This means, you get this event when the position/orientation of the joystick changes. Thus from "centered", if you push it forward, you get 1 event (theoretically, usually, the computer is faster, and you get several events with smaller movements instead). If you then keep the joystick in the same position, you don't get any event anymore, until you move it again.

(You can verify this by printing something from within your event-handling code, you should see that you only get output when you move the joystick, not when you keep it steady.)

 

To preserve movement, your event-handling code should modify velocity rather than position of the rectangle. Make a 'speed' variable, and change that with the events. In the main game-loop, update position from the speed.

Edited by Alberth
2

Share this post


Link to post
Share on other sites

Alright, cool. Thanks for the info. After coming back to this, here is what I have come up with:

//more code above
else if (e.type == SDL_JOYAXISMOTION)
           {
                if (e.jaxis.which == 0)//x axis
                {
                    if (e.jaxis.axis == 0)
                    {
                        if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                        {
                            pVelX = -1;
                            if (e.jaxis.value == 0)
                                pVelX = 0;
                        }

                        else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                        {
                            pVelX = 1;
                            if (e.jaxis.value == 0)
                                pVelX = 0;
                        }
                    }

                    else if (e.jaxis.axis == 1)//y axis
                    {
                        if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                        {
                            pVelY = -1;
                            if (e.jaxis.value == 0)
                                pVelY = 0;
                        }

                        else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                        {
                            pVelY = 1;
                            if (e.jaxis.value == 0)
                                pVelY = 0;
                        }
                    }
                }
           }
//more code below

//more code above
void World1::movePlayer()
{
    playerBase.x+= pVelX;
    if(playerBase.x < 0 || (playerBase.x + playerBase.w > 1024))
        playerBase.x-= pVelX;

    playerBase.y+= pVelY;
    if(playerBase.y < 0 || (playerBase.y +playerBase.h > 768))
        playerBase.y-= pVelY;
}//more code below
//more code above
//main loop within a class declaration
SDL_Event e;

    quit = false;

    this->loadPlayer(renderer);

    while (!quit)
    {
        this->handlePlayerEvents(e, quit);
        this->movePlayer();
        this->renderPlayer(renderer);
    }
//more code below

Currently, this code (with more) moves the player all the way to the end of the screen when pushing down the joystick, even if the joystick held down for a short time. Additionally, if I quickly slide my finger off of the controller in the direction that I have the stick pushed, the character will go in the opposite direction that I pushed, and the character will go all the way to the end of the screen. How can I improve this code to work as it should so that the character will only move a little bit when I barely push it down and so that it does not go in the opposite direction if I take my thumb off of the joystick too quickly (hope that makes sense)?

0

Share this post


Link to post
Share on other sites

 This looks fishy inside the inner if, at least.

if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
{
   // [deleted lines]
}
else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
{
    pVelX = 1;
    if (e.jaxis.value == 0)
        pVelX = 0;
}

Assuming CONTROLLER_DEAD_ZONE is a non-zero, positive value, please explain why "pVelX = 0" is ever reached. That is, I don't know any number that is bigger than non-zero, and 0 at the same time.

A second point is, what should happen between -CONTROLLER_DEAD_ZONE and +CONTROLLER_DEAD_ZONE? Currently you're not doing anything, pVelX stays what it was.

I don't know if the latter is intended (that is, it can be correct).

Your posts speak about desired changes relative to the code (ie "My code isn't doing it correct, I want X and Y solved"), rather than a statement of what you want to achieve (ie "I want the player to move when the joystick is pushed, and stop when it is centered"). The latter gives a clear goal, independent of what the code is doing or not doing (or even if you don't have any code yet!).

 

Likely you have such a goal in your mind (or you could not speak about desired changes), but for us it would help if you also make clear what you want as final result. We may be able to point out alternative directions to try, or point out other flaws in your code you haven't seen yet.

2

Share this post


Link to post
Share on other sites

Posted (edited)

if (e.jaxis.axis == 0)// x axis
                    {
                        if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                        {
                            pVelX = -1;
                        }

                        else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                        {
                            pVelX = 1;
                        }
                        else
                            pVelX = 0; //new code that works
//same idea for y axis

Many edits later: I have figured it out. I just needed to have an else statement, and I accidentally put the wrong variable name for the y axis. I increased the dead zone value a bit so that it is not so sensitive. I just wonder if I can slow down the movement a bit. It feels like my character is holding down the run button or something.

Edited by ghostk91
0

Share this post


Link to post
Share on other sites

Great that it works.

I just wonder if I can slow down the movement a bit.
One solution is to make the velocity and position a real number, so you can have a 0.8 velocity. The disadvantage is that you can't display an image halfway a pixel (unless you use the GPU), so you have to round or truncate the position to an integer before blitting the image of the player.

I would recommend you make a constant for the velocities (in X and Y direction). While you are at it, make a few constants for the screen size too. Having constants reduces effort if you ever want to change such a value. Instead of manually searching and replacing all values 1024 that are screen-width, just modify the constant, and you're done!

2

Share this post


Link to post
Share on other sites

Great idea about the constants.

If I understand you correctly, the units of the velocity are in pixels, then. That is why you can't have a decimal speed. How can I use the GPU? Does SDL 2 have functions for that? If so, what are they? Can you give me some links to tutorials (or perhaps books) that explain using SDL 2 with the GPU? Perhaps other libraries may be required?

0

Share this post


Link to post
Share on other sites

Posted (edited)

If I understand you correctly, the units of the velocity are in pixels, then.

Well, euhm, yeah, you coded it like that :)

A quick tour through your own code:

if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
{
    pVelX = -1;

Here you set speed to 1 unit (there are other places too, I just picked one at random).

playerBase.x+= pVelX;
if(playerBase.x < 0 || (playerBase.x + playerBase.w > 1024)) {
    playerBase.x-= pVelX;
}

Here you update position with 1 unit velocity, and then compare against a well-known horizontal screen-width. From this I concluded you work in pixel units.

[I added the curly brackets here. I used to program like you, leaving them out if you could, but I found it was too confusing and error-prone, so I switched to always including them.]

 

There is nothing wrong with using pixels as unit of position and velocity. The biggest problem is that you can't display a pixel at (for example) position 34.6, it has to be either 34 or 35, ie units, with SDL2 (without GPU, but I'll come to that later).

In your case, you feel the player character is running too fast. That can be solved in 3 ways

- Reduce speed to some fraction (what I proposed), by far the simplest.

- Enlarge the size of the playing area. Instead of 1024 we could use 2048 as width. It takes longer for the player character to reach the other side with the current speed. Unfortunately, that does not come with a new monitor that is, say, twice as wide and high. The answer to that is that you scale the bigger playing area back to your current screensize. However, 2048 wide at speed 1 is equivalent to 1024 at speed 0.5, so this is just another form of "reduce speed".

- Update the position of the player character less often. If you skip updating the position 50% of the time (ie call "movePlayer" once every 2 iterations), the player character moves 1 pixel every 2 iterations, instead of 1 pixel every iteration. It thus seems to move twice as slow. This solution is however not so flexible. It works well enough for simple cases like 50% or 75%, but imagine you want 7/12th speed, so update position every 7 out of 12 iterations. This gets horribly complicated, while changing speed to a fraction can manage the same thing easily.

 

 

That is why you can't have a decimal speed.

As you will find out, "cannot" hardly exists in software. If you want it badly enough, almost anything can be done. The normal breaking point is "it's a bad idea to want this", because you yourself won't be able to understand what happens, or why.

For fractional speed however, you're well within safety limits, it just takes a simple conversion.

int posX = ...;
int posY = ...;
int velX = ...; // Assign units
int velY = ...;
...
posX += velX;
posY += velY;
...
SDL_Rect dst_rect;
dst_rect.x = posX;
dst_rect.y = posY;
dst_rect.w = ...
dst_rect.h = ...
SDL_BlitSurface(this->image, nullptr, this->surface, &dst_rect);

Your current code likely mostly looks like this. You have an integer posX/Y, and an integer velX/Y, you update position with the velocity, and then blit the player character image to the window surface. As you can see, SDL_Rect is the data structure to give the position at the screen. Its definition is described at https://wiki.libsdl.org/SDL_Rect

As you can see, "x" and "y" of SDL_Rect are "int", SDL_BlitSurface can only handle unit positions, not fractions.

 

That's not a problem, we can still have floating point speeds and positions:

double posX = ...;
double posY = ...;
double velX = ...; // Assign fractions
double velY = ...;
...
posX += velX;
posY += velY;
...
SDL_Rect dst_rect;
dst_rect.x = posX; // <-- here, double to int conversion takes place, it rounds towards zero.
dst_rect.y = posY;
dst_rect.w = ...
dst_rect.h = ...
SDL_BlitSurface(this->image, nullptr, this->surface, &dst_rect);

I only changed the type of position and velocities. (There is also "float" as type. It is also a floating point, but it's smaller and computes slightly faster. neither is relevant for your application currently.) Not shown is that you should also assign a fraction to the speed, or it has no effect.

You can compile this, and it will work. Now you may wonder, what if posX == 34.6, what happens then? The answer is that the C++ compiler converts your double posX value to an SDL_Rect integer x coordinate by rounding towards zero. 34.6 becomes 34, -1.999 becomes -1. If you want 'normal' rounding instead, for positive numbers, add 0.5, ie "dst_rect.x = posX + 0.5;". 34.6 + 0.5 = 35.1, which rounded towards zero, becomes 35. For negative numbers, this trick likely fails (if you like math puzzles, it can be fun to figure out such things), but since your positions are non-negative anyway, it doesn't matter much.

 

 

 

How can I use the GPU? Does SDL 2 have functions for that?

The GPU is a graphics monster, It can draw filled triangles incredibly quickly. It can draw 'pixels' at every position, including at fractional positions. Your screen however cannot display such pixels directly, so the GPU uses a trick known as anti-aliasing to do it. Basically, it splits your pixel at 34.6 in two parts, and draws 0.4 part of it at position 34, and 0.6 part of it at position 35. Our eyes and brain then merge both pixels again, and we perceive it as a single pixel again.

SDL2 does have GPU support, and I think it already uses the GPU internally, for its better performance. I started looking at how to use the GPU with SDL2 myself a few days ago, and while I found a tutorial for it ( http://lazyfoo.net/tutorials/SDL/index.php lesson 7 and further), I am not so happy with it, and decided not to use it myself.

For you, I'd advice to stick with SDL2 as you have it now. Given the kind of questions and problems you post here, switching to a GPU is a not-smart move. You'll get loads of additional problems, and no useful benefit from it, since you can easily use fractional positions and speeds with your current program, as I showed above.

Instead, try to slow the character down a bit, then extend the program so it becomes a fun game :)

Edited by Alberth
2

Share this post


Link to post
Share on other sites

Thanks for all the advice. You have been very helpful.

Alright, so it looks like I should change the constants in the class defintion like I did below:

//code excerpt
private:

        SDL_Texture* pTexture;
        SDL_Rect playerBase;

        double pVelX;
        double pVelY;
        double pPosX;
        double pPosY;

        bool quit;

        static constexpr double PLAYER_VELOCITY_X = 0.8;
        static constexpr double PLAYER_VELOCITY_Y = 0.8;
        static const int CONTROLLER_DEAD_ZONE = 20000; //dead zone for controller

Now, the code does not work yet. Perhaps I should change something in the controller function?

else if (e.type == SDL_JOYAXISMOTION)
    {
        if (e.jaxis.which == 0)//controller 0
        {
            if (e.jaxis.axis == 0)// x axis
            {
                if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                    pVelX = -PLAYER_VELOCITY_X;
                else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                    pVelX = PLAYER_VELOCITY_X;
                else
                    pVelX = 0;
            }

            else if (e.jaxis.axis == 1)//y axis
            {
                if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                    pVelY = -PLAYER_VELOCITY_Y;
                else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                    pVelY = PLAYER_VELOCITY_Y;
                else
                    pVelY = 0;
            }
        }
    }

Or the move function?

void World1::movePlayer()
{
    playerBase.x+= pVelX;
    if (playerBase.x < 0 || (playerBase.x + playerBase.w > 1024))
        playerBase.x-= pVelX;

    playerBase.y+= pVelY;
    if (playerBase.y < 0 || (playerBase.y + playerBase.h > 768))
        playerBase.y-= pVelY;
}

Here is the render function that I am using too, with the function to load player.

//renders player to screen
void World1::renderPlayer(SDL_Renderer*& renderer)
{
    SDL_RenderClear(renderer); //clears screen
    SDL_RenderCopy(renderer, pTexture, NULL, &playerBase);
    SDL_RenderPresent(renderer); //puts image on screen
}
//loads player
void World1::loadPlayer(SDL_Renderer*& renderer)
{
    pTexture = IMG_LoadTexture(renderer, "male_base-test-anim.gif");

    if (pTexture == 0)
    {
        cout << "Unable to load texture.";
    }

    playerBase.x = 0;
    playerBase.y = 0;
    playerBase.w = 948;
    playerBase.h = 335;

}

Given how I have my main loop, hopefully I do not need to dramatically alter anything:

//main loop of program
void World1::mainLoop(SDL_Renderer*& renderer)
{
    SDL_Event e;

    quit = false;

    this->loadPlayer(renderer);

    while (!quit)
    {
        this->handlePlayerEvents(e, quit);
        this->movePlayer();
        this->renderPlayer(renderer);
    }
}

You think I should go ahead and use a position variable in the move function, maybe?

0

Share this post


Link to post
Share on other sites

Posted (edited)

Now, the code does not work yet.

| have coffee.

 

The probem with statements like that is they give absolutely no information about context. The only conclusion of "does not work" that I can draw is "it does not work", period. The same as with my statement, you have no idea what I want with coffee, especially since I already seem to have it.

To give you any kind of direction, I need to understand how it does not work. Did you turn on the computer? And the screen? Did you save the file after compiling? Does it run at all? Did you plug in the joystick? Does it crash if you turn the joystick to the left? Does the player character move?

 

All these, and more, are forms of "does not work". Without you doing some pre-selection work, I can only do random guessing. I don't want to do that.

Please tell what happens, under which conditions. If it deviates from your expectations, tell what you expect to happen. Also explain what it does instead, and why that is a problem or unwanted.

 

Edit: What's the type of the "x" and "y" coordinate of playerBase? If it is "int" what do you think will happen if you add 0.8, and you remember from my previous post that assignment of (x+0.8) is truncated?

Edited by Alberth
2

Share this post


Link to post
Share on other sites

Ok yeah, I keep doing that. You can't read my mind or anything. I have to say why. Thanks for the feedback.

It was not moving at first, but I was able to get it to work. Thank you for the reminder about the int problem, my mind just did not make the connection for some reason! It's a bit choppy looking as it is moving across the screen though. 

I've actually gone by those SDL tutorials for a little while that you mentioned when talking about the GPU to render non integral types to the screen. I notice that alot of the graphics tutorials are further down than where I am now. Perhaps I can improve the choppiness in a little while, unless my mind is not thinking creatively enough and I know enough (I passed the 30th tutorial a little while ago). 

0

Share this post


Link to post
Share on other sites

I would recommend storing your positions, velocities, etc. as float. When it's time to actually draw stuff with SDL, use the float position coordinates, just cast them to int when SDL requires the arguments to be int.

1

Share this post


Link to post
Share on other sites

I would recommend storing your positions, velocities, etc. as float. When it's time to actually draw stuff with SDL, use the float position coordinates, just cast them to int when SDL requires the arguments to be int.

Convert with static cast?

0

Share this post


Link to post
Share on other sites

Convert with static cast?

Yes. I don't remember the syntax for drawing in SDL right now so this is just pseudo-code, but basically something like:

SDL_Draw(texture, static_cast<int>(posX), static_cast<int>(posY)); 
1

Share this post


Link to post
Share on other sites

Posted (edited)

I am guessing that you are recommending float for memory purposes? It's half as large, so I like your suggestion since I know it's good to not use up memory if it is not needed. The screen will not be bigger in pixel size than what float can hold, so float is adequate in this case.

I just use SDL_RenderCopy and it passes in the whole rectangle as shown in the above code, so I cannot convert when rendering. I can convert before rendering during the move function, but I cannot pass arguments like that with the way that I have things coded. I just wonder why the rectangle on the screen disappeared when changing my position and velocity variables and my velocity constants to float. It all worked fine with double.

Edited by ghostk91
0

Share this post


Link to post
Share on other sites

I am guessing that you are recommending float for memory purposes?

No, this was in relation to Alberth's last post edit regarding the type of playerBase.x and .y being an int. Sorry, I should have specified better.

You can cast when you're going to render -- just create an SDL_Rect before rendering, and plonk the values in at that point (casting from doubles/floats). Either create your own float/double rect struct/class, or store the values separately (which you update when moving the player, etc.), then convert it to something SDL understands for drawing only.

Doubles and floats should both work. I'm not sure you need the extra precision doubles offer, but I also don't think you need to worry about it taking twice the memory. If you're having memory issues, it won't be related to a few bytes here and there, but more large-scale problems. Use whichever you feel like, with doubles of course being more precise.

0

Share this post


Link to post
Share on other sites

Posted (edited)

What is now the position of the player? You seem to have both pPosX/pPosY and playerBase.x/playerBase.y .

Unless there is some additional step somewhere, use one of them for the player position. Delete the other set, it's just confusing the issue.

So, I am trying to get things to work that way now. My solution looked something like this:

void World1::movePlayer()
{
    playerBase.x+= static_cast<int>(pVelX);
    if (pPosX < 0 || (pPosX + playerBase.w > window_width))
        playerBase.x-= static_cast<int>(pVelX);
        
    playerBase.y+= static_cast<int>(pVelY);
    if (pPosY < 0 || (pPosY + playerBase.h > window_height))
        playerBase.y-= static_cast<int>(pVelY);
} 

However, the character position does not update on the screen. It stays at its original position when it is loaded. Why could this be, you think?

Edited by windghost91
0

Share this post


Link to post
Share on other sites

However, the character position does not update on the screen. It stays at its original position when it is loaded. Why could this be, you think?

There is something wrong in your program.

 

The trouble with computers is that they do what you wrote in the program. In particular, that may be different from what you think you wrote.

In other words, your ideas of how the program works may be different from what you actually told the computer to do.

In this case, you even have proof of that. It is "not working".

 

The next step is to find out why, and that starts with finding where. If you programmed exactly what you thought you programmed, then the computer would do what you thought it would do. It doesn't, so there is a point in the program where it doesn't behave like you think. Values of variables don't change as you think.

The process of finding that point is called "debugging". A simple way to get a general picture is to add 'print' statements. (It's called 'print-debugging' for obvious reasons :) )

Here, your player position is not changing. So add a 'print' statement to output the player position each frame. Being very old-school, I still use printf-style coding here, eg

printf("playerBaseXY=%3.1f, %3.1f\n", playerBase.x, playerBase.y);

It needs a "#include <cstdio>" near the top if you don't have that already. Then compile, and run the program.

 

Now as the program runs, you get a stream of lines from the program as well. Check the general value. Is it what you expect? Try to move the player. Does the output change as you expect?

If the answer is "yes", then you know that where you added the "printf", the code works like you think it should. The problem is thus in a next step in the computation. Find the code that uses the working variable (playerBase in my example), to compute some new variable that is related to player positioning, and repeat the trick. ("find the code" usually works best by searching for the variable "playerBase" in this example, which will simply return all spots in the code where the variable is either assigned, or it is used to derive other values.)

If the answer is "no", then you concluded that "playerBase" value is messed up. The problem must thus be earlier. Somewhere in the code, you use other data that causes playerBase to change (or rather, not to change, probably). Here, it looks like pVelX and pVelY would be one cause. If playerBase is not working, are those variables working as expected? Repeat the trick, and check the output.

In this way, you can sort-of "walk" backward or forward through all the steps that do some computation step in deciding the player position. I added a unique string prefix to the printf, so you can have several of these lines active at the same time, and still understand all the numbers that are produced.

On the other hand, it quickly becomes too much, so comment or remove such statements after you found they are not of interest in finding the problem.

 

A second form of debugging is to use a tool called "debugger". Some IDEs have one built-in, with other systems it is supplied separately. I fully recommend playing with that too, as it's a very powerful tool, where you can walk through code step-by-step. This is not simple to do in above 'printf-debugging'.

The general process with a debugger is to find a line in the code where the interesting stuff starts, and you set a breakpoint. Then run the program in the debugger. When the program passes the breakpoint, it stops, and you get control what to do next. You can print variables, change variables, execute a line of code, run again until it hits the next breakpoint, etc etc.

In your case, you'd set a breakpoint where you read player-movement from the input. Then you can step through the code, seeing each statement being run or skipped (if the "if" around it doesn't hold), after each assignment you can print the values of the variables, and thus trace how a change at the input propagates to the displayed player position (or in your case, fails to propagate somewhere).

2

Share this post


Link to post
Share on other sites

It feels like this is the same problem as before, which we've tried to explain is due to you using SDL_Rect (which uses ints internally) to store and manage positions. Unless your velocities are really high, this will not work as you expect. If your velocities are less than 1 (e.g. 0.85f), casting them to int will be the same as adding 0.

You only want to deal with ints when it comes to rendering, not when it comes to storing and managing positions/velocities.

  1. Delete your playerBase variable.
  2. Use floats to store and update the positions. Either 2 floats (e.g. float positionX & float positionY), or create your own container (e.g. a Vector2 struct/class). I would recommend the 2 floats initially, then swapping to a Vector2 class when that works.
  3. Use floats to deal with movement speed (same as above). It seems like this is already the case.
  4. (You might also need to store width/height somewhere, storing those as ints is fine.)
  5. When updating movement, use these float values to compute the updated position. positionX += velocityX;
  6. When rendering, create a new SDL_Rect at that time, and populate it with static_cast<int> versions of the float positions you have. Pass that to the relevant SDL render call.

You might also want to check a int-casted version of the positions when checking whether you move out of the window or not.

 

Later on you'll probably also want to include delta time in the movement, but for now just sort out the float/int issues and get your player moving.

1

Share this post


Link to post
Share on other sites

Posted (edited)

Alberth, thanks for the debugging tips. I could definitely use more pointers with that.

Lactose, I like your suggestion of changing things at rendering time and not mixed with updating the positions. The code feels cleaner and more separate like it should (if that makes any sense).

 

Edit: Now the controller support does not work. The character does not move when I use the controller when the controller is connected.

//renders player to screen
void World1::renderPlayer(SDL_Renderer*& renderer)
{
    //prepare postions for rendering
    playerBase.x = static_cast<int>(pPosX);
    playerBase.y = static_cast<int>(pPosY);

    //render
    SDL_RenderClear(renderer); //clears screen
    SDL_RenderCopy(renderer, pTexture, nullptr, &playerBase);
    SDL_RenderPresent(renderer); //puts image on screen
}
void World1::movePlayer()
{
    pPosX += pVelX;
    if (pPosX < 0 || (pPosX + playerBase.w > window_width))
        pPosX -= pVelX;
    

    pPosY += pVelY;
    if (pPosY < 0 || (pPosY + playerBase.h > window_height))
        pPosY -= pVelY;
}

Success! As far as the keyboard movement goes, anyways (which I did before this topic). Now the controller does not move. It's like one thing works and something else pops out.

    //controller movement support
    else if (e.type == SDL_JOYAXISMOTION)
    {
        if (e.jaxis.which == 0)//controller 0
        {
            if (e.jaxis.axis == 0)// x axis
            {
                if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                    pVelX = -PLAYER_VELOCITY_X;
                else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                    pVelX = PLAYER_VELOCITY_X;
                else
                    pVelX = 0;
            }

            else if (e.jaxis.axis == 1)//y axis
            {
                if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                    pVelY = -PLAYER_VELOCITY_Y;
                else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                    pVelY = PLAYER_VELOCITY_Y;
                else
                    pVelY = 0;
            }
        }
    }

I printed out playerBase's coordinates to the console via cout, and they just stayed the same. I hope there is not something silly that I am missing.

Edited by windghost91
0

Share this post


Link to post
Share on other sites

I printed out playerBase's coordinates to the console via cout, and they just stayed the same.
So the variable is messed up, and the problem is earlier.

I assume that playerBase is assigned with the lines in "World1::renderPlayer" ? If so, is pPos[XY] changing? Is World1::renderPlayer executed? (having assignments in code that never runs also tends to not do much :) )

Murphy tends to do things you don't expect, like having more than one bug, or you changing a line that was crucial for some other part of the code, code that is never executed, or something else.

 

In this case, you seem to have both pPos[xy] variables and playerBase.[xy] variables. Can you explain how they are related?

1

Share this post


Link to post
Share on other sites

Posted (edited)

pPosX and pPosY change in the movePlayer function. renderPlayer executes in the main loop after the movePlayer function. All the code executes.

I added pPosX and pPosY because I changed the velocity to a decimal and playerBase cannot be a decimal due to the values being pixels which are only whole numbers.

I'm not sure why you are asking those questions or if you are trying to hint something, but I think it is strange how only the controller part of the code does not work at this point, yet I see nothing wrong with the controller code. The keyboard movement with the arrow keys works fine, but trying to move with a controller does nothing and the rectangle does not move on the screen. The problem is still unclear.

Relevant code so far:

void World1::movePlayerEvent(SDL_Event& e) //function is called inside handlePlayerEvents
{
    //keyboard movement support
    if (e.type == SDL_KEYDOWN)
    {
        switch(e.key.keysym.sym)
        {
            case SDLK_UP:
                if (this->getPlayerRectPosY() == 0)
                    pVelY = PLAYER_VELOCITY_Y;
                pVelY = -PLAYER_VELOCITY_Y; break;
            case SDLK_DOWN:
                if (this->getPlayerRectPosY() == window_height - this->getPlayerRectH())
                    pVelY = -PLAYER_VELOCITY_Y;
                pVelY = PLAYER_VELOCITY_Y; break;
            case SDLK_LEFT:
                if (this->getPlayerRectPosX() == 0)
                    pVelX = PLAYER_VELOCITY_X;
                pVelX = -PLAYER_VELOCITY_X; break;
            case SDLK_RIGHT:
                if (this->getPlayerRectPosX() == window_width - this->getPlayerRectW())
                    pVelX = -PLAYER_VELOCITY_X;
                pVelX = PLAYER_VELOCITY_X; break;
            default:
                break;
        }
    }

    //keyboard movement support continued
    else if (e.type = SDL_KEYUP)
    {
        switch(e.key.keysym.sym)
        {
            case SDLK_UP: case SDLK_DOWN:
                pVelY = 0; break;
            case SDLK_LEFT: case SDLK_RIGHT:
                pVelX = 0; break;
            default:
                break;
        }
    }

    //controller movement support
    else if (e.type == SDL_JOYAXISMOTION)
    {
        if (e.jaxis.which == 0)//controller 0
        {
            if (e.jaxis.axis == 0)// x axis
            {
                if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                    pVelX = -PLAYER_VELOCITY_X;
                else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                    pVelX = PLAYER_VELOCITY_X;
                else
                    pVelX = 0;
            }

            else if (e.jaxis.axis == 1)//y axis
            {
                if (e.jaxis.value < -CONTROLLER_DEAD_ZONE)
                    pVelY = -PLAYER_VELOCITY_Y;
                else if (e.jaxis.value > CONTROLLER_DEAD_ZONE)
                    pVelY = PLAYER_VELOCITY_Y;
                else
                    pVelY = 0;
            }
        }
    }
}
//moves player
void World1::movePlayer()
{
    //update x and collision check
    pPosX += pVelX;
    if (pPosX < 0 || (pPosX + playerBase.w > window_width))
        pPosX -= pVelX;

    //update y and collision check
    pPosY += pVelY;
    if (pPosY < 0 || (pPosY + playerBase.h > window_height))
        pPosY -= pVelY;
}
void World1::renderPlayer(SDL_Renderer*& renderer)
{
    //prepare postions for rendering
    playerBase.x = static_cast<int>(pPosX);
    playerBase.y = static_cast<int>(pPosY);

    //render
    SDL_RenderClear(renderer); //clears screen
    SDL_RenderCopy(renderer, pTexture, nullptr, &playerBase);
    SDL_RenderPresent(renderer); //puts image on screen
}
void World1::mainLoop(SDL_Renderer*& renderer)
{
    SDL_Event e;

    quit = false;

    this->loadPlayer(renderer);

    while (!quit)
    {
        this->handlePlayerEvents(e, quit);
        this->movePlayer();
        this->renderPlayer(renderer);
    }

}
Edited by windghost91
0

Share this post


Link to post
Share on other sites

Posted (edited)

I'm not sure why you are asking those questions or if you are trying to hint something,

I am only trying to help you, I don't have more information.

I don't have your program in my head, so reasoning what might be wrong is difficult to say the least.

The best I can do is explain how to use debugging tactics to find the spot.

 

 

 

but I think it is strange how only the controller part of the code does not work at this point, yet I see nothing wrong with the controller code.

It's not strange, the problem is just very cleverly hiding. I agree that the code looks alright, but you have proof it fails, so "looks ok" is a false conclusion, even though currently, we both don't know why.

In cases like this, trust the result when you execute things. If it fails, something is wrong, no matter what your eyes or code seems to say. Bugs are often very small, just a wrong number, or a reversed test, or a missing semi-colon. Bugs have a lot of code lines to hide in, and our reading is very error-prone, especially for text you have read a few times already. (We read text mostly by the shape of the words, we don't really read all letters, for example.)

 

The debugging tactic however, will still work. Also, you have a nicely reproducible case (it fails every time), so it's simple enough to run tests to check some values.

Printf-style debugging would work like

- Verify playerBase.x and playerBase.y don't change (just before rendering). This will likely hold. While you're at it, you may want to print pPosX and pPosY too there, and they probably don't  change either.

If this holds, the problem is not in SDL_RenderCopy, but earlier.

 

Assuming this is the case, print values of pPosX and pPosy in "movePlayer". Again, since they heavily depend on pVelX and pVelY, you may want to print those too.

I am not sure what the result will be, my guess is that pPosX and pPosY don't change, which would mean pVelX and pVelY are both 0

If that is the case, the move code is working alright (you want pPosX and pPosY to stay where they are if their velocities are 0), so the problem is earlier.

Following your code, this leads to the "movePlayerEvent" function, where pVelX and pVelY are decided. Ignore the keyboard stuff, as you know that works. The question is thus, what are the values of pVelX and pVelY, from the joystick code?

Assuming you find the pVelX and pVelY are both 0 there, it is likely the code takes the "else ... = 0;" path there (you can verify by adding a 'printf' line in that else block (don't forget the curly brackets!) ).

Assuming you find that, the problem is thus earlier. That leads to the values of e.jaxis.which, e.jaxis.axis, e.jaxis.value, CONTROLLER_DEAD_ZONE, and PLAYER_VELOCITY_X and PLAYER_VELOCITY_Y.

print them, and check that the right 'if' case is selected, and that a useful velocity is assigned.

"if" statements also decide the value of an assignment, and you can print them like any other variable, and verify correctness.

 

I hope you find the bug this way.

Edited by Alberth
2

Share this post


Link to post
Share on other sites

Posted (edited)

For the keyboard input, I had it print out "hello" for SDL_KEYDOWN and "hi" for SDL_KEYUP whenever I pushed down and released an arrow key to see if the code went into these else if branches in movePlayerEvent.

I did this after I tried the same approach with SDL_JOYAXISMOTION. I turned on the controller so that the "no controllers detected" code would not execute in my init function (completely outside the world class in main). So, I know that the controller turned on. The code is not going into the else if branch for JOYAXISMOTION. After pushing on the joystick, the following code, if statement and all, never executed:

else if (e.type == SDL_JOYAXISMOTION)
{
    cout << "JOYAXISMOTION: " << e.type;
//rest of code below  did not execute either
//...
}

It worked just fine with SDL_KEYDOWN and SDL_KEYUP, and I even found that else if (e.type == SDL_KEYUP) was constantly executing. This looked fishy. So, I commented out keyboard support entirely after finding out that SDL_JOYAXISMOTION was not executing. Guess what happened next? Controller supported worked! This is telling because I added SDL_KEYUP just recently after changing up the keyboard support. So, now I know that SDL_KEYUP is the problem and that it executes when no buttons on the keyboard are pressed.

I'm not sure why this affects SDL_JOYAXISMOTION, since it seems like control flow could just go to the next else if statement, which is the controller, even if SDL_KEYUP was constantly executing. Just commenting out SDL_KEYUP also caused it to not work after changing SDL_KEYDOWN to work with the newly implemented SDL_KEYUP (instead of  having playerbase.x++ and playerBase.y++, I changed it to work with velocities instead just like the controller). Not sure how to change keyboard support to work with controller support now that I have changed keyboard support to work with velocity, but I will have to figure all this out more tomorrow. Alberth/anyone, feel free to post thoughts so far between now and tomorrow when I get back to this (later tonight really since it's past 12AM now where I live).

Edited by windghost91
0

Share this post


Link to post
Share on other sites
cout << "JOYAXISMOTION: " << e.type;

Be careful here, stdout is often line-based, which means output won't show up at your screen until you end the line by printing a \n or std::endl (iirc, it's a long time ago that I last used << for printing).

 

SDL_KEYUP shouldn't trigger for a joystick, I think, unless there is some joystick-to-keyboard mapping going on somewhere???

I don't have time to look at your code in more detail, maybe you should make a new test program that only detects events, and prints them. That gives room for testing how events work, without the added stuff of the game getting in the way.

1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0