Unity for Windows X–Finishing “SteamLands”, Part 5

image

Welcome to the final part of the “SteamLands” tutorial series, where we will complete the game!

In this tutorial, we are going to modify minor stuff to the game, add some power ups and add another enemy. We are also going to handle the main menu. I won’t be as detailed as in the earlier tutorials since you should know these things by now so congratulations! Smilefjes

This tutorial might seem very long – but fear not, there is a lot of source code listings and images. Anyways, hope you will learn a lot! Smilefjes

Download the project, source and assets of the complete game here.

I. Fixing the Intertia Tensor exception

Let’s start with fixing a few issues from the previous tutorials. I waited with fixing them because I didn’t want to give you evertything at once.

The first one is the issue that you propbably have notices – an exception saying there is an issue with some kind of inertia tension:

“Actor::updateMassFromShapes: Compute mesh inertia tensor failed for one of the actor’s mesh shapes! Please change mesh geometry or supply a tensor manually!”

The reason for this is that there is Mesh Colliders attached to all our Quads (except for the background, we removed it alread).

Go to the Players Texture Game Object and remove the Mesh Collider component:
image

Do the same for the Enemy prefab and the Laser prefab.

Play the game again and notice that the exceptions are gone! Smilefjes

II. Fixing the Enemy

First of all, the size of our enemy is a bit too small. The prefab spawns the enemy at the scale 1,1, but it should be 1.28, 0.93 to reflect the texture size. Change this in the prefab of the Enemy:
image

Also, change the shader of the enemys texture game object to the same we used for our player:
image

Then change the collision box for the enemy:
image

The EnemyHandler script was also changed so the enemies won’t spawn outside of the sidewalk, and how they are spawned (so it doesnt get insane).

 

III. Fixing the player

The players level list is essensial. Right now, the level up system is linear. We will change it so you will need more and more xp before leveling up for each level you get.

Change the level list builder code in the PlayerHandler to this:

void Start () {
    int currentXp = 0;
    int xpToIncrease = 50;

    for (int i = 0; i < 20; i++)
    {
        currentXp += xpToIncrease;
        levelList.Add(currentXp);
        xpToIncrease = (int)(xpToIncrease * 1.25);
    }

    playerHandler = this.GetComponent<PlayerHandler>();

}

The levels list now look like this:

image

This means that we must introduce more enemies as well!

IV. Adding more enemies

Let’s add a new enemy by duplicating the exsiting enemy and call it enemy2.
image

Let’s also give the enemy HP and the same hit effect that the player got. Open the EnemyHandler and add a new public variable that will keep track of the enemy HP:
public int Hp;

Change the routine that kills the enemy so it subracts the HP. If HP is less or equal zero, we destroy it:

void OnCollisionEnter(Collision collision)
{
    if (collision.rigidbody != null)
    {
        if (collision.rigidbody.name == “Player”)
        {
            Instantiate(explodePrefab, this.transform.position, Quaternion.identity);
            Instantiate(smokePrefab, this.transform.position, Quaternion.identity);
            Destroy(this.gameObject);
        }

        if (collision.rigidbody.tag == “Laser”)
        {
            Hp -= 1;
               
           Instantiate(smokePrefab, this.transform.position, Quaternion.identity);

            if (Hp <= 0)
            {
                isDead = true;
                Destroy(collision.gameObject);

                if (animationHandler != null)
                {
                    animationHandler.playAnimationSetNumber = 2;
                }
            }
        }
    }
}

On the Enemy2 prefab, change the Hp to 2:
image

(Notice that we don’t need to set the Hp if the enemy is just having one life. This is because our test for Hp is inside the collision function, so it won’t run before we hit it with a laser).

In the GameHandler script, add a new public variable that will contain the Enemy2 prefab:
public GameObject enemy2;

Perfect, now we need to spawn enemy2 randomly after turning level 2.

using UnityEngine;
using System.Collections;
public class GameHandler : MonoBehaviour
{
    public int level;

    public GameObject enemy;
    public GameObject enemy2;

    public GameObject player;

    public PlayerHandler playerHandler;

    public GUIText levelLabel;
    public GUIText xpLabel;

    float colorModifier = 1.0f;

    float spawnNewEnemyTimer = 2;

    // Use this for initialization
    void Start()
    {
        playerHandler = player.GetComponent<PlayerHandler>();
    }

