[Unity Mini Game] Game development case, easily create a tower defense game! (Down)

Welcome to the second part of how to create a tower defense game in Unity. You are making a tower defense game in Unity, and by the end of the first part, you can place and upgrade monsters. You also have an enemy attacking the cookie.

However, the enemy doesn't know which way to face! Furthermore, this was a critical blunder on the part of the attack. In this part, you'll add enemy spawn waves and arm your monsters so they can protect your precious cookies.

start

In Unity, open the project you completed in the first part of this tutorial series, or if you're just joining now, download the starter project and open TowerDefense-Part2-Starter.

Make the enemy spin

At the end of the last tutorial, enemies were following the path but didn't seem to know which way to face.

Open MoveEnemy.cs in the IDE and add the following method to resolve this issue.

private void RotateIntoMoveDirection()
{
  
  Vector3 newStartPosition = waypoints [currentWaypoint].transform.position;
  Vector3 newEndPosition = waypoints [currentWaypoint + 1].transform.position;
  Vector3 newDirection = (newEndPosition - newStartPosition);
  
  float x = newDirection.x;
  float y = newDirection.y;
  float rotationAngle = Mathf.Atan2 (y, x) * 180 / Mathf.PI;
  
  GameObject sprite = gameObject.transform.Find("Sprite").gameObject;
  sprite.transform.rotation = Quaternion.AngleAxis(rotationAngle, Vector3.forward);
}

RotateIntoMoveDirectionrotates the enemy so that it always looks forward, like so:
RotateIntoMoveDirectionRotates the enemy so that it always looks forward, like so:

它通过从下一个航点的位置中减去当前航点的位置来计算 虫子 的当前移动方向。
它使用 `Mathf.Atan2` 来确定 `newDirection` 指向的角度(以弧度为单位),假设零点向右。将结果乘以 `180 / Mathf.PI` 会将角度转换为度数。
最后,它检索名为 Sprite 的子项,并沿 z 轴将其旋转 `rotationAngle` 度。请注意,您旋转的是子项而不是父项,因此生命条(您很快就会添加它)保持水平状态。

In Update(), // TODO: Rotate into move directionreplace the comment with RotateIntoMoveDirectionthe following call to :

RotateIntoMoveDirection();

Save the file and switch to Unity. Run the scene; now your monster knows where he's going.

BugFollowsRoad.gif

Where does the error appear now?

An enemy? Hardly impressive. Let the crowds come. Like every tower defense game, the hordes will come in waves!

Notify players

Before you send the horde into action, you need to let the players know about the impending onslaught. Also, why not show the number for the current wave at the top of the screen?

Several game objects need to generate wave information, so you need to add it to the GameManagerBehavior component on the GameManager.

Open GameManagerBehavior in .cs in the IDE, and then add the following two variables:

public Text waveLabel;
public GameObject[] nextWaveLabels;

waveLabelStores a reference to the wave reading in the upper right corner of the screen. nextWaveLabelsStore two game objects that when combined together will create an animation that you will display when a new wave starts, like this:

nextWaveAnimation

Save the file and switch to Unity. Select Game Manager in the hierarchy. Click the small circle to the right of Generate Wave Labels, and then in the Select Text dialog box, select Generate Wave Labels in the Scenarios tab.

Now set the size of the next wave of labels to 2. Then assign element 0 to NextWaveBottomLabel and element 1 to NextWaveTopLabel in the same way as the Wave Label.

This is how your game manager behavior should be

If a player loses the game, he should not see the next wave of messages. To fix this, switch back to GameManagerBehavior.cs in the IDE and add another variable:

public bool gameOver = false;

In gameOver, you will store whether the player lost the game.

Likewise, you'll use properties to keep game elements in sync with the current wave. Add the following code to GameManagerBehavior:

