Introduction

As part of my school project, Freelancing, I took on the development of an exciting 2D earthbending-like fighting game. This project allowed me to tackle various challenges and gain new skills.

One of the key aspects was creating a technical design for the game. I carefully planned and documented the mechanics, interactions, and overall structure to guide the development process.

During the assignment, I had the opportunity to learn and implement two new things:

  • I used the new Unity input system to enable local multiplayer functionality. This feature allowed players to enjoy the game together on the same screen, enhancing the overall experience.
  • I made a SOLID mechanism for casting earthboulders. I aimed to strike the right balance between engaging gameplay and visually appealing effects.

Moreover, I dived into 3D modeling, crafting low-poly rock models used within the game. This added an extra dimension to the gameplay and enhanced the overall aesthetics.

Additionally, I took on the task of creating all character pixel art and animations from scratch, giving the game a personalized touch.

You can check out the video below to see the final product of my efforts:

The Making of Boulder Brawl

Technical Design

For this project, I needed to create a technical design before I could actually start working on it. We already had a format from school, so it was just a matter of filling that out. You can see the results below: (Note that it is in dutch)

Casting Boulders

As you can see in the technical design, I challenged myself to create a solid script that could handle all the boulders I wanted to cast. To achieve this, I created a function that, when called, goes through all the necessary steps to spawn a boulder. First, it spawns the actual boulder, then it adds it to a list in the GameManager, which is used for cleanup if the game were to end. After that, it applies the necessary forces, and finally, it returns the boulder, completing the casting process.


/// <summary>
/// Spawns a stone and adds an upwards force but also has a different casting position. Returns the stone that it spawned so that it can be used in other code.
/// </summary>
/// <param name="stoneToCast">The stone object that needs to be casted.</param>
/// <param name="castingForce">The force that the stone needs to be lifted upwards.</param>
/// <param name="castingPos">The position the stone needs to be casted at</param>
/// <returns></returns>
public GameObject CastStones(GameObject stoneToCast, float castingForce, Transform castingPos)
{
  //Spawns stone and adds it to the list of stones.
  GameObject stone = Instantiate(stoneToCast, castingPos.position, castingPos.rotation);
  GameManager.Instance.Stones.Add(stone);
  Rigidbody2D rb = stone.GetComponent<Rigidbody2D>();
  //Add an upwards velocity.
  rb.velocity = new Vector2(rb.velocity.x, castingForce);
  //Returns the stone object.
  return stone;
}
						

Health and Damage

A fighting game without damage and health isn't very enjoyable. So, I created a system that could easily manage the players' health, and I also developed a script that enables the stones to deal damage. As you can observe in the health code snippet below, if the player takes damage, their health will be reduced by the amount of damage they receive. Additionally, if the health reaches 0 or falls below, the player is defeated, and the player who died loses a life point. The damage code snippet is quite straightforward: if a boulder collides with an object containing the IDamagable component, it inflicts damage and then destroys itself; otherwise, it simply destroys itself.


public float Health { get; set; }
public float maxHealth;

public virtual void TakeDamage(float damage)
{
	  if (GameManager.Instance.GameState == GameState.Waiting) return;
	  Health -= damage;

	  HandleDeath();
}

protected virtual void HandleDeath()
{
	  if (Health <= 0)
	  {
		  CameraShaker.Instance.ShakeOnce(1f * GameManager.Instance.ScreenShakeMultiplier, 1f * GameManager.Instance.ScreenShakeMultiplier, .1f, 1f);

		  PlayerInfo player = GetComponent<PlayerInfo>();

		  if (player == GameManager.Instance.Players[1])
			  GameManager.Instance.Player2Score--;
		  if (player == GameManager.Instance.Players[0])
			  GameManager.Instance.Player1Score--;

		  GameManager.Instance.CheckWinState();
	  }
}

public void ResetHealth()
{
	  Health = maxHealth;
}
				  

private float _impactDamage;
[SerializeField] private bool _destroyOnImpact = true;

private void OnTriggerEnter(Collider collision)
{
	  if (collision.gameObject.TryGetComponent<IDamagable>(out IDamagable other))
	  {
		  other.TakeDamage(_impactDamage);

		  DestroyOnImpact();
	  }

	  if (collision.gameObject.GetComponent<IDamagable>() == null)
		  DestroyOnImpact();
}

private void DestroyOnImpact()
{
	  if (_destroyOnImpact)
		  Destroy(this.gameObject);
}
				  

Game Art

As part of this project, I had to learn Maya and create 3D models for my game. But not only did I make the 3D models, I also created a lot of pixel art for the characters.

I wanted to make a pixel art game, but the project required that you model and texture at least one object in your game. So, I had to improvise, and the way I did that was by having two cameras. One camera is for the player and all the actual pixel art. The other camera is a bit special because it has a render texture with a very low pixel size, which gives the game this really cool effect where it looks pixelated.

Pixel Art Image

These were all the boulder models I made in Maya.

Pixel Art Image

And this is how the game looks with the camera pixel art effect.

I created the character in Aseprite, and for the animation, I used a sprite sheets as you can see in the image below. I'm not the best artist, but I still really liked how it turned out in the final game.

Pixel Art Image

Discover Boulder Brawl

If you want to play boulder brawl you can download it on my itch page right here: