Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
Tags

Coding Convention/Tips/Tricks Chat

A topic by MonkeyWrenchSoft created Aug 11, 2021 Views: 217 Replies: 5
Viewing posts 1 to 5
Submitted(+1)

Hi everyone! The boss rush jam has been a lot of fun, and has been a huge learning experience! Seeing everyone else's game, I thought it would be a good opportunity to learn from each other.

Feel free to post coding/technical tips you learned, or help others out with improvements to their implemented methods!

I'll start with my implementation of melee combat
At first I started with RaycastHit2D implementations, specifically circle casts. They proved to be easy to use, but have problems.

I wanted the sword to hit based on the animation, so there is a wind up, an aoe, and an active timed hitbox. The raycast could be delayed via Invokes, but that did not allow for an active hitbox over time, rather it only had a single frame of casts. So sometimes when it looked like the player should have hit the enemy, it was either too soon or too late.

I looked into fighting game hitboxes and collisions and Frankensteined a solution! First, I created a separate collisions box from the player. I fit the collision to be in the range of the sword swing


Next I set the hitbox to isTrigger, and added a MonoBehaviour script with one OnTriggerEnter2D function


Then I added a reference to the GameObject in the Player class, so that sword object is turned on and off appropriately in the attack function

MoveSwordCol() moves and rotates the sword collider based on default sprite positions, so that the hitbox always fits correctly




It's not the cleanest implementation, but it certainly got the job done! If there is a better ( more conventional) implementation, or if there are mistakes I am happy to listen!
Submitted (2 edits)

Interestingly, I did nearly the same in Godot ! A tutorial from KidsCanCode recommended to use an AnimationPlayer to toggle the collider on and off (since in Godot everything can be animated) so you don’t need to write code to toggle it. Since my gameplay components were split among several scenes, it was simpler for me to use a timer variable though.

I was very generous with the hit collider, but I think it was the right choice - it fits the animation well and gives a bigger feeling of control to the player.

Now, a thing I discovered and used for the first time during this jam was object pooling ! In every engine and language, creating a new object has a cost. In Godot, since you’re often loading other scenes (which is similar to prefabs in Unity) this cost can be reduced by loading the scene at the start and instancing it when needed. But even that is slow because the instance needs to be added to the scene tree.

For a single object that is only created once, or created multiple times but at very large intervals, this is perfectly fine. But in a bullet-hell where hundreds of bullets are created each second, this will slow down the game extremely fast !

In our game, we have a bullet-hell boss that shoots around 10 bullets/s. The player’s bullets use instancing, but it wasn’t gonna be possible for this boss. The solution to this problem is bullet pooling: You instance a set number of objects at the start of the scene, and just show/hide them when needed !

Here’s how I do it for the Flesh Monolith:

  • at the start of the scene, I instance a limited amount of bullets (200 here)
  • I hide and deactivate them all, then put their references in an array and use a pool_index variable to track the current bullet
  • when the boss needs to shoot a bullet, I take the current bullet (the bullet at the pool_index in the array), show it and activate it with the wanted direction and speed. Then I increase the pool_index by 1
  • when the bullet leaves the screen or hits something, it hides itself

If you instance enough bullets at the start of the scene, it will be unnoticeable ! There’s also a version where I switched bullets from a pool_available to a pool_process array, but reindexing the array every time wasn’t the best idea.

Submitted (3 edits)

Suzieus also has some interesting collision testing logic in it [Javascript in our case, not Unity or Godot]. The bosses are big and have a lot of animations that change shape and size, and we learned from a previous attempt in this genre (https://goodbunnystudio.itch.io/suzie-demo) that fighting a big boss where the hitbox isn't related to its drawn shape and size is counterintuitive. We needed to make the boss hit detection match with the animation frames, and we had a lot of animation frames to do that with.

Instead of giving the bosses geometric hitboxes at all, we decided to take a hitscan-like approach. The alpha channel of the boss's actual sprite is what tells us which parts are collidable, at pixel precision. Since the boss is constantly scaling and rotating, without any optimizations we'd need to compute an entirely new alpha mask to test against every frame, and that would be slow using the Javascript 2D canvas API. Instead, for each stop-motion frame from the spritesheet we store the alpha mask just once, and we apply the inverse of the boss's position, scaling, and rotation to the bullet so we can test against the same mask over and over using a "boss-relative" coordinate no matter what transform we're applying to the boss's sprite.

(If we were using an API with more direct access to the GPU, we probably wouldn't have needed to use the optimization, but the game would have taken more time to code. The 2D canvas API is very convenient for making quick 2D games.)

Submitted

I ended up making a video doing an overview of how I made my game although I didn’t get into anything too technical but if people wanna know how I did something they can comment on this video and I’ll do my best to reply more in depth.

https://youtu.be/0RBuMwMJ280

Submitted

I abused coroutines for the boss patterns

Submitted

Oh, that would have helped a lot with some of Suzieus's AI! A lot of conceptually "local" variables about AI state had to be object properties instead so they wouldn't get forgotten between frames of gameplay, and with coroutines a lot of them could have been real locals.