private int wave;
public int Wave
{
  get
  {
    return wave;
  }
  set
  {
    wave = value;
    if (!gameOver)
    {
      for (int i = 0; i < nextWaveLabels.Length; i++)
      {
        nextWaveLabels[i].GetComponent<Animator>().SetTrigger("nextWave");
      }
    }
    waveLabel.text = "WAVE: " + (wave + 1);
  }
}

Creating private variables, properties, and getters should be second nature by now. But again, the setter is a bit tricky.

Use new valueupdates wave.

Then you check if the game is not over. If so, iterates through all labels in nextWaveLabels—labels that have Animator components. To trigger an animation on an animator, set the trigger nextWave.

Finally, you set waveLabelthe of textto wave + 1the value of . Why +1? – Normal people don’t start counting from zero. Weird, I know :]

In Start(), set the value of this property:

Wave = 0;

You Wavestart counting from the number 0.

Save the file and run the scene in Unity. Wave readings start correctly at 1.

Internally you start counting with 0, but for the player everything starts with wave 1.

For players, it all starts with Wave 1.

Generate enemy waves

This sounds obvious, but you need to be able to create more enemies to unleash hordes of enemies - you can't do that right now. Additionally, once the current spawn wave is erased, you shouldn't be able to spawn the next wave - at least not yet.

Therefore, the game must be able to identify whether there are enemies in the scene, and tags are a good way to identify game objects.

Set enemy tags

Select the enemy prefab in the project browser. At the top of the inspector, click the Marks drop-down list and select Add Mark.

Create Tag

Create a tag named enemy_.
create a new tag

Select the enemy prefab. In the Inspector, set its label to Enemy.

Define enemy wave information

Now you need to define a wave of enemies. Open SpawnEnemy.cs in the IDE and SpawnEnemyadd the following class implementation before:

[System.Serializable]
public class Wave
{
  public GameObject enemyPrefab;
  public float spawnInterval = 2;
  public int maxEnemies = 20;
}

A Wave contains a enemyPrefab, which is the basis for instantiating all enemies in that wave, spawnIntervalthe time in seconds between enemies in the wave, and maxEnemies, the number of enemies spawned in that wave.

This class is serializable, which means you can change the value in the inspector.

Add the following variables to SpawnEnemythe class:

public Wave[] waves;
public int timeBetweenWaves = 5;

private GameManagerBehavior gameManager;

private float lastSpawnTime;
private int enemiesSpawned = 0;

This will set up some variables for spawning that are very similar to how you move enemies along waypoints.
You'll wavesdefine the game's various waves in and track the number of enemies spawned and their spawn times in enemiesSpawnedand , respectively.lastSpawnTime

Players need to rest after a kill, so timeBetweenWavesset to 5 seconds

Replace Start()the contents of with the following code.

lastSpawnTime = Time.time;
gameManager =
    GameObject.Find("GameManager").GetComponent<GameManagerBehavior>();

Here you lastSpawnTimeset to the current time, which will be the time when the script starts immediately after the scene loads. Then retrieve it in a familiar way GameManagerBehavior.

Add this to Update():

int currentWave = gameManager.Wave;
if (currentWave < waves.Length)
{
  
  float timeInterval = Time.time - lastSpawnTime;
  float spawnInterval = waves[currentWave].spawnInterval;
  if (((enemiesSpawned == 0 && timeInterval > timeBetweenWaves) ||
       timeInterval > spawnInterval) && 
      enemiesSpawned < waves[currentWave].maxEnemies)
  {
    
    lastSpawnTime = Time.time;
    GameObject newEnemy = (GameObject)
        Instantiate(waves[currentWave].enemyPrefab);
    newEnemy.GetComponent<MoveEnemy>().waypoints = waypoints;
    enemiesSpawned++;
  }
  
  if (enemiesSpawned == waves[currentWave].maxEnemies &&
      GameObject.FindGameObjectWithTag("Enemy") == null)
  {
    gameManager.Wave++;
    gameManager.Gold = Mathf.RoundToInt(gameManager.Gold * 1.1f);
    enemiesSpawned = 0;
    lastSpawnTime = Time.time;
  }
  
}
else
{
  gameManager.gameOver = true;
  GameObject gameOverText = GameObject.FindGameObjectWithTag ("GameWon");
  gameOverText.GetComponent<Animator>().SetBool("gameOver", true);
}

