C ++ SDL 2D Hopping (gravity)

I myself am learning SDL, OpenGL, etc. with C ++. Online tutorials helped me create a box that moves across the screen and a ledge that the box cannot go through ...

I was unable to find a tutorial that explains and shows you how to make the window jump or jump. Ideally, I want my box to move and jump onto a ledge that looks like an old school mario.

I'm not sure if this can be done with SDL? I'm guessing something needs to be added to get the box back to ground level after pressing the up button ... I experimented with the up

/ keys down

and tried to return the box to the ground after pressing it, but that didn't work. I tried to set the box to the ground, but then the up key doesn't work. I also tried as soon as the press was pressed and the box moved to make the box the same distance ... I somehow understand how this should be implemented (I think), I just don't know enough C ++ to write it, or if it can even be done.

Currently I can navigate left or right and up, but it stays floating in the air :(

Here is my code:

    #include "SDL.h"
    #include "SDL_image.h"
    #include <string>

    const int SCREEN_WIDTH = 480;
    const int SCREEN_HEIGHT = 480;
    const int SCREEN_BPP = 32;
    const int FRAMES_PER_SECOND = 20;
    const int SQUARE_WIDTH = 20;
    const int SQUARE_HEIGHT = 20;

    SDL_Surface *square = NULL;
    SDL_Surface *screen = NULL;
    SDL_Event event;
    SDL_Rect wall;

    class Square{
        private:
        SDL_Rect box;
        int xVel, yVel;
        public:
        Square();
        void handle_input();
        void move();
        void show();};

    class Timer{
        private:
        int startTicks;
        int pausedTicks;
        bool paused;
        bool started;
        public:
        Timer();
        void start();
        void stop();
        void pause();
        void unpause();
        int get_ticks();
        bool is_started();
        bool is_paused();};

    SDL_Surface *load_image( std::string filename ){
        SDL_Surface* loadedImage = NULL;
        SDL_Surface* optimizedImage = NULL;
        loadedImage = IMG_Load( filename.c_str() );
        if( loadedImage != NULL )
        {
            optimizedImage = SDL_DisplayFormat( loadedImage );
            SDL_FreeSurface( loadedImage );
            if( optimizedImage != NULL )
            {
                SDL_SetColorKey( optimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB( optimizedImage->format, 237, 145, 33 ) );//optimizedImage->format, 0, 0xFF, 0xFF
            }
        }
        return optimizedImage;}

    void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL ){
        SDL_Rect offset;
        offset.x = x;
        offset.y = y;
        SDL_BlitSurface( source, clip, destination, &offset );}

    bool check_collision( SDL_Rect A, SDL_Rect B ){
        int leftA, leftB;
        int rightA, rightB;
        int topA, topB;
        int bottomA, bottomB;

        leftA = A.x;
        rightA = A.x + A.w;
        topA = A.y;
        bottomA = A.y + A.h;

        leftB = B.x;
        rightB = B.x + B.w;
        topB = B.y;
        bottomB = B.y + B.h;

        if( bottomA <= topB ){return false;}
        if( topA >= bottomB ){return false;}
        if( rightA <= leftB ){return false;}
        if( leftA >= rightB ){return false;}
        return true;}

    bool init(){
        if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ){return false;}

        screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );
        if( screen == NULL ){return false;}

        SDL_WM_SetCaption( "Move the Square", NULL );
        return true;}

    bool load_files(){
        square = load_image( "square.bmp" );
        if( square == NULL ){return false;}
        return true;}

    void clean_up(){
        SDL_FreeSurface( square );
        SDL_Quit();}

    Square::Square(){
        box.x = 50;
        box.y = 360;
        box.w = SQUARE_WIDTH;
        box.h = SQUARE_HEIGHT;
        xVel = 0;
        yVel = 0;}

    void Square::handle_input(){
        if( event.type == SDL_KEYDOWN ){
            switch( event.key.keysym.sym ){
                case SDLK_UP: yVel -= SQUARE_HEIGHT / 2; break;
                //case SDLK_DOWN: yVel += SQUARE_HEIGHT / 2; break;
                case SDLK_LEFT: xVel -= SQUARE_WIDTH / 2; break;
                case SDLK_RIGHT: xVel += SQUARE_WIDTH / 2; break;}}
        else if( event.type == SDL_KEYUP ){
            switch( event.key.keysym.sym ){
                case SDLK_UP: yVel += SQUARE_HEIGHT / 2; break;
                //case SDLK_DOWN: yVel -= SQUARE_HEIGHT / 2; break;
                case SDLK_LEFT: xVel += SQUARE_WIDTH / 2; break;
                case SDLK_RIGHT: xVel -= SQUARE_WIDTH / 2; break;}}}

    void Square::move(){
        box.x += xVel;
        if( ( box.x < 0 ) || ( box.x + SQUARE_WIDTH > SCREEN_WIDTH ) || ( check_collision( box, wall ) ) ){
            box.x -= xVel;}

        box.y += yVel;

        if( ( box.y < 0 ) || ( box.y + SQUARE_HEIGHT > SCREEN_HEIGHT ) || ( check_collision( box, wall ) ) ){
            box.y -= yVel;}}

    void Square::show(){
        apply_surface( box.x, box.y, square, screen );}

    Timer::Timer(){
        startTicks = 0;
        pausedTicks = 0;
        paused = false;
        started = false;}

    void Timer::start(){
        started = true;
        paused = false;
        startTicks = SDL_GetTicks();}

    void Timer::stop(){
        started = false;
        paused = false;}

    void Timer::pause(){
        if( ( started == true ) && ( paused == false ) ){
            paused = true;
            pausedTicks = SDL_GetTicks() - startTicks;}}

    void Timer::unpause(){
        if( paused == true ){
            paused = false;
            startTicks = SDL_GetTicks() - pausedTicks;
            pausedTicks = 0;}}

    int Timer::get_ticks(){
        if( started == true ){
            if( paused == true ){
                return pausedTicks;}
            else{return SDL_GetTicks() - startTicks;}
        }return 0;}

    bool Timer::is_started(){return started;}
    bool Timer::is_paused(){return paused;}

    int main( int argc, char* args[] ){
        bool quit = false;
        Square mySquare;
        Timer fps;
        if( init() == false ){return 1;}
        if( load_files() == false ){return 1;}

        wall.x = 130;
        wall.y = 300;
        wall.w = 220;
        wall.h = 20;

        while( quit == false ){
            fps.start();
            while( SDL_PollEvent( &event ) ){
                mySquare.handle_input();
                if( event.type == SDL_QUIT ){
                    quit = true;}}

            mySquare.move();
            SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 1, 1, 1 ) );
            SDL_FillRect( screen, &wall, SDL_MapRGB( screen->format, 237, 145, 33 ) );
            mySquare.show();

            if( SDL_Flip( screen ) == -1 ){return 1;}
            if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND ){
                SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );}}

        clean_up();
        return 0;}

      

