Global Game Jam (Tokyo) 2023
Artwork by brisk.pen
I finally got around to signing up to a Global Game Jam, which has been something I’ve always wanted to try out but have been a little shy to do!
This year’s theme was “Roots”, and I participated in GGJ 2023 at the Code Chrysalis site. Met lots of great people, worked with a great group, and made a cute game about a normal bunny collecting root vegetables in a normal and legal way.
🐰 Jumping the Gun
👉 Game link: Jumping The Gun
👉 Source code: https://github.com/Solomar/HoppingStegos
It’s a 2D game made with Unity! This was my first time using Unity in awhile, and my first time working with 2D in Unity at all, so it was a great learning experience and super interesting.
I worked together with Solomar (developer and sound/music, helped me out a ton with answering my Unity questions), and brisk.pen (artist, and also helped me out a ton with debugging and explaining Unity!!). Both were experienced gamedevs who were super patient and helpful and made the weekend a great time for me 🤗
🍕 GGJ
I think it can be alarming how little time you actually get to code during the Jam! Time management is super vital. This is what the schedule looked like for our team:
- Friday
- 6:30PM — Everyone meets at the game site and we watch a keynote video together which announces the theme for this year. We learn about the schedule for the weekend.
- 7:30PM — Ice breakers; everyone breaks up into random groups and creates a 15 minute provisional game pitch. After the initial brainstorming, we roam around and decide on a final team that we want to join. This is a fun mix of individuals trying to find a project you’re interested in, and teams trying to find contributors who can fill their needs.
- 8:00PM — Plan gameplay and initial art direction, and set up the Unity project and repo.
- 9:00PM — It's 節分! Take a break to throw some beans at an oni, graciously played by jam site mentor Josiah.
- 10:30PM — Go home and get some rest 😴 brisk.pen worked late setting up the initial art assets so we could get a strong start on Saturday!
- Saturday
- 10:30AM — Work on the bulk of our art assets and coding, with a focus on knocking out all the key gameplay features (player controls, carrot pick up & score tracking, enemy navigation, gun implementation)
- 8:00PM — Grab some Shake Shack near Roppongi Hills with the team 🍔 🍟
- Sunday
- 11:00AM — Grind out the rest of the art assets, clean up, add secondary features (larger map, camera controls, music and sound effects, sprite layering, collision boxes and the final navmesh, title and credits screen, cheat codes).
- 3:00PM — Finalize, build, and upload the game to the GGJ site.
- 5:00PM — Game presentations and demos! Each team sets up a station where people can come and play our game, and we roam around individually to try out everyone’s games and mingle. This was super fun, especially trying to beat each other’s high scores.
- 7:30PM — Mingling time! Pizza graciously provided by Code Chrysalis (also the benefactor of snacks and drinks throughout the weekend)🍕 😋
- 10:00PM — Time to go home, good work! 🎉
🧑🎓 Coding & learnings
I had spent some time grinding ThreeJS to prepare for the event and was thinking I might try doing a solo game with JavaScript, but once I was on-site it felt like such a pity to just do my own thing in a social event so I changed my mind in the end!
Since it looked like the vast majority of submissions used Unity, I had luckily also spent some time on Unity’s Learning Portal the week leading up to the jam, so I wasn’t completely lost. It’s a great learning program that gave me the foundation I needed to dive in.
My primary contributions to the project included the carrots, the gun, the enemy AI and nav mesh (with lots of help!).
Carrot
- Spawn a carrot on the map, and whenever one is destroyed, spawn the next one on a random spawner. I handled this with empty carrot spawner objects which we could select the next spawn position from — this made it super easy to set the spawn positions for the carrots.
- When the player collides with a carrot, destroy the carrot and increment the player’s score. I initially implemented this with triggers, but my biggest takeaway from this was to avoid using triggers where I could use colliders instead. We ran into a number of collision issues later trying to figure out which entities used triggers vs colliders, and which Solomar helped me debug!
Enemy AI navigation
When looking up 2D pathfinding, I was originally thinking of using an A* algorithm, but my teammates helpfully recommended that I instead check out Unity’s NavMesh. Jam site mentor Josiah also popped by to help!
This was a little bit of a struggle to set up because a lot of the guides were either 3D-only, or were on outdated versions of Unity. The following process was what I found worked:
- Unity’s default NavMesh support (called AI Navigation in version 2021.3.17f1) only works for 3D scenes. To create a 2D NavMesh, you’ll have to use a third party package — this is the one that we picked out for this game:
- I ran into an error “NavMeshExtension requires a NavMeshSurface component”. This is caused by a naming collision with Unity’s native 3D NavMesh support. In my scenario, I looked up AI Navigation and removed it from the package manager.
- For further reading: https://github.com/h8man/NavMeshPlus/issues/103
- I ran into problems with the NavMesh components not showing up as an option — I spent an absurd amount of time trying to debug this before brisk.pen casually recommended closing Unity and reopening it… Which fixed the problem immediately! 😭🙏
- From here on out, simply following the NavMeshPlus documentation worked perfectly:
Game over
When the enemy runs into the player, it should grab the player, after which the game ends. This got broken up into two separate phases of development —
- Phase 1, instant game over:
- When the enemy runs into the player, the game will instantly end. I set up a trigger which would end the game and show a game over screen.
- We opted to completely detach the controller scripts upon game over, so it’s no longer possible for the gameplay loop to continue after the game ends. I had initially opted to detach the NavMeshAgent too, but this caused errors after game over — Solomar helped with debugging this by adding extra logic to prevent navigation updates when the game has ended.
- Phase 2, the granny grab:
- Instead of instantly ending the game, the enemy should stop and grab the player after colliding, and the game should not end until the grab animation is completed. If the player runs away before the enemy finishes the grab animation, the player avoids game over. As the enemy gets faster over time, we also wanted grab time to get appropriately faster over time.
- I initially put some time into listening for the animation ending, but had some trouble getting this working — since time was short I ended up just hacking the implementation 😱 Since we know the animation duration, we can simply trigger the animation and add the game over logic on a delayed Invoke!
Gun
-
This was straightforward because Solomar had already written the logic to track the cursor 🙇 🙏 The only thing I had to do was to hook into that logic to create a bullet, and apply a force onto the enemy when a collision is detected.
-
Something that I found helpful to know about was the second parameter in
Rigidbody2D.AddForce
which allows you to specify a force mode. If you set this toForceMode2D.Impulse
it will turn the force into an instant impulse rather than assuming it’s a persistent force. This made it easier to represent the behaviour we were looking for.rb.AddForce( (new Vector2(direction.x, direction.y)).normalized * pushPower, ForceMode2D.Impulse );
💫 Wrap up
I’m a web dev so it was difficult to feel like I’d be useful; this was the reason it took me so long to try signing up for a jam at all. The most important thing I took away from the weekend was to not be scared of just diving in 🏃💨 Even if you’re worried about what you can contribute, there’ll always be your teammates to help you out!
Looking forward to next year!