Lisp Game Jam Log #7

I made a lot of progress today. I finished narrow phase collision detection (NPCD) between entities and tiles, added support for partial-sized tiles, added collision resolution and friction for players (and ice tiles!), decoupled the physics simulation from the render framerate, and added support for a live REPL so I can type in commands to modify the game while it is running!

Screenshot of a game, with characters standing on top of floating platforms, and some floating ice platforms above them. I already wrote about NPCD in my last post, so I won't say much more here, except that I finished implementing it. In the process, I added support for partial-sized tiles. Certain tiles only fill part of a grid cell. For example, the floating grass tile that the yellow character is standing on only fills the top half of its cell, and the thin wood tile that the pink alien is standing on only fills the bottom third of its cell. The shape of each tile is defined in its properties as a symbol, e.g. 'top-half or 'bottom-third, and the tile's hitbox is generated based on the symbol.

I also added collision resolution for players. Resolution is the step after detection, when you resolve (i.e. react to) the collisions that were detected. In the case of the player, this means canceling their velocity on one axis (so they stop moving) and repositioning them so that they are no longer colliding with the tile. This is where having four hitboxes comes in handy. Depending on which hitbox is colliding with the tile, I can easily determine what to do. For example, if the player's bottom hitbox is colliding with the tile, I need to cancel downward velocity and move them so they are above the tile. If the player's left hitbox is colliding with the tile, I need to cancel leftward velocity and move them so they are to the right side of the tile.

After I implemented that, players would no longer move through solid tiles, but they would continue sliding along the ground, never slowing down or stopping until they ran into a wall or went off the side of the screen. So, I added a simple friction simulation. Basically, if a player is touching a tile, their velocity decreases by a certain percentage, depending on how much time passed since the last frame, and how slippery the tile is. The hitboxes are used here too: if the top or bottom hitbox is touching the tile, the player's horizontal velocity is decreased; if the left or right hitbox is touching the tile, their vertical velocity is decreased.

I added a 'friction tile property to define its friction scale, and added some frictionless ice tiles to try it out. A friction scale of 1 means normal friction, 0.5 means half friction (somewhat slippery), 0 means no friction (very slippery), 2 means double friction (very sticky), etc. A negative friction scale causes the player to accelerate, which is pretty funny and might be worth using in the gameplay somewhere.

Also today, I decoupled the physics simulation from the rendering framerate. Specifically, the physics simulation is always update with a fixed step, currently 0.005 seconds (5 ms). The rendering framerate is currently limited to a maximum of 100 FPS, or minimum of 0.010 seconds (10 ms) per frame. So, that means the physics simulation is updated at least twice for every time the scene is rendered. Updating the physics more often with smaller steps leads to more precise and stable simulations. For a game as simple as this, it is probably not really necessary, but it was really easy to implement, so I did it anyway just to show how to do it. (This is intended as an example game, after all.)

Finally, I added support for a live REPL (Read-Eval-Print Loop) while the game is running. If you run the make repl command, or compile the program or run the interpreter with the -D repl flag, the program will run the main loop in a background thread and start a REPL. This allows you to type in commands while the game is running. For example you can set one of the player's velocity and watch them go flying. If you're running in the interpreter, these even allows you to redefine procedures on the fly and see their effect immediately. Later I might play around with Geiser, which would let me update the code live from within Emacs, much like you can do with SLIME and Common Lisp.

One change I had to make in order to run the main loop in a background thread, is to use SRFI-18's thread-sleep! procedure instead of SDL_Delay for limiting framerate. CHICKEN Scheme uses "green threads", which means that all of its threads are actually running inside a single native thread. SDL_Delay will cause the native thread to pause, and thus cause all CHICKEN threads to pause. This would make it very difficult to interact with the REPL. Fortunately, thread-sleep! causes only the current CHICKEN thread to pause, so the REPL thread can continue unhindered.

By the way, the fact that CHICKEN Scheme uses green threads has a nice side effect. When using SDL in C, only the thread that initialized SDL's display system can interact with it, e.g. drawing to the window. But since every CHICKEN thread is running in one native thread, every CHICKEN thread can interact with the display system. This made it painless to run the main loop in a background thread.

So, there are 3 days left in the game jam, and I'm feeling pretty good about what I've accomplished so far. Even though the physics and collision detection took me longer than I hoped, and I had to postpone some features, it was a good exercise. Here are the remaining features I'd like to complete before the jam ends, in order of priority:

We'll see how far I can get!

P.S. Since this is probably the last post I will write about collision detection and resolution, I wanted to link to a couple resources that I found useful:

Previous post: Lisp Game Jam Log #6 Next post: Lisp Game Jam Log #8