Day 1 to Day 3 Code Review
Hey everyone!
Wow, it’s been two months since I started learning Unity. Time flies, doesn’t it?
I had a great opportunity to work on a small Unity project through learn.unity.com. Previously, I tried creating an FPS game called ‘Kill Zombie’ (you can check out the code on my GitHub if you’re interested). However, as you might expect, programming of FPS game can be quite challenging for beginners, so unfortunately, that project is still ongoing😞
I was feeling quite stressed due to the ‘Kill Zombie’ project, but since this game is very simple, working on it felt like a refreshing break.
You can play my project using this link. Please note that it’s only playable on a computer.
Go to play Flyin’ Fish ‘n’ Fowl
Now, let me explain how I programmed it.
Aug 2, 2024
1) Downloaded and Checked the Provided Assets
The scene contains a simple UI counter, a box, and several spheres placed above it. All of the spheres have Rigidbody components attached. When you play the scene, the spheres drop, and the UI counter updates to show how many of them landed in the box.
A script named Counter.cs is applied to the box mesh, which has a Box Collider set as a trigger. This trigger is scaled to the exact size of the interior of the box. Counter.cs has a public text variable for the UI counter.
Counter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Counter : MonoBehaviour
{
public Text CounterText;
private int Count = 0;
private void Start()
{
Count = 0;
}
private void OnTriggerEnter(Collider other)
{
Count += 1;
CounterText.text = "Count : " + Count;
}
}
2) Create a Design Document
Mission
The project provided to you is very basic, but there are a lot of opportunities to turn it into a variety of interesting prototypes! Take some time to think about what kind of application you’d like to make and write up a design document describing the application’s functionality. This concept phase is part of the overall exercise, so spend as much time as you need to come up with something interesting.
There are only two requirements for your new prototype:
- The prototype must still count something.
- There must be a UI element displaying that count.
Everything else is up to you! Here are a few suggestions for inspiration:
- A hoops style game: Toss a basketball into the net as many times as possible in the allotted time.
- A pachinko game: Drop several small balls down a pegboard and have each section at the bottom have a different point value.
- A stock counter: Different objects get sorted into their own spaces and then the total count is displayed for each.
- A neighborhood planner: Items found in a neighborhood, such as streets, sidewalks, houses, and streetlights, are contained in drag-and-drop style UI boxes. Users can drag out the items and plan their neighborhood design, and the UI tracks how many of each item they’ll need to build it in the real world.
To be honest, I had no idea what to do at first. The song “It’s Raining Men” kept playing in my head LOL So, I called my friend for her opinion, and she suggested a game concept involving squirrels dropping nuts. I thought it was a great idea, so I started designing my game based on her suggestion.
Thanks to SeongEun😘
It’s quite a simple concept, right?
3) Make the Player Movement
The first thing I did was implement the box movement. I created a new script, PlayerController.cs, and used Rigidbody because I needed physics for the box. I wanted the box to spill over when it was full, and using Rigidbody allowed me to detect collisions between the spheres and the inside of the box.
PlayerController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PlayerController : MonoBehaviour
{
private Rigidbody playerRb;
public float speed;
void Start()
{
playerRb = GetComponent<Rigidbody>();
}
void Update()
{
float horizontalInput = Input.GetAxis("Horizontal");
playerRb.AddForce(Vector3.right * horizontalInput * speed);
}
}
However, I encountered a problem: when the box caught a dropped sphere, its weight increased, causing the box’s movement to slow down. I considered fixing this by adjusting gravity calculations, but I was concerned about memory usage and didn’t think it was a crucial feature for this game. So, I decided to use transform.Translate()
instead of Rigidbody for the box’s movement.
PlayerController.cs
1
2
3
4
5
6
7
8
9
10
11
public class PlayerController : MonoBehaviour
{
public float speed;
void Update()
{
float horizontalInput = Input.GetAxis("Horizontal");
transform.Translate(Vector3.forward * horizontalInput * speed * Time.deltaTime);
}
}
4) Create a Game Manager and Destroy the Sphere that Collides with the Box
A Game Manager is essential when creating a game because it manages all of the game’s resources. However, at this stage, I haven’t implemented much functionality yet. For now, in the GameManager script, I’ve included the same code as in the Counter.cs script provided earlier.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class GameManager : MonoBehaviour
{
public Text CounterText;
private int Count = 0;
private void Start()
{
Count = 0;
}
public void UpdateScore()
{
Count += 1;
CounterText.text = "Count : " + Count;
}
}
I also used the OnTriggerEnter()
method in the PlayerController script to destroy the sphere when it collides with the box. The reason I chose OnTriggerEnter()
instead of OnCollisionEnter()
is that I don’t need any physics calculations for this interaction.
After destroying the sphere, the score should be updated because the box caught the sphere, right? So I called the GameManager script in the PlayerController script and used the UpdateScore()
method of GameManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PlayerController : MonoBehaviour
{
private GameManager gameManager;
public float speed;
void Start()
{
gameManager = GameObject.Find("GameManager").GetComponent<GameManager>();
}
void Update()
{
float horizontalInput = Input.GetAxis("Horizontal");
transform.Translate(Vector3.forward * horizontalInput * speed * Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
gameManager.UpdateScore();
Destroy(other.gameObject);
}
}
Aug 5, 2024
1) Spawn the Dropped Objects at Random Places
In the scene you saw earlier, the ball was initially positioned at a specific location. However, I wanted the ball to spawn at random places to increase the challenge for the player. This was straightforward to implement. I created a new script, SpawnManager.cs, and also set up a new GameObject named Spawn Manager in the Hierarchy.
SpawnManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SpawnManager : MonoBehaviour
{
public GameObject dropPrefab;
public float spawnRange = 19;
private float startDelay = 3;
private float spawnInterval = 2;
void Start()
{
InvokeRepeating("SpawnObject", startDelay, spawnInterval);
}
private void SpawnObject()
{
Instantiate(dropPrefab, GenerateSpawnPosition(), dropPrefab.transform.rotation);
}
private Vector3 GenerateSpawnPosition()
{
float spawnPosZ = Random.Range(-spawnRange, spawnRange);
Vector3 randomPos = new Vector3(0, 14, spawnPosZ);
return randomPos;
}
}
2) Destroy the Object Upon Collision with the Ground
I created a new tag for the ground and wrote a new script, FallingObjectController.cs. Here’s the initial code I wrote:
1
2
3
4
5
6
7
8
9
10
public class FallingObjectController : MonoBehaviour
{
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
Destroy(gameObject);
}
}
}
I was frustrated because the OnCollisionEnter method wasn’t working for the objects that were spawned, meaning it wasn’t being triggered for prefabs when they were instantiated. However, it worked for objects that were already in the Hierarchy.
After several hours of searching Google, I found that other people had encountered the same issue, and many had not found a solution. I eventually came across a comment in the Unity forums suggesting the use of OnTriggerEnter()
instead. I replaced OnCollisionEnter()
with OnTriggerEnter()
, and surprisingly, it worked as intended.
1
2
3
4
5
6
7
8
9
10
public class FallingObjectController : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Ground"))
{
Destroy(gameObject);
}
}
}
And I still don’t know why.
3) Asset Changes
At this point, the game allows me to spawn spheres at random positions, move a box left and right, and destroy spheres that collide with the ground or the box. These three features are central to the gameplay.
I started searching for assets, and literally I hate this time.
I needed to find free assets (because I’m poor), but most of the assets I wanted were NOT available for free. As a result, the game differs from my initial design document😇. I couldn’t find assets for branches, squirrels and nuts, so I had to make some adjustments to the game design.
So yeah, here are the assets I ended up using.
3D Props
3D model Pack Baskets : Basket/Box(Apply)
RPG Food & Drinks Pack : Fishes/Spheres should’ve been nuts(Apply)
3D Environments
LoyPoly Water : Ocean(Apply)
Super Beach Pack : Island(Apply)
3D Characters
Living Birds : Birds should’ve been squirrels(Not Yet)
2D GUI Tools
Simple Heart Health System : Player Lives(Not Yet)
Fantasy Wooden GUI : Free : Title and GameOver etc(Not Yet)
Aug 6, 2024
1) Removed Spawn Manager and Added Random Bird Spawning
Since I already have a Game Manager, so technically I didn’t need a separate Spawn Manager. I removed the Spawn Manager and integrated the bird spawning functionality into the GameManager script.
My goal was to spawn birds on either the left or right side randomly. If a bird spawns on the right side, it needs to move to the left. I configured the regular bird to start on the left side, facing right. However, when a bird spawns on the right side, it rotates to move left.
Fortunately, the bird asset pack I’m using includes six different bird types, so I implemented random selection for spawning different kinds of birds.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class GameManager : MonoBehaviour
{
...
public GameObject[] birds;
private Vector3[] birdPos = { new Vector3(13.3f, 22.62f, -13.49f), new Vector3(13.3f, 18.91f, 32.75f) };
private Quaternion birdRotation;
private int[] direction = { 0, 180 };
private float birdSpawnInterval = 4;
...
private void Start()
{
...
StartCoroutine(SpawnRandomBirds());
}
IEnumerator SpawnRandomBirds()
{
yield return new WaitForSeconds(birdSpawnInterval);
int birdIndex = Random.Range(0, birds.Length);
int birdDir = Random.Range(0, direction.Length);
birdRotation = Quaternion.Euler(0, direction[birdDir], 0);
Instantiate(birds[birdIndex], birdPos[birdDir], birdRotation);
}
...
}
I just realized that I should have used a Dictionary instead of an array. It would have made things much easier😂
2) Give the bird a forward motion
I created a new Script for the bird called FlightOfBird. Very kindly, the bird asset includes all the necessary animations, so I utilized those directly. I also made the bird to move forward and to destroy the bird object when it leaves the screen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FlightOfBird : MonoBehaviour
{
private Animator birdAni;
public float speed;
void Start()
{
birdAni = GetComponent<Animator>();
birdAni.SetBool("flying", true);
}
void Update()
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);
if (transform.position.z > 55 || transform.position.z < -33)
{
Destroy(gameObject);
}
}
}
3) Make the Fish Drop from the Bird
I needed the bird to drop fish, so I added the fish spawning functionality directly into the FlightOfBird script, not the Game Manager.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class FlightOfBird : MonoBehaviour
{
...
public GameObject[] fishes;
private GameManager gameManager;
void Start()
{
...
gameManager = GameObject.Find("GameManager").GetComponent<GameManager>();
...
StartCoroutine(SpawnRandomFishes());
}
IEnumerator SpawnRandomFishes()
{
yield return new WaitForSeconds(fishSpawnInterval);
int fishIndex = Random.Range(0, fishes.Length);
Vector3 fishPos = new Vector3(13.3f, transform.position.y, transform.position.z);
Instantiate(fishes[fishIndex], fishPos, fishes[fishIndex].transform.rotation);
}
}
I have two types of fish: fresh fish and leftover fish, which is why I used an array to manage them.
4) Check If the Fish Is Fresh or Not
After downloading a fish asset, I came up with an idea to increase the difficulty of the game. I decided to use both fresh and leftover fish assets to add a challenge: if you catch a fresh fish, you earn 1 point, but if you catch a leftover one, you lose a point.
To implement this, I added a condition in the PlayerController script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PlayerController : MonoBehaviour
{
...
private bool isFishFresh;
...
private void OnTriggerEnter(Collider other)
{
gameManager.UpdateScore();
if(other.gameObject.CompareTag("Fresh"))
{
isFishFresh = true;
} else
{
isFishFresh = false;
}
gameManager.UpdateScore(isFishFresh);
Destroy(other.gameObject);
}
}
Of course, I also made some changes to the GameManager script.
1
2
3
4
5
6
7
8
9
10
11
12
public void UpdateScore(bool fresh)
{
if(fresh)
{
Count += 1;
}
else
{
Count -= 1;
}
CounterText.text = "Fish : " + Count;
}
I haven’t implemented player lives yet, so for now, catching a leftover fish simply subtracts 1 point from the score.
Okay, I’ll be posting Days 4 and 5 soon, which will be the final updates for this project. See you soon!