+3


source to share


4 answers


You need to apply some very basic physics. Libraries are available, but a simple case like this only requires minimal code.

You already have the xVel and yVel variables. When the UP key is pressed, the field "jumps" up, you add a value to yVel. Gravity is a constant force that affects the speed of the box. The box accelerates downward. In practice, you apply gravity to the velocity every frame in the move () function.

yVel -= GRAVITY;    // Zero in space, small value on Moon, big value on Jupiter
box.y += yVel;

      

Note that when the field hits the floor, you change the y speed. That's okay, but when gravity is applied, gravity will build up and the box ends up bouncing at an absurd rate. The box must lose "energy" when it bounces, so the speed can be reduced to half, for example.



The problem in your code is that you are using integer values. Gravity is a weak force, so it must be small to make it look real. With integer values, this value may not be found. (No fixed point search, but that's a different topic.)

So, consider changing xVel and yVel to float. Of course, the position of the box should also use floats.

For really tricky solutions with more accurate physics and account frame rate accounting, check out: https://gamedev.stackexchange.com/questions/15708/how-can-i-implement-gravity

+3


source


I'm pretty sure old school Mario is using some kind of manual function for jumping and nothing like real physics or sine / parabola curves in the pure sense (although they might use math like this for the top of the jump)

The inputs and states that you want to consider look something like this: STATE_PRESSED, STATE_DECELERATE, STATE_DESCEND, STATE_FREEFALL.

STATE_PRESSED will perform a linear upward movement for as long as the user presses a button or until a timeout occurs. This allows the user to control the jump height at the maximum jump height. STATE_DECELERATE is usually found in a parabolic function. But keep in mind that many platformers will slowly slow down the deceleration rate during this state if the user continues to hold down the transition button. You can tell when any game is doing this if you implicitly find yourself holding the jump button until the top of the jump to get the maximum height.



STATE_DESCEND is pretty obvious and a parabolic function is probably enough. The state should transition to STATE_FREEFALL as soon as a certain speed is reached, at which point the speed is no longer increasing. None of these states affect user button presses.

You can roll the last two states together if you like, although at the point where I created the state machine, I prefer to clearly define the states. It can help for other parts of your game as well, which can, for example, check the current state of a player jumping or falling.

Trivia: The original Mario games on the NES actually used manual lookup tables due to the fact that the old NES processor was rather ill-equipped to do anything other than basic addition / subtraction, but they resemble exponential / parabolic curves quite well.

+3


source


you should check out the box2d 2D physics engine which is developed in C ++ and is open source. BOX2D

+1


source


You can use a sine curve for the transition speed. It's fast at first, but slows down until the speed is zero at the top of the jump, and then the movement follows the same curve (but vice versa, so slow at first). If the key was pressed Up, you only change the Y position, otherwise if you pressed the left or Right, you also changed the X position.

If the sprite bumps into an object on its way up, you immediately change direction upward before reaching the top of the jump. Down speed starts at the same speed as the sprite fired and accelerates along the same sine curve until it returns to ground.

The exact equation of acceleration and speed with which you can experiment.

+1


source







All Articles