使用Crystal的SDL2进行RPG风格的精灵移动

时间:2018-07-12 16:17:50

标签: sdl-2 2d-games crystal-lang

我正在使用SDL2 with Crystal来制作16位RPG风格的基于图块的游戏。我已经看过这个问题了很多,但是即使我遇到了所有的答案,我仍然没有找到想要的动作。您是否曾经在SNES上玩过《最终幻想IV》,《 V》或《 VI》?我正在寻找这样的运动。没有对角线,角色始终在图块上方,并且永不停在2个图块之间。

# main game loop
loop do
  ticks = Time.monotonic.milliseconds / 1000.0
  case event = SDL::Event.poll
  when SDL::Event::Keyboard
    case event.sym
    when .right?
      character.move_right(ticks)
    end
  end

  character.draw(renderer)
  renderer.present
  #other code handling break and stuff omitted
end

# character.cr
VELOCITY = 100
def move_right(delta_ticks)
  @direction_facing = "east"
  @x += VELOCITY * delta_ticks
end

def draw(renderer)
  sprite = @directions[@direction_facing]
  renderer.copy(sprite, dstrect: SDL::Rect[@x.to_i, @y.to_i, 64, 64])
end

我当前动作的方式是,角色开始缓慢行走,然后加快速度,然后又下降到缓慢行走,就像换档一样。我知道我的行@x += VELOCITY * delta_ticks是错误的,但是我找不到能满足我需求的行。这也不会考虑直接在图块(在本例中为64x64)上方停下来。

编辑:我尝试过转@genpfault提出的建议。它仍然不能满足我的要求,但是由于我不了解C ++,所以我可能错过了一些东西。该代码更新为here

1 个答案:

答案 0 :(得分:0)

  • 使用一些“ tasklet”助手(我对Crystal的了解为 zero ;在C ++中,我将其作为具有成员数据和函数的类/结构)来封装角色的当前图块x / y位置(以及细分的x / y精细位置)
  • 在处理左/右/上/下输入时,请检查当前的tasklet是否仍在执行它的工作;如果没有,请按照所需的方向制作一个新的小任务
  • 任务框处于活动状态时,每帧都要对其进行处理:递增/递减(1px /帧?由您决定)字符的精细x / y位置,直到达到目标图块位置;如果Tasklet在此帧上击中目标位置,则将其删除(并更新角色的平铺位置)

这样,您可以防止新输入干扰正在进行中的字符运动,以及平滑地动画平铺过渡。

类似这样的东西:

#include <SDL2/SDL.h>
#include <memory>

struct Character
{
    int m_TileX;
    int m_TileY;
    int m_FineX; // in 16ths of a tile
    int m_FineY; // in 16ths of a tile
};

class ITask
{
public:
    virtual ~ITask() {};

    // override & return true to indicate this task is done
    virtual bool Run() = 0;
};

class CharacterAnimator : public ITask
{
public:
    CharacterAnimator( Character& c, int dx, int dy )
        : m_C( c )
        , m_Dx( dx )
        , m_Dy( dy )
    {}

    ~CharacterAnimator() override {}

    bool Run() override
    {
        m_C.m_FineX += m_Dx;
        m_C.m_FineY += m_Dy;
        bool done = false;
        if( m_C.m_FineX <= -16 ) { m_C.m_TileX--; m_C.m_FineX = 0; done = true; }
        if( m_C.m_FineY <= -16 ) { m_C.m_TileY--; m_C.m_FineY = 0; done = true; }
        if( m_C.m_FineX >= 16 )  { m_C.m_TileX++; m_C.m_FineX = 0; done = true; }
        if( m_C.m_FineY >= 16 )  { m_C.m_TileY++; m_C.m_FineY = 0; done = true; }
        return done;
    }

private:
    Character& m_C;
    int m_Dx;
    int m_Dy;
};

int main( int argc, char** argv )
{
    SDL_Init( SDL_INIT_EVERYTHING );
    SDL_Window * window = SDL_CreateWindow
        (
        "SDL2", 
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 
        640, 480, 
        SDL_WINDOW_SHOWN
        );
    SDL_Renderer* renderer = SDL_CreateRenderer
        (
        window,
        0,
        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
        );
    SDL_RenderSetLogicalSize( renderer, 320, 240 );

    Character c;
    c.m_TileX = 9;
    c.m_TileY = 7;
    c.m_FineX = 0;
    c.m_FineY = 0;

    std::unique_ptr< ITask > movementTask;

    bool running = true;
    while( running )
    {
        if( movementTask && movementTask->Run() )
        {
            movementTask.reset();
        }

        SDL_Event ev;
        while( SDL_PollEvent( &ev ) )
        {
            if ( ev.type == SDL_QUIT )
                running = false;
            if( ev.type == SDL_KEYUP && ev.key.keysym.sym == SDLK_ESCAPE )
                running = false;
            if( ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_UP    && !movementTask )
                movementTask = std::unique_ptr< ITask >( new CharacterAnimator( c,  0, -1 ) );
            if( ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_DOWN  && !movementTask )
                movementTask = std::unique_ptr< ITask >( new CharacterAnimator( c,  0,  1 ) );
            if( ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_LEFT  && !movementTask )
                movementTask = std::unique_ptr< ITask >( new CharacterAnimator( c, -1,  0 ) );
            if( ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_RIGHT && !movementTask )
                movementTask = std::unique_ptr< ITask >( new CharacterAnimator( c,  1,  0 ) );
        }

        SDL_SetRenderDrawColor( renderer, 0, 0, 0, 255 );
        SDL_RenderClear( renderer );

        // draw character
        SDL_SetRenderDrawColor( renderer, 255, 0, 0, 255 );
        SDL_Rect r =
        {
            c.m_TileX * 16 + c.m_FineX,
            c.m_TileY * 16 + c.m_FineY,
            16,
            16
        };
        SDL_RenderFillRect( renderer, &r );

        SDL_RenderPresent( renderer );
    }

    SDL_DestroyRenderer( renderer );
    SDL_DestroyWindow( window );
    SDL_Quit();
    return 0;
}