Kodeco Forums

How to Create a Tower Defense Game in Unity – Part 2

In this second and final part of the Unity tower defense tutorial, you'll add some shooting monsters into the the mix.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/1622-how-to-create-a-tower-defense-game-in-unity-part-2

Hi Barbara, wonderful tutorial! Just wonder why in the Open Fire session, you wrote a Shoot(Collider2D target) function, instead of Shoot(GameObject target)? I tried with the latter one and it worked as well.

@artwen In the current implementation, you are correct. There is no difference between using a Collider2D or GameObject, as we assume the bullet to be circular and hence can simply calculate its distance from the target to detect a collision. However, if you would like to do something a bit more special for your bullet, using Unitys colliders can make things easier for you. Hence only allowing GameObjects as bullet that have a Collider2D and the Shoot method would enforce it.

Can I use the audio here for my game?

The music is from http://www.bensound.com/. He has the rules for using his music on his website. The sound effects were generated by me with cfxr or sfxr. Feel free to use them.

Great tutorial! I’m glad I found this!

Just have a quick question regarding the OnTriggerEnter/OnTriggerExit. I completed this tutorial, but my turrets stop shooting after destroying one enemy. When looking in the inspector the turrets are still trying to shoot the first “enemy” in the enemiesInRange array. However, since being destroyed, it returns “missing” throughout the remainder of the play-through. I understand that the OnTriggerExit and OnEmeyDestroyed() is supposed to remove the destroyed enemy from the array using the EnemyDestructionDelegate script. For reasons I cannot explain, it doesn’t work. In order for the turret to fire at the next target, I have to drag the next enemy clone in to the first spot on the array. Otherwise, it just shows Element 0 is missing. The remaining elements will fill up, and then empty as they should. I just can’t get them to replace element 0. Element 0 is never removed.

Any thoughts on why that is?

From what I can gather on the internet, it doesn’t call the OnTriggerExit() since it has been destroyed. But I’m lost as to why OnEnemyDestroy() doesnt work as I thought it should. It’s like it cannot remove it, because there is “nothing” to remove. I tried using enemiesInRange.Shift(); but I may just be doing it wrong.

Thanks you liked the tutorial. I will try to figure out how to fix your problem.
First of all you say you think OnEnemyDestroyed is not called. Did you try and set a breakpoint to check, whether this is the case? You can find how to use the debugger here.

If it is not called you should check for the most common reason delegate methods are not called. That is, you might have forgotten to set the delegate. So make sure the delegate is not null. Again breakpoints are a great way to find out.

I hope those two steps could help you to fix your problem.

Hey.
I find your tutorial very helpful and of good quality. :slight_smile:
I’ve completed it whole, step by step and everything seemed to work just fine with the exception of targeting system.
Monsters seemed to loose their targets for a while just to go back to them a while later (especially those placed near curves). I’ve also noticed that it happened around way points.

My investigation revealed that targeting by monsters looks fine, they choose the enemy closest to the cookie. My next suspect was distance calculation and it appeared to be the case of all the trouble.
I’ve added public variable to enemy, storing it’s distance from the cookie and in OnUpdate() I’ve added updating this variable so I could track this value as enemies were approaching their goal.

It appeared that the value never reaches 0 and what is more it increases after reaching way point, which was the case of target changes. When I analysed distanceToGoal() it appeared that the function first counts distance between current location and the way point the enemy is heading to. Then it enters the ‘for’ loop, where it sums all the distances between all neighboring way points. The outcome was ‘length of the whole road’ + ‘distance to the nearest way point’.

I fixed it by adding this:
float distance = 0;
int nextWaypoint = currentWaypoint + 1;
distance += Vector3.Distance (gameObject.transform.position, waypoints [nextWaypoint].transform.position);
for (int i = nextWaypoint; i < waypoints.Length - 1; i++) {

The best part of the story is that when I double checked the code in the tutorial it appeared that it is already there and I’ve let auto-complete put 0 in the ‘for’ loop. :smiley:

I really enjoyed your tutorial it was very helpful and pretty easy to understand. I was wondering how to have enemies 2 mixed in with enemies 1?

@diamorotic You sound like you had a lot of fun in debugging the game :]
I am happy that it went well in the end.

@minnejo You could do different types of enemies in a wave, by changing the Wave class slightly. Instead of one enemy, you store an array of enemies. Then you rotate through the different types of enemies within the wave when creating a new enemy. If you do not want to have a regularly repeating enemies you can instead put them randomly or store the exact order of enemies you want to create in the array.

Hope that helps. It should not be too hard to do.

Hi, I like your tutorial and now I’m making my game based on it. I just wanna ask, at which part did you call the function to restart? Because when the wave ends the game automatically restart but I want to do something else with my game.

I’ve realized that you have a function that restarts the game, but I can’t find the class or event where you call that function. If you can help me on editing the end part of the game that would be great.

thanks

Hi @daggio,
In short: RestartLevel() in GameOver
the GameManager checks whether the player has lost the game. If so, it tells the GameOver label in Canvas to start its animation. At the end of the animation an event triggers a call to RestartLevel() in the GameOver script. Hope this helps.

1 Like

Wow that’s a fast reply

So, can you point me to a reference or something about calling a function from an animation? Because i’m basically a programmer and still not completely familiar with unity, I don’t know how to call a function from animation.

Thanks

@daggio: Have a look at the chapter on Animation Events in my tutorial on Unity Animations. There I explain how to connect animations with code with screenshots and all :slight_smile:

1 Like

I’ll check that out, thanks

Hi :slight_smile:
Im having trouble in the section “Give Monsters a License to Kill” :confused:

After adding this piece of code:

void OnTriggerEnter2D (Collider2D other) {
		if (other.gameObject.tag.Equals("Enemy")) {
			enemiesInRange.Add(other.gameObject);
			EnemyDestructionDelegate del =
				other.gameObject.GetComponent<EnemyDestructionDelegate>();
			del.enemyDelegate += OnEnemyDestroy;
		}
	}
	void OnTriggerExit2D (Collider2D other) {
		if (other.gameObject.tag.Equals("Enemy")) {
			enemiesInRange.Remove(other.gameObject);
			EnemyDestructionDelegate del =
				other.gameObject.GetComponent<EnemyDestructionDelegate>();
			del.enemyDelegate -= OnEnemyDestroy;
		}
	}

i got this error on unity :frowning:
NullReferenceException: Object reference not set to an instance of an object
UnityEngine.UI.Graphic.OnRebuildRequested () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Graphic.cs:480)
UnityEngine.UI.GraphicRebuildTracker.OnRebuildRequested () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/GraphicRebuildTracker.cs:33)
UnityEngine.CanvasRenderer.RequestRefresh () (at C:/buildslave/unity/build/artifacts/generated/common/modules/UI/CanvasRendererBindings.gen.cs:314)

More specifically every time the enemy enters and exits the monster collider i got the game paused and the console writing the message from above.

Very nice tutorial btw :slight_smile:

Regards

@jquin One of the objects you call a method on is null. You could check a few things:

  • Did you initialize enemiesInRange with new List();
  • Did you add an EnemyDestructionDelgate
  • Did you set the “Enemy” tag

Everything was right, i just didnt assign the enemy script to the enemies xD, I felt so stupid haha. Thanx :slight_smile:

@jqin Just happy you could resolve the issue. Happens to everyone :slight_smile: