2025-186

14:42:57, atom feed.

I implemented simple collision with the environment. This time, it is probably clearer for me to show it in action first.

There are a few new things going on in this video. The first is that the map looks different from last time. I updated the tilemap to add a few things the player can collide with besides the walls along the edges. I am currently exclusively using Aseprite for tilesets and tilemaps. Unlike map editors such as Tiled, Aseprite does not provide a way to annotate tiles in a tilemap with auxiliary information. In Tiled, you can do so, and one such attribute is drawing invisible bounding boxes which can be used to indicate collision areas.

The way I annotated collision in Aseprite was to use an additional tilemap layer. The tileset currently has only 2 tiles, technically 1 tile and the empty tile which occupies an index in the tileset. I set the collision layer to be higher in the layer stack than the map itself, set the opacity of the collision layer to somewhere between 25 and 50 percent, and then I drew over any tile I wanted to add collision to. I used a pink and black checkered pattern for the tile indicating a collision area in hopes that it would stand out from other tiles I may draw on the map.

This approach is not convenient because I need to manage at least 2 tilemaps for every level now. It's bug prone because I might change the underlying map without updating the collision layer or vice versa, or I may change the index values of tiles in the collision map but forget to do so in the game code. It also forces a particular software design on my game, having to parse a unique collision map for every tilemap I load. With all of that said, it's simple, and it works, so I'm going with it for now.

I could design an alternative tilemap representation toward my game, as I do control the binary format of the exported tilemap. One alternative is to set one of the high bits in the tile IDs in the map that logically have collision, essentially merging my collision map into a bitfield in each corresponding tile in the game map. If I recall correctly, Tiled does something similar with bit-flag attributes on tiles. At this point in time, it's not worth me implementing this myself, though.

The ultimate goal is an in-game level editor, but I'm far from that at this time.

The next thing in the video is the red square that seems to be following the player. This is a visual annotation of the tile the player is considered to occupy. I've added it for debugging. Unlike other kinds of software development, it's not so straightforward or even realistic to write traditional, automated unit tests for implementations of various things in games. Often times we get more information more quickly from visual feedback. Testing this way could also explain how so many bugs slip through QA in games all the time.

Computing the tile the player occupies introduces us to what will become a handful of coordinate systems in the game. Currently, for ease of development and debugging, I have only one coordinate system in the game. (0, 0) is at the upper-left corner of the game window, and it is the origin for both the camera and the map. Furthermore, everything is measured in pixels, until now. Determining the tile coordinate from a pixel coordinate, assuming the pixel and tile coordinates share the same origin, is straightforward. In my case, positions support sub-pixel accuracy and so are floating-point numbers.

Vector2d round(Vector2f v) {
  return {
    .x = (int32_t)(v.x + 0.5f),
    .y = (int32_t)(v.y + 0.5f),
  };
}

Vector2f pixel_xy = // ... ;
Vector2d int_pos = round(pixel_xy);
size_t tile_pos_x = int_pos.x / TILE_WIDTH_PX;
size_t tile_pos_y = int_pos.y / TILE_HEIGHT_PX;

I designed the collision map and game map so that they are the same dimensions and so that the tile width and height are the same across maps. I can therefore compute the index into the collision map tiles and check whether a tile has collision area or not. If the player tries to move into a map tile with backing collision area, I disallow the position update. That's what we see with me running into walls in the first video.

Now that we color the tile the player is occupying, we can also see that although the player is currently rendered as an entire tile, the player position is represented as a single point, the upper-leftmost point of the player tile. Because of this, the player can somewhat walk into walls to their right and below them. I'll address this later on by changing the position of the player relative to the player's rendered tile or perhaps changing the player's collision point to a collision area.

Lastly, although it is difficult to tell, we stick to walls somewhat. For example, if I press against a wall on my left side and then attempt to move diagonally up-left or down-left, I won't move at all. This is because my proposed position is up-left or down-left of me which enters the collision area on my left, and my entire position update is disallowed. I'll fix this eventually, likely by checking whether we can move horizontally separately from whether we can move vertically.