    // Update is called once per frame
    void Update()
    {
        spawnNewEnemyTimer -= Time.deltaTime;
        if (spawnNewEnemyTimer <= 0)
        {
            spawnNewEnemyTimer = 5;

            int spawnNumberOfEnemies = 1 + (level/1);

            for (int i = 0; i < spawnNumberOfEnemies; i++)
            {
                GameObject enemyToSpawn;
                enemyToSpawn = enemy;

                if (level > 2)
                {
                    float rndEnemy = Random.Range(0.0f, 1.0f);
                    if (rndEnemy >= 0.5)
                    {
                        enemyToSpawn = enemy;
                    }
                    else
                    {
                        enemyToSpawn = enemy2;
                    }
                }

                float modifier = Random.Range(-1.0f, 1.0f) * 3;

                Instantiate(enemyToSpawn, new Vector3(player.transform.position.x + 20.0f + i * 3,
                       player.transform.position.y + modifier, 0.0f), Quaternion.identity);
            }

        }

        if (playerHandler != null)
        {
            level = playerHandler.Level;
            levelLabel.text = “Level ” + (level+1);

            int xpInLevel = playerHandler.Xp;
            if (level > 0)
            {
                xpInLevel = playerHandler.Xp – playerHandler.levelList[level – 1];
            }

            int xpForNextLevel = (playerHandler.levelList[level]);
            if(level > 0)
                xpForNextLevel = playerHandler.levelList[level] – playerHandler.levelList[level – 1];

            xpLabel.text = xpInLevel + “/” + xpForNextLevel;
        }
    }
}

Set the enemy2 variable in the editor:
image

Also, set the Texture vaiable in both Enemy and Enemy2 prefabs to their own Texture Game Object:

image
image

We also want to set the texture of the new Enemy2 prefab to another one. Add this texture to the Enemy Texture folder (can be found in this chapters assets folder):

image

01_bot

Duplicate the enemy material, name it Enemy2Material and assign the new texture to it.

Drag it on the Enemy2Prefabs Texture Game Object, inside the Materials collection:
image

Now, change the size of the Enemy2Prefab main game object:
image

And the collision box:
image

 

 

 

 

 

 

 

V. Drawing our health bar

The next thing we want to do is to have a UI for the Hp our player got left.

Duplicate the LevelLabel and rename it to HpLabel, and set the following properties:

image

 

Now, modify the GameHandler script so we take this label and set it to our HP:

using UnityEngine;
using System.Collections;
public class GameHandler : MonoBehaviour
{
    public int level;

    public GameObject enemy;
    public GameObject enemy2;

    public GameObject player;

    public PlayerHandler playerHandler;

    public GUIText levelLabel;
    public GUIText xpLabel;
    public GUIText hpLabel;

    float colorModifier = 1.0f;

    float spawnNewEnemyTimer = 2;

    // Use this for initialization
    void Start()
    {
        playerHandler = player.GetComponent<PlayerHandler>();
    }

    // Update is called once per frame
    void Update()
    {
        spawnNewEnemyTimer -= Time.deltaTime;
        if (spawnNewEnemyTimer <= 0)
        {
            spawnNewEnemyTimer = 5;

            int spawnNumberOfEnemies = 1 + (level/2);

            for (int i = 0; i < spawnNumberOfEnemies; i++)
            {
                GameObject enemyToSpawn;
                enemyToSpawn = enemy;

                if (level > 2)
                {
                    float rndEnemy = Random.Range(0.0f, 1.0f);
                    if (rndEnemy > 0.5)
                    {
                        enemyToSpawn = enemy;
                    }
                    else
                    {
                        enemyToSpawn = enemy2;
                    }
                }

                float modifier = Random.Range(-1.0f, 1.0f) * 3;

                Instantiate(enemyToSpawn, new Vector3(player.transform.position.x + 20.0f + i * 3,
                       player.transform.position.y + modifier, 0.0f), Quaternion.identity);
            }

        }

        if (playerHandler != null)
        {
            level = playerHandler.Level;
            levelLabel.text = “Level ” + (level+1);

            int xpInLevel = playerHandler.Xp;
            if (level > 0)
            {
                xpInLevel = playerHandler.Xp – playerHandler.levelList[level – 1];
            }

            int xpForNextLevel = (playerHandler.levelList[level]);
            if(level > 0)
                xpForNextLevel = playerHandler.levelList[level] – playerHandler.levelList[level – 1];

            xpLabel.text = xpInLevel + “/” + xpForNextLevel;

           hpLabel.text = “[ “;
            for (int i = 0; i < playerHandler.HP; i++)
            {
                hpLabel.text += “|”;
            }
            hpLabel.text += “] “;

            if (playerHandler.HP <= 3)
                hpLabel.color = Color.red;
            else hpLabel.color = new Color(88.0f / 255.0f, 82.0f / 255.0f, 75.0f / 255.0f);

        }
    }
}

