RTS tutorial - Unity

Share
Rising Lands RTS Tutorial Development

This tutorial is honestly one of the best tutorials I have ever used. I had already done several Unity tutorials, but I learnt more on this single tutorial than on all of them put together.

As I follow this tutorial I will be making some changes along the way. Some will be simple unit name changes and some will be code updates to bring the project in line with Unity 5.5. I have also noticed a couple of bugs that I will fix as I document the project, although to be honest the code is still very well written. I do not plan on changing any of the asset models while following the tutorial, although I will use some of the 2D images from Rising Lands in place of the tutorial images. It is also important to note that I have not filled my assets in the same structure as the tutorial. I have followed the typical structure from the Official Unity tutorials, as best I could remember them at the time.

I decided to document my following of this tutorial and the making of my game. I learn a lot of things following this tutorial, plus I figured there are changes here that others may also be interested in. I will also be expanding the code beyond what is shown in this tutorial. I hope you enjoy following this and it is of benefit to you.

Part 1: Initial Setup

This section of the tutorial deals with setting up Unity and adding the first few objects to your new project. The only difference I had here was that, at time of writing, I am using Unity 5.5 and not 4.1 as recommended in this tutorial.

Part 2: Players and Camera

I hadn't actually started documenting my exploits by this point, but I don't believe I made many changes. I filled my scripts here differently than in the tutorial. Also the code "Camera.mainCamera." is deprecated in Unity 5.5 and should be changed to "Camera.main.". This appears in quite a few lines of code in this part.

Part 3: Heads-Up Display

No Changes.

Part 4: World Objects

Again, no changes, apart form filling. I promise this will get more exciting further down the line.

Part 5: Buildings and Basic Selection

No Changes here, but when you get to the "ChangeSelection()" method you will notice "worldObject" should be capitalised. It should have been "WorldObject" as it is case sensitive.

Part 6: Units and Selection Box

No Changes.

Part 7: Custom Mouse Cursor

No Changes. Happy days, we are flying through this tutorial.

Part 8: Basic Unit Movement

If it ain't broke don't fix it.

Part 9: Player Resources

This would be where I made the first really notable change. Here instead of using the resources in the tutorial I opted to use Food, Stone and Metal instead. That is more that the tutorial currently uses, but added in the same format. You will also need to add/update the related integers in player.cs to account for the different resources. If you are planning on writing your own game while following this tutorial then have a think about what resources you want to use now. "InitResourceList()", "AddStartResourceLimits()" and "AddStartResources()" also need to be modified to account for your resource types. "Start()" in "Hud.cs" also needs to be edited to account for the changes. When adding textures for the buttons I also added my own images here.

Part 10: Creating New Units

My units and building are named differently here along with their related scripts. At this point in the tutorial I used a Speeder and Barracks instead of Tank and War Factory. Phew, that was a long one, grab a cup of coffee. There are more like that coming up.

Part 11: Rally Point and Selling Buildings

No Changes to the logic here, but I changed the unit being referred to when selling buildings.

Part 12: Resources

This is where I created a Builder instead of a Harvester. Also I wanted to follow the game logic currently in place for the resource collection and to be honest it was late and I didn't wat to put much thought into it. Instead of creating the "Ore" resource, I created "Rock" and changed all the relevant references. My Builder harvests Rock and delivers Stone. I am sure I could have changed this to only require Stone instead and not need Rock at all, but I didn't have the confidence to deviate from the tutorial too much by this point. When creating the Harvester Unit I also created a "PickAxe" instead of "Arm" and changed the relevant code references. When I got to the Refinery section I did have the Refinery make my builder, but later on I came back to this and made my Sanctuary create the Builder. I also updated the Refinery to be a Storehouse and replaced the game objects to make it more representative of the Storehouse in Rising Lands.

Part 13: Constructing New Buildings

At this point I realised I was going to have 3 types of harvester units, but my code had all the harvester code in "Builder.cs". I renamed it to "Harvester.cs" and changed the code references to match.

At this point the tutorial goes on to create a "Worker" unit and a new script is written for a unit to be able to place buildings. However, in my game, my builder fulfils the function of both "Harvester" and "Worker" as they are so named in the tutorial. Instead of "Worker.cs", I called the new script "Builder.cs". I also had it inherit from "Harvester" and not "Unit". This way it inherits all the Unit code and the Harvester code. All the additional code will be adding to its harvesting abilities.