Step through this code:

  • Get the index of the current wave and check if it is the last wave.
  • If so, calculate how much time has passed since the last enemy spawned, and whether it's time to spawn an enemy. Here you consider two scenarios. If it's the first enemy in the wave, check timeIntervalif is greater than timeBetweenWaves. Otherwise, please check timeIntervalwhether it is larger than this wave spawnInterval. In either case, you want to make sure you don't spawn all the enemies for this wave.
  • enemyPrefabIf necessary, spawn enemies from instantiated copies of . You can also increase enemiesSpawnedthe count.
  • You check the number of enemies on the screen. If not, it's the last enemy in the wave and you spawn the next wave. You also give the player 10% of any gold remaining at the end of the wave.
  • After beating the last wave of runs, the game wins animation.

Set generation interval

Save the file and switch to Unity. Select a road in the hierarchy. In the inspector, set the size of the waves to 4.

Now set the "Enemy Prefab" for all four elements to "Enemy". Set the spawn interval and maximum number of enemies fields as follows:

  • Element 0: Spawn Interval: 2.5, Max Enemies: 5
  • Element 1: Spawn Interval: 2, Max Enemies: 10
  • Element 2: Spawn Interval: 2, Max Enemies: 15
  • Element 3: Spawn Interval: 1, Max Enemies: 5

The final setup should look like the screenshot below.

Waves
Of course, you can use these settings to increase or decrease attack damage.
run game. Aha! Bugs are heading for your cookies!

bugs.gif

Optional: Add different types of enemies

No tower defense game is complete with only one type of enemy. Luckily, the prefabs folder contains another option, Enemy2.

Select the prefab\Enemy2 in the inspector and add the MoveEnemy script to it. Set its speed to 3 and its label to enemy. You can now use this quick bug to keep players on their toes!

Update player health

Even if swarms of bugs rush towards the cookie, the player will not take any damage. But that's it. When the player lets an enemy invade, he should take a hit.

Open "Game Manager Behavior" in .cs in the IDE, and then add the following two variables:

public Text healthLabel;
public GameObject[] healthIndicator;

You'll use to healthLabelaccess the player's health readout, and use to healthIndicatoraccess the five green cookie crunchy little monsters - they just represent player health in a more interesting way than the standard health tags.

Manage health

Next, add a property to GameManagerBehaviormaintain the player's health in :

private int health;
public int Health
{
  get
  {
    return health;
  }
  set
  {
    
    if (value < health)
    {
      Camera.main.GetComponent<CameraShake>().Shake();
    }
    
    health = value;
    healthLabel.text = "HEALTH: " + health;
    
    if (health <= 0 && !gameOver)
    {
      gameOver = true;
      GameObject gameOverText = GameObject.FindGameObjectWithTag("GameOver");
      gameOverText.GetComponent<Animator>().SetBool("gameOver", true);
    }
    
    for (int i = 0; i < healthIndicator.Length; i++)
    {
      if (i < Health)
      {
        healthIndicator[i].SetActive(true);
      }
      else
      {
        healthIndicator[i].SetActive(false);
      }
    }
  }
}

This will manage the player's health. Once again, most of the code is in the setters:

  • If you want to lower the player's health, use CameraShakethe component to create a nice shaking effect. This script is included in the project and is not covered here.
  • Update private variables and health labels in the upper left corner of the screen.
  • If health drops to 0 and the game is not over yet, gameOverset to trueand trigger GameOverthe animation.
  • Remove one of the monsters from the cookie. It would be simpler to write this bit if it just disabled them, but it also supports re-enabling them when adding health.

Initialize Health in Start():

Health = 5;

When the scene starts playing, Healthset to 5.

After setting this property, you can now update the player's health when a bug reaches the cookie. Save this file and switch to MoveEnemy.cs still in the IDE.

