Jump to content

View more

Image of the Day

Adding some finishing touches...
Follow us for more
#screenshotsaturday #indiedev... by #MakeGoodGames https://t.co/Otbwywbm3a
IOTD | Top Screenshots

The latest, straight to your Inbox.

Subscribe to GameDev.net Direct to receive the latest updates and exclusive content.


Sign up now

Continous Movement for SDL 2 Joysticks?

4: Adsense
  • You cannot reply to this topic
29 replies to this topic

#1 windghost91   Members   

141
Like
0Likes
Like

Posted 24 March 2017 - 11:13 PM

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?



#2 Alberth   Members   

9165
Like
2Likes
Like

Posted 24 March 2017 - 11:55 PM

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, 24 March 2017 - 11:56 PM.


#3 windghost91   Members   

141
Like
0Likes
Like

Posted 14 April 2017 - 11:15 PM

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)?



#4 Alberth   Members   

9165
Like
2Likes
Like

Posted 15 April 2017 - 03:12 AM

 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.



#5 windghost91   Members   

141
Like
0Likes
Like

Posted 15 April 2017 - 06:24 PM

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, 15 April 2017 - 09:20 PM.


#6 Alberth   Members   

9165
Like
2Likes
Like

Posted 16 April 2017 - 12:09 AM

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!



#7 windghost91   Members   

141
Like
0Likes
Like

Posted 16 April 2017 - 03:18 PM

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?



#8 Alberth   Members   

9165
Like
2Likes
Like

Posted 17 April 2017 - 12:51 AM

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, 17 April 2017 - 12:58 AM.


#9 windghost91   Members   

141
Like
0Likes
Like

Posted 21 April 2017 - 11:56 PM

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?



#10 Alberth   Members   

9165
Like
2Likes
Like

Posted 22 April 2017 - 04:49 AM

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, 22 April 2017 - 04:53 AM.


#11 windghost91   Members   

141
Like
0Likes
Like

Posted 22 April 2017 - 04:26 PM

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). 



#12 Lactose   GDNet+   

11053
Like
1Likes
Like

Posted 22 April 2017 - 04:33 PM

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.


Hello to all my stalkers.

#13 windghost91   Members   

141
Like
0Likes
Like

Posted 22 April 2017 - 05:24 PM

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?



#14 Lactose   GDNet+   

11053
Like
1Likes
Like

Posted 22 April 2017 - 06:24 PM

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)); 

Hello to all my stalkers.

#15 windghost91   Members   

141
Like
0Likes
Like

Posted 22 April 2017 - 11:12 PM

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, 22 April 2017 - 11:36 PM.


#16 Lactose   GDNet+   

11053
Like
1Likes
Like

Posted 23 April 2017 - 04:52 AM

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.


Hello to all my stalkers.

#17 windghost91   Members   

141
Like
0Likes
Like

Posted 24 April 2017 - 08:05 PM

Lactose, let me be sure I understand what you are saying. You are saying to work with float, and then convert to int when it is time to update the position of the rectangle on the screen? If I understand you, you mean something like:

float pVelX; //declare necessary variables and constants
float pVelY;
float pPosX;
float pPosY;
  
static constexpr float PLAYER_VELOCITY_X = 0.5;
static constexpr float PLAYER_VELOCITY_Y = 0.5;
void World1::movePlayer() // then convert to int when updating SDL_Rect?
{
    pPosX+= pVelX;
    if (pPosX < 0 || (pPosX + playerBase.w > 1024))
        pPosX-= pVelX;
    playerBase.x = static_cast<int>(pPosX); //like this?

    pPosY+= pVelY;
    if (pPosY < 0 || (pPosY + playerBase.h > 768))
        pPosY-= pVelY;
    playerBase.y = static_cast<int>(pPosY); //like this?
} 

The code as is causes the rectangle to disappear from the screen. Why is this happening, and how can I fix it?


Edited by windghost91, 24 April 2017 - 08:31 PM.


#18 Alberth   Members   

9165
Like
2Likes
Like

Posted 24 April 2017 - 10:37 PM

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.

Both the player position and the player speed should be floating point numbers, so the player can actually be in-between two pixels, and can have a speed of 0.8 (or whatever you set its value to).

 

The float -> int conversion is done while copying the player position into the drawing coordinates for SDL. The player position variables are thus always exactly where the player is supposed to be, the coordinates where you draw the character are sometimes rounded/truncated to integers.

 

I don't know why your character is not drawn, common sources of errors are drawing off-screen, or drawing with 0 width or 0 height (it's hard to see something that has no height or width). Simplest solution is to print the numbers you store into the destination rectangle of SDL after you copied the numbers, and check whether they make sense.

 

Edit: As for why the player currently disappears, a random guess:

You copy pPos into playerBase (eg playerBase.x = static_cast<int>(pPosX); //like this? ), what value did you initialize pPosX to? Same question for pPosY, of course.


Edited by Alberth, 24 April 2017 - 10:59 PM.


#19 Lactose   GDNet+   

11053
Like
1Likes
Like

Posted 25 April 2017 - 04:40 AM

Lactose, let me be sure I understand what you are saying. You are saying to work with float, and then convert to int when it is time to update the position of the rectangle on the screen? If I understand you, you mean something like:

No. Something like (just quick example, let me know if more information is required) -- only doing x for brevity:

//Variables
float playerPositionX;
float playerVelocityX; //Pixels per second

//Updating
playerPositionX += playerVelocityX * deltaTime; //Delta time in seconds

//Drawing
SDL_Rect drawRect;
drawRect.x = static_cast<int> playerPositionX;
//Set the other variables as well...
SDL_RenderCopy(/* whatever else is needed */ &drawRect);

Hello to all my stalkers.

#20 windghost91   Members   

141
Like
0Likes
Like

Posted 25 April 2017 - 07:20 PM

Ok. Thank you all for your suggestions. It worked to initialize pPosX and pPosY to 0 when loading the texture (in a different function), though I have no idea why using float instead of double for the position and velocity caused things to disappear unless I forgot that I changed something else.