Bubble Struggle Replica

Bubble Struggle Replica is a game prototype I built while following along with Brackey’s How to make a Bubble Struggle replica in Unity tutorial on YouTube. The core gameplay loop is:

  • Move left and right
  • Avoid the balls
  • Shoot all the balls

Play the game in your browser here.

View my GitHub repo here.

Features

  • Single-screen level
  • Player character
  • Player death
  • Player shoots chain
  • Chain continues upward trajectory until it hits something
  • Bouncing enemy ball
  • Enemy ball splits in two when hit with the chain
  • Enemy ball splits five times
  • Game resets on player death

What did I like about building this prototype?

One of my favourite classic retro games is Pang. The gameplay loop was very satisfying, and the music and artwork was charming and memorable. So it was a joy to prototype something inspired by that.

Implementing ball splitting in this prototype was interesting. It involved instantiating two smaller versions of the ball one vector to the left and one vector to the right as soon as the player’s chain collides with the initial ball, and then immediately adding a ForceMode2D.Impulse force to the two new balls.

The deployment of the chain from the player was quite clever, yet simple. Using a basic square block sprite, the code extends the local scale (size) of the square block upward at a set speed. It continues to extend upwards until it collides with a ball or the ceiling.

Player movement uses Rigidbody.MovePosition, which transitions from an initial position to the next position, dictated by a movement speed combined with the input from a controller/keyboard. It results in smooth and consistent movement.

Also as part of player movement, I liked using Input.GetAxisRaw (instead of Input.GetAxis). GetAxisRaw feels much snappier than GetAxis, meaning that the player can instantly stop once a button/key is no longer pressed.

What could have been improved?

Using Time.fixedDeltaTime in FixedUpdate seems redundant. According to Unity’s own documentation:

For reading the delta time it is recommended to use Time.deltaTime instead because it automatically returns the right delta time if you are inside a FixedUpdate function or Update function.

Using Collision.collider.tag also does not seem optimal. CompareTag would be a better choice, since it includes validation of whether the tag exists, and is also more efficient from a performance perspective:

Another unexpected cause of heap allocations can be found in the functions GameObject.name or GameObject.tag. Both of these are accessors that return new strings, which means that calling these functions will generate garbage. Caching the value may be useful, but in this case there is a related Unity function that we can use instead. To check a GameObject’s tag against a value without generating garbage, we can use GameObject.CompareTag().

There are fields (variables) in this prototype that are public that shouldn’t be. The danger in having public fields is that any other script could potentially modify these values. While this isn’t much of a concern in a small prototype like this, it could make debugging a nightmare for a much bigger project.

There are two ways of dealing with this, depending on the reason why the field was made public to begin with. If it was made public so that it’s visible and editable in the Inspector, then it’s better to use SerializeField, which does not allow other scripts to access or edit the value.

But if the field was made public because other scripts need to access that field, it’s preferable to do that through its own method or properties (eg. a getter, a setter, or both) instead.

Using Instantiate for the ball splitting mechanic is also not optimal. Like most improvements mentioned above, this isn’t really a problem for such a small prototype, but could present performance issues for a bigger game that instantiates more objects simultaneously, and more frequently. In that situation, the CPU would need to allocate considerably more resources to manage the constant cycle of creating and removing these objects (the latter which uses Unity’s Garbage Collection). For those sorts of scenarios, it would be better to use a design pattern, such as an object pool. An object pool pre-instantiates all the objects a scene will need into a pool before gameplay begins. The game then reuses the objects in this pool by activating and deactivating them, which is much less costly in terms of CPU processing power.

Code Recipes

Whenever I build anything, I look for opportunities to create reusable code that I can repurpose elsewhere. This helps me with the learning process, as well as being helpful to me and others on future prototypes and projects.

I create “gists” in GitHub to store these reusable code recipes. Here are the gists I created after building this prototype:

Unity and C# Documentation