The code "GetComponentsInChildren<Renderers>()" needs to be "GetComponentsInChildren<Renderer>()". I don't know if this was a typo or a difference between Unity versions, but it is easily fixed.

The Issue with "Camera.mainCamera." pops up here a few times again.

By this point I was pretty much in awe of this tutorial. I am not only able to follow it, but am also developing an understanding of what is going on. I am also wanting to go ahead and add more units and building so my game is more complete, but I have decided to hold back and concentrate on finishing the tutorial first.

Part 14: Basic Combat

No Changes to the logic here, but how the renderer is defined in set team colour needs to change as the previous method is deprecated. Change "teamColor.renderer.material.color" to "teamColor.GetComponent<Renderer>().material.color".

The code variable "protected movingIntoPosition = false;" should actually be defined as "protected bool movingIntoPosition = false;". I think this was just a typo. Its a big tutorial, there are bound to be a few.

The tutorial is also missing a variable "public float weaponAimSpeed = 1.0f;", which needs to go at the top of "WorldObjects.cs" before you test the game as the end of the initiate attack section.

I didn't change the name of the tank projectile despite changing my "Tank" to "Speeder". It was a little lazy, but i figure the Speeder doesn't care what it is called as long as it can fire it.

I am not sure if I missed it somewhere else in the tutorial, but before I was able to compile my code I needed to add "using RTS;" to the top of "Speeder.cs"

I added my "TankProjectile.cs" script to the projectile instead of "Projectile.cs". It wasn't clear which one the tutorial author used unless you understand what's going on. He used "Projectile.cs". As I used "TankProjectile.cs" I had to make a couple of modifications. I changed the update method in "Projectile.cs" to be a protected virtual void and the update method in "TankProjectile.cs" to be a protected override void. I also added "base.Update();" to the latter update routine.

Part 15: Simple Menu

First off when you add the code here to open the Pause Menu you get a complie error because "Screen.showCursor" has been deprecated. Instead use "Cursor.visible".

When you test this code the select cursor still comes up over the menu, while the normal system curson should be active instead. To fix this remove the "Cursor.visible = true/false;" line from "Resume()" and "OpenPauseMenu()". 

Before finishing this section I recommend you pull up "HUD.cs" and change the line "if (mouseOverHud)" to be "if (mouseOverHud || ResourceManager.MenuOpen)". The more observant of you will have noticed someone mentioned this in the comments. I incorporated it into my code.

I took the opportunity at this point to add my additional buttons, as my buttons won;t match those in the tutorial. In the "PauseMenu.cs" script I updated the string array for the buttons to match my requirements and I updated the "OnGUI()" method to include the additional select cases. This makes the menu too small for the number of button I had.

I also made a change to how the button width was calculated in "ResourceManager.cs". The line now reads "public static float ButtonWidth { get { return ((MenuWidth - (padding * 2)) * 0.8F); } }". This takes the menu width, subtracts 2 lots of padding, leavingt the area inside the padding. This is then multiplied by 0.8 to make the button 80% of the area inside the padding. I had no need to change this, but it seemed a more logical way of calculating the button width. Each to their own.

Part 16: Main Menu

When I got to creating the main menu I did create an empty game object to put the script on. This seems more logical to me.

In the "NewGame()" method of "MainMenu.cs" the line "Application.LoadLevel("Map");" is deprecated and needs to be changed to "SceneManager.LoadScene("Map");", with "using UnityEngine.SceneManagement;" added to the top of the script.

When you add the "ReturnToMainMenu()" method it needs to be updated to the following;

    private void ReturnToMainMenu()
    {
        SceneManager.LoadScene("Map");
        Cursor.visible = true;
    }

"using UnityEngine.SceneManagement;" also needs to be added to the top of "PauseMenu.cs".

While I was editing the Pause Menu to allow it to return to the main menu I also added the ability to restart the level, because it doesn't look like it will be covered in this tutorial. In my game I already added the restart button earlier, I just didn't add any code to it. The required method is as follows;

    private void RestartLevel()
    {
        SceneManager.LoadScene("Map");
        ResourceManager.MenuOpen = false;
        Time.timeScale = 1.0f;
    }

