C++ SDL 2D跳跃 (重力)

4
我正在自学使用C++编写SDL、OpenGL等技术。网络上的教程已经帮助我创建了一个可以在屏幕上移动的框和一个框无法通过的台阶……
但是我没有找到一个能够解释并且展示如何让框跳跃或者跳跃的教程。理想情况下,我想让我的盒子沿着台阶移动并跳上去,就像老式马里奥游戏一样。
我不确定这是否可以使用SDL实现?我猜可能需要添加一些内容以便在按下up键后框能够返回地面…
我尝试过使用上下键和让框在被按下后返回地面,但是失败了。我已经在地面上设置过框,但是这样up键无效。我也尝试过在框向上移动后再次向下移动相同的距离…
我有点理解需要如何实现(至少我认为),只是我不知道足够的C++语言水平去编写它,甚至不确定它是否能够做到。
目前我可以左右移动以及向上移动,但是它会悬浮在空中 :(
以下是我的代码:
    #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;}

1
SCREEN_WIDTH = 480 .. 这是有意为之吗?还是你想要写成 640 - jstine
我刚刚把它做成了一个正方形屏幕。有哪些屏幕尺寸更常用?@jstine - Reanimation
4个回答

3
我相信老式的马里奥游戏使用了一些手工制作的基于状态的跳跃函数,与真正的物理学或正弦/抛物线曲线毫不相似(尽管它们可能会在跳跃的顶点使用这种数学)。您需要考虑的输入和状态如下:STATE_PRESSED、STATE_DECELERATE、STATE_DESCEND和STATE_FREEFALL。
STATE_PRESSED会在用户按下按钮或超时发生之前进行线性向上运动。这使得用户可以控制跳跃高度,同时限制最大跳跃高度。通常情况下,STATE_DECELERATE是抛物线函数发挥作用的地方。但请注意,如果用户继续按住跳跃按钮,许多平台游戏会稍微减缓减速率。如果您发现在跳跃的顶点通过保持跳跃按钮来获得最大高度,则可以告诉任何游戏都这样做。
STATE_DESCEND非常明显,再次使用抛物线函数可能足以胜任。一旦达到特定速度,状态应转移到STATE_FREEFALL - 在这一点上,速度不再增加。这两个状态都不受用户按钮操作的影响。
如果您愿意,可以将最后两个状态合并在一起,但是在我制作状态机的时候,我更喜欢保持状态清晰明确。这也有助于游戏中可能希望检查玩家当前跳跃或下落状态的其他部分。
小知识:由于旧版NES CPU无法执行除基本加/减外的任何操作,因此NES上的原始马里奥游戏实际上使用手工制作的查找表,但它们足以很好地类似指数/抛物线曲线。

3
你需要应用一些非常基本的物理知识。虽然有现成的库可用,但对于这种简单情况,只需要很少的代码就可以了。
你已经有了xVel和yVel变量。当按下UP键时,盒子会向上“跳”,你要给yVel添加值。重力是一个恒定的力,影响盒子的速度。盒子向下加速。在实践中,你需要在每帧的move()函数中将重力应用到速度上。
yVel -= GRAVITY;    // Zero in space, small value on Moon, big value on Jupiter
box.y += yVel;

请注意,当物箱撞到地面时,您需要反转y-速度。这很好,但是由于受重力影响,重力将积累,物箱最终以荒谬的速度弹跳。物箱在弹跳时应该失去“能量”,因此速度可能会减半。
您代码中的问题是您正在使用整数值。重力是一种微弱的力,为了使其看起来真实,它应该是一个小值。使用整数值,可能很难找到这个值。(不玩固定点值,但那是另一个话题。)
因此,请考虑将xVel和yVel更改为浮点数。当然,物箱的位置也应使用浮点数。
对于确切解决方案并考虑帧率的更为精确的物理学,请查看:https://gamedev.stackexchange.com/questions/15708/how-can-i-implement-gravity

1
你应该看看Box2D 2D物理引擎,它是用C++开发的,并且是开源的。 BOX2D

1

你可以使用正弦曲线来表示跳跃速度。一开始很快,但会逐渐减慢,直到跳跃到达顶部时速度为零,然后运动沿着相同的曲线(但相反,所以先慢)下降。如果只按键,则仅更改Y位置,否则如果同时按下左或键,则还会更改X位置。

如果精灵在上升途中与物体碰撞,而未到达跳跃的顶部,则立即反转上下方向。向下的速度从精灵碰撞时的相同速度开始,并沿着相同的正弦曲线加速,直到回到“地面”。

加速和速度的确切方程式可以进行实验。


2
抛物线更好地描述了运动。对于碰撞,您可能不希望弹性达到100%。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接