c++ SDL 2D跳转(重力)

C++ SDL 2D Hopping (gravity)

本文关键字:重力 跳转 SDL 2D c++      更新时间:2023-10-16

我正在用c++自学SDL, OpenGL等。在线教程帮助我创建了一个可以在屏幕上移动的盒子和一个盒子不能通过的窗台…

我还没有成功地找到一个教程来解释和展示如何使盒子跳跃或跳跃。理想情况下,我希望我的盒子可以移动并跳上窗台,就像旧学校的马里奥一样。

我不确定这是否可以用SDL完成?我猜需要添加一些东西来使盒子在按下向上键后返回地面…我用up/down键进行了实验,并试图使盒子在按下后返回地面,但它不起作用。我试过把盒子放在地上,但是向上键不工作。我也试过一次向上按压,盒子移动,让盒子向下移动同样的距离……我有点明白它需要如何实现(我认为),我只是不知道足够的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;}

你需要应用一些非常基本的物理。有可用的库,但对于这样一个简单的情况,只需要最少的代码。

你已经有了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

我很确定老派的马里奥使用一些手工制作的基于状态的功能来跳跃,没有一点类似于真正的物理,甚至是纯粹意义上的正弦/抛物线曲线(尽管他们可能使用这样的数学来跳跃)

你想要考虑的输入和状态是这样的:STATE_PRESSED, state_减速,state_descent, STATE_FREEFALL。

只要用户按下按钮或直到超时发生,

STATE_PRESSED就会进行线性向上移动。这允许用户在限制最大跳跃高度的同时控制跳跃高度。state_减速通常是抛物线函数发挥作用的地方。但请记住,如果用户继续按下跳跃按钮,许多平台游戏在这种状态下会略微减慢减速速度。如果你发现自己为了获得最大高度而一直按住跳跃按钮,你便能够判断出任何游戏是否会这么做。

state_descent很明显,抛物线函数可能就足够了。一旦达到特定的速度,状态就会转变为STATE_FREEFALL——在这一点上速度不再增加。这两种状态都不受用户按键的影响。

如果您愿意,可以将最后两种状态组合在一起,但是在我创建状态机的时候,我更喜欢保持状态的清晰定义。它也可以帮助你的游戏的其他部分,例如,可能想要检查玩家当前的跳跃或下降状态。

琐事:NES上最初的《马里奥》游戏实际上使用了手工制作的查找表,因为旧的NES CPU除了基本的加法/减法之外无法执行任何操作,但它们与指数/抛物线曲线非常相似。

你应该检查box2d 2D物理引擎,它是用c++开发的,是开源的。BOX2D

您可以使用窦形曲线表示跳跃速度。它一开始很快,但在跳跃的顶部减慢速度,直到速度为零,然后运动遵循相同的曲线(但相反,所以先慢下来)。如果只按键,则只改变Y方向的位置,否则,如果同时按左或键,则也改变X方向的位置。

如果精灵在上升过程中与物体碰撞,在到达跳跃的顶部之前,你将立即逆转上下方向。减速开始时的速度与精灵相撞时的速度相同,并沿着相同的窦形曲线加速,直到它回到"地面"。

加速度和速度的精确方程,你可以试验。