Creating an RTS in Unity: Part X

Update: This can now also be found at stormtek.geek.nz/rts_tutorial/part10.php, where the entire tutorial is now being hosted.

The goal this time around is to introduce the ability to create new units from buildings. While we are at it we will also implement a build queue, enabling the player to create multiple units from a single building.

Build Queue

Let’s start out by defining a build queue for our building and methods for interacting with it. The first thing that we need to do is to add some variables to the top of Building.cs.

public float maxBuildProgress;
protected Queue< string > buildQueue;
private float currentBuildProgress = 0.0f;
private Vector3 spawnPoint;

We will use currentBuildProgress and maxBuildProgress to make sure that a unit takes time to build. This will be a standard production speed for all units produced from that building. We will probably want to upgrade this at a later date, but a flat production speed will be fine for now. By making maxBuildProgress public we can play around with it in Unity – set it to 10 for now.

To have access to the Queue we need to add

using System.Collections.Generic;

to the very top of Building.cs. We also need to make sure that our queue and spawn point are initialized so add

buildQueue = new Queue< string >();
float spawnX = selectionBounds.center.x + transform.forward.x * selectionBounds.extents.x + transform.forward.x * 10;
float spawnZ = selectionBounds.center.z + transform.forward.z + selectionBounds.extents.z + transform.forward.z * 10;
spawnPoint = new Vector3(spawnX, 0.0f, spawnZ);

to Awake(). We are setting the spawn point to be in the middle of the front wall of our building, set out by a bit to make sure that the unit is not created inside the building. To actually create a unit we need a method that will add a unit to our build queue.

protected void CreateUnit(string unitName) {
	buildQueue.Enqueue(unitName);
}

Once we can add items to the build queue we want a way to make sure that it gets emptied too, so add the following line to Update()

ProcessBuildQueue();

and then add the definition of this method as well.

protected void ProcessBuildQueue() {
	if(buildQueue.Count > 0) {
		currentBuildProgress += Time.deltaTime * ResourceManager.BuildSpeed;
		if(currentBuildProgress > maxBuildProgress) {
			if(player) player.AddUnit(buildQueue.Dequeue(), spawnPoint, transform.rotation);
			currentBuildProgress = 0.0f;
		}
	}
}

This method is saying that if we have items in our build queue then we need to update our build progress. If this is now greater than our maximum allowed build progress then we need to tell the Player that owns this building to create the unit at the front of the queue. Once this is done we need to reset our current build progress to 0 so that we can start building the next unit in the queue. You will notice the reference to ResourceManager.BuildSpeed. This is used to make sure that all buildings update their progress for building units at the same rate. By adjusting maxBuildProgress we can make some buildings take longer to complete their work than others. We should add that variable to ResourceManager now

public static int BuildSpeed { get { return 2; } }

and then add

using RTS;

to the top of Building to make sure that we can access it. We also need to add the method for the creation of a unit to Player.cs too, which we will come back to later in this post.

public void AddUnit(string unitName, Vector3 SpawnPoint, Quaternion rotation) {
	Debug.Log ("add " + unitName + " to player");
}

In order to draw things correctly in our HUD we also need to be able to retrieve some details about our build queue (from Building.cs).

public string[] getBuildQueueValues() {
	string[] values = new string[buildQueue.Count];
	int pos=0;
	foreach(string unit in buildQueue) values[pos++] = unit;
	return values;
}

public float getBuildPercentage() {
	return currentBuildProgress / maxBuildProgress;
}

This will allow us to find out what the current entries in the build queue are as well as how far through constructing the current Unit the Building is.

Prefabs and Global list of types