Part 17: Player Selection

I made it through quite a lot of this long section without customising the tutorial. When you get to the section that you download the files for JSON I recommend not using the files the tutorial author used. There is now a Unity package version available.

https://github.com/SaladLab/Json.Net.Unity3D/releases

If you download and run the package the import window pops up in Unity to add it to your project. The rest of this section of the tutorial is the same, it was only the method of adding the libraries that I changed.

My own game does not require player names for single player, but I have kept it in for the moment. I don't want to change too much of the tutorial until afterwards so I don't lose track of what I have changed.

Part 18: Saving A Game

I have to admit I am a little sad the end of the tutorial is in sight. I have really enjoyed following it. I am also a little apprehensive that I will have to start coming up with much more extensive code of my own after this.

Save Menu

As I jumped ahead in my menu creation when I created the pause menu originally I don't need to update the script to add a new button, but I do need to add the code for when the button is clicked.

When I first previewed the save menu by running my game I realised why the author made the original menu buttons so narrow. They match the Save button widths. My buttons needed to be wider in order to have space for the amount of text on them. This meant my Save buttons don't fit on the menu.

I added the following line to "ResourceManager.cs";

public static float ButtonWidthSmall { get { return ((MenuWidth - (padding * 2)) * 0.4F); } }

Then I replaced "ButtonWidth" with "ButtonWidthSmall" in the "DrawMenu()" method of "SaveMenu.cs". The save buttons don't line up perfectly, but they do fit now. I will come back and play with the alignment later.

Menu Adjustments

While making the Menu Adjustments I noticed Unity prompting me that "OnLevelWasLoaded()" will soon be deprecated and it should be replaced. You will need to add the following code as well as "using UnityEngine.SceneManagement;" to "MainMenu.cs";

    private void OnEnable()
    {
        SceneManager.sceneLoaded += OnLevelFinishedLoading;
    }
    private void OnDisable()
    {
        SceneManager.sceneLoaded -= OnLevelFinishedLoading;
    }
    void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode)
    {
        Debug.Log("check");
        if (initialised)
        {
            if (ResourceManager.LevelName != null && ResourceManager.LevelName != "")
            {
                LoadManager.LoadGame(ResourceManager.LevelName);
            }
            else
            {
                WorldObject[] worldObjects = GameObject.FindObjectsOfType(typeof(WorldObject)) as WorldObject[];
                foreach (WorldObject worldObject in worldObjects)
                {
                    worldObject.ObjectId = nextObjectId++;
                    if (nextObjectId >= int.MaxValue) nextObjectId = 0;
                }
            }
        }
    }

I don't completely understand this change, but you can read about it here on the Unity forum;

http://answers.unity3d.com/questions/1174255/since-onlevelwasloaded-is-deprecated-in-540b15-wha.html

The game will still run without this change, but I want to future proof the code I am writing a little bit. You will also need to make this same change when you get to the "LevelLoader.cs" script.

Start Save

I also added the menu skin and selection skin to the Save Menu script where it is added to the HUD. Either I missed it or the original author did.

Confirm Dialog

When you get to adding the confirmation dialogue to the save menu you will need to add "private ConfirmDialog confirmDialog = new ConfirmDialog();" to the top of "SaveMenu.cs". I think the original author missed writing it into their tutorial because it is in the repo code.

Save Object Details

When you get to the "SaveCamera()" method,  note again that you need to update the call "Camera.mainCamera" to "Camera.main".

At the section asking you to add "unitObject.ObjectId = ResourceManager.GetNewObjectId();" to the "CreateUnit()" method, the method the code actually requiring this code is the "AddUnit()" method. I think the original author just mixed up the name of the method here.

The method "SavePlayerResources()" is missing a couple of lines in the code and it will cause errors when you try to run the game. It should be setup as the code below.

        public static void SavePlayerResources(JsonWriter writer, Dictionary<ResourceType, int> resources)
        {
            if (writer == null) return;

            writer.WritePropertyName("Resources");
            writer.WriteStartArray();
            foreach (KeyValuePair<ResourceType, int> pair in resources)
            {
                writer.WriteStartObject();
                WriteInt(writer, pair.Key.ToString(), pair.Value);
                writer.WriteEndObject();
            }

            writer.WriteEndArray();
        }

