文章目录
- 介绍
- 寻路系统
- 怪物生成器
- 制作3种初级炮台、3种升级炮台
- 设置炮台属性
- 选择炮台,添加监听事件
- 炮弹追踪攻击敌人
- 拖动鼠标实现相机视角转换
- 鼠标光标放在cube上变色
- 文字动画
介绍
关键技术:
寻路系统
生成怪物算法
粒子系统
line renderer制作追踪射线
相机视角移动、放大
炮弹追踪算法
粒子特效
按钮动画制作
寻路系统
设置几个基准点,用于偏移方向
- 定义一个Move方法
- 判断当前行数是否超过位置数组的长度,如果是则直接返回
- 根据当前位置与目标位置计算出移动方向,并乘以移动速度和时间,用transform.Translate方法进行移动
- 判断当前位置是否接近目标位置,如果是则将当前行数index加1
- 如果当前行数大于位置数组的长度减1,说明已经到达终点,调用ReachDestination方法
- 完成移动方法的定义
void Move()
{
if (index > positions.Length - 1) return;
transform.Translate((positions[index].position - transform.position).normalized * Time.deltaTime * speed);
if (Vector3.Distance(positions[index].position, transform.position) < 0.2f)
{
index++;
}
if (index > positions.Length - 1)
{
ReachDestination();
}
}
怪物生成器
一波一波生成敌人,相邻波时间间隔为wavaRate
相邻敌人的时间间隔为rate
序列化,设置每一波敌人的基本属性
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//保存每一波敌人生成所需要的属性
[System.Serializable]
public class Wave {
public GameObject enemyPrefab;
public int count;
public float rate;
}
只有当前波完全销毁,才能有下一波攻势
// 这个脚本负责按波次生成敌人,并跟踪当前有多少敌人存活
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemySpawner : MonoBehaviour {
// 一个静态变量,用于跟踪当前有多少敌人存活
public static int CountEnemyAlive = 0;
// 一个波次数组,指定每个波次要生成的敌人类型和数量
public Wave[] waves;
// 敌人生成的起始位置
public Transform START;
// 每个波次之间的时间间隔
public float waveRate = 0.2f;
// 生成敌人的协程的引用
private Coroutine coroutine;
void Start()
{
// 开始生成敌人的协程
coroutine = StartCoroutine(SpawnEnemy());
}
public void Stop()
{
// 停止生成敌人的协程
StopCoroutine(coroutine);
}
IEnumerator SpawnEnemy()
{
// 循环遍历每个波次的敌人
foreach (Wave wave in waves)
{
// 为这个波次生成指定数量的敌人
for (int i = 0; i < wave.count; i++)
{
// 在起始位置实例化敌人预制件
GameObject.Instantiate(wave.enemyPrefab, START.position, Quaternion.identity);
// 增加当前存活敌人的计数
CountEnemyAlive++;
// 在生成最后一个敌人之前,等待指定的时间
if(i != wave.count - 1)
yield return new WaitForSeconds(wave.rate);
}
// 在这个波次的所有敌人被消灭之前,等待
while (CountEnemyAlive > 0)
{
yield return 0;
}
// 等待指定的时间,然后开始下一个波次
yield return new WaitForSeconds(waveRate);
}
// 在最后一个波次的所有敌人被消灭之前,等待
while (CountEnemyAlive > 0)
{
yield return 0;
}
// 在GameManager实例上调用Win方法,以胜利结束游戏
GameManager.Instance.Win();
}
}
制作3种初级炮台、3种升级炮台
设置炮台属性
炮台预制体、炮台价格、升级后的预制体、升级价格、枚举三种炮台类型
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class TurretData {
public GameObject turretPrefab;
public int cost;
public GameObject turretUpgradedPrefab;
public int costUpgraded;
public TurretType type;
}
public enum TurretType
{
LaserTurret,
MissileTurret,
StandardTurret
}
选择炮台,添加监听事件
public TurretData laserTurretData;
public TurretData missileTurretData;
public TurretData standardTurretData;
//表示当前选择的炮台(要建造的炮台)
private TurretData selectedTurretData;
public void OnLaserSelected(bool isOn)
{
if (isOn)
{
selectedTurretData = laserTurretData;
}
}
public void OnMissileSelected(bool isOn)
{
if (isOn)
{
selectedTurretData = missileTurretData;
}
}
public void OnStandardSelected(bool isOn)
{
if (isOn)
{
selectedTurretData = standardTurretData;
}
}
炮弹追踪攻击敌人
以下是每个函数的作用:
-
void OnTriggerEnter(Collider col)
:当有物体进入防御塔触发器范围内时,将其添加到敌人列表中。 -
void OnTriggerExit(Collider col)
:当有物体离开防御塔触发器范围时,将其从敌人列表中移除。 -
void Start()
:在开始时初始化计时器。 -
void Update()
:在每一帧中更新防御塔的逻辑。 -
void Attack()
:发射子弹攻击敌人。 -
void UpdateEnemys()
:更新敌人列表,移除已经死亡的敌人。
在 Update 函数中:
-
如果敌人列表不为空且敌人列表中的第一个敌人不为空,则瞄准敌人的头部位置。
-
如果不使用激光攻击,则增加计时器。如果敌人列表不为空且计时器超过攻击间隔时间,则攻击敌人。
-
如果使用激光攻击且敌人列表不为空,则显示激光渲染器和激光特效,并攻击敌人。
-
如果敌人列表为空,则隐藏激光特效和激光渲染器。
在 Attack 函数中:
-
如果敌人列表中的第一个敌人为空,则更新敌人列表。
-
如果敌人列表不为空,则发射子弹攻击敌人,否则重置计时器。
在 UpdateEnemys 函数中:
-
创建一个空列表,用于存储已经死亡的敌人在敌人列表中的下标。
-
遍历敌人列表,找出已经死亡的敌人。
-
遍历已经死亡的敌人在敌人列表中的下标,将它们从敌人列表中移除。
// 引入命名空间
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 炮塔脚本
public class Turret : MonoBehaviour {
// 炮塔攻击的敌人列表
private List<GameObject> enemys = new List<GameObject>();
// 敌人进入触发器
void OnTriggerEnter(Collider col)
{
if (col.tag == "Enemy")
{
enemys.Add(col.gameObject);
}
}
// 敌人离开触发器
void OnTriggerExit(Collider col)
{
if (col.tag == "Enemy")
{
enemys.Remove(col.gameObject);
}
}
// 炮塔攻击频率
public float attackRateTime = 1;
// 计时器
private float timer = 0;
// 子弹预制体
public GameObject bulletPrefab;
// 发射子弹的位置
public Transform firePosition;
// 炮塔头部
public Transform head;
// 是否使用激光武器
public bool useLaser = false;
// 激光武器伤害值
public float damageRate = 70;
// 激光武器的LineRenderer组件
public LineRenderer laserRenderer;
// 激光武器的特效
public GameObject laserEffect;
// 初始化
void Start()
{
timer = attackRateTime;
}
// 更新
void Update()
{
// 如果有敌人
if (enemys.Count > 0 && enemys[0] != null)
{
// 瞄准第一个敌人
Vector3 targetPosition = enemys[0].transform.position;
targetPosition.y = head.position.y;
head.LookAt(targetPosition);
}
// 如果不使用激光武器
if (useLaser == false)
{
// 计时器累加
timer += Time.deltaTime;
// 如果有敌人并且计时器时间到
if (enemys.Count > 0 && timer >= attackRateTime)
{
// 重置计时器
timer = 0;
// 进行攻击
Attack();
}
}
// 如果使用激光武器
else if(enemys.Count>0)
{
// 开启激光线和特效
if (laserRenderer.enabled == false)
laserRenderer.enabled = true;
laserEffect.SetActive(true);
// 如果第一个敌人为空,更新敌人列表
if (enemys[0] == null)
{
UpdateEnemys();
}
// 如果有敌人
if (enemys.Count > 0)
{
// 激光线设置起始位置和结束位置
laserRenderer.SetPositions(new Vector3[]{firePosition.position, enemys[0].transform.position});
// 对第一个敌人造成伤害
enemys[0].GetComponent<Enemy>().TakeDamage(damageRate *Time.deltaTime );
// 特效跟随第一个敌人
laserEffect.transform.position = enemys[0].transform.position;
// 特效朝向炮塔
Vector3 pos = transform.position;
pos.y = enemys[0].transform.position.y;
laserEffect.transform.LookAt(pos);
}
}
// 如果没有敌人,关闭激光特效和线
else
{
laserEffect.SetActive(false);
laserRenderer.enabled = false;
}
}
// 攻击
void Attack()
{
// 如果第一个敌人为空,更新敌人列表
if (enemys[0] == null)
{
UpdateEnemys();
}
// 如果有敌人
if (enemys.Count > 0)
{
// 实例化子弹
GameObject bullet = GameObject.Instantiate(bulletPrefab, firePosition.position, firePosition.rotation);
// 设置子弹目标为第一个敌人
bullet.GetComponent<Bullet>().SetTarget(enemys[0].transform);
}
else
{
// 如果没有敌人,重置计时器
timer = attackRateTime;
}
}
// 更新敌人列表
void UpdateEnemys()
{
// 找到所有空的敌人,并将其索引加入空索引列表中
List<int> emptyIndex = new List<int>();
for (int index = 0; index < enemys.Count; index++)
{
if (enemys[index] == null)
{
emptyIndex.Add(index);
}
}
// 根据空索引列表,移除空敌人
for (int i = 0; i < emptyIndex.Count; i++)
{
enemys.RemoveAt(emptyIndex[i]-i);
}
}
}
拖动鼠标实现相机视角转换
把这个脚本,挂载到主相机上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ViewController : MonoBehaviour {
public float sizespeed = 1; // 定义了一个名为sizespeed的公共(public)浮点型(float)变量,初始值为1
public float mouseSpeed = 10; // 定义了一个名为mouseSpeed的公共浮点型变量,初始值为10
private Vector3 lastMousePosition; // 定义了一个名为lastMousePosition的私有(private)Vector3类型变量
// Update is called once per frame
void Update () { // 定义了一个名为Update的方法,在每一帧(frame)中被调用
float mouse = -Input.GetAxis("Mouse ScrollWheel"); // 获取鼠标滚轮的输入值,并将其赋值给名为mouse的局部(local)浮点型变量
// 鼠标中键按住拖动
if (Input.GetMouseButton(2)) { // 检测if语句中的条件是否为真,如果鼠标中键被按住,则执行大括号内的代码块
Vector3 deltaMousePosition = Input.mousePosition - lastMousePosition; // 获取当前鼠标位置和上一次鼠标位置之间的差值,并将其赋值给名为deltaMousePosition的局部Vector3类型变量
transform.Translate(-deltaMousePosition.x * mouseSpeed * Time.deltaTime, -deltaMousePosition.y * mouseSpeed * Time.deltaTime, 0); // 将摄像机的位置向左右和上下移动,移动的距离由鼠标的移动距离和鼠标速度决定
}
transform.Translate(new Vector3(0, mouse * sizespeed, 0) * Time.deltaTime, Space.World); // 将摄像机的位置向上或向下移动,移动的距离由鼠标滚轮的输入值和大小速度决定
lastMousePosition = Input.mousePosition; // 将鼠标当前位置赋值给lastMousePosition变量,以便下一帧计算鼠标位置差值
}
}
鼠标光标放在cube上变色
给cube添加脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class aaa : MonoBehaviour {
private Renderer renderer; // 渲染器组件
void Start()
{
renderer = GetComponent<Renderer>(); // 获取自身的渲染器组件
}
// 当鼠标指针进入该物体的渲染范围内时执行
void OnMouseEnter()
{
// 如果鼠标不在UI元素上,则将该物体的材质颜色改为红色
if (EventSystem.current.IsPointerOverGameObject() == false)
{
renderer.material.color = Color.red;
}
}
// 当鼠标指针离开该物体的渲染范围时执行
void OnMouseExit()
{
// 将该物体的材质颜色改回白色
renderer.material.color = Color.white;
}
}