DEV Community

Cover image for Crafting My First 3D Game in Unity
Atena Razm
Atena Razm

Posted on • Edited on

Crafting My First 3D Game in Unity

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!

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.LoadScene() method, which accepts the index of the scene we want it to load. Check out the SceneManager papular methods here.

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 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 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 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.

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.
Then I created a Terrain 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 method to check if the player touched the LavaGround. Read more about the Collision class here.
I might make the lava ground move with an offset to make it more dynamic.

Leaderboard Canvas

For the Leaderboard, I used Canvas, 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
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 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("Player").GetComponent();
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() 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() 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;
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

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.

  • 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();
        }
    }
Enter fullscreen mode Exit fullscreen mode

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.
Note: The OnTriggerEnter(Collider) method is very similar to the OnCollisionEnter(Collision), 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, ref-2, ref-3
  • The smoke Triggers in case of Player colliding. Learn more about the ParticleSystem here.

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);
        }
    }
Enter fullscreen mode Exit fullscreen mode

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
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.LoadScene() method can accept the scene index number or its name as an argument. Read more here.

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, 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.

Build and Deployment

I built the app locally and published it on 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!

Top comments (0)