To actually create a new Unit we need to a way to create a fully fledged Unit. The last thing we want to have to do is to painstakingly define how to create each of our units and buildings at runtime. Thankfully Unity gives us some help in this regard through the concept of prefabs. The basic overview is that a prefab serves as a template for an object instance. So we can define the collection of objects and scripts that make up our complex object, as we have done for Unit and for Building already. We can then drag our object from the Hierarchy view down into a folder in the project view, which will create a prefab object there. Once you have a prefab you can then drag it from your folder into your scene and have multiple identical copies of the same object without having to create each of them from scratch. There is also a way to create and instance of a prefab at runtime in our code (which we will get to later on in this post). Any changes you make to a prefab are immediately copied to all instances of that prefab in your scene. However, it is important to note that any changes made to an instance are not copied to the prefab. This is a very good thing, but it means that you need to remember to make any development changes to the prefab, not to an instance of it. If you want to know more details (which is always a good thing to want when using things like this) check out the Unity documentation.

Having prefabs is very useful. However, what we also need is a list of available units and buildings which we can create at runtime. Having them stored nicely in folders within our project is good, but it will not help us when our game is running. Therefore, we will create a new object whose purpose is to contain a reference to all the prefabs that we can use in our game. Inside your Assests directory create a new folder called Resources. Inside this folder create a new C# script called GameObjectList.cs. Now create a new empty object, rename it to GameObjectList, and attach the script to it.

Let us start off by defining the lists of objects that we wish to keep track of in GameObjectList.

public GameObject[] buildings;
public GameObject[] units;
public GameObject[] worldObjects;
public GameObject player;

All of these arrays need to be of type GameObject, since we will be storing prefabs in them. We are now able to drop appropriate prefabs into these lists from within Unity. We will add a couple of prefabs later in this tutorial, once we are ready to create some actual units from an actual building.

We want to make sure that we can only ever have one instance of our GameObjectList present on a map at all times. This will make more sense later on once we add in menu systems and the ability to save and load our maps. We will use a boolean variable to determine whether the GameObjectList has already been created.

private static bool created = false;

By defining this as a static variable we are making sure that it is not tied down to any particular instance of our GameObjectList. We now need to define the initialization logic inside the Unity Awake method.

void Awake() {
	if(!created) {
		DontDestroyOnLoad(transform.gameObject);
		ResourceManager.SetGameObjectList(this);
		created = true;
	} else {
		Destroy(this.gameObject);
	}
}

The Unity method call DontDestroyOnLoad makes sure that if we load a new map this object will persist. This is good because it means we will only need to initialise this once, at the entry point for our game. The call to Destroy when created is set to true guarantees that we only ever have one instance present on a map. We are also giving ResourceManager a reference to this object so that we have an easy way of calling any methods that we need to later on. We should add this method to ResourceManager now

public static void SetGameObjectList(GameObjectList objectList) {
	gameObjectList = objectList;
}

as well as the variable gameObjectList which is being set in this method.

private static GameObjectList gameObjectList;

We also need to add

using RTS;

to the top of GameObjectList to make sure that we can reference ResourceManager.

Now let us define some accessor methods within our GameObjectList to allow us to retrieve any object that we want.

public GameObject GetBuilding(string name) {
	for(int i=0; i<buildings.Length; i++) {
		Building building = buildings[i].GetComponent< Building >();
		if(building && building.name == name) return buildings[i];
	}
	return null;
}

public GameObject GetUnit(string name) {
	for(int i=0; i<units.Length; i++) {
		Unit unit = units[i].GetComponent< Unit >();
		if(unit && unit.name == name) return units[i];
	}
	return null;
}

public GameObject GetWorldObject(string name) {
	foreach(GameObject worldObject in worldObjects) {
		if(worldObject.name == name) return worldObject;
	}
	return null;
}

public GameObject GetPlayerObject() {
	return player;
}

public Texture2D GetBuildImage(string name) {
	for(int i=0; i<buildings.Length; i++) {
		Building building = buildings[i].GetComponent< Building >();
		if(building && building.name == name) return building.buildImage;
	}
	for(int i=0; i<units.Length; i++) {
		Unit unit = units[i].GetComponent< Unit >();
		if(unit && unit.name == name) return unit.buildImage;
	}
	return null;
}

We also need to add some wrapper methods to ResourceManager which we will use to access these methods in GameObjectList.

public static GameObject GetBuilding(string name) {
	return gameObjectList.GetBuilding(name);
}

