自学内容网 自学内容网

Unity教程(十六)敌人攻击状态的实现

Unity开发2D类银河恶魔城游戏学习笔记

Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进

Unity教程(十)Tile Palette搭建平台关卡
Unity教程(十一)相机
Unity教程(十二)视差背景

Unity教程(十三)敌人状态机
Unity教程(十四)敌人空闲和移动的实现
Unity教程(十五)敌人战斗状态的实现
Unity教程(十六)敌人攻击状态的实现
Unity教程(十七)敌人战斗状态的完善


如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录



前言

本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。

本节实现敌人的攻击状态。

Udemy课程地址

对应视频:
Enemy’s Attack State


一、概述

在骷髅进入战斗状态并走到距离玩家很近的距离时,就要进入攻击状态攻击玩家。本节中我们就实现骷髅的攻击状态。
这一部分状态转换条件较多,如下图:
在这里插入图片描述
在这里插入图片描述

二、创建骷髅攻击动画

我们创建动画skeletonAttack
层次面板中选中Enemy_skeleton下的Animator,在Animation面板中创建动画
将精灵表SkeletonAttack内容全部拖入,采样率改为15
动画创建的更详细讲解见Unity教程(零)Unity和VS的使用相关内容
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

连接状态机,并添加过渡条件Attack,并修改过渡设置
添加bool型条件变量Attack,并连接过渡
在这里插入图片描述
Entry->skeletonAttack的过渡,加条件变量
在这里插入图片描述

skeletonAttack->Exit的过渡,加条件变量,并更改设置
在这里插入图片描述

三、骷髅攻击的实现

(1)整理代码

上一节我们用到了很多次骷髅的速度,所以我们在敌人状态中创建刚体,使代码更简洁。


    protected Rigidbody2D rb;
    
    public virtual void Enter()
    {
        rb = enemyBase.rb;
        triggerCalled = false;
        enemyBase.anim.SetBool(animBoolName, true);
    }

SkeletonBattleState状态Update函数中改为

    public override void Update()
    {
        base.Update();

        if (enemy.IsPlayerDetected())
        {
            if (enemy.IsPlayerDetected().distance < enemy.attackDistance)
            {
                stateMachine.ChangeState(enemy.attackState);
            }
        }


        if(player.position.x > enemy.transform.position.x)
            moveDir = 1;
        else if(player.position.x < enemy.transform.position.x)
            moveDir = -1;

        enemy.SetVelocity(enemy.moveSpeed * moveDir, rb.velocity.y);
    }

SkeletonMoveState状态Update函数中改为

    public override void Update()
    {
        base.Update();

        enemy.SetVelocity(enemy.moveSpeed*enemy.facingDir,rb.velocity.y);

        if(!enemy.isGroundDetected() || enemy.isWallDetected())
        {
            enemy.Flip();
            stateMachine.ChangeState(enemy.idleState);
        }
    }

(2)创建SkeletonAttackState

首先创建SkeletonAttackState,它继承自EnemyState,通过菜单生成构造函数和重写。
在这里插入图片描述
添加Enemy_Skeleton变量,并修改构造函数中传入值

    protected Enemy_Skeleton enemy;

    public SkeletonAttackState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
    {
        enemy = _enemy;
    }

(3)触发器的实现

和Player一样,敌人攻击的结束也要借助触发器来实现。
详细讲解请见Unity教程(八)角色基本攻击的实现
我们在状态基类EnemyState中添加改变参数的函数,若触发器被调用,则triggerCalled置为真。添加代码:

    //修改触发器参数
    public virtual void AnimationFinishTrigger()
    {
        triggerCalled = true;
    }

在Enemy中进行调用

    //设置触发器
    public virtual void AnimationTrigger() => stateMachine.currentState.AnimationFinishTrigger();

我们创建骷髅的触发器脚本Enemy_SkeletonAnimationTriggers。获取Animator的父组件Enemy_Skeleton,并调用其中的AnimationTrigger()

//Enemy_SkeletonAnimationTriggers:触发器组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy_SkeletonAnimationTriggers : MonoBehaviour
{

    private Enemy_Skeleton enemy => GetComponentInParent<Enemy_Skeleton>();
    private void AnimationTrigger()
    {
        enemy.AnimationTrigger();
    }
}

把触发器脚本挂在Enemy_Skeleton的Animator下面 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/41a27165de72476f966005e4d1ccdc39.png#pic_center) 我们在skeletonAttack的最后一帧设置事件帧,调用触发器函数设置triggerCalled参数。

先把白色竖线拉到最后一帧,点击动画面板左边钉子符号的标志添加事件。
在这里插入图片描述
在这里插入图片描述
点击选中事件帧,在右边面板选中函数AnimationTrigger
Fuction->Enemy_SkeletonAnimationTriggers->Methods->AnimationTrigger()
在这里插入图片描述

(4)攻击的实现

我们要在Enemy_Skeleton中创建骷髅攻击状态。

