现在呢我们来搞一下防御塔对怪物的检测逻辑。
首先呢,我想到了触发器检测,并配以一条列表储存在该防御塔射程内的所有敌人。
那么我们来建一个CheckEnemy脚本,用来监测敌人,跟随注视并攻击。
话不多说,贴上代码先:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CheckEnemy : MonoBehaviour {
/// <summary>
/// 炮塔价格
/// </summary>
public float value = 100;
/// <summary>
/// 开火频率
/// </summary>
public int firewaittime = 2;
/// <summary>
/// 临时ID,测试
/// </summary>
public int idi = 0;
/// <summary>
/// 炮塔转动速度
/// </summary>
public float turnSpeed = 2;
/// <summary>
/// 最远攻击距离
/// </summary>
public float disFire = 12;
/// <summary>
/// 是否有正在攻击的目标
/// </summary>
private bool isAlarm;
/// <summary>
/// 子弹
/// </summary>
public GameObject ziDan;
/// <summary>
/// 子弹生成位置
/// </summary>
public Transform firePos;
/// <summary>
/// 攻击目标
/// </summary>
private Transform targetEnemy;
/// <summary>
/// 存储所有检测到的敌人
/// </summary>
private List<Transform> enemyList;
/// <summary>
/// 开火的协程是否开启
/// </summary>
private bool isFire = false;
private Input_Key ik;
void Start ()
{
enemyList = new List<Transform>();
}
void Update () {
//攻击队列里有敌人
if (enemyList.Count != 0)
{
if (enemyList[0] == null)
{
enemyList[0] = enemyList[1];
enemyList.Remove(enemyList[1]);
}
else
{
if (!isFire)
{
isFire = true;
StartCoroutine("Fire");
}
//瞄准敌人
//transform.LookAt(enemyList[0]);
Vector3 direction = enemyList[0].position - transform.position;
Quaternion qua = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Lerp(transform.rotation, qua, Time.deltaTime * turnSpeed);
////如果敌人脱离攻击范围,或者死亡
if (Vector3.Distance(transform.position, enemyList[0].position) > disFire || enemyList[0].GetComponent<EnemyContrl>().Blood <= 0)
{
enemyList.Remove(enemyList[0]);
}
}
}
else
{
if (isFire)
{
StopCoroutine("Fire");
isFire = false;
}
}
}
/// <summary>
/// 检测触发物是否为敌人,是的话放入攻击列表
/// </summary>
void OnTriggerEnter(Collider other)
{
//可以改成检测空中和地上
if (other.transform.tag == "Enemy"||other.transform.tag == "FlyEnemy")
{
if (!enemyList.Contains(other.transform)) {
enemyList.Add(other.transform);
}
}
}
/// <summary>
/// 炮塔射击敌人
/// </summary>
IEnumerator Fire() {
while (true) {
if (enemyList.Count != 0 && enemyList[0] != null)
{
//生成子弹并指定发射的目标
GameObject go = Instantiate<GameObject>(ziDan, firePos.transform.position, firePos.transform.rotation);
go.GetComponent<ZhaDan>().target = enemyList[0];
}
else if (enemyList[0] == null) {
enemyList.Remove(enemyList[0]);
}
yield return new WaitForSeconds(firewaittime);
}
}
}
这里有一小点:如果不写下面的这个就会出现n座塔同时攻击有一个目标,怪物被其中一座塔收走人头,其他以该怪物为目标的塔就会丢失目标而哑火。
if (enemyList[0] == null)
{
enemyList[0] = enemyList[1];
enemyList.Remove(enemyList[1]);
}
为了改正这个bug,我一开始是想直接在enemyList[0] = null时enemyList.Remove(enemyList[0]),但是不知道为什么还是会出错,所以我就改成把enemyList[1]提前一个单位,并enemyList.Remove(enemyList[1]),这样就好使了,坑爹啊,各位大神如果知道原因请在评论区赐教贪狼感激不尽。
然后我们迎来了本游戏的重头戏“防御塔”攻击方式——弹药。我分成了三大类:1.单体攻击类,2.范围攻击类。而这两类又可以分别派生出对地类,对空类甚至减速buff这种buff类
首先,我们来看看单体攻击类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour {
public float damageValue = 25f;
/// <summary>
/// 攻击目标
/// </summary>
public Transform target;
/// <summary>
/// 子弹发射速度
/// </summary>
public float speed = 10f;
void Update ()
{
if (target&&target!=null)
{
transform.position = Vector3.MoveTowards(transform.position, target.transform.position+new Vector3(0,1.5f,0), speed * Time.deltaTime);
}
//丢失目标
else {
Destroy(transform.gameObject);
}
}
//此处为对地对空双功能,你可以改成单一功能
void OnTriggerEnter(Collider other) {
if (other.transform.tag == "Enemy"|| other.transform.tag == "FlyEnemy") {
other.GetComponent<EnemyContrl>().EnemyHurt(damageValue);
Destroy(this.gameObject);
}
}
}
然后是范围伤害类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ZhaDan : MonoBehaviour {
public float DamageOffeset;
public float damageValue;
/// <summary>
/// 攻击目标
/// </summary>
public Transform target;
/// <summary>
/// 子弹发射速度
/// </summary>
public float speed = 10f;
// Use this for initialization
/// <summary>
/// AOE半径
/// </summary>
public float r = 1f;
void Start()
{
}
void Update()
{
if (target.GetComponent<CapsuleCollider>() !=null)
{
if (transform.tag != "Ice")
{
transform.position = Vector3.MoveTowards(transform.position, target.transform.position + new Vector3(0, 2.5f, 0), speed * Time.deltaTime);
}
else {
transform.position = Vector3.MoveTowards(transform.position, target.transform.position + new Vector3(0, 0.1f, 0), speed * Time.deltaTime);
}
}
else
{
Destroy(transform.gameObject);
}
}
void OnTriggerEnter(Collider other)
{
if (other.transform.tag == "Enemy")
{
Grenade_AOE_Damage(transform, r, damageValue);
}
}
void Grenade_AOE_Damage(Transform _grenade, float _AOE_radius, float _damage)
{
//获取手雷_AOE_radius范围内所有的碰撞体(敌人)
Collider[] colliders = Physics.OverlapSphere(_grenade.position, _AOE_radius);
//遍历范围内所有敌人并给予伤害
for (int i = 0; i < colliders.Length; i++)
{
bool isCap = colliders[i].GetType() == (typeof(CapsuleCollider));
if (isCap && (colliders[i].gameObject.tag=="Enemy"))
{
//判断距离中心爆炸点距离,并实现伤害衰减
float dis = Vector3.Distance(colliders[i].transform.position, _grenade.position);
float dam = _damage - dis * DamageOffeset;
//加上特效
//获取生命脚本组件,调用伤害函数
colliders[i].gameObject.GetComponent<EnemyContrl>().EnemyHurt(dam);
}
}
StartCoroutine(Boom());
}
IEnumerator Boom() {
transform.GetComponent<MeshRenderer>().enabled = false;
GameObject go;
if (transform.tag == "Bomb")
{
go = Instantiate<GameObject>((GameObject)Resources.Load("fireball/Boom/Prefabs/Bomb"), transform.position, transform.rotation);
} else if (transform.tag == "Ice") {
go = Instantiate<GameObject>((GameObject)Resources.Load("fireball/IceEffect/Prefabs/Fx/IceGo"), transform.position, transform.rotation);
}
else {
go = null;
}
yield return new WaitForSeconds(0.8f);
Destroy(this.gameObject);
if (go != null) {
Destroy(go);
}
}
}
然后我们上网下载一个冰霜特效和爆炸特效,可以增加真实感,并在冰霜特效上挂载脚本控制减速协程:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class IceBullet : MonoBehaviour {
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnTriggerEnter(Collider other) {
if (other.transform.tag == "Enemy" && other.transform.GetComponent<NavMeshAgent>())
{
other.transform.GetComponent<EnemyContrl>().DealSpeed();
}
}
}
这时,我们需要在EnemyContrl脚本及加些“零件”:
private bool isLowSpeed = false;
public void DealSpeed() {
if (isLowSpeed)
{
return;
}
else {
isLowSpeed = true;
StartCoroutine(LowSpeed());
}
}
IEnumerator LowSpeed() {
transform.GetComponent<NavMeshAgent>().speed /= 2;
yield return new WaitForSeconds(2.5f);
if(transform.GetComponent<NavMeshAgent>())
transform.GetComponent<NavMeshAgent>().speed *= 2;
![Uploading IS}]3IW`}1{XXX_(8YB{98F_078727.png . . .]
isLowSpeed = false;
}
其中isLowSpeed是为了防止同一怪物被反复减速,然后可以将减速幅度设为public类型,以便想要防御塔升级时加大减速幅度的朋友控制。
另外还有激光枪,喷火筒两种,这里我将粒子特效挂在枪口,并为粒子特效增加碰撞器,使其只检测Enemy层敌人。
然后有两种思路,第一种是在CheckEnemy时,判断自己是否是激光枪,喷火筒(tag=“strong”)
然后监测到敌人就向目标放“特效”(用ParticleSystem my_PS;my_PS.Stop();my_PS.Play();)控制。然后通过粒子碰撞器,将碰到的敌人扣除血量(可以用OnParticleCollisionStay判断)。
第二种思路是我们偷懒想出的办法,特效照开不误,但是用“隐形的”子弹和炮弹(激光单体,喷火群体)以很快的速度攻击怪物(哈哈这样就可以用旧代码实现新功能了,我好懒)。