前言

  到目前为止,我们的游戏场景里面只有两个怪物。为了让游戏更有挑战,我们需要在游戏运行时每隔一段时间就随机生成一个新的怪物。接下来,我们开始实现随机生成更多怪物的功能。


制作Generator

  为了能在场景中随机生成更多的怪物,我们需要有一个Generator在场景中不断实例化我们已经做好的怪物Prefab。首先,我们在Assets\Scripts\Utility文件夹下创建一个名为Generator的C#脚本,然后编辑Generator.cs如下:

Generator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 朝向
public enum Orientation {
Left, // 固定朝左
Right, // 固定朝右
Random, // 随机朝向
None // 不需要考虑朝向
}

public class Generator : MonoBehaviour {
[Tooltip("多久之后开始实例化预设对象")]
public float GenerateDelay = 2f;
[Tooltip("实例化预设对象的时间间隔")]
public float GenerateInterval = 3f;
[Tooltip("预设对象的朝向")]
public Orientation PrefabOrientation = Orientation.Right;
[Tooltip("预设对象")]
public GameObject[] Prefabs;

private ParticleSystem m_Particle;

private void Awake() {
// 获取引用
m_Particle = GetComponent<ParticleSystem>();

if(Prefabs == null || Prefabs.Length == 0) {
Debug.LogError("请至少为Prefabs添加一个预设对象");
}
}

private void Start () {
// GenerateDelay秒之后第一次调用Generate函数,然后每隔GenerateInterval调用Generate函数一次
InvokeRepeating("Generate", GenerateDelay, GenerateInterval);
}

private void Generate() {
// 随机选择一个预设进行生成
int index = Random.Range(0, Prefabs.Length);

// 实例化预设对象
GameObject prefab = Instantiate(Prefabs[index], transform.position, Quaternion.identity);

// 播放粒子特效
if(m_Particle != null) {
m_Particle.Play();
}

// 不需要考虑朝向
if(PrefabOrientation == Orientation.None) {
return;
}

if(PrefabOrientation == Orientation.Left) {
Wander wander = prefab.GetComponent<Wander>();
if(wander.FacingRight) {
wander.Flip();
}
return;
}

if(PrefabOrientation == Orientation.Right) {
Wander wander = prefab.GetComponent<Wander>();
if(!wander.FacingRight) {
wander.Flip();
}
return;
}

if(PrefabOrientation == Orientation.Random) {
Wander wander = prefab.GetComponent<Wander>();
// 有一半的概率进行翻转
if(Random.value <= 0.5) {
wander.Flip();
}
return;
}
}
}

代码说明:
  这里,我们使用MonoBehaviour提供的静态方法InvokeRepeating来实现等待一段时间后每隔一定时间重复调用某个函数的功能。如果我们只想实现等待一段时间后调用一次某个函数的功能,我们可以使用MonoBehaviour提供的静态方法Invoke来实现。

  接着,我们还需要修改Assets\Scripts\Enemy下的Wander.cs如下:

Wander.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class Wander : MonoBehaviour {
[Tooltip("是否朝向右边")]
public bool FacingRight = true;

[Tooltip("怪物水平移动的速度")]
[SerializeField]
private float MoveSpeed = 2f;


//用于设置怪物对象的物理属性
private Rigidbody2D m_Rigidbody;
// 用于保存当前的水平移动速度
private float m_CurrentMoveSpeed;

// 获取组件引用
private void Awake() {
m_Rigidbody = GetComponent<Rigidbody2D>();
}

// 设置字段的初始值
private void Start() {
if(FacingRight) {
m_CurrentMoveSpeed = MoveSpeed;
} else {
m_CurrentMoveSpeed = -MoveSpeed;
}
}

// 执行和物理相关的代码
private void FixedUpdate() {
m_Rigidbody.velocity = new Vector2(m_CurrentMoveSpeed, m_Rigidbody.velocity.y);
}

// 在Wander.cs脚本被禁用时被调用
private void OnDisable() {
// 设置水平方向上的速度为0
m_Rigidbody.velocity = new Vector2(0f, m_Rigidbody.velocity.y);
}

// 转向函数
public void Flip() {
FacingRight = !FacingRight;

m_CurrentMoveSpeed *= -1;

this.transform.localScale = Vector3.Scale(new Vector3(-1, 1, 1), this.transform.localScale);
}
}