public SkeletonAttackState attackState { get; private set; }

protected override void Awake()
{
    base.Awake();
    idleState = new SkeletonIdleState(stateMachine,this,this,"Idle");
    moveState = new SkeletonMoveState(stateMachine, this,this, "Move");
    battleState = new SkeletonBattleState(stateMachine, this, this, "Move");
    attackState = new SkeletonAttackState(stateMachine, this, this, "Attack");
}

在SkeletonBattleState中添加状态转换,当距离过近时转换到攻击状态。

    public override void Update()
    {
        base.Update();

        if (enemy.IsPlayerDetected())
        {
            if (enemy.IsPlayerDetected().distance < enemy.attackDistance)
            {
                stateMachine.ChangeState(enemy.attackState);
            }
        }


        if(player.position.x > enemy.transform.position.x)
            moveDir = 1;
        else if(player.position.x < enemy.transform.position.x)
            moveDir = -1;

        enemy.SetVelocity(enemy.moveSpeed * moveDir, enemy.rb.velocity.y);
    }

我们在SkeletonAttackState中实现攻击的具体内容。
首先我们希望骷髅靠近玩家时,先停止移动再攻击,所以我们先把骷髅速度赋零。
在攻击动画播放完触发器被触发时,攻击结束,转换到战斗状态。
在这里插入图片描述
点击Animator也能看到左侧Attack变量在转换(切换很快,不仔细看不清楚)。

四、骷髅攻击的冷却

现在骷髅的攻击是连续不断的,在游戏中通常小怪是按照一定的频率进行的,让玩家有一定的躲避时间。
我们给骷髅小怪加上攻击冷却时间,实现上可以参照Player的连击。
我们在Enemy中添加冷却时间attackCoolDown,记录上次攻击的时间lastTimeAttacked用于实现。

    [Header("Attack Info")]
    public float attackDistance;
    public float attackCoolDown;
    [HideInInspector] public float lastTimeAttacked;

在SkeletonAttackState中,每次退出攻击状态时,更新lastTimeAttacked。

    public override void Exit()
    {
        base.Exit();
        enemy.lastTimeAttacked=Time.time;
    }

在SkeletonBattleState中添加CanAttack函数,利用现在的时间与上次攻击时间加冷却时间比较,判断冷却时间是否已经过去,骷髅是否处于可攻击状态。
先判断是否能检测到玩家,再判断玩家与骷髅的距离,再做攻击冷却的判断,条件都成立时转到战斗状态。


    public override void Update()
    {
        base.Update();

        if (enemy.IsPlayerDetected())
        {
            if (enemy.IsPlayerDetected().distance < enemy.attackDistance)
            {
                if(CanAttack())
                    stateMachine.ChangeState(enemy.attackState);
            }
        }


        if(player.position.x > enemy.transform.position.x)
            moveDir = 1;
        else if(player.position.x < enemy.transform.position.x)
            moveDir = -1;

        enemy.SetVelocity(enemy.moveSpeed * moveDir, rb.velocity.y);
    }

    private bool CanAttack()
    {
        if (Time.time >= enemy.lastTimeAttacked + enemy.attackCoolDown)
            return true;
        return false;
    }

设置合适的冷却时间
在这里插入图片描述
效果如下
在这里插入图片描述
我们可以看到骷髅每隔一段时间进行攻击了。但是有个很明显的问题,在攻击的间隙骷髅不会停止移动,玩家会被推着走。这个问题下节我们再来解决。

总结 完整代码

EnemyState.cs

增加刚体和触发函数调用

//EnemyState:敌人状态基类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyState
{
    protected EnemyStateMachine stateMachine;
    protected Enemy enemyBase;
    protected Rigidbody2D rb;

    private string animBoolName;

    protected float stateTimer;
    protected bool triggerCalled;

    //构造函数
    public EnemyState(EnemyStateMachine _stateMachine, Enemy _enemyBase, string _animBoolName)
    {
        this.stateMachine = _stateMachine;
        this.enemyBase = _enemyBase;
        this.animBoolName = _animBoolName;
    }

    public virtual void Enter()
    {
        rb = enemyBase.rb;
        triggerCalled = false;
        enemyBase.anim.SetBool(animBoolName, true);
    }

    public virtual void Update()
    {
        stateTimer-= Time.deltaTime;
    }

    public virtual void Exit()
    {
        enemyBase.anim.SetBool(animBoolName, false);
    }

    //修改触发器参数
    public virtual void AnimationFinishTrigger()
    {
        triggerCalled = true;
    }
}

Enemy.cs

添加攻击相关变量,添加触发函数调用。

