0
点赞
收藏
分享

微信扫一扫

【Unity植物大战僵尸】僵尸攻击(十一)

月孛星君 2022-03-19 阅读 30

目录

20、僵尸攻击

21、僵尸攻击逻辑优化

测试


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;
    }
}

测试

举报

相关推荐

0 条评论