Part 19: Loading A Game

Load Menu

When you add the "LoadGame()" method to "Menu.cs", it will need to be a public void. Otherwise it wont be able to be accessed by the other menu scripts.

While creating the "LoadMenu.cs" script, like with the Save Menu, I replaced "ButtonWidth" with "ButtonWidthSmall" when the menu is being drawn.

We also need to edit the snippets "Application.loadedLevelName" and "Application.LoadLevel". They are obsolete and we need to use the scene manager instead. Add "using UnityEngine.SceneManagement;" to the top of "LoadMenu.cs" and then update the "StartLoad()" method to be the following;

private void StartLoad()
{
    string newLevel = SelectionList.GetCurrentEntry();
    if (newLevel != "")
    {
        ResourceManager.LevelName = newLevel;
        if (SceneManager.GetActiveScene().name != "BlankMap1") SceneManager.LoadScene("BlankMap1");
        else if (SceneManager.GetActiveScene().name != "BlankMap2") SceneManager.LoadScene("BlankMap2");
        //makes sure that the loaded level runs at normal speed
        Time.timeScale = 1.0f;
    }
}

Load World Objects

At the point where we add the GameObjectList to the MainMenu scene I actually made mine a prefab so that the one in the Map scene matches.

At the point you add the "LoadCamera()" method, you will need to, again, replace "Camera.mainCamera" with "Camera.main". I had a problem at this point in my code that the game was not finding the object in my scene. I think I had deleted and replaced the cameras in my blank scenes at some point while playing around. The new camera that was created was "Camera", while the code was looking for "MainCamera".

Load Players

When adding the "LoadResources()" method added/updated the switch statement to correspond to my resources. I also commented out the first line of the "LoadColor()" method because I don;t think this check is required and it is causing a compile error because it returns without setting a colour.

Part 20: Audio

Ok, so here when we create  the "AudioElement.cs" script there are a number of deprecated elements. In the various methods replace "temp.audio.", with " temp.GetComponent<AudioSource>()." This appears a number of times in the early code of this part.

At the end of this section running your game and going straight to single player causes an error, "MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it...." when you try and build something. There is a quite complex fix in the comment section that I have not tried. For the moment I have just removed my "GameObjectList" Game Object from my Map scene. The game runs fine if loaded from the menu, but doesn't load the HUD properly if you run the map scene in Unity directly. I plan to introduce a check for this so I don't have to remember to add the GameObjectList prefab when testing a level.

Part 21: Victory Conditions

We have to deal with some deprecated code again. Change "Screen.showCursor" to "Cursor.visible" in "GameManager.cs" and "ResultsScreen.cs". "ResultsScreen.cs" also needs to be updated to use the new Scene Management. The "GameManager.cs" script also has the a similar "OnLevelWasLoaded()" method that we replaced for "LevelLoader.cs". Make the same changes you did for that script.

When I got to the build wonder victory conditions I altered this to be "BuildStorehouse". My game won't have a wonder or a building like this that allows the player to achieve victory. As it stands my game can be won simply by building a Storehouse. This seems pretty basic, but I plan to combine victory conditions after the tutorial so I will return to this later.

In the Escort Convoy script "cube.renderer." needs to be replaced with "cube.GetComponent<Renderer>().". I did nto create a Convoy Truck unit, instead I modified my Speeder unit to work with this victory condition.

At the end of this section we update some code to ensure resources are removed from the player when buildings and units are created. I updated this to remove stone as this is currently the only resource I can gather. I also only have one type of resource cost associated with my units and buildings. I will be revisiting this later to add the additional resource types.

Part 22: World Object AI

In this part the only change I made was the name of my Turret, which for me is "Tower Catapult".

There we have it folks. We have reached the end of Edgar Storm's tutorial. If you have followed like I have then hopefully you now have a working game in Unity 5.5. I notice the tuitorial didn't really seem to reach a conclusion, it looks as though Edgar intended to add a few more charters. I can only imagine he has not had the time since to commit the time to this project.

I will be expanding on the work as I play around with concepts I will need for my own game. You can find the details about that here. I have submitted my game to GitHub as it stands at the end of the RTS tutorial. Feel free to download it and look through what I have changed.

https://github.com/brotherkennyh/Rising-Lands-RTS-Tutorial