目录
20、僵尸攻击
僵尸攻击动画导入
开始在写僵尸攻击的主逻辑,在Zombie.cs中更新代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Zombie : MonoBehaviour
{
……
// 是否在攻击中
private bool isAttackState;
// 攻击力
private float attackValue = 100;
……
/// <summary>
/// 僵尸移动
/// </summary>
private void Move()
{
// 没有得到网格,没有僵尸
if (currGrid == null) return;
// 如果在攻击中也跳过移动检测
if (isAttackState) return;
currGrid = GridManager.instance.GetGridByWorldPos(transform.position);
// 当前网格中有植物 并且 它在僵尸的左边 且 距离很近
if (currGrid.HavePlant && currGrid.CurrPlantBase.transform.position.x < transform.position.x
&& transform.position.x - currGrid.CurrPlantBase.transform.position.x < 0.3f)
{
// 开始攻击植物
Attack(currGrid.CurrPlantBase);
}
// -1.33f是一个网格的距离,所以-1.33f*(1/speed)等于每秒走了1.33f*(1/speed)的距离【速度】,然后乘上时间Time.deltaTime,就是每帧走的距离
transform.Translate(new Vector2(-1.33f,0)*(1/speed)*Time.deltaTime);
}
private void Attack(PlantBase plant)
{
isAttackState = true;
// 自身播放攻击动画
animator.Play("Zombie_Attack");
// 植物相关逻辑
StartCoroutine(DoHurt(plant));
}
/// <summary>
/// 附加伤害给植物
/// </summary>
/// <returns></returns>
IEnumerator DoHurt(PlantBase plant)
{
// 植物生命值大于0才扣血
while (currGrid.CurrPlantBase.Hp > 0)
{
plant.Hurt(attackValue/5);
// 每0.2s扣一次血
yield return new WaitForSeconds(0.2f);
}
}
}
由于攻击时,阳光植物会闪烁,所以我们将SunFlower.cs中颜色闪烁的代码放在PlantBase中去
然后在SunFlower.cs中更新代码
可以看到通过继承调用就行,PlantBase.cs更新代码为:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class PlantBase : MonoBehaviour
{
……
// 植物的生命值
public float hp;
public float Hp
{
get => hp;
}
……
/// <summary>
/// 受伤方法,被僵尸攻击时调用
/// </summary>
public void Hurt(float hurtValue)
{
hp -= hurtValue;
StartCoroutine(ColorEF(0.2f, new Color(0.5f, 0.5f, 0.5f), 0.05f, null));
if (hp <= 0)
{
// 死亡
Dead();
}
}
/// <summary>
/// 颜色变化效果
/// </summary>
/// <returns></returns>
protected IEnumerator ColorEF(float wantTime, Color targetColor, float delayTime, UnityAction fun)
{
float currTime = 0;
float lerp;
while (currTime < wantTime)
{
yield return new WaitForSeconds(delayTime);
lerp = currTime / wantTime;
currTime += delayTime;
// 实现一个从白到红的插值计算,lerp为0就是白色(原色),如果为1就是Color(1,0.6f,0)
spriteRenderer.color = Color.Lerp(Color.white, targetColor, lerp);
}
// 恢复原来的附加色(白色)
spriteRenderer.color = Color.white;
if (fun != null) fun();
}
private void Dead()
{
// CurrPlantBase修改为空的同时,也将currGrid.HavePlant改为false了,这样Move函数中就可以防止出错
currGrid.CurrPlantBase = null;
Destroy(gameObject);
}
……
}
测试
21、僵尸攻击逻辑优化
由于僵尸的状态是僵尸行走=>僵尸攻击=>植物死亡=>僵尸行走,所以优化的话,可以使用有限状态机FSM
Zombie.cs的全部代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using Random = UnityEngine.Random;
public enum ZombieState
{
Idel,
Walk,
Attack,
Dead
}
public class Zombie : MonoBehaviour
{
// 僵尸的状态
private ZombieState state;
private Animator animator;
private Grid currGrid;
// 僵尸速度表示1格speed秒,所以1/(speed)是每秒1/(speed)格 单位:格/秒
private float speed = 6;
// 是否在攻击中
private bool isAttackState;
// 攻击力
private float attackValue = 100;
// 行走动画的名称,随机来的
private string walkAnimationStr;
public ZombieState State
{
get => state;
// 如果状态被写入,则播放相关动画
set
{
state = value;
switch (state)
{
case ZombieState.Idel:
// 播放行走动画,但是要卡在第一帧
animator.Play(walkAnimationStr,0,0);
animator.speed = 0;
break;
case ZombieState.Walk:
animator.Play(walkAnimationStr);
animator.speed = 1;
break;
case ZombieState.Attack:
animator.Play("Zombie_Attack");
animator.speed = 1;
break;
case ZombieState.Dead:
break;
}
}
}
private void Awake()
{
// 从[1,4)中随机一个整数
int rangeWalk = Random.Range(1, 4);
// 在初始化时随机播放一个动画
switch (rangeWalk)
{
case 1:
walkAnimationStr = "Zombie_Walk1";
break;
case 2:
walkAnimationStr = "Zombie_Walk2";
break;
case 3:
walkAnimationStr = "Zombie_Walk3";
break;
}
GetGridByVerticalNum(0);
}
void Start()
{
// 获得子物体的动画组件
animator = GetComponentInChildren<Animator>();
}
void Update()
{
// 每帧中判断状态
FSM();
}
/// <summary>
/// 状态检测
/// </summary>
private void FSM()
{
switch (State)
{
case ZombieState.Idel:
State = ZombieState.Walk;
break;
case ZombieState.Walk:
// 一直向左走并且遇到植物会攻击,攻击结束继续走
Move();
break;
case ZombieState.Attack:
if (isAttackState) break;
Attack(currGrid.CurrPlantBase);
break;
case ZombieState.Dead:
break;
}
}
/// <summary>
/// 获取一个网格,并决定在第几排出现
/// </summary>
/// <param name="verticalNum"></param>
private void GetGridByVerticalNum(int verticalNum)
{
// 得到网格
currGrid = GridManager.instance.GetGridByVerticalNum(verticalNum);
// x坐标不变,修正它的y坐标为网格的世界坐标
transform.position = new Vector3(transform.position.x, currGrid.Position.y);
}
/// <summary>
/// 僵尸移动
/// </summary>
private void Move()
{
// 没有得到网格,没有僵尸
if (currGrid == null) return;
currGrid = GridManager.instance.GetGridByWorldPos(transform.position);
// 当前网格中有植物 并且 它在僵尸的左边 且 距离很近
if (currGrid.HavePlant && currGrid.CurrPlantBase.transform.position.x < transform.position.x
&& transform.position.x - currGrid.CurrPlantBase.transform.position.x < 0.3f)
{
// 开始攻击植物,切换状态
State = ZombieState.Attack;
return;
}
// -1.33f是一个网格的距离,所以-1.33f*(1/speed)等于每秒走了1.33f*(1/speed)的距离【速度】,然后乘上时间Time.deltaTime,就是每帧走的距离
transform.Translate(new Vector2(-1.33f,0)*(1/speed)*Time.deltaTime);
}
private void Attack(PlantBase plant)
{
isAttackState = true;
// 植物相关逻辑
StartCoroutine(DoHurt(plant));
}
/// <summary>
/// 附加伤害给植物
/// </summary>
/// <returns></returns>
IEnumerator DoHurt(PlantBase plant)
{
// 植物生命值大于0才扣血(plant是形参,所以currGrid.CurrPlantBase=null,是不会出现空引用的)
while (plant.Hp > 0)
{
plant.Hurt(attackValue/5);
// 每0.2s扣一次血
yield return new WaitForSeconds(0.2f);
}
isAttackState = false;
// 植物死亡后继续走
State = ZombieState.Walk;
}
}