代码说明:
  这里,我们将FacingRight的可见性变为public,且在Filp方法中,我们动态地更新FacingRight字段的值。这是为了在生成预设时,我们能动态地决定预设被实例化之后要往那边运动。

  编辑完成之后,我们删除场景中的AlienSlugAlienShip物体,然后新建一个名为GeneratorEmpty GameObject。接着,我们为Generator物体创建一个名为EnemyGeneratorEmpty GameObject,并将Generator.cs添加到EnemyGenerator物体上。最后,我们将Prefab Orientation设置为Random,并将Assets\Prefabs\Character下的AlienShipAlienSlug拖拽到Prefabs的赋值框,再运行游戏,我们可以看到场景中不断有随机朝向的怪物在EnemyGenerator的位置处产生。

设置Generator
设置Generator

添加粒子特效

  此时,怪物是在场景中凭空出现,为了在随机生成怪物时增加提示,我们为EnemyGenerator增加粒子特效。首先,我们为EnemyGenerator物体添加Particle System组件。

EnemyGenerator物体的Particle System组件设置:

  • Main Module:
    • Looping: false
    • Start Lifetime: 0.7
    • Start Speed: 0
    • Start Size(Random Between Two Constants): (0.9, 1)
    • Start Rotation(Random Between Two Constants): (0, 360)
    • Start Color: (173, 173, 173, 255)
    • Scaling Mode: Shape
    • Play On Awake: false
  • Emission:
    • Rate Over Time: 0
    • Brusts:
      • Element1: Time(0.000), Count(1), Cycles(1), Interval(0.010)
  • Shape:
    • Shape: Sphere
    • Radius: 0.01
  • Color over Lifetime:
    • Color(Gradient):
      • 左上角:Aplha(255), Location(78.8%)
      • 右上角:Aplha(0), Location(100%)
      • 左下角:Color(90, 90, 90), Location(0%)
      • 右下角:Color(90, 90, 90), Location(100%)
        Color Gradient
  • Size over Lifetime:
    • Size(Random Between Two Curves):
      • 上面是从(0, 0.32)(1, 1)的曲线
      • 下面是从(0, 0.25)(1, 0.85)的曲线
      • 纵坐标的最大值为10
      • 曲线的走势可以根据自己喜好调整
        Size Curves
  • Render
    • Material: Assets\Materials下的part_star
    • Max Particle Size: 10
    • Sorting Layer: Character
    • Order In Layer: 5

  设置完毕之后再次运行游戏,可以看到当怪物随机生成时,会出现发光的粒子效果。至此,我们已经完成EnemyGenerator的制作。


给场景添加多个EnemyGenerator

  EnemyGenerator制作完毕之后,我们要在场景里面加入多个EnemyGenerator。我们可以直接复制现有的EnemyGenerator,但如果后面我们需要对EnemyGenerator进行修改,那么我们就需要对所有的EnemyGenerator物体修改一遍,效率低下,且容易出错。

  而前面提到,当我们修改Prefab的时候,Prefab在场景中的所有实例对象都会被修改,因此,我们选择将EnemyGenerator制作为Prefab。首先,我们在Assets\Prefabs下创建一个名为Generators的文件夹,然后将EnemyGenerator物体拖拽至Assets\Prefabs\Generators下将其制作为Prefab。接着,我们将EnemyGenerator的Prefab往场景里面拖拽两次,创建两个EnemyGenerator的Prefab实例对象。最后,我们在Generator物体下创建一个名为EnemyGeneratorsEmpty GameObject,将EnemyGeneratorEnemyGenerator(1)EnemyGenerator(2)拖拽至EnemyGenerators下成为其子物体。

创建多个EnemyGenerator的实例对象
创建多个EnemyGenerator的实例对象

  当然,我们不能让怪物在场景中间生成,所以我们需要调整EnemyGeneratorEnemyGenerator(1)EnemyGenerator(2)的位置。

它们的位置信息如下:

  • EnemyGenerator: Position(-15.5, 15, 0)
  • EnemyGenerator(1): Position(0, 15, 0)
  • EnemyGenerator(2): Position(15.5, 15, 0)

  调整完成之后,运行游戏,可以看到空中不断落下随机生成的怪物。


后言

  至此,我们已完成实现随机生成更多怪物的所有工作。本篇文章涉及到的一些数值参数,大家可以根据自己的喜好进行修改。最后,本篇文章所做的修改,可以在PotatoGloryTutorial这个仓库的essay12分支下看到,读者可以clone这个仓库到本地进行查看。


参考链接

  1. Unity的Prefabs
  2. InvokeRepeating说明