Remember to set the label in the Main Cameras properties:

image

This draws a string of characters (|) based on how much Hp you got left. Then this is set to a color based on how low your hp is.

 

VI. Adding power-ups

The next thing we want is a power-up that enables us to refill our Hp! Add a new GameObject to the scene and name it “HPPowerUp”.

image

 

Then create a new Material called HPPowerUp and add this texture to it (can also be found in the assets folder of this tutorial):
image

Set the texture to GUI:
image

Add a new Quad to the HPPowerUp Game Object and name it Texture. Then apply the new material to it. As for the Materials Shader, choose the same as we did for our player and enemy:

image

Set the size of the game object:

 

Add a new tag “PowerUp”:
image

And set it to our HPPowerUp:
image

Create a prefab out of it (in the prefrabs folder) and delete it from the scene:
image

Also, set the scale of the transform, remove the Mesh Renderer from the Texture of the powerup, as well as adding a RigidBody and a Box Collider to the HPPowerUp Game Object:

image

In the GameHandler, we are adding a new public variable that holds the powerup, and then create some logic to instanciate at random:

using UnityEngine;
using System.Collections;
public class GameHandler : MonoBehaviour
{
    public int level;

    public GameObject enemy;
    public GameObject enemy2;

    public GameObject player;

    public PlayerHandler playerHandler;

    public GUIText levelLabel;
    public GUIText xpLabel;
    public GUIText hpLabel;

    public GameObject hpPowerUp;

    float colorModifier = 1.0f;

    float spawnNewEnemyTimer = 2;

    // Use this for initialization
    void Start()
    {
        playerHandler = player.GetComponent<PlayerHandler>();
    }

    // Update is called once per frame
    void Update()
    {
        spawnNewEnemyTimer -= Time.deltaTime;
        if (spawnNewEnemyTimer <= 0)
        {
            spawnNewEnemyTimer = 5;

            int spawnNumberOfEnemies = 1 + (level/2);

            for (int i = 0; i < spawnNumberOfEnemies; i++)
            {
                GameObject enemyToSpawn;
                enemyToSpawn = enemy;

                if (level > 2)
                {
                    float rndEnemy = Random.Range(0.0f, 1.0f);
                    if (rndEnemy > 0.5)
                    {
                        enemyToSpawn = enemy;
                    }
                    else
                    {
                        enemyToSpawn = enemy2;
                    }
                }

                float modifier = Random.Range(-1.0f, 1.0f) * 3;

                Instantiate(enemyToSpawn, new Vector3(player.transform.position.x + 20.0f + i * 3,
                       player.transform.position.y + modifier, 0.0f), Quaternion.identity);

            }

            float rndPowerupHp = Random.Range(0.0f, 1.0f);
            if (rndPowerupHp < 0.1)
            {
                Instantiate(hpPowerUp, new Vector3(player.transform.position.x + 30.0f,
                       player.transform.position.y, 0.0f), Quaternion.identity);
            }

        }

        if (playerHandler != null)
        {
            level = playerHandler.Level;
            levelLabel.text = “Level ” + (level+1);

            int xpInLevel = playerHandler.Xp;
            if (level > 0)
            {
                xpInLevel = playerHandler.Xp – playerHandler.levelList[level – 1];
            }

            int xpForNextLevel = (playerHandler.levelList[level]);
            if(level > 0)
                xpForNextLevel = playerHandler.levelList[level] – playerHandler.levelList[level – 1];

            xpLabel.text = xpInLevel + “/” + xpForNextLevel;

            hpLabel.text = “[ “;
            for (int i = 0; i < playerHandler.HP; i++)
            {
                hpLabel.text += “|”;
            }
            hpLabel.text += ” ] “;
            if (playerHandler.HP <= 3)
                hpLabel.color = Color.red;
            else hpLabel.color = new Color(88.0f / 255.0f, 82.0f / 255.0f, 75.0f / 255.0f);
        }
    }
}

In the PlayerHandler script, we check for the collision of the powerup and then we destroy it and add Hp (cap at 10 hp).

if (collision.rigidbody.tag == “PowerUp”)
{
    Destroy(collision.gameObject);
    HP += 5;
    if (HP > 10)
        HP = 10;
}

What is the goal of the game? To get as much Xp and Levels as possible. When it’s game over, we will store the best level and score in the PlayerPrefs.

VII. Adding some scenery items