public static GameObject GetUnit(string name) {
	return gameObjectList.GetUnit(name);
}

public static GameObject GetWorldObject(string name) {
	return gameObjectList.GetWorldObject(name);
}

public static GameObject GetPlayerObject() {
	return gameObjectList.GetPlayerObject();
}

public static Texture2D GetBuildImage(string name) {
	return gameObjectList.GetBuildImage(name);
}

This layer of abstraction also allows us to forget about how these values are being stored when it comes to the majority of our code base. All we care about is that our ResourceManager knows how to retrieve the items that we want whenever we want them.

Initiate Unit Creation

We now have a way to access all of the objects we might want to use in our world (even though this is not yet populated). We have also defined a way for a building to add units to a build queue and then to initiate construction of those. What we are still lacking is a way to tell a building when to add a unit to it’s build queue, as well as what that unit is. To do this we will make use of actions, which we loosely defined quite a while back for a WorldObject.

But before we get onto that, we should first define our first actual building. Once we have this we will define a build action for it along with how to handle it. We will start with a WarFactory, since we have what looks like a tank floating round. Besides, what better way to advance our cause than by building an army? Let’s first create some space to put our new WarFactory in by setting the z-position for our existing unit to 25. Now create a new empty object, rename it to WarFactory, and make sure that it’s position is set to (0, 0, 0). Add 5 cubes to this object called GroundFloor, Corner1, Corner2, Corner3, and Corner4. Set their transform properties as follows:

  • GroundFloor: position = (0, 1.5, 0), scale = (10, 3, 10)
  • Corner1: position = (-4, 3.5, -4)
  • Corner2: position = (-4, 3.5, 4)
  • Corner3: position = (4, 3.5, 4)
  • Corner4: position = (4, 3.5, -4)

Inside the Building folder create a new folder called WarFactory. Inside that folder create a new C# script called WarFactory. Make sure that this script inherits from Building and overrides the Start method, defining an action that we are going to want to perform at some point.

public class WarFactory : Building {
	protected override void Start () {
		base.Start();
		actions = new string[] { "Tank" };
	}
}

Here each action we want to perform is the name of a unit to construct. These names will need to match the names of any prefabs that we are wanting to build. Since we have Tank as part of this list we are going to need to create a prefab later for a Tank unit. Now attach this script to the WarFactory object we just created. Finally, drag the WarFactory object down into the WarFactory folder in the project view. This will create a prefab object called WarFactory in that folder. Any further changes that we make to WarFactory should be made to this prefab.

For completeness we should also create a build image for our WarFactory. To satisfy Unity’s import ratios we will make this to be 64×64, but we will make use of some transparency to give us a smaller image than that. (This is to help make sure that we do not get weird scaling of the image) I have gone with a transparent border of 13 pixels (which will make more sense by the end of this post) to give me the image below.

War Factory

War Factory

It is up to you to make your images as ornate or simplistic as possible. All we want is a representation of the building so that by glancing at it players will be able to tell what they are choosing to make. Now drag this image onto the Build Image property of your WarFactory (remember to use the prefab).

We should create our Tank object now too, but for this we will cheat slightly. Rename your Unit object to Tank. Now create a new folder inside the Unit folder called Tank and create a C# script called Tank inside that. We want this to inherit from Unit and to override the Start method (similar to what we did with our WarFactory just before).

public class Tank : Unit {
	protected override void Start () {
		base.Start ();
	}

	protected override void Update () {
		base.Update();
	}
}

Now we want to remove the Unit script from our Tank object and replace it with the Tank script that we just created. Dragging the Tank object into the Tank folder will give us a Tank prefab. We also need a BuildImage for our tank, something like the one below,

Tank

Tank

which we need to attach to the Build Image property of our Tank prefab. Let’s now add our two newly created prefab’s to our GameObjectList, making sure to add them to the appropriate lists.