Update health value display

To update the player's health, find the comment in Update()and // TODO: deduct healthreplace it with the following code:

GameManagerBehavior gameManager =
    GameObject.Find("GameManager").GetComponent<GameManagerBehavior>();
gameManager.Health -= 1;

This will take GameManagerBehaviorand subtract one Healthfrom .

Save the file and switch to Unity.

Select Game Manager in the hierarchy and set its Health Label to Health Label.

Expand Cookie in the hierarchy and drag and drop its five health indicator children into the GameManager's health indicators array - health indicators are little green monsters that happily eat cookies.

Play the scene and wait for the bug to reach the cookie. Do nothing until you lose.

cookie-attack

Monster Wars: Monster's Revenge

Monster in place? complete. The enemy is advancing? complete. - They look mean! Time to cut those fools off!

一个生命条,所以玩家知道哪些敌人是强的,哪些是弱的
探测怪物范围内的敌人
决策点——向哪个敌人开火

Enemy Health Bar enemy health bar

You'll implement the health bar using two images, one for a dark background and another for a slightly smaller green bar that you'll scale to match the enemy's health.

Drag Prefabs\Enemy from the Project Browser into the scene.

Then drag " Images\Objects\HealthBarBackground " onto "Enemy" in the hierarchy, adding it as a child.

In the Inspector, set the HealthBarBackground's position to (0, 1, -4).

Next, select " Images\Objects\HealthBar " in the Project Browser and make sure its "Perspective" is set to "Left". Then, add it as a child of the enemy in the hierarchy and set its position to (-0.63, 1, -5). Set its X scale to 125.

Add a new C# script named HealthBar to the HealthBar game object. Later, you'll edit it to adjust the length of the health bar.

After selecting the enemy in the hierarchy, make sure it's position is (20, 0, 0).

Click Apply at the top of the inspector to save all changes as part of the prefab. Finally, remove the enemy from the hierarchy.

bugs with healthbar

Now, repeat these steps to add the health bar to the prefab _Prefabs\Enemy2_.

Adjust Health Bar Length Adjust the length of the health bar

Open HealthBar.cs in the IDE and add the following variables:

public float maxHealth = 100;
public float currentHealth = 100;
private float originalScale;

maxHealthStores the enemy's maximum health and currentHealthkeeps track of remaining health. Finally, originalScalethe original size of the health bar is remembered.

originalScaleStore the object's Start()in :

originalScale = gameObject.transform.localScale.x;

Save the value localScaleof x.

Set the scale of the health bar by adding the following Update()to :

Vector3 tmpScale = gameObject.transform.localScale;
tmpScale.x = currentHealth / maxHealth * originalScale;
gameObject.transform.localScale = tmpScale;

Copy localScaleto a temporary variable since you cannot just adjust its x value. Then, calculate the new x-tick based on the bug's current health and set the temporary variable back localScale.

Save the file and run the game in Unity. You'll see a health bar above the enemy.

Resistance is futile! - Wait, what resistance?

While the game is running, expand an enemy (clone) object in the hierarchy and select its HealthBar child object. Change its "Current Health" value and check the health bar to see if you want to change it.

AdjustHealthbar

Track Enemies in Range Track enemies within range

Now monsters need to know which enemies to target. Before implementation, you have some preparation work to do with monsters and enemies.

Select Prefab\Monster in the Project Browser and add the Circle Collider 2D component to it in the Inspector.

Set the Collider's radius to 2.5 - this will set the monster's range.

Check " Is Trigger " so that the object passes through the area instead of hitting it.

Finally, at the top of the inspector, set the monster layer to ignore raycasting. Click Yes, change subkeys in the dialog box. If raycasting is not ignored, the collider reacts to click events. This is a problem because monsters block events targeting open spots below them.

Screenshot 2015-06-05 at 14.47.15

To allow enemies to be detected in the trigger area, you need to add a collider and a rigidbody to it, as Unity only sends the trigger event if one of the colliders has a rigidbody attached to it.