We can use the same technique as we did with the powerups (except we dont need collisions) to spawn buildings.

We add two new Materials, one for the tower and one for the factory:
image

image

(remember to set both textures to GUI)

And then we create a new GameObject with a Texture for each of these two buildings and assign the Materials to them, they way we have been doing in this tutorial.

We then add a new script to the scripts folder that is named GameObjectDestroyer.

This script will automatically destroy the game objects that are outside of the screen:

using UnityEngine;
using System.Collections;

public class GameObjectDestroyer : MonoBehaviour {
    GameObject player;

    // Use this for initialization
    void Start () {
        GameObject _p = GameObject.Find(“Player”);
        if (_p != null)
        {
            player = _p;
        }
    }
   
    // Update is called once per frame
    void Update () {
        if (player != null)
        {
            if (this.transform.position.x <= player.transform.position.x – 20.0f)
            {
                Destroy(this.gameObject);
            }
        }
    }
}

Then we add these as a reference in the GameHandler script and spawn them randomly, as we did with the HP Power Up:

float rndBuilding = Random.Range(0.0f, 1.0f);
if (rndPowerupHp < 0.5)
{
    GameObject whatBuilding = building1;

    float rndWhatBulding = Random.Range(0.0f, 1.0f);

    if (rndWhatBulding > 0.5)
    {
        whatBuilding = building1;
    }
    else
    {
        whatBuilding = building2;
    }

    Instantiate(whatBuilding, new Vector3(player.transform.position.x + 30.0f,
            0.0f, 0.0f), Quaternion.identity);
}

image

image

VIII. Creating the main menu and the Game Over scene

Find the two textures SteamLandsLogo and GameOver, and set them to GUI.

Next, create a new scene and save it as MainMenu.

Add a new GUI Texture and name it MainTexture:
image

 

Now, set the MainMenuLogo as the texture, and set the following properties:

image

Create a new script called MainMenu, add it to the Main Camera and modify it:

using UnityEngine;
using System.Collections;

public class MainMenu : MonoBehaviour {
    float startTimer = 2.0f;
    // Use this for initialization
    void Start () {
   
    }
   
    // Update is called once per frame
    void Update () {
        startTimer -= Time.deltaTime;

        if (startTimer <= 0)
        {
            if (Input.GetMouseButtonDown(0))
            {
                Application.LoadLevel(“Level”);
            }
        }
    }
}

The startTimer is there to make sure we aren’t loading the scene since “as many impatiently tap or click the mouse while loading”.

Also, click the Main Camera and set the background to:
image

image

 

Click File->Build Settings and then Add the current scene:

image

Now, load the Level scene and do the same thing to add it:

image

 

Add a new scene and save it as GameOver, do the same procedure as when creating the MainMenu scene – but now use the GameOver texture.

Add a GUI Text Game Object to the scene and name it “ScoreLabel”:

image

Create a GameOver script (instead of MainMenu) and add it to the Main Camera.

using UnityEngine;
using System.Collections;

public class GameOver : MonoBehaviour {
    float gameOverTimer = 5.0f;
    public GUIText scoreLabel;

    int score = 0;
    int level = 0;
    int highscore = 0;

    // Use this for initialization
    void Start () {
        score = PlayerPrefs.GetInt(“Score”, 0);
        level = PlayerPrefs.GetInt(“Level”, 1);
        highscore = PlayerPrefs.GetInt(“HighScore”, 0);

        if (score > highscore)
        {
            highscore = score;
        }

        scoreLabel.text = “Level ” + level + ” / Score: ” + score + “\nHigh score: ” + highscore;
    }
   
    // Update is called once per frame
    void Update () {
        gameOverTimer -= Time.deltaTime;
        if (gameOverTimer <= 0)
        {
            scoreLabel.text = “Tap to restart”;
            if(Input.GetMouseButtonDown(0))
            {
                Application.LoadLevel(“MainMenu”);
            }
        }
    }
}

