本节最终效果
前言
本节紧跟着上一篇,主要实现对象池程序化生成敌人和属性配置。
敌人追击玩家
新增不同敌人预制体,并配置默认跑步动画
新增一个敌人类的脚本,实现了敌人向玩家移动并面对玩家的功能。注释已添加在相应的代码行上。
public class Enemy : MonoBehaviour { public float speed; // 移动速度 public Rigidbody2D target; // 目标(玩家) bool isLive = true; // 是否存活 Rigidbody2D rigid; // 刚体组件 SpriteRenderer spriter; // 精灵渲染器组件 void Awake() { rigid = GetComponent<Rigidbody2D>(); spriter = GetComponent<SpriteRenderer>(); } private void Start() { target = GameManager.instance.player.GetComponent<Rigidbody2D>(); } void FixedUpdate() { if(!isLive) return; // 根据目标的位置和自身的位置决定是否翻转精灵 spriter.flipX = (target.position.x < rigid.position.x); // 计算目标位置和当前位置之间的方向向量 Vector2 dirVec = target.position - rigid.position; // 计算下一帧移动的位置向量,保持单位长度并乘以速度和固定时间步长 Vector2 nextVec = dirVec.normalized * speed * Time.fixedDeltaTime; // 移动到下一帧位置 rigid.MovePosition(rigid.position + nextVec); } }
挂载脚本,配置参数,一般敌人移速都是比主角低
我们放几个敌人到场景进行测试,效果
处理超出游戏区域的敌人
因为敌人的移速肯定比主角慢,主角一直往前走,如果我们不做处理,后面的敌人会越来越多,而且超出了屏幕,这部分的敌人对我们来说是没有意义的,而且还占用资源,我们可以复用前面的Reposition代码,超出游戏区域,重新定位敌人对象的位置瞬移到角色前面。
修改Reposition
Collider2D coll; private void Awake() { coll = GetComponent<Collider2D>(); } // 当碰撞体离开触发器时调用 void OnTriggerExit2D(Collider2D collision) { //... // 根据自身的标签进行不同的处理 switch (transform.tag) { //... // 如果自身是"Enemy"标签,重新定位敌人对象的位置,以确保它不会超出游戏区域。 case "Enemy": if (coll.enabled) { Vector3 dist = playerPos - myPos; Vector3 ran = new Vector3(Random.Range(-3f, 3f), Random.Range(-3f, 3f), 0); transform.Translate(ran + dist * 2); } break; } }
敌人挂载脚本,并配置Enemy标签
效果
定义对象池
对象池介绍:【Unity小技巧】Unity探究自制对象池和官方内置对象池(ObjectPool)的使用
新增对象池脚本PoolManager
public class PoolManager : MonoBehaviour { public GameObject[] prefabs; // 预制体数组 List<GameObject>[] pools; // 对象池数组 void Awake() { // 初始化对象池数组 pools = new List<GameObject>[prefabs.Length]; for (int index = 0; index < pools.Length; index++) { pools[index] = new List<GameObject>(); } Debug.Log(pools.Length); } public GameObject Get(int index) { GameObject select = null; // 在对象池中查找未激活的对象 foreach (GameObject item in pools[index]) { if (!item.activeSelf) { // 如果找到未激活的对象,选择它并激活 select = item; select.SetActive(true); break; } } // 如果对象池中没有未激活的对象,则创建一个新对象 if (!select) { select = Instantiate(prefabs[index], transform); pools[index].Add(select); } // 返回选择的对象 return select; } }
挂载脚本,配置敌人预制体
修改GameManager,绑定对象池
public PoolManager pool; // 对象池
挂载参数
使用对象池生成敌人
新增Spawner 敌人生成器类的脚本,用于在不同的出生点上生成敌人。
public class Spawner : MonoBehaviour { public Transform[] spawnPoints; // 出生点数组 float timer; void Awake() { spawnPoints = GetComponentsInChildren<Transform>(); } void Update() { timer += Time.deltaTime; if (timer >= 0.2f) { timer = 0; Spawn(); } } void Spawn() { // 从对象池中获取敌人 GameObject enemy = GameManager.instance.pool.Get(Random.Range(0, 5)); // 设置敌人的位置为随机选择的出生点 enemy.transform.position = spawnPoints[Random.Range(1, spawnPoints.Length)].position; } }
添加敌人生成点位置
挂载配置参数
效果
随时间推移生成不同属性的敌人,提升难度
修改GameManager
public float gameTime; // 游戏时间 public float maxGameTime = 100f; // 最大游戏时间 void Update() { gameTime += Time.deltaTime; // 更新游戏时间 // 将游戏时间限制在最大游戏时间内 if (gameTime >= maxGameTime) gameTime = maxGameTime; }
修改Spawner
int level; public SpawnData[] spawnData;//生成物体的数据配置 void Update() { level = Mathf.Min(Mathf.FloorToInt(GameManager.instance.gameTime / 10f), spawnData.Length - 1); timer += Time.deltaTime; if (timer > spawnData[level].spawnTime) { timer = 0; Spawn(); } } void Spawn() { // 从对象池中获取敌人 GameObject enemy = GameManager.instance.pool.Get(0); // 设置敌人的位置为随机选择的出生点 enemy.transform.position = spawnPoints[Random.Range(1, spawnPoints.Length)].position; enemy.GetComponent<Enemy>().Init(spawnData[level]); } //用于存储生成物体的数据 [System.Serializable] public class SpawnData { public int spriteType; // 精灵索引 public float spawnTime;// 生成时间 public int health;// 生命值 public float speed;// 速度 }
配置每一种怪物的属性值
修改Enemy,根据参数,动态配置敌人动画和血量
Animator anim; public float health;//当前生命值 public float maxHealth;//最大生命值 public RuntimeAnimatorController[] animCon;// 不同类型精灵的动画控制器 void Awake() { //。。。 anim = GetComponent<Animator>(); } private void Start() { target = GameManager.instance.player.GetComponent<Rigidbody2D>(); isLive = true; health = maxHealth; } //初始化敌人的属性和状态 public void Init(SpawnData data) { // 根据生成数据设置敌人的动画控制器、速度、最大生命值和当前生命值 anim.runtimeAnimatorController = animCon[data.spriteType]; speed = data.speed; maxHealth = data.health; health = data.health; }
保留一个敌人预制体即可,配置不同的敌人动画控制器参数
当然,对象池也保留一个敌人配置
效果,每过10秒生成下一种敌人,召唤不同等级的怪物
参考
【视频】https://www.youtube.com/watch?v=MmW166cHj54&list=PLO-mt5Iu5TeZF8xMHqtT_DhAPKmjF6i3x
源码
源码在最后一节