2025-185

20:25:29, atom feed.

I implemented basic player movement in the game. First, I created a simple tileset for the player and loaded that into the game as a single SDL texture. Each tile in the tileset is 16x16 pixels, and there are 4 tiles. The tiles are blue squares with a white arrow showing which direction the player is facing, so I have one tile per cardinal direction.

I then created a simple Player struct to hold a pointer to the texture and other supporting information such as the cardinal direction the player is facing and the player's position.

On each iteration through the game loop, I poll for and handle keyboard events. In the event handling logic, I save off the new key states into an instance of my own Buttons struct. I don't modify the player state immediately in the event-handling code. The simple Buttons struct looks as follows.

struct Button {
  bool pressed = false;
};

struct Buttons {
  Button up = {};
  Button down = {};
  Button left = {};
  Button right = {};
};

For now, we just keep track of whether the button is down on the current frame. We don't track whether it was pressed this frame or has been held down for some number of frames. With this setup, if we wanted to understand the input delta from the last frame, we could simply create a second instance of Buttons and store the latest frame's inputs in the second instance at the end of the game loop so that it is available on the next iteration through the loop.

Buttons previous_buttons = {};

while (running) {
  Buttons buttons = {};
  while (SDL_PollEvent(&event)) {
    switch (event.type) {
      case SDL_EVENT_KEY_UP:
        // Fall through.
      case SDL_EVENT_KEY_DOWN: {
          if (event.key.key == SDLK_DOWN) {
            buttons.down.pressed = event.key.type == SDL_EVENT_KEY_DOWN;
          } else if (/* ... */) {
            // ...
          }
       }
        break;
      default:
        break;
    }
  }

  // ...
  previous_buttons = buttons;
}

When it comes time to update the player based on input, I just check whether a certain button is pressed and then update the player's facing direction and a proposed position.

Vector2f proposed_position = player.position;
float dpos = (TARGET_FRAME_TIME_NS / 1000000000.0f) *
             RUNNING_VELOCITY_MS / METERS_PER_PIXEL;

if (buttons.down.pressed) {
  player.facing = Direction::DOWN;
  proposed_position.y += dpos;
} else if (buttons.up.pressed) {
  player.facing = Direction::UP;
  proposed_position.y -= dpos;
}

// The the same for right, left.

player.position = proposed_position;

Here I assume that I always hit my target frame time, and then I multiply that time converted to seconds with my configured running velocity to get a distance the player would have moved in meters. The screen is drawn in pixels, though, so I convert meters to pixels, and that is my delta position (dpos).

I also handle down-up and right-left separately. This way the player can move both vertically and horizontally at the same time. I handle down before up, meaning that if the player presses both down and up at the same time, down takes priority. They will face and move downward. (Notice that I add, not subtract, dpos in the down case--SDL's coordinate system places (0, 0) at the top-left of the rendering space.)

Handling right-left separately means that the player will always prioritize facing a right-left cardinal direction instead of a down-up one if either right or left were pressed on a frame.

Although I simply update the player's position with the proposed position here, I am setting myself up for handling collision. It's possible we should not be able to move where we want to move, so I keep the proposed position separate so I can test it before updating the player state.

Here is a video of what I have so far. The grey border is supposed to be walls, and the white is just a placeholder tile for the ground. You can see the blue player moving around with the arrow updating in the direction the player is facing (or the prioritized direction if the player is moving diagonally). The frame rate is low, but you can tell by the diagonal movement that while the map is based on tiles, the movement is based on pixel positions and is not tile-based. Two outstanding issues are the fact that there is no collision yet and that the player is faster if moving diagonally as opposed to just vertically or horizontally.