//Enemy:敌人基类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : Entity
{
    [SerializeField] protected LayerMask WhatIsPlayer;

    [Header("Move Info")]
    public float moveSpeed = 1.5f;
    public float idleTime = 2.0f;

    [Header("Attack Info")]
    public float attackDistance;
    public float attackCoolDown;
    [HideInInspector] public float lastTimeAttacked;

    public EnemyStateMachine stateMachine;

    protected override void Awake()
    {
        base.Awake();
        stateMachine = new EnemyStateMachine();
    }


    protected override void Update()
    {
        base.Update();
        stateMachine.currentState.Update();
    }

    //设置触发器
    public virtual void AnimationTrigger() => stateMachine.currentState.AnimationFinishTrigger();

    public virtual RaycastHit2D IsPlayerDetected()=>Physics2D.Raycast(transform.position, Vector2.right * facingDir, 50 ,WhatIsPlayer);

    protected override void OnDrawGizmos()
    {
        base.OnDrawGizmos();

        Gizmos.color = Color.yellow;
        Gizmos.DrawLine(transform.position, new Vector3(transform.position.x + attackDistance * facingDir, transform.position.y));

    }
}

Enemy_SkeletonAnimationTriggers.cs

触发器组件

//Enemy_SkeletonAnimationTriggers:触发器组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy_SkeletonAnimationTriggers : MonoBehaviour
{

    private Enemy_Skeleton enemy => GetComponentInParent<Enemy_Skeleton>();
    private void AnimationTrigger()
    {
        enemy.AnimationTrigger();
    }
}

Enemy_Skeleton.cs

增加攻击状态创建

//Enemy_Skeleton:骷髅敌人
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy_Skeleton : Enemy
{
    #region 状态
    public SkeletonIdleState idleState { get; private set; }
    public SkeletonMoveState moveState { get; private set; }
    public SkeletonBattleState battleState { get; private set; }
    public SkeletonAttackState attackState { get; private set; }
    #endregion

    protected override void Awake()
    {
        base.Awake();
        idleState = new SkeletonIdleState(stateMachine,this,this,"Idle");
        moveState = new SkeletonMoveState(stateMachine, this,this, "Move");
        battleState = new SkeletonBattleState(stateMachine, this, this, "Move");
        attackState = new SkeletonAttackState(stateMachine, this, this, "Attack");
    }

    protected override void Start()
    {
        base.Start();

        stateMachine.Initialize(idleState);
    }

    protected override void Update()
    {
        base.Update();
    }

}

SkeletonBattleState.cs

添加攻击冷却判断条件,修改转到攻击状态的条件,修改刚体速度部分代码

//SkeletonBattleState:骷髅战斗状态
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SkeletonBattleState : EnemyState
{
    private Transform player;
    private Enemy_Skeleton enemy;
    private int moveDir;

    public SkeletonBattleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
    {
        enemy=_enemy;
    }

    public override void Enter()
    {
        base.Enter();

        player = GameObject.Find("Player").transform;
    }

    public override void Exit()
    {
        base.Exit();
    }

    public override void Update()
    {
        base.Update();

        if (enemy.IsPlayerDetected())
        {
            if (enemy.IsPlayerDetected().distance < enemy.attackDistance)
            {
                if(CanAttack())
                    stateMachine.ChangeState(enemy.attackState);
            }
        }


        if(player.position.x > enemy.transform.position.x)
            moveDir = 1;
        else if(player.position.x < enemy.transform.position.x)
            moveDir = -1;

        enemy.SetVelocity(enemy.moveSpeed * moveDir, rb.velocity.y);
    }

    private bool CanAttack()
    {
        if (Time.time >= enemy.lastTimeAttacked + enemy.attackCoolDown)
            return true;
        return false;
    }
}

SkeletonAttackState.cs

创建攻击状态,攻击时速度置零,触发器触发转回到战斗状态

//SkeletonAttackState:骷髅攻击状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SkeletonAttackState : EnemyState
{

    protected Enemy_Skeleton enemy;

    public SkeletonAttackState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
    {
        enemy = _enemy;
    }

    public override void Enter()
    {
        base.Enter();
    }

    public override void Exit()
    {
        base.Exit();
        enemy.lastTimeAttacked=Time.time;
    }

    public override void Update()
    {
        base.Update();

        enemy.ZeroVelocity();
        if (triggerCalled)
            stateMachine.ChangeState(enemy.battleState);
    }
}

SkeletonMoveState.cs

修改刚体速度部分代码

//SkeletonMoveState:骷髅移动状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SkeletonMoveState : SkeletonGroundedState
{
    public SkeletonMoveState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton enemy, string _animBoolName) : base(_stateMachine, _enemyBase, enemy, _animBoolName)
    {
    }

    public override void Enter()
    {
        base.Enter();
    }

    public override void Exit()
    {
        base.Exit();
    }

    public override void Update()
    {
        base.Update();

        enemy.SetVelocity(enemy.moveSpeed*enemy.facingDir,rb.velocity.y);

        if(!enemy.isGroundDetected() || enemy.isWallDetected())
        {
            enemy.Flip();
            stateMachine.ChangeState(enemy.idleState);
        }
    }
}


原文地址:https://blog.csdn.net/cyr___/article/details/142317803

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!