The last thing we need to do is to check in the PlayerHandler that it’s game over. If it’s game over, set the dead animation, store the score and go to the GameOver scene:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PlayerHandler : MonoBehaviour {
    float speed = 4.0f;
    public GameObject laserPrefab;
    public GameObject textureObject;

    float shootTimer = 0.0f;
    float setShootTimerTo = 0.5f;

    public int Xp = 0;
    public int Level = 0;

    public List<int> levelList = new List<int>();
    PlayerHandler playerHandler;

    public int HP;
    float colorModifier = 1.0f;

    bool isGameOver = false;
    float gameOverTimer = 3.0f;

    AnimationHandler animationHandler;

    void OnCollisionEnter(Collision collision)
    {
        if (collision.rigidbody != null)
        {
            if (collision.rigidbody.tag == “Enemy”)
            {
                HP -= 1;
                colorModifier = 0.0f;
            }

            if (collision.rigidbody.tag == “PowerUp”)
            {
                Destroy(collision.gameObject);
                HP += 5;
                if (HP > 10)
                    HP = 10;
            }
        }
    }

    // Use this for initialization
    void Start () {
        int currentXp = 0;
        int xpToIncrease = 50;

        for (int i = 0; i < 20; i++)
        {
            currentXp += xpToIncrease;
            levelList.Add(currentXp);
            xpToIncrease = (int)(xpToIncrease * 1.25);
        }

        playerHandler = this.GetComponent<PlayerHandler>();
        animationHandler = this.GetComponent<AnimationHandler>();

    }
   
    // Update is called once per frame
    void Update () {
        if (HP <= 0)
        {
            isGameOver = true;
            animationHandler.playAnimationSetNumber = 2;
            gameOverTimer -= Time.deltaTime;

            if (gameOverTimer <= 0.0f)
            {
                PlayerPrefs.SetInt(“Score”, Xp);
                PlayerPrefs.SetInt(“Level”, Level);

                Application.LoadLevel(“GameOver”);
            }
        }

 

        if (!isGameOver)
        {

            Vector3 movePlayerVector = Vector3.right;
            shootTimer -= Time.deltaTime;

            textureObject.gameObject.renderer.material.color = new Color(1.0f, colorModifier, colorModifier);

            if (colorModifier < 1.0f)
                colorModifier += Time.deltaTime;

            if (Input.GetMouseButton(0))
            {
                Vector3 touchWorldPoint = Camera.main.ScreenToWorldPoint(
                    new Vector3(Input.mousePosition.x,
                                Input.mousePosition.y,
                                10.0f));

                if (touchWorldPoint.x < this.transform.position.x + 5.0f)
                {
                    if (touchWorldPoint.y > this.transform.position.y)
                    {
                        movePlayerVector.y = 1.0f;
                    }
                    else movePlayerVector.y = -1.0f;
                }
                else
                {
                    if (shootTimer <= 0)
                    {
                        Vector3 shootPos = this.transform.position;
                        shootPos.x += 2;
                        Instantiate(laserPrefab, shootPos, Quaternion.identity);
                        shootTimer = setShootTimerTo;
                    }
                }
            }

            this.transform.position += movePlayerVector * Time.deltaTime * speed;

            if (transform.position.y > -2.0)
            {
                transform.position = new Vector3(transform.position.x,
                                                -2.0f,
                                                transform.position.z);
            }

            if (transform.position.y < -5.5)
            {
                transform.position = new Vector3(transform.position.x,
                                                -5.5f,
                                                transform.position.z);
            }

            if (playerHandler.Xp >= levelList[Level])
            {
                Level++;
            }
        }
    }
}


 

This script is now disabling player input if game over, and then start counting for a few seconds so the player can understand he died and then we store the scores and redirect to to game over scene.

Be sure to add the GameOver scene too:
image

Additional stuff to do with this game:
You can also add a lable the show the Xp (Score) the player have revieved:
image

IX. Fun stuff to add – and is added in this projects tutorial solutiuon

This is a few minor things you can add to your game – it’s easy to implement and you should be capable to do it. Are you ready for some excerises?

And you could add more enemies and change how the levels affects spawing new stuff.

Also, refactoring of the Game Logic and code would be a good ide – I wrote it as simple as possible so it would be easier to understand the conecpts. You should put more stuff in functions and follow good programming practices and arcitecture.

You can change how much the potion gives you in Hp, 5 is a lot.

Also, when you crash in to another player – some times you

X. Publishing your game

Follow tutorial 3 and/or 4 to publish your game to Windows Store or Windows Phone Store. Do it now, and grab the potential of a new and growing market!

Good luck with your game development! Smilefjes

 

XI. Conclusion

This concludes the “SteamLands” tutorial. The next tutorial in this series will cover how you can write plugins to add the functionality of InApp purchases and programming the Live Tile!

 

Download the project, source and assets of the complete game here.

This entry was posted in Tutorial, Unity. Bookmark the permalink.

1 Response to Unity for Windows X–Finishing “SteamLands”, Part 5

  1. Pingback: Unity Game Starter Kit for Windows Store and Windows Phone Store games | digitalerr0r

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.