Now that we finally have a building which will create a type of unit, it is time to display that available action to the player and to allow them to initiate that action. This will be done through our HUD, but only if the player has selected the WarFactory. We should first add a WarFactory to our player, since we will only be displaying actions to them if they own the building / unit that is selected. The last thing we want is for all players to have full control of all buildings and units on the map. Inside the DrawOrdersBar method we want to add the following code immediately after we have set the selectionName.

if(player.SelectedObject.IsOwnedBy(player) {
	//reset slider value if the selected object has changed
	if(lastSelection && lastSelection != player.SelectedObject) sliderValue = 0.0f;
	DrawActions(player.SelectedObject.GetActions());
	//store the current selection
	lastSelection = player.SelectedObject;
}

The method IsOwnedBy will allow us to determine whether to show actions etc. in the HUD. Create that method in WorldObject now.

public bool IsOwnedBy(Player owner) {
	if(player && player.Equals(owner)) {
		return true;
	} else {
		return false;
	}
}

We are wanting to call the method DrawActions() and pass it the actions that the currently selected object has. Since there may be more actions than can fit on screen we are going to implement a scroll bar for our list of actions. I mention this now, since if the selection changes we want to make sure that we are showing the list from the top, not from where a previous list may have been scrolled to, which is why we are making use of the variable lastSelected. The variable sliderValue is what we are using to determine what position the scrollbar should be drawn at. We should define these at the top of HUD now.

private WorldObject lastSelection;
private float sliderValue;

Obviously we now need to define DrawActions().

private void DrawActions(string[] actions) {
	GUIStyle buttons = new GUIStyle();
	buttons.hover.background = buttonHover;
	buttons.active.background = buttonClick;
	GUI.skin.button = buttons;
	int numActions = actions.Length;
	//define the area to draw the actions inside
	GUI.BeginGroup(new Rect(0,0,ORDERS_BAR_WIDTH,buildAreaHeight));
	//draw scroll bar for the list of actions if need be
	if(numActions <= MaxNumRows(buildAreaHeight)) DrawSlider(buildAreaHeight, numActions / 2.0f);
	//display possible actions as buttons and handle the button click for each
	for(int i=0; i<numActions; i++) {
		int column = i % 2;
		int row = i / 2;
		Rect pos = GetButtonPos(row, column);
		Texture2D action = ResourceManager.GetBuildImage(actions[i]);
		if(action) {
			//create the button and handle the click of that button
			if(GUI.Button(pos, action)) {
				if(player.SelectedObject) player.SelectedObject.PerformAction(actions[i]);
			}
		}
	}
	GUI.EndGroup();
}

There are a lot of things going on in this method, and a lot of things we need to define in order for it to work. First up we are defining a custom skin for just the buttons (this is actually just a modification of whatever skin happens to be active). This is making use of two textures to simulate a button hover and a button click for our build actions. I have used the following two images

Button Click

Button Click

Button Hover

Button Hover

in my project. We then need fields for these at the top of HUD,

public Texture2D buttonHover, buttonClick;

and then we need to assign the textures we added to the fields. (Add the textures into the Images folder found inside the HUD folder) You may need to wait with adding the textures from inside Unity until we have added all the pieces of code that our method still needs …

We need to define some constant values for the dimensions of our build image

private const int BUILD_IMAGE_WIDTH = 64, BUILD_IMAGE_HEIGHT = 64;

and a value for determining the height of the area we will draw our actions in.

private int buildAreaHeight = 0;

By using this value we can make sure that we always fill the available screen space with our build area. We need to initialize this in our Start method, which is called when our HUD is created.

buildAreaHeight = Screen.height - RESOURCE_BAR_HEIGHT - SELECTION_NAME_HEIGHT - 2 * BUTTON_SPACING;

I have added this initialization immediately after we have added all of our resource types to the HUD. This initialization introduces another constant that we need to add.

private const int BUTTON_SPACING = 7;

With all the variables now declared, we are left with 3 methods that need to be added to HUD.

private int MaxNumRows(int areaHeight) {
	return areaHeight / BUILD_IMAGE_HEIGHT;
}

private Rect GetButtonPos(int row, int column) {
	int left = SCROLL_BAR_WIDTH + column * BUILD_IMAGE_WIDTH;
	float top = row * BUILD_IMAGE_HEIGHT - sliderValue * BUILD_IMAGE_HEIGHT;
	return new Rect(left,top,BUILD_IMAGE_WIDTH,BUILD_IMAGE_HEIGHT);
}

private void DrawSlider(int groupHeight, float numRows) {
	//slider goes from 0 to the number of rows that do not fit on screen
	sliderValue = GUI.VerticalSlider(GetScrollPos(groupHeight),sliderValue,0.0f,numRows-MaxNumRows(groupHeight));
}

This in turn introduces one more method that we need to add

private Rect GetScrollPos(int groupHeight) {
	return new Rect(BUTTON_SPACING,BUTTON_SPACING,SCROLL_BAR_WIDTH,groupHeight - 2 * BUTTON_SPACING);
}

and this has a final constant to add.

private const int SCROLL_BAR_WIDTH = 22;

We are drawing our build options from the top of the order bar area, so let’s just change the display of the name to the bottom of that area (the rest of our draw code is actually already expecting this). We do this by changing the top position of the label for our selection name (found at the end of DrawOrdersBar()).

if(!selectionName.Equals("")) {
	int topPos = buildAreaHeight + BUTTON_SPACING;
	GUI.Label(new Rect(0,topPos,ORDERS_BAR_WIDTH,SELECTION_NAME_HEIGHT), selectionName);
}

Of course, if we want to see a name for our WarFactory we are going to need to set a value for it’s ObjectName field. We also need to make sure this is set for our tank. If you now run your project from inside Unity and select your WarFactory you should see the build image for the tank being displayed. To see that the scroll bar works (and the full two column display in action) we can change the number of times Tank is present in the actions list for our WarFactory up to something like 17. If you entered 17 instances of “Tank” to actions, you should see 17 images of a tank in your orders bar, though you may need to scroll down to see them all.

To actually start the construction of the unit we are going to make use of the PerformAction method. If you recall from when we were writing DrawActions(), the actual click of the button that is being made from the build images is calling player.SelectedObject.PerformAction(). So to initiate the build from our WarFactory add the following override for PerformAction.

public override void PerformAction(string actionToPerform) {
	base.PerformAction(actionToPerform);
	CreateUnit(actionToPerform);
}

If we run this now we should see the debug message we added to Player.AddUnit() showing almost immediately after we click on the buildImage for Tank in our WarFactory. If we change the value of Max Build Progress for the WarFactory up to 5 we can see that it now takes some 5 seconds for the message to be displayed.

Create Actual Unit

Now that we can add units into our build queue, it is time to actually create an instance of that Unit and add it to our Player. This needs to take place in AddUnit, where we just tested that the code reaches once a Unit reaches the end of the build queue. We need to add this code to that method.

Units units = GetComponentInChildren< Units >();
GameObject newUnit = (GameObject)Instantiate(ResourceManager.GetUnit(unitName),spawnPoint, rotation);
newUnit.transform.parent = units.transform;

Units is simply a wrapper object used within our player to give ready access to all of the Units that they currently have. Create a new C# script Units inside the folder for Unit and also strip out all the code in that class.

public class Units : MonoBehaviour {
	//wrapper class for unit listing for a player
}

We will do the same thing now for Buildings. Inside the Building folder create a new C# script called Buildings.cs and strip all of the code in the class.

public class Buildings : MonoBehaviour {
	//wrapper class for building listing for a player
}

Now create two new empty objects inside Unity, one called Units and the other called Buildings. Add both of these objects to your Player object. Now add WarFactory to Buildings and Tank to Units.

If we take a look back at the AddUnit method now I will explain briefly where the magic happens. The Unity method Instantiate takes a prefab, a position, and a rotation and returns us an instance of that prefab at that location. We then tell that new object that it’s parent object is to be the wrapper objects for the Units that our Player owns. Just like that we have added the newly created Unit to our Player. Feel free to run this in Unity now. You should see a new Tank appearing in front of the WarFactory once it has finished it’s time in the build queue (assuming you just told the WarFactory to make a new Tank). If you now look at the Units object for the Player you should see that it now contains Tank and Tank(Clone).

Display Build Queue

There is one last thing that I want to add in this time (and I know this post has turned out really long …) – display of our build queue. At the moment there is no indication that a Unit is being created, or how much longer that is going to take either, it simply appears. This final addition also needs to take place within HUD. Add this code

Building selectedBuilding = lastSelection.GetComponent< Building >();
if(selectedBuilding) {
	DrawBuildQueue(selectedBuilding.getBuildQueueValues(), selectedBuilding.getBuildPercentage());
}

to DrawOrdersBar() right after we set lastSelection. Note that we are making sure that the player has a building selected before attempting to draw the build queue. And now for the definition of DrawBuildQueue.

private void DrawBuildQueue(string[] buildQueue, float buildPercentage) {
	for(int i=0; i<buildQueue.Length; i++) {
		float topPos = i * BUILD_IMAGE_HEIGHT - (i+1) * BUILD_IMAGE_PADDING;
		Rect buildPos = new Rect(BUILD_IMAGE_PADDING,topPos,BUILD_IMAGE_WIDTH,BUILD_IMAGE_HEIGHT);
		GUI.DrawTexture(buildPos,ResourceManager.GetBuildImage(buildQueue[i]));
		GUI.DrawTexture(buildPos,buildFrame);
		topPos += BUILD_IMAGE_PADDING;
		float width = BUILD_IMAGE_WIDTH - 2 * BUILD_IMAGE_PADDING;
		float height = BUILD_IMAGE_HEIGHT - 2 * BUILD_IMAGE_PADDING;
		if(i==0) {
			//shrink the build mask on the item currently being built to give an idea of progress
			topPos += height * buildPercentage;
			height *= (1 - buildPercentage);
		}
		GUI.DrawTexture(new Rect(2 * BUILD_IMAGE_PADDING,topPos,width,height),buildMask);
	}
}

Once again we need another constant value

private const int BUILD_IMAGE_PADDING = 8;

and access to two more textures.

public Texture2D buildFrame, buildMask;

Both of these textures need to be 64×64, and can look something like this.

Build Frame

Build Frame

Build Mask

Build Mask

These are used to provide a little more definition to the build queue – buildFrame provides a border around each buildImage, and buildMask is used to show the user how far through the current build we are on the object at the front of the queue. If you run this now you will see that the display works, it is just in a strange place. The idea is to have our build queue displayed just to the left of our orders area. But to do that we are going to need to tweak some of our existing positioning code for our orders bar. Basically we need to shift the start of the drawing area left by BUILD_IMAGE_WIDTH and then shift all of the other drawing right by that much. In DrawOrdersBar we need to change these two lines for setup of the draw area

GUI.BeginGroup(new Rect(Screen.width-ORDERS_BAR_WIDTH-BUILD_IMAGE_WIDTH,RESOURCE_BAR_HEIGHT,ORDERS_BAR_WIDTH+BUILD_IMAGE_WIDTH,Screen.height-RESOURCE_BAR_HEIGHT));
GUI.Box(new Rect(BUILD_IMAGE_WIDTH+SCROLL_BAR_WIDTH,0,ORDERS_BAR_WIDTH,Screen.height-RESOURCE_BAR_HEIGHT),

as well as the position of our selection name,

if(!selectionName.Equals("")) {
	int leftPos = BUILD_IMAGE_WIDTH + SCROLL_BAR_WIDTH / 2;
	int topPos = buildAreaHeight + BUTTON_SPACING;
	GUI.Label(new Rect(leftPos,topPos,ORDERS_BAR_WIDTH,SELECTION_NAME_HEIGHT), selectionName);
}

and this line fixes the draw area for DrawActions.

GUI.BeginGroup(new Rect(BUILD_IMAGE_WIDTH,0,ORDERS_BAR_WIDTH,buildAreaHeight));

There, that looks better. Feel free to run it now and check it out. If you add lots of build actions to the WarFactory now (e.g 17 odd entries of “Tank”) you will also notice that the scroll bar now sits nicely outside our orders area as well.

Well, I think that about does it for this time. Sorry for the length of the post, but I felt that all of these steps needed to be presented together. If you want to enable building of new units from a building it is now as simple as copying what we did with WarFactory. Just remember that each action should correspond to the name of a prefab that you want to create, and that the prefabs for the units you want need to be added to GameObjectList. Make those small changes and all of the hard work of build queues and display etc. have been taken care of already. The full code for this post is up on github under the commit for Part10.

Advertisements

17 thoughts on “Creating an RTS in Unity: Part X

  1. binakot says:

    Hello. You never cease to amaze me. Such beauty and elegance of code. I like your blog. Are you going to provide access to the project files of your game? I would like to take a look at all of the project as a whole.

  2. The project as a whole is up on github (the last sentence of each blog typically contains the link, though it is hard to tell in this theme …). You are more than welcome to download it and check it out.

  3. anton says:

    hey man faced a little problem,

    the AddUnit() Method has this signature :

    public void AddUnit(string unitName, Vector3 SpawnPoint, Quaternion rotation) {
    Debug.Log (“add ” + unitName + ” to player”);
    }

    meanwhile in here we’re giving a parameter that is not a string, which brings up an error, i’ve checked the code that is on the github but still it’s the same as in here but a little bit more modified for this method “it has more parameters and the method signature is a bit different. but still it has the stirng parameter in it that is never being provided.

    oh btw i am using Unity 4 and Unity 4 does not allow me to attach any script to any object unless everything compiles without errors.
    that’s one of the major problems am facing 😀

    here are the errors i get:

    error CS1502: The best overloaded method match for `Player.AddUnit(string, UnityEngine.Vector3, UnityEngine.Quaternion)’ has some invalid arguments

    error CS1503: Argument `#1′ cannot convert `object’ expression to type `string’

    which basically refer to the same thing.

    protected void ProcessBuildQueue() {
    if(buildQueue.Count > 0) {
    currentBuildProgress += Time.deltaTime * ResourceManager.BuildSpeed;
    if(currentBuildProgress > maxBuildProgress) {
    if(player) player.AddUnit(buildQueue.Dequeue(), spawnPoint, transform.rotation);
    currentBuildProgress = 0.0f;
    }
    }
    }

    • The AddUnit() method needs to have it’s signature updated. I think I missed that when writing out the post the first time (my mistake sorry). I have updated the post to have the correct method signature when we first define it.

  4. Hello Elgar, I’m running into a problem with the project. I have finished part 10, there are no compiling errors, but when i run the program it tells me

    NullReferenceException: Object reference not set to an instance of an object
    HUD.DrawOrdersBar () (at Assets/Player/HUD/HUD.cs:99)
    HUD.OnGUI () (at Assets/Player/HUD/HUD.cs:80)

    here is line 99:

    if(player.SelectedObject.IsOwnedBy(player)){

    Thanks for any help you can provide, keep up the great work man!

    • Hey,

      The problem is that an object in that line is null. There are two options that I can see here.

      1) The player is null, but this seems unlikely since if it was then your HUD would not be drawing at all (which I’m guessing is not the case).

      2) player.SelectedObject is null. Off the top of my head this section of code is dealing with drawing details / options for a selected WorldObject. If nothing is selected then it will complain.

      I would update that check to include finding out whether the player has an object selected. So the if statement would become

      if(player.SelectedObject && player.SelectedObject.IsOwnedBy(player)) {
      

      This makes sure that the second check is only performed if the first check is true, and so you will not be trying to call a method on a null object.

  5. Joseph says:

    Hey Im loving the tutorial I’ve followed step by step with no errors till now, I got 2 (from same line of code) :

    Assets/WorldObjects/Building/Building.cs(47,51): error CS1502: The best overloaded method match for `Player.AddUnit(string, UnityEngine.Vector3, UnityEngine.Quaternion)’ has some invalid arguments

    Assets/WorldObjects/Building/Building.cs(47,51): error CS1503: Argument `#1′ cannot convert `object’ expression to type `string’

    here is the coding for the AddUnit Method from Building.cs:

    protected void ProcessBuildQueue() {
    if (buildQueue.Count > 0) {
    currentBuildProgress += Time.deltaTime * ResourceManager.BuildSpeed
    if(currentBuildProgress > maxBuildProgress) {
    if(player) player.AddUnit(buildQueue.Dequeue(), spawnPoint, transform.rotation);
    currentBuildProgress = 0.0f;
    }
    }
    }

    public void AddUnit(string unitName, Vector3 spawnPoint, Quaternion rotation) {
    Debug.Log (“Add” + unitName + “to Player”);
    }

    And from Player.cs:

    public void AddUnit (string unitName, Vector3 spawnPoint, Quaternion rotation){
    Units units = GetComponentInChildren ();
    GameObject newUnit = (GameObject)Instantiate (ResourceManager.GetUnit (unitName), spawnPoint, rotation);
    newUnit.transform.parent = units.transform;
    }

    I really help you can help me out on here so I can continue on your awesome tutorial! 🙂

    • That is a bug caused by a formatting error from WordPress sorry. It likes to strip the parameters from generics. A space between angle brackets and the actual type fixes it. I thought I had found them all, but apparently not …

      The issue is that Queue expects a type when we declare it – in this case we need

      Queue< string > buildQueue;
      

      and then in Awake() we need

      buildQueue = new Queue< string >();
      

      to make sure that we declare that this Queue is holding strings, not just objects.

  6. Chris says:

    Once my first tank is done building, it immediately makes all of the prefabs. None of these are children of Units and are obviously unselectable because of this. I also get a NullReferenceException at Player.AddUnit. Any suggestions?

  7. Dan says:

    Hi Elgar,

    I’m really enjoying this tutorial but I’ve hit a problem that I just can’t solve.

    NullReferenceException: Object reference not set to an instance of an object
    Player.AddUnit (System.String unitName, Vector3 spawnPoint, Quaternion rotation,)(at Assets/Player/Player.cs:64)

    Here is the AddUnit method I’ve got in Player.cs
    public void AddUnit(string unitName, Vector3 spawnPoint, Quaternion rotation)
    {
    Units units = GetComponentInChildren ();
    GameObject newUnit = (GameObject)Instantiate (ResourceManager.GetUnit(unitName),spawnPoint,rotation);
    newUnit.transform.parent = units.transform;
    }

    I’ve gone through your github for part 10 and part 10 updated but I can’t see what I’m missing/doing wrong.
    Any suggestions?

    • I do know that the entire system is case sensitive. My guess is that ResourceManager.GetUnit(unitName) is returning a null value. So I would check 1) what is the value being passed to this method? (so what is unitName when things break) and 2) is there a unit in the game object list that has the same name as this value? My guess is that you have not added the prefab for the unit that you want to create into the list of available game objects that we created. Check that first and see what you come up with.

  8. Hi Elgar

    So I might have the same problem as elgarstorm:

    NullReferenceException: Object reference not set to an instance of an object

    Player.AddUnit (System.String unitName, Vector3 spawnPoint, Quaternion rotation) (at Assets/Player/Player.cs:78)

    Building.ProcessBuildQueue () (at Assets/WorldObject/Building/Building.cs:54)
    Building.Update () (at Assets/WorldObject/Building/Building.cs:33)

    I followed the advice above and put code to show in the console what unitName is sent and its Tank.And the tank prefab is also added to the GameObjectList Under units.

    Just a little extra information… this error only shows up the moment the first tank has been created.

    Any more advice that might help?

    Thanks

  9. Thomas says:

    I had the same NullReferenceException like Dan. I found out, that I had’t attached the units script to the Units object and the building script to the Building object. I had to postpone that beacuse I had some errors while compiling and thus couldn’t add any script to an object, beacuse Unity doesn’t allow that. Once I solved the errors, I continued with the tut and simply forgot to attach the scripts^^

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s