In the Project Browser, select Prefab\Enemy. Add a Rigid Body 2D component with body type set to Kinematic. This means that the body should not be affected by physics.

Adds a 2D circular collider with a radius of 1. Repeat these steps for Prefab\Enemy2

Triggers are now set so monsters detect when enemies are within range.

One thing you need to prepare: a script that notifies monsters when an enemy is destroyed so they don't cause exceptions by continuing to shoot.

Create a new C# script named EnemyDestructionDelegate and add it to the Enemy and Enemy2 prefabs.

Open EnemyDestructionDelegate.cs in the IDE and add the following delegate declaration:

public delegate void EnemyDelegate (GameObject enemy);
public EnemyDelegate enemyDelegate;

Here you create one delegate, which is a container for a function that can be passed around like a variable.

Note: Use delegates when you want one game object to proactively notify other game objects of changes. For more information about delegation, see the Unity documentation.

Add the following methods:

void OnDestroy()
{
  if (enemyDelegate != null)
  {
    enemyDelegate(gameObject);
  }
}

Unity automatically calls this method after destroying the game object and checks if the delegate is not null. In this case you can gameObjectcall it with as argument. This lets all listeners registered as delegates know that the enemy has been destroyed.

Save the file and return to Unity.

Give Monsters a License to Kill

Give the monster a killing script

Monsters can now detect enemies within range. Add a new C# script to the Monster prefab and name it ShootEnemies.

Open ShootEnemies.cs in the IDE and add the following usingstatements to access it Generics.

using System.Collections.Generic;

Add a variable to track all enemies in range:

public List<GameObject> enemiesInRange;

In enemiesInRange, you will store all enemies within range.

Initialize Start()fields in .

enemiesInRange = new List<GameObject>();

Initially, there are no enemies in range, so you create an empty list.

Fill out enemiesInRangethe list! Add this code to the script:

void OnEnemyDestroy(GameObject enemy)
{
  enemiesInRange.Remove (enemy);
}

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;
  }
}

  • In OnEnemyDestroy, you remove the enemy enemiesInRangefrom . OnTriggerEnter2DSummoned when enemies pull the trigger around your monster .

  • Then add enemies to enemiesInRangethe list and OnEnemyDestroyadd to EnemyDestructionDelegate. This ensures that it is called when the enemy is destroyed OnEnemyDestroy. You don't want monsters wasting ammo on dead enemies now - do you?

  • In OnTriggerExit2D, you remove the enemy from the list and unregister your representative. Now you know which enemies are in range.

  • Save the file and run the game in Unity. To test if it works, place a monster, select it and watch enemiesInRangethe changes to the list in the inspector.

Select a Target Select a target

Monsters now know which enemies are within range. But what do they do when there are multiple enemies within range?

Of course, they attack the person closest to the cookie!

Open MoveEnemy.cs in the IDE and add the following new method to calculate:

public float DistanceToGoal()
{
  float distance = 0;
  distance += Vector2.Distance(
      gameObject.transform.position, 
      waypoints [currentWaypoint + 1].transform.position);
  for (int i = currentWaypoint + 1; i < waypoints.Length - 1; i++)
  {
    Vector3 startPosition = waypoints [i].transform.position;
    Vector3 endPosition = waypoints [i + 1].transform.position;
    distance += Vector2.Distance(startPosition, endPosition);
  }
  return distance;
}

This code calculates the length of road the enemy has not yet advanced. It uses Distanceto calculate Vector3the distance between two instances.

You'll use this method later to identify targets to attack. However, your monster is unarmed, so deal with that first.

Save the file and return to Unity to start setting up bullet points.

Give the monsters bullets - lots of bullets!

Drag and drop Image/Object/Bullet1 from the Project Browser into the scene. Set the z position to -2 - the x and y positions don't matter because they are set every time a new bullet is instantiated at runtime.

Add a new C# script named BulletBehavior and add the following variables to it in the IDE:

