《土豆荣耀》重构笔记(十四)随机生成更多的怪物


前言

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


制作Generator

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

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) &#123;
            Debug.LogError("请至少为Prefabs添加一个预设对象");
        &#125;
    &#125;

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

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

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

        // 播放粒子特效
        if(m_Particle != null) &#123;
            m_Particle.Play();
        &#125;

        // 不需要考虑朝向
        if(PrefabOrientation == Orientation.None) &#123;
            return;
        &#125;

        if(PrefabOrientation == Orientation.Left) &#123;
            Wander wander = prefab.GetComponent<Wander>();
            if(wander.FacingRight) &#123;
                wander.Flip();
            &#125;
            return;
        &#125; 

        if(PrefabOrientation == Orientation.Right) &#123;
            Wander wander = prefab.GetComponent<Wander>();
            if(!wander.FacingRight) &#123;
                wander.Flip();
            &#125;
            return;
        &#125;

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

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

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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

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


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

    // 获取组件引用
    private void Awake() &#123;
        m_Rigidbody = GetComponent<Rigidbody2D>();
    &#125;

    // 设置字段的初始值
    private void Start() &#123;
        if(FacingRight) &#123;
            m_CurrentMoveSpeed = MoveSpeed;
        &#125; else &#123;
            m_CurrentMoveSpeed = -MoveSpeed;
        &#125;
    &#125;

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

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

    // 转向函数
    public void Flip() &#123;
        FacingRight = !FacingRight;

        m_CurrentMoveSpeed *= -1;

        this.transform.localScale = Vector3.Scale(new Vector3(-1, 1, 1), this.transform.localScale);
    &#125;
&#125;

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

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

设置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的实例对象

  当然,我们不能让怪物在场景中间生成,所以我们需要调整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说明

文章作者: RainbowCyan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 RainbowCyan !
 上一篇
《土豆荣耀》重构笔记(十五)实现角色和怪物掉入河中被销毁的功能 《土豆荣耀》重构笔记(十五)实现角色和怪物掉入河中被销毁的功能
前言  我们在上篇文章中已经实现随机生成更多怪物的功能,但怪物和角色死亡时,并不会被销毁。当生成的怪物增多时,这会占用大量游戏内存。为了节约内存,我们需要实现角色和怪物掉入河中就会在游戏场景中被销毁的功能。 制作浪花溅
下一篇 
《土豆荣耀》重构笔记(十三)实现放置炸弹的功能 《土豆荣耀》重构笔记(十三)实现放置炸弹的功能
前言  在上篇文章,我们已经实现了对怪物造成伤害的功能,但此时我们只有发射导弹一种攻击方式。为了增加游戏的可玩性,我们将制作炸弹,玩家可以通过放置炸弹来对范围内的怪物造成伤害。在开始制作炸弹之前,我们先梳理一下和炸弹有关
  目录