Your Logo

About Duel defenders:

Duel defenders is a tower defense game where you also have to attack the enemy base, hence the name.

This was a developer only project, I worked together with one other developer.

Unity C# 4 weeks

Trailer video:

My contribution:

Wave generation

This wave generation system was a lot of fun to make, it was a bit of a challenge to make it so that the waves would get harder over time, but I managed to do it. The way I did this was by making a probability system, where the probability of an enemy being spawned was based on the wave number and difficulty scale given to each enemy type.


View whole script.

 public IEnumerator SpawnWave(int wave, float enemyAmnt, float multiplier)
 {
	 // wait for wave to start
	 OnStartNewWave?.Invoke();
	 yield return new WaitForSeconds(timeBetweenWave);
	 float amount = enemyAmnt * ((wave * multiplier) / 2);
							
	 while (true)
	 {
		 // spawn enemies
		 for (int i = 0; i < amount; i++)
		 {
			 // get random EnemyKind
			 EnemyKind enemyType = GetEnemyKind(wave);
			 GameObject randomEnemy = null;
							
			 // set randomEnemy var
			 switch (enemyType)
			 {
				 case EnemyKind.Easy:
					 randomEnemy = easy[Random.Range(0, easy.Count)];
					 break;
				 case EnemyKind.Medium:
					 randomEnemy = medium[Random.Range(0, medium.Count)];
					 break;
				 case EnemyKind.Hard:
					 randomEnemy = hard[Random.Range(0, hard.Count)];
					 break;
		     }
							
			 // choose random tile to place enemy on
			 MeshRenderer mesh = randomEnemy.GetComponentInChildren<MeshRenderer>();
			 int randomTile = Random.Range(0, spawnableTiles.Length);
							
			 // calculate position to spawn new enemy
			 Vector3 offset = new Vector3(0, mesh.bounds.size.y / 0.9f, 0);
			 Vector3 pos = spawnableTiles[randomTile].transform.position + offset;
							
			 // spawn spawn effect
			 Instantiate(SpawnEffect, pos, Quaternion.Euler(-90, 0, 0));
			 yield return new WaitForSeconds(0.3f);
							
			 // spwawn new enemy on calculated position
			 GameObject newEnemy = Instantiate(randomEnemy, pos, Quaternion.identity);
							
			 // play audio
			 if (Cam.WhereCamAt == CamAt.player) SpawnAudio.Play();
							
			 // add enemy to currentEnemies list
			 AiBase enemy = newEnemy.GetComponent<AiBase>();
			 currentEnemies.Add(enemy);
							
			 // set enemy path to a random path on the player field. (3, 6) is player field, (0, 2) is enemy field
			 enemy.PathToFollow = Random.Range(3, 6);
							
			 // wait between each enemy spawned
		     yield return new WaitForSeconds(timeBetweenEnemies - (timeBetweenEnemies / 10 * wave) + 0.1f);
		 }
		 break;
	 }
	 spawnWaveCoroutine = null;
 }

 private EnemyKind GetEnemyKind(int wave)
 {
    // calculate easy and medium probability (hard is not necessary)
    float easy = SpawnProbability(EnemyKind.Easy, wave);
    float medium = SpawnProbability(EnemyKind.Medium, wave);

    // random value to choose what enemy to return
    float randomValue = Random.value;

    // return the enemykind of which the probability is closest to the value created
    if (randomValue < easy)
        return EnemyKind.Easy;
    else if (randomValue < easy + medium)
        return EnemyKind.Medium;
		else
        return EnemyKind.Hard;
	}

Towers

This turret system is pretty straight forward, it checks if enemies are near it and which one is the closest, then proceeds to shoot that. This, of course, is the base turret class, all turrets have their own script that inherrit from this.


View whole script.

	public virtual Vector3 ClosestEnemy()
	{
		// start with position zero and high distance
		Vector3 closestPosition = Vector3.zero;
		float closestDistance = 100f;
							
		// loop through each enemy in enemylist (each enemy in your trigger collider)
		foreach (AiBase enemy in EnemyList)
		{
			// check distance per enemy
			float distance = Vector3.Distance(transform.position, enemy.transform.position);
							
			// if distance closer than closestDistance, set it to this distance
			if (distance < closestDistance)
			{
				closestDistance = distance;
				closestPosition = enemy.transform.position;
			}
		}
							
		// return position of closest enemy
		return closestPosition + new Vector3(0, 0.5f, 0);
	}

	public virtual void Shoot()
    {
        // remove empty slots from EnemyList with reverse for-loop
        for (int i = EnemyList.Count - 1; i != -1; i--) 
        { 
            if (EnemyList[i] == null) EnemyList.RemoveAt(i);
        }

        // if there is atleast 1 enemy
        if (EnemyList.Count > 0)
        {
            // play shoot sound
            if (IsCamInRightplace()) shootSound.Play();

            // make turret lookat enemy
            turretOrienter.transform.LookAt(ClosestEnemy());

            // instantiate bullet, make it lookat enemy and set the damage
            Bullet projectile = Instantiate(bullet, turretOrienter.transform.position, Quaternion.Euler(-90, 0, 0));
            projectile.transform.LookAt(ClosestEnemy());
            projectile.damage = Damage;
        }
    }


                        

Building towers

At first, I had a bit of trouble making this system; I didn't know how to make it compact and still give it all the functionality I wanted it to have. When I began working on this, I made it have a reference to every kind of tower, but then I realised that that wasn't necessary; it only needed to have a reference to the tower that it was going to build, so what I ended up doing was making the shop buttons instantiate an instance of this class, and giving it a turret refrence which that button is supposed to spawn. This fixed all my problems and made it so that I could have this class as compact as I wanted it to be.


View whole script.

	private void MakeGhostTower(GameObject tower)
	{
		// instantiate turret
		currentTowerRef = Instantiate(tower, new Vector3(0, -100, 0), Quaternion.identity);
							
		// set refrence to which side spawned turret
		currentTowerRef.GetComponent<Turret>().WhoSpawnt(1);
							
		// set turret variable and set its active state to false
		turret = currentTowerRef.GetComponent<Turret>();
		currentTowerRef.GetComponent<IPlacable>().SetEnabled(false);
	}

	private void UpdatePos()
	{
		// get mousepos and make world pos raycast
		Vector2 mousePos = Input.mousePosition;
		Ray ray = Camera.main.ScreenPointToRay(mousePos);
							
		// shoot raycast from camera to mouse world pos
		if (Physics.Raycast(ray, out RaycastHit hit, 1000, LayerMask, QueryTriggerInteraction.Ignore))
		{
			// if hit tile set the position to that tile + offset
			if (hit.transform.TryGetComponent<Tile>(out Tile tile) && tile.occupent == null)
			{
				MeshRenderer mesh = currentTowerRef.GetComponentInChildren<MeshRenderer>();
				Vector3 offset = new Vector3(0, mesh.bounds.size.y, 0);
				currentTowerRef.transform.position = hit.transform.position + offset;
			}
		}
	}