Lazy Foo' Productions

SDL Forums external link SDL Tutorials 🐰SDL3 Tutorials🥚 Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Motion

Motion screenshot

Last Updated: Oct 19th, 2024

Now that we can have a consistent frame rate, we can start moving things around the screen.
class Dot
{
    public:
        //The dimensions of the dot
        static constexpr int kDotWidth = 20;
        static constexpr int kDotHeight = 20;

        //Maximum axis velocity of the dot
        static constexpr int kDotVel = 10;

        //Initializes the variables
        Dot();

        //Takes key presses and adjusts the dot's velocity
        void handleEvent( SDL_Event& e );

        //Moves the dot
        void move();

        //Shows the dot on the screen
        void render();

    private:
        //The X and Y offsets of the dot
        int mPosX, mPosY;

        //The velocity of the dot
        int mVelX, mVelY;
};
Here is the class for the dot we will be moving around the screen. First we have some constants the size of the dot and it velocity. Then we have functions to construct the dot, handle input, move it, and render it. For variables, we have an x/y position and an x/y velocity.
//Dot Implementation
Dot::Dot():
    mPosX{ 0 },
    mPosY{ 0 },
    mVelX{ 0 },
    mVelY{ 0 }
{

}
Our constructor does basic variable initialization.
void Dot::handleEvent( SDL_Event& e )
{
    //If a key was pressed
    if( e.type == SDL_EVENT_KEY_DOWN && e.key.repeat == 0 )
    {
        //Adjust the velocity
        switch( e.key.key )
        {
            case SDLK_UP: mVelY -= kDotVel; break;
            case SDLK_DOWN: mVelY += kDotVel; break;
            case SDLK_LEFT: mVelX -= kDotVel; break;
            case SDLK_RIGHT: mVelX += kDotVel; break;
        }
    }
    //If a key was released
    else if( e.type == SDL_EVENT_KEY_UP && e.key.repeat == 0 )
    {
        //Adjust the velocity
        switch( e.key.key )
        {
            case SDLK_UP: mVelY += kDotVel; break;
            case SDLK_DOWN: mVelY -= kDotVel; break;
            case SDLK_LEFT: mVelX += kDotVel; break;
            case SDLK_RIGHT: mVelX -= kDotVel; break;
        }
    }
}
Our event handler adjusts the dots velocity when the user presses or releases keys. We check to make sure to only handle the first key press (when repeat equals 0) in case the user has key repeat on.

Now you may be wondering why we just aren't incrementing the position every time the user presses a key. If you were to move the x position every time the user pressed the key, you would have to repeatedly press the key to keep the dot moving. This is why we use velocity instead.

Velocity is the speed and direction of the movement. A positive x velocity moves right, a negative x velocity moves left, and a negative/positive y velocity moves up/down (remember that -y is up in SDL). So when we press right, the dot's velocity becomes positive 10, and it will move 10 pixels every frame until right key is released and the velocity returns to 0.
void Dot::move()
{
    //Move the dot left or right
    mPosX += mVelX;

    //If the dot went too far to the left or right
    if( ( mPosX < 0 ) || ( mPosX + kDotWidth > kScreenWidth ) )
    {
        //Move back
        mPosX -= mVelX;
    }

    //Move the dot up or down
    mPosY += mVelY;

    //If the dot went too far up or down
    if( ( mPosY < 0 ) || ( mPosY + kDotHeight > kScreenHeight ) )
    {
        //Move back
        mPosY -= mVelY;
    }
}
When we move our dot, we apply the velocity to the position, then we check if the dot hit the edge of screen. If it did, we undo the motion. We do this for the x axis first, then the y axis.

This method of collision response is a bit janky, but since the dot only moves in increments of 10 and the screen is 640 by 480 (both multiples of 10), it will work.
void Dot::render()
{
    //Show the dot
    gDotTexture.render( static_cast<float>( mPosX ), static_cast<float>( mPosY ) );
}
And in the rendering function we just draw the dot texture.
            //The quit flag
            bool quit{ false };

            //The event data
            SDL_Event e;
            SDL_zero( e );

            //Timer to cap frame rate
            LTimer capTimer;

            //Dot we will be moving around on the screen
            Dot dot;
Before we go into the main loop, we declare the frame rate regulating timer and the dot.
            //The main loop
            while( quit == false )
            {
                //Start frame time
                capTimer.start();

                //Get event data
                while( SDL_PollEvent( &e ) )
                {
                    //If event is quit type
                    if( e.type == SDL_EVENT_QUIT )
                    {
                        //End the main loop
                        quit = true;
                    }

                    //Process dot events
                    dot.handleEvent( e );
                }
                
                //Update dot
                dot.move();

                //Fill the background
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF,  0xFF );
                SDL_RenderClear( gRenderer );

                //Render dot
                dot.render();

                //Update screen
                SDL_RenderPresent( gRenderer );

                //Cap frame rate
                constexpr Uint64 nsPerFrame = 1000000000 / kScreenFps; 
                Uint64 frameNs = capTimer.getTicksNS();
                if( frameNs < nsPerFrame )
                {
                    SDL_DelayNS( nsPerFrame - frameNs );
                }
            }
Here we are inserting our dot into the main loop. We put the event handler in the event loop, the updater right before rendering, and the rendering function in the rendering portion of the main loop.

As you can also see, we're capping the frame rate to get a consistent frame rate which you need for frame based motion. Since we run at 60 FPS and the dot moves at 10 pixels per frame along each axis, it moves at 600 pixels per second along each axis.

And for the love of pointers, don't email me asking me to up the frame rate (yes, there are people that obsessed with frame rates). 60 FPS is fine quick demos like this.

Addendum: Per frame movement vs time based movement

The vast majority of games made within the last few decades don't use frame based movement. Frame based movement was more common in the pre 3D era of games where resources were more limited like how Super Mario Bros 3 on the NES was around 385kb where SDL3.dll (which is fairly thin wrapper library) is over 6 times that.

While time based movement is objectively better, it is slightly more complicated. You have to know a few physics fundamentals (like only a single semester's worth) to get it working right. For the sake of simplicity, we will be using frame based movement mostly. I also recommend using frame based movement for your Nasty Tetris Project as it will make things faster to complete and finishing as quickly as you can is one of the main goals of the project.