Crafting My First 3D Game in Unity
Tags: unity3d, programming, beginners, programmers
Published: 2023-11-28 04:07:54

## How did it all start?
Well, the very first steps of making this game were based on the "Create Your First 3D Game in Unity" tutorial from Zenva Academy, with the intention of learning how to do it!
After a few months, I added the new stuff I'd learned here and there from other websites to it. A few weeks ago, one of my developer friends played it on my computer and encouraged me to polish it and deploy it. He believed that I should publish a blog about it and get feedback on it from the people out there to make better games in the future. Here you go, help me improve my next game with your feedback!
It's noteworthy that the whole purpose of making this game was to learn and use the essential and basic Unity assets, namespaces, classes, and build-in methods, not using fancy outstanding purchasable assets or expressing a wonderful game story. So, let's keep it simple, and I will define different projects for those matters soon!!
Wanna give it a try? Play it [here](https://atenajoon.itch.io/levelup)!

## The Game Story and Rules
In all of the levels, the player character needs to collect 20 coins and reach the end flag within 60 seconds to pass to the next level. By passing a level, the dangerous items and obstacles get increased. Some enemies eliminate the character, and others just reduce the health by 1.
* Up & down arrow keys: moving the object forward & backward (Z-axis)
* Right & left arrow keys: moving right & left (X-axis)
* Space key: jumping up (Y-axis)

## Elements and GameObjects I Created

### Scenes
I created two level scenes, one starting menu scene, and one win menu scene. Loading different scenes is done by [SceneManager](https://docs.unity3d.com/ScriptReference/SceneManagement.Scene.html).LoadScene() method, which accepts the index of the scene we want it to load. Check out the SceneManager papular methods [here](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.html).

### Player Object
* The Player character is made with the assets that came with the tutorial.
* I made an empty object named Player, added the "advancedCharacter" Model to it as a child, and set the proper Scale (11, 11, 11) and position (0, 0, 0) for the best practice.
* To make the player object respect gravity and stay on the ground, I added a [Collider](https://docs.unity3d.com/ScriptReference/Collider.html) component.
* To prevent the Player object from falling to the ground due to gravity I set the *Rigidbody Component >> Constraints tab >> "Freez Rotation" option >> check the X, Y, and Z checkboxes.*

**Note:** The [RigidBody](https://docs.unity3d.com/ScriptReference/Rigidbody.html) component applies physics to a GameObject, and Constraints checkboxes can freeze a certain axis, either the position or the rotation axis, so that the physics won't be applied to it.
* Create the "PlayerController" C# script, and attach it to the Player object to control its logic and functionality.

**Note:** The functionality of the Player object is explained here in my [**_The functionality of my Player GameObject in my first 3D_**](https://dev.to/atenajoon/the-functionality-of-my-player-gameobject-in-my-first-3d-game-1fo7-temp-slug-2349821?preview=11153d953810299f97ce7dada7ea647d9a4ba3ba04033abc85999954a6eb82fcd6c337565869894deff998b3434a2245448c9f7040dceb3fe523222c) game blog.
* The offset property is set to the Main Camera to make a bit of space between the camera and the player object.
* I used Unity InputSystem to set the game for Gamepad, following [this tutorial](https://www.youtube.com/watch?v=p-3S73MaDP8).

## Main Camera
**Note:** To make the camera follow the player object, Make it a child of the Player gameObject, and in its script file, set the transform.position equal to _target.position_. (in Unity Inspector, assign the target property to the Player object)
Offset property is set to the camera to make a bit of space between the camera and the player object.
## Dangerous Terrian and Lava Hills
This prefab is not part of the tutorial, I made it by myself. I got the lava material from the Unity Asset store [here](https://assetstore.unity.com/packages/2d/textures-materials/stylized-lava-materials-180943).
Then I created a [Terrain](https://docs.unity3d.com/Manual/terrain-UsingTerrains.html) GameObject, called LavaGround. In the Inspector, I picked the "Paint Terrian" brush (the second tab from the left side) and chose the "Paint Texture" option from the dropdown. Then clicked the "Edit Terrian Layers" and chose the Lava Asset I downloaded earlier into my materials folder. I also chose the "Rise or Lower Terrain" option to create the hills.
Then I added the LavaGround script to it, and since the Terrain collider is different from the Enemy's box Collider, I used [Collision.collider](https://docs.unity3d.com/ScriptReference/Collision-collider.html) method to check if the player touched the LavaGround. Read more about the Collision class [here](https://docs.unity3d.com/ScriptReference/Collision.html).
I might make the lava ground move with an offset to make it more dynamic.
## Leaderboard Canvas
For the Leaderboard, I used [Canvas](https://docs.unity3d.com/ScriptReference/Canvas.html), which is a game object that contains all of the different UI elements that we wanna display on screen. It is supposed to show our collected Coins, Health, and Timer. Later on, I also added the Flashing "Game Over" message to it.

**Note:** I might add a "Pause" and a "Speaker" button to it.

The health text blinks on touching the bonfires. To do so, I kept switching its activation via _SetActive(true/false);_

If the player reaches the EndFlag before collecting all coins, the leftover coins' text flashes red for 1.5 seconds. For the red flash functionality, I made another exact same text object, placed it on top of the first one, and switched its scale between 0 and 1 on the desired moment via _GameObject.transform.localScale = new Vector3(0,0,0);_ [ref](https://discussions.unity.com/t/how-can-i-hide-a-gameobject-without-active-false/64919)
To add the color background, I had to add an "image" component to it.
## Timer
TextMeshProUGUI is used to display the timer's characters. I followed [this tutorial](https://www.youtube.com/watch?v=27uKJvOpdYw) to make the timer, and then I deactivated the minutes and separator components since the game round was just 60 seconds. I also added the "GameOver" object to the Timer script code and made it flash for 5 seconds along with 00 seconds, and then called the Start-Menu scene afterward. The Player object moveSpeed is set to 0 at GameOver time. To reach the Player's moveSpeed property from another component, I used: _[GameObject.FindWithTag](https://docs.unity3d.com/ScriptReference/GameObject.FindWithTag.html)("Player").GetComponent<PlayerController>();_
The timer is set to 00:00 by default, but it's being reset at the Start() method. In the Update() method we keep reducing deltaTime from the timer as long as the timer is more than 0, and we call Flash() once it reaches 0. I used [String.Format](https://learn.microsoft.com/en-us/dotnet/api/system.string.format?view=net-5.0#Starting)() in UpdateTimerDisplay().
## Enemies
Enemy is a simple red cube from the same tutorial that moves back and forth between two positions with the specified speed. This game object passes through any other objects in the game but the Player. Touching the Enemy kills the Player and restarts the same scene.
* In the Update method of the Enemy script, we used the [MoveTowards()](https://docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html) method to make the enemy object move between two positions.
```
// Update is called once per frame
    void Update()
    {
        transform.position = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);

        if(transform.position == targetPos)
        {
            if(targetPos == startPos)
            {
                targetPos = startPos + moveOffset;
            }
            else
            {
                targetPos = startPos;
            }
        }
    }
```
**Note:** Speed is gonna move us at the rate of "speed" per frame. What I needed was moving 1 unit per second, not per frame. That's why I am multiplying speed by [Time.deltaTime](https://docs.unity3d.com/ScriptReference/Time-deltaTime.html).
* When the Player object overlaps an Enemy object, the _OnTriggerEnter()_ method is called.
```
private void OnTriggerEnter(Collider other)
    {
        if(other.CompareTag("Player"))
        {
            other.GetComponent<PlayerController>().GameOver();
        }
    }
```
**Note:** "other" is the object the Enemy hit, which is the Player object here. First I checked the object's tag to ensure it's the Player. Then from that object, I called the GameOver() method from the PlayerController component using [GetComponent](https://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html).
**Note:** The [**OnTriggerEnter(Collider)**](https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html) method is very similar to the [**OnCollisionEnter(Collision)**](https://docs.unity3d.com/ScriptReference/Collider.OnCollisionEnter.html), but instead of detecting if two objects hit each other, it detects if they **overlap**.
## Bonfires and Smoke Particles
* Touching the bonfire objects reduces the "Health" by 1. This prefab was not part of the Zenva tutorial, I added it by myself.
* This object is created with 3 different objects:

Tim

FireParticles

BurnEffect
* I have used Unity ParticleSystem to make the fire and the smoke. [ref-1](https://www.youtube.com/watch?v=TEZm8cfJnDg), [ref-2](https://www.youtube.com/watch?v=FEA1wTMJAR0), [ref-3](https://www.youtube.com/watch?v=m0fjrQkaES4)
* The smoke [Triggers](https://docs.unity3d.com/Manual/PartSysTriggersModule.html) in case of Player colliding. Learn more about the ParticleSystem [here](https://learn.unity.com/tutorial/introduction-to-particle-systems).

## Coins

The coin object was created the same way as the Enemy object and got a script assigned to it.
```
void Update()
    {
        transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime);
        
    }

    private void OnTriggerEnter(Collider other)
    {
        if(other.CompareTag("Player"))
        {
            other.GetComponent<PlayerController>().TotalCoins(1);
            Destroy(gameObject);
        }
    }
```
**Note:** Since I wanted the coin to rotate around the Y axis, I passed  "Vector3.up" as the first argument and the desired rotation speed as the second argument to the **transform.Rotate()** method.
## Level Creation
Before making the levels, I made all of the mutually used game objects like the Player, Enemy, Count, and MainCamera prefabs, so that I can use them all in all of the levels without having to remake them over and over.

Then I made the Platforms prefabs and added them to the scene. Then I set the lighting:
1. Click the Auto Generate Lighting to open the lighting window
2. Make a New lighting
3. Click the Auto Generate checkbox
4. You can also play around with the Directional Light

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79gg98c9j3tomba61z0l.png)
Then I made the EndFlag prefab.

## EndFlag
1. Made the EndFlag prefab
2. Created the Level-2 scene
3. Added the second scene to the scenes list on:
File >> Build Settings…
Drag and drop the scenes into the "Scenes in Build" list

**Note:** The scenes are being called based on their index number in the list.

Then in the EndFlag script, I set conditions to change the level in case the Player object collides with the flag, and all of the coins are collected. [SceneManager](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.html).LoadScene() method can accept the scene index number or its name as an argument. Read more [here](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html).

## Start Menu
For each menu scene, I created a separate canvas to display. In each menu scene's Main camera inspector, I changed the skybox option to a solid color. Then I added 1 TextMashPro and 2 Button-TextMashPro objects from the UI tab, named "Play", and "Quit!".
Each of them is calling a method on the Menu script, which is attached to our canvas. To do so, I dragged and dropped the Canvas object to the onClick event listener in the inspector, and picked the relevant method for each button.

## Audio & Sound Management
Following [this tutorial](https://www.youtube.com/watch?v=eNi67i5my84), we need 3 things:

- Creating an empty object called AudioManager
- Sound Source Component (in the AudioManager EmptyObject)
- Audio Listener Component (in the Main Camera Object)

I recorded my own voice with Mac's VoiceMemo application and converted the m4a file to mp3 on [CloudConvert](https://cloudconvert.com/m4a-to-mp3).

## Build and Deployment
I built the app locally and published it on [itch.io](https://itch.io/).

## Improvement Ideas & Optimization

- Adding some movement and animation to the dangerous ground and the Player game object
- Adding more sound effects to the Player's movements and background music
- using more decent audio and material assets from the Unity Asset Store to give it a more professional look
- Adding more enemies for the next levels and increasing the type of challenges
- Adding the "Pause" and "Mute" buttons to the Leaderboard.

## Conclusion
Making my first 3D game in Unity was not just about pixels and code; it was about getting real and trusting the little voice in my head saying: "Just do it, it'll get easier next time!". While the initial intention of crafting this little game was to showcase what I knew about Unity and game development, it actually opened my eyes to the world of my unknowns. As a result, now I have a longer To_Learn list!