public float speed = 10;
public int damage;
public GameObject target;
public Vector3 startPosition;
public Vector3 targetPosition;

private float distance;
private float startTime;

private GameManagerBehavior gameManager;

speedDetermine the bullet's flight speed; damageis self-explanatory.

target, startPositionand targetPositiondetermine the direction of the bullet.

distanceand startTimekeeps track of the bullet's current position. gameManagerRewards the player when they crush enemies.

Start()Assign values ​​to these variables in :

startTime = Time.time;
distance = Vector2.Distance (startPosition, targetPosition);
GameObject gm = GameObject.Find("GameManager");
gameManager = gm.GetComponent<GameManagerBehavior>();

Set startTimeto the current time and calculate the distance between the start and target positions. You can also get it as usual GameManagerBehavior.

Add the following code Update()to to control bullet movement:

float timeInterval = Time.time - startTime;
gameObject.transform.position = Vector3.Lerp(startPosition, targetPosition, timeInterval * speed / distance);


if (gameObject.transform.position.Equals(targetPosition))
{
  if (target != null)
  {
    
    Transform healthBarTransform = target.transform.Find("HealthBar");
    HealthBar healthBar = 
        healthBarTransform.gameObject.GetComponent<HealthBar>();
    healthBar.currentHealth -= Mathf.Max(damage, 0);
    
    if (healthBar.currentHealth <= 0)
    {
      Destroy(target);
      AudioSource audioSource = target.GetComponent<AudioSource>();
      AudioSource.PlayClipAtPoint(audioSource.clip, transform.position);

      gameManager.Gold += 50;
    }
  }
  Destroy(gameObject);
}

您可以使用 `Vector3.Lerp` 计算新的项目符号位置,以在开始位置和结束位置之间进行插值。
如果项目符号到达 `targetPosition` ,则验证 `target` 是否仍然存在。
检索目标的 `HealthBar` 组件,并通过项目符号的 `damage` 降低其生命值。
如果敌人的生命值降至零,您可以摧毁它,播放声音效果并奖励玩家的枪法。

Save the file and return to Unity.

Get Bigger Bullets Get bigger bullets

Wouldn't it be cool if your monsters shot bigger bullets at higher levels? - Yes, yes, it will! Fortunately, this is easy to achieve.

Drag and drop the Bullet1 game object from the Hierarchy to the Project tab to create the bullet's prefab. Remove the original object from the scene - you no longer need it.

Duplicate the Bullet1 prefab twice. Name the copies Bullet2 and Bullet3.

Select bullet 2. In the Inspector, set the Sprite Renderer component's Sprite field to Image/Object/Bullet2. This makes Bullet2 appear slightly larger than Bullet1.

Repeat the process, setting the Bullet3 prefab's sprite to Image/Object/Bullet3.

Next, set the damage caused by the bullet in the bullet behavior.

Select the Bullet1 prefab in the Project tab. In the inspector you can see the bullet behavior (script), where you set the damage of bullet 1 to 10, the damage of bullet 2 to 15, and the damage of bullet 3 to 20 - or whatever makes you happy.

Note: I set the values ​​so that at higher levels, the cost per damage is higher. This offsets the fact that upgrades allow players to improve monsters in optimal locations.

Bullet prefabs - size increases with level

Bullet prefab - size increases with levels

Leveling the Bullets Leveling the Bullets

Assign different bullets to different monster levels so stronger monsters shred enemies faster.

Open MonsterData in .cs in the IDE and add these variables to MonsterLevel:

public GameObject bullet;
public float fireRate;

These will set the bullet prefabs and fire rates for each monster level. Save the file and return to Unity to complete the monster's setup.

Select the monster prefab in the project browser. In the Inspector, expand the level in the Monster Data (Script) component. Set each element's "rate of fire" to 1. Then set the bullets for elements 0, 1, and 2 to bullet 1, bullet 2, and bullet 3 respectively.

Your monster levels should be configured as follows:

MonsterData with bullets

