# Global game jam recap – Gravity Jane A few weeks back I participated in the global game jam. In this post I'll describe roughly my experience of the jam, what went well and what could be improved, and some interesting technical details. ## Location Since I work nearby, I brought my screen, keyboard, mouse, and webcam with me. It was nice to have the comfort of a big screen and not having to crouch over the laptop for the whole jam. The webcam was useful for streaming and making calls. I think I'll try to bring my gear to the next gamejams I'll attend, even if they are further, the comfort is worth it. We were lucky to be welcomed in a nice jam location, with a view on the lake! ![my desk](res/ggj_25_desk.jpg width=380px) ![the location](res/ggj_25_location.png width=380px) ## Pre-jam I teamed up with a fellow swiss game dev, Rinaldo. He lives in Japan, so we had to coordinate around time zone differences. One constraint was that I wanted to use my own engine, like I've done for my last 4-5 game jams or so. That meant we'd need to work within the limitations of the engine, or of whatever tool could be implemented in such a short amount of time. For example, Rinaldo wouldn't be able to open up a scene and start adding particle systems, or create character animations directly inside the engine, like he would in Unity. Instead, he would have to rely on me to implement specific effects. One of my personal goals was to make good enough tools so that he could independently progress on the level design and look of the game. Because we had never worked together before, and because we needed to work within the limitations of my engine, we opted to pick a genre of game and set up a project before the start of the jam. He had been doing [some pixel art]("https://x.com/ryofougere/status/1885456395509592343") recently and wanted to explore that style, and I had made some [tilemap level design tools]("https://bsky.app/profile/sebdegraff.bsky.social/post/3lf3oub4fsk2g"), so we naturally landed on making a platformer. We set up a repository and ironed out some workflow details. Rinaldo made a basic tileset which allowed us to preemptively solve some problems. For example, we realized that to achieve natural-looking edges we'd be better off using a ["dual-grid"](https://x.com/OskSta/status/1448248658865049605) setup where the visual grid is offset from the grid used for level design and collision. ![Final tilset using the dual grid. The __orange grid__ is the level design / collision grid, and the __cyan rectangles__ are the visual tiles boudaries. Notice how the shape of the ground is able to naturally cross orange boundaries](res/ggj_25_dual_grid.png) We also realized that the size of 64px per tile we initially had could be problematic. I was concerned that higher-res tiles would not read as pixel art or would show scaling artefacts if we tried to fit too many of them on one screen width. After experimenting a bit we setteled on 32px tiles. That allowed for enough expressiveness while still looking like pixel-art. ## Coming up with the concept On Friday night, after the theme "Bubble" was revealed, we started brainstorming. Rinaldo wrote down a couple of concepts, with sketches explaining what he had in mind. Here's the one we opted for: ![Rinaldo's game design concept]("res/ggj_25_BubbleShieldConcept.jpg" width=90%) The concept is simple: a classic platformer with a twist—at the press of a button, you can create a bubble around yourself, altering the way you jump, move, and fall. One bubble makes you lighter and floaty, one bubble makes you heavy. By combining the two bubbles you can do interesting things like gain height with the light bubble, then switch to the heavy one to smash down a wooden platform. It's a quite straightforward interpretation of the theme, but it was a design that sounded fun to both of us and that we felt we could achieve in two days. ## The editor Once the jam was kicked off, it was time to actually start working on the game. Normally I would have started by prototyping the main gameplay elements, but I wanted to let Rinaldo work in-engine as soon as possible. An approach to game making I really like is to build design tools along with the game itself. In this case I carried a tilemap editor from previous project, which worked well for our game: ![Popping in the editor while in game. Editing the tilemap.](res/ggj_25_editor_tiles.mp4 width=90%) I had a good base already, but Rinaldo needed more creative control on the look. He also wanted to play with parallax. So my goal on that first night was to extend the level editor so it could be used to place arbitrary images – I called them _props_ – on different layers anywhere in the level. ![Placing, moving and deleting some props.](res/ggj_25_props.mp4 width=90%) New types of props can be added by simply putting image files in the assets/props folder of the project. At first the multiple layers and the parallax effect were hardcoded, but I figured I might as well make it editable so Rinaldo would have full control. Using the UI functions of my engine, I made a stack of buttons that represent the layers. Clicking each one _selects_ that layer, and lets you edit the name and properties of that layer, such as parallax distance. You can place props on any layer and some layers have a tilemap that you can edit. I also added a little visibility button so you can see what you're doing when editing background layers. ![The layers and parallax controls in action](res/ggj_25_layers.mp4 width=90%) I gradually moved from code-driven to data-driven/editor-driven layers, and I was pretty happy with how the code turned out, with a blend of both. Here's the code to draw a couple of layers, some with tilemap and props, some with only props, and one with code-driven fireflies. When turning off the visibility of a layer, `layer_draw_begin(4)` will start returning false, allowing me to skip rendering the fireflies in this example (the `layer_draw_tilemap` and `layer_draw_props` functions do their own visibility checking too). ![How the layers integrate with the code](res/ggj_25_layers_code.png width=90%) And lastly, I added some sliders to control the colors of the background, as well as the color of a vignette effect. The color sliders also come with the engine, carried from a previous project. ![Changing the background color with the sliders.](res/ggj_25_colors.mp4 width=90%) Overall I'm very happy with how the editor turned out. Most of all, it was an incredible feeling logging in on Sunday and discovering the beautiful level Rinaldo had built. I think general-purpose engines like unity have habituated us to a certain kind of workflow where everything is tweakable from the scene editor. The problem, in my opinion, is that this editor is generic and must work for all kinds of games. Because of that, we tend to forget the possiblity of simple and efficent tooling made for specific usecases. That said, I have to recognise that the versatility of unity (or godot, unreal...) and the fact that anyone on the team can go in and tweak any aspect of a scene can be a huge plus (though sometimes a pain). I'm sort of relieved to see that it's possible to achieve similar versatility for a specific game and with a custom engine, if you are well prepared and the stars align. I'll try to continue developping the engine in this direction, providing bricks for the dev to make an editor, rather than aiming to make a generic scene editor. ## Some editor problems When I coded the panel to place props, I hardcoded the height of the scroll view that contained the thumbnails of the props that you could place. I set it to what I thought was a rather high value. When Rinaldo went to build and decorate the level, he added many new props, and soon couldn't select the props that went past the scroll view. He then had to delete the props he used less often. All of this because I was lazy and hardcoded the height instead of dynamically calculating it the night before! When I started working the next morning, it only took me a few minutes to add the dynamically sized scrollview. ![Props selection UI with a properly sized scroll view]("res/ggj_25_scroll.png" width=60%) But the trouble had only started. When I launched the game, it crashed with an error linked to asset importing. I had had many problems with the asset system, so I suspected another bug, but it turned out that the props Rinaldo had removed from the project files to make room were still referenced in the level, and the game couldn't load them. Their asset id didn't map to anything valid on my side, while on his side the assets were still in his import cache. I changed the game code so instead of crashing with an error upon encountering prop referencing an unexisting asset, it would show a purple square instead. ![The problematic pink props](res/ggj_25_pink.png width=90%) Then it was just a matter of deleting the purple props and replacing them with the newer versions. There are two important features that are missing from the editor: - undo/redo - copy paste regions of tilemap These wouldn't be particulary hard to add – I'd use [that approach]("https://blog.voxagon.se/2018/07/10/undo-for-lazy-programmers.html") for undo – but in a game jam you have to pick your battles. I'll implement them for a future jam though! ## Player collisions On the second night, I struggled for way too long on collisions. Until then I had very simple player-tilemap collision code. It just checked a point at the feet of the character and prevented it from moving on solid tiles. It kind of did the trick, but failed on some edge cases. I needed to implement collision with the props for the doors and buttons, so I figured I would give the player a proper collision box. I tried multiple approaches, but they all seemed to have weird bugs that sometimes teleported the player inside the walls and floor, or just didn't work properly. I couldn't figure it out, and at some and even considered moving to integer-based collisions ([like in celeste](https://maddymakesgames.com/articles/celeste_and_towerfall_physics/index.html)), for more robustness. I was desperately scrolling through the code when I suddenly noticed a forgotten line that was just moving the player by its speed, disregarding the collision completely. I think it might well have been the source of all the bugs I was banging my head against. Anyway, when I commented it out, it worked and I could move on... My final approach was to keep track of "limits" in each direction that I then use to clamp player movement. Depending on the direction, limits start at infinty or -infinity. I then check the tiles close to the player, and all the props of the level. If they could collide with the player, I would calculate how far they would allow us to go and adjust the limits. ![Collision detection in action. The red lines represent the limit in each direction.]("res/ggj_25_collisions_hd.mp4" width=90%) Before going to sleep in the second night, I finally implemented the bubbles logic, which would let Rinaldo design a level while I slept. Just before logging of, I extracted various gameplay values related to player movement and put them at the top of the file. I didn't have time to make a UI to modify these, so this was a pragmatic way to let Rinaldo edit the values according to what he needed for the level design. ![Player movement values for each bubble state.]("res/ggj_25_gd_vals.png") When I logged back on the last day, Rinaldo had designed a level, placed many props, and I just had to implement the interactions of doors, buttons and spikes. ## The music On the last day, [Eric](https://www.eric-holzer.ch) joined us remotely to work on the soundtrack of the game. In one day he managed to compose a neat atmospheric track that fits the game pretty well. Fun fact, his Ableton Live license had just expired, and he was waiting for his new student licence to be approved, so he wasn't able to save and had to record the audio output of his computer to produce the final audio file. Integration of the music was pretty straightforward, I converted the wav file to a vorbis-compressed .ogg file, bundled it with the game and played it at startup. It still took a little bit of time at the end of the jam where it is the most stressfull; it would be great if the "import" processing and bundling was automated as part of the asset pipeline. TODO! ## Post jam After the jam, I worked on implementing the collision and interaction with the breakable wooden platforms, and added a couple missing sfx. I also worked on non-game specific features like gamepad support and web build. A highlight is that gamepad now kind of works on the web, at least in chrome and safari (firefox behaves strangely for me). The web build even runs fine on mobile, although there's no way to control the player character. Or is there? A friend tried it on her phone and we had the idea to plug a switch gamepad with a usb-C cable that was lying around. And to our surprise it worked right away. The future is now! ![The web build of the game running on a phone, controlled with a nintendo switch controller.]("res/ggj_25_phone_gamepad_small.mp4") ## The result In the end we managed to do a short, nice looking game that people seem to enjoy. I think all three of us are happy with both the result and the experience of the jam. You can play the game direclty in your browser here: https://sebdegraff.com/games/ggj_25/ggj_25.html Or download it on itch.io, which might be a better option for smooth gameplay: The editor is included in the game; you can try it yourself! Just hit ctrl-E to switch to it. Click the button labeled "tilemap" to modify the level (click to place tile, right-click to remove, hold shift to draw/erase rectangles). And now a few somewhat random... ## Lessons learned: - The asset system must be rock solid, so no time is wasted fighting bugs with it * Make sure cache gets evicted if a source asset gets deleted. Super important to catch bugs early * Similarly, the cache mechanism must be super robust. I thought it was, but there were a couple instances where Rinaldo deleting his cache folder fixed a bug, which doesn't inspire confidence. - Having the game build on launch for the dev team is a good idea. Look into setting up a CI to make sure the game doesn't break for other devs on other platforms. - A jam with participants in very different time zones is totally doable. It can even be a plus. - I should avoid fucking up my sleep schedule if possible. I stayed up very late on the first night because I wanted the editor to be functionnal for Rinaldo. It was probably a good idea but seriously impacted my productivity and mood during the rest of the jam. ## Tech "stack" I plan to do a post about my engine and how it works, but in the meantime here are a bunch of technical facts: - Written completely in C, for speed and simplicity. - Game code hot-reload on windows/macos/linux, using dynamic libraries. - Sokol libraries used for windowing/input, graphics and audio. They do the job that the SDL library could do, but are much more lightweight and easy to integrate. - Custom immediate mode drawing functions, including ui. - No scene graph or ECS, everything is in immediate mode. The game owns its state. - Asset pipeline that tracks changes to source asset files and imports them according to rules defined by the game code. Texture compression and atlassing are examples of preprocessing that the asset pipeline can do. Assets are also hot-reloaded, so you can save a file in Photoshop and see it update in the running game. If you're interested in engine dev, follow me on [bluesky](https://bsky.app/profile/sebdegraff.bsky.social) or [mastodon](http://mastodon.gamedev.place/@seb_degraff). I also stream from time to time on [Youtube](http://youtube.com/@seb_degraff/live), subscribe to my channel to be notified when I go live.