Christmas game development
December 23, 2024
A small devlog of a Christmas project. Done in odd hours here and there.
2024-12-23 Day 1
Only got a couple of hours in on the first day, but setup a basic project (build script and C
file), and got an OpenGL enabled window up using X and GLX.
Only one file so far, and the screen is filled with a solid color.
2024-12-24 Day 2
A light restructure to separate the platform layer (GLX setup and related functions), the rendering (which is OpenGL at present), and the game data which is can be modified independently of both.
Got “hello triangle” up and running, compiling and linking shaders, and getting the basic OpenGL objects setup for rendering.
Next I generated the map geometry for tiles, which is really just a grid of quads. These are currently rendered as a solid colour (red), so the entire screen just looks red. Rendering as points shows the grid structure more readily.
Did some light investigation into device input - I don’t want to use X11 because its API is annoying - I want to use something more direct.
evdev
seems to be the modern linux interface, and devices are present at /dev/input/eventX
for reading (structured) events.
This seems straightforward enough, and indeed the file descriptors can be set as non-blocking and used with the various linux polling APIs, but I need to figure out which devices I’m to listen on.
The device descriptions are all in /proc/bus/input/devices
, and information can be parsed out of that.
A more programmatic API where I don’t need to write a parser would be nice, but I haven’t found anything better yet.
Another option is just to listen on all of devices and filter the events.
2024-12-25
Nothing on Christmas day.
2024-12-26 Day 3
Setup a reasonable base for drawing tiled maps - the data is mostly invariant over the lifetime of a map, so can just be sent to the GPU and then transformed to draw the right pieces.
There is a mesh of tiles for a map, and each layer of tiles is a set of UV coordinates into a tile texture. These can be bound and drawn to draw layers.
This ended up being quite simple, and I got the tiles textured (with generated coloured tiles) so that the tiling pattern is clear.
Set up transforms to get tiles scaled and positioned correctly using a uniform passed into the shader (a vec4
to capture translation in xy
and scale in vw
).
2024-12-27 Day 4
Wrote a basic system for reading input from a joystick (using a PlayStation 4 Dual Shock controller).
Used the (older, legacy) joystick devices (/dev/input/jsX
) because they’re simpler to disambiguate, even if the interface is a bit more limited.
Use the joysticks on the device to scroll around the map and clamp to the edges, and the central PlayStation button to quit the game (as a simple option for now).
Introduced new vertex buffers for entities which move around in the map (the player, for example).
These use GL_DRAW_DYNAMIC
buffers since vertices have to be changed as player input is processed.
These currently use the same shader as the map tiles for simplicities sake.
To prove this worked, introduced a tile which acts as the player, and moved it around the map. First task for tomorrow is a) clamp the player to the map and then b) scroll the map around (clamped) as the player moves.
2024-12-28 Day 5
Got the player pinned inside the bounds of the map, and got the map scrolling with the player.
Added in some “enemies”, which follow the player around the map.
Added (crudely) the ability to aim at the enemies using the right joystick of the gamepad.
Found instance rendering, which seems more appropriate for the kind of data being submitted to the GPU (there is a lot of repeat data, or data which varies by some easily computable offset(s), so this probably offers a lot of chance for compression of data being sent to the GPU). I don’t want to mess around getting that working right now, but it’s more likely useful when adding more dynamism to the enemies (for example removing enemies from the render), where keeping the changing set of data minimal helps simplify the overall system.
The enemies and dynamic here are not really that close to what I want ultimately, but it’s getting close to something that can be built out into something a bit more fun.
Some simple (even if bad) art would go a long way - currently everything is coloured squares.
2024-12-29 Day 6
Added the ability to fire at enemies, and detect which enemy was hit. Took much longer than I’d like to admit working out how to do this best, and even then probably ended up with a quite poor solution. Good enough to get going with, can probably be optimized. Enemies currently get knocked back when you hit them.
Switched to instanced rendering - most objects rendered are tiles of a fixed size. Per-tile, the only thing that varies is the position and uv coordinates (at present), which compresses to 4 floats
, compared with 4 vertices (8 floats
), 4 uv coordinates (8 floats
) and 6 elements (6 u32
s).
This also makes it a lot easier to update position and tile image data (much more natural to compute, and much lighter to update).
While this reduction in data is nice from an efficiency perspective, all of this is preparation for just being able to make the current entities much more dynamic - the array of position and uv offsets can just be written to in one big loop each frame, allowing enemies to be removed, animated, and moved around the map in quite a natural way. This was possible before, but required more code and was more fiddly.
Moving around the enemies and trying to knock enemies back feels quite good given how little has gone into it - I’m hopeful tihs can be made quite entertaining with a lot of refinement.
2024-12-30 Day 7
Started with some simplifications.
Added the ability to query time so that calculations can adapt to different frame rates (roughly, anyway), and made the enemies take a path when hit (with easing to make this look a bit more dynamic).
Enemies highlighted when hit (a different tile), and after their health has been depleted stop moving (and are stored as dead).
Started laying out a simple map and generating the tiling from it.
Experimented with some line-of-sight detection so enemies can start pursuing the player once they’re visible. Threw this away since it wasn’t really good enough. Kept a “debug” tile which was useful for visualizing the effect of the calculations.
Fixed a bug in the tile generation.
Wrote a simple procedure to clamp the player and enemies to the walkable part of the map (making it essentially the entire playable space). This procedure might hit some issues with fast moving objects which traverse more than one tile in a frame (protected with an assertion).
2024-12-31 Day 8
Improved the generated aim tile.
Add assertion messages so failed assertions print what failed and where, as well as hitting an illegal instruction for breaking in the debugger.
Fixed a bug in the enemy liveness counter (where enemies would be reported all dead, but some still track the player). Caused by dead enemies taking hits and counting as dead multiple times.
Fixed a failed assertion caused by a division by zero.
2025-01-01 - 2025-01-04
Doing other things / being ill on these days - no progress.
2025-01-05 Day 9
Final day before going back to work, so last for the holiday devlog - I’ll carry on working on this in the evenings.
Stopped the player from being able to shoot through walls (by finding a rough point on the map which is hit by a projectile, and excluding enemies further away).
Use the same functionality to then make enemies only chase the player once they’re visible. If the player then disappears from sight, the enemies go to the point they last saw the player, (taking it slow around the corner the player disapppeared), and then return to where they last saw the player, if they player is still not in sight. Hardly advanced AI, but better than blindly chasing the player through walls.