Bullets kill your enemies? -examine! Fire!

open fire

Open ShootEnemies.cs in the IDE and add some variables:

private float lastShotTime;
private MonsterData monsterData;

As the name suggests, these variables track the last time this monster fired, as well as MonsterDatathe structure, which includes information about this monster's bullet type, rate of fire, etc.

Assign values ​​to Start()these fields in :

lastShotTime = Time.time;
monsterData = gameObject.GetComponentInChildren<MonsterData>();

Here you lastShotTimeset to the current time and access MonsterDatathe component of this object.

Add the following method to achieve shooting:

void Shoot(Collider2D target)
{
  GameObject bulletPrefab = monsterData.CurrentLevel.bullet;
  
  Vector3 startPosition = gameObject.transform.position;
  Vector3 targetPosition = target.transform.position;
  startPosition.z = bulletPrefab.transform.position.z;
  targetPosition.z = bulletPrefab.transform.position.z;

  
  GameObject newBullet = (GameObject)Instantiate (bulletPrefab);
  newBullet.transform.position = startPosition;
  BulletBehavior bulletComp = newBullet.GetComponent<BulletBehavior>();
  bulletComp.target = target.gameObject;
  bulletComp.startPosition = startPosition;
  bulletComp.targetPosition = targetPosition;

  
  Animator animator = 
      monsterData.CurrentLevel.visualization.GetComponent<Animator>();
  animator.SetTrigger("fireShot");
  AudioSource audioSource = gameObject.GetComponent<AudioSource>();
  audioSource.PlayOneShot(audioSource.clip);
}

  • Get the bullet's starting position and target position. Set the z position to bulletPrefabthe position of . Previously, you set the z-position value of the bullet prefab to ensure that the bullet appears behind the monster that fired it, but in front of the enemy.
  • Instantiate a new bullet using bulletPrefabrepresentation . MonsterLevelAssign bullets startPositionand targetPosition.
  • Make the game more interesting: run shooting animations and play laser sounds when monsters shoot.

put everything together

It's time to connect everything together. Identify the target and have your monster look at it.

Still in ShootEnemies.cs, add this code to Update():

GameObject target = null;

float minimalEnemyDistance = float.MaxValue;
foreach (GameObject enemy in enemiesInRange)
{
  float distanceToGoal = enemy.GetComponent<MoveEnemy>().DistanceToGoal();
  if (distanceToGoal < minimalEnemyDistance)
  {
    target = enemy;
    minimalEnemyDistance = distanceToGoal;
  }
}

if (target != null)
{
  if (Time.time - lastShotTime > monsterData.CurrentLevel.fireRate)
  {
    Shoot(target.GetComponent<Collider2D>());
    lastShotTime = Time.time;
  }
  
  Vector3 direction = gameObject.transform.position - target.transform.position;
  gameObject.transform.rotation = Quaternion.AngleAxis(
      Mathf.Atan2 (direction.y, direction.x) * 180 / Mathf.PI,
      new Vector3 (0, 0, 1));
}

Step through this code.

  • Determine the monster's target. Start from the maximum possible distance minimalEnemyDistancein . Iterate through all enemies within range, and if the enemy's distance from the cookie is less than the current minimum, set it as the new target.
  • If the elapsed time is greater than the monster's rate of fire, call it Shootand lastShotTimeset to the current time.
  • Calculate the angle of rotation between the monster and its target. You set the monster's rotation to this angle. Now it always faces the target.

Save the file and play the game in Unity. Your monster vigorously protects your cookies. You're totally, totally done!

end

Wow, so you really did a lot between the two tutorials and you have a cool game to show it off.
Here are some ideas to build on the work you've done:

  • More enemy types and monsters
  • Multiple enemy paths
  • different enemy levels

The blogger is a self-taught player. If you are also a Unity beginner, welcome to join my group chat for mutual help and communication: 618012892

Guess you like

Origin blog.csdn.net/weixin_72715182/article/details/130629834
Recommended