Unity引擎2D游戏开发,敌人追击状态的转换

发布时间 2023-12-28 15:29:16作者: 心霖の雨

思路:

从敌人的位置发射一道射线或者一片区域来对玩家实体进行检测,如果检测倒玩家,则进行追击进攻

利用BoxCast()即可实现

BoxCast()官方文档:https://docs.unity3d.com/cn/2022.3/ScriptReference/Physics2D.BoxCast.html

创建检测区域

由于BoxCast需要众多参数,所以在Enemy中创建相关的全局变量

[Header("检测")]
// 因为中心点在脚底,所以加一个偏移量
public Vector2 centerOffset;
// 检测大小
public Vector2 checkSize;
// 检测距离
public float checkDistance;
// 图层
public LayerMask attackLayer;

创建FoundPlayer()方法,在内部调用BaxCast()方法

public bool FoundPlayer()
{
    return Physics2D.BoxCast(transform.position + (Vector3)centerOffset, checkSize, 0, faceDirect, checkDistance, attackLayer);
}

创建OnDrawGizmosSelected()方法,用于在Unity图形界面中能够绘制出检测距离(checkDistance)的位置

private void OnDrawGizmosSelected()
{
    Gizmos.DrawWireSphere(transform.position + (Vector3)centerOffset + new Vector3(checkDistance * -transform.localScale.x, 0), 0.2f);
}

切换敌人状态

由于需要给Enemy赋予一个状态,同时也需要知道该状态的变量,再赋值给currentState。因此,在Enemy类中,编写一个切换的方法。不过,在此之前,需要创建一个枚举类(Enum),来表明当前的状态。

在Scripts下创建Utils文件夹,在文件夹内创建StateEnum C#文件
image

在内部创建三种状态

public enum NPCState
{
    Patrol, Chase, Skill
}

在Enemy中创建SwitchState()方法,形参写为刚才创建的NPCState

public void SwitchState(NPCState state)
{
}

此方法内部实现,通过传入的NPCState,转换为已经创建的三种状态变量

var newState = state switch
{
    NPCState.Patrol => patrolState,
    NPCState.Chase => chaseState,
    _ => null
};

currentState.OnExit();
currentState = newState;
currentState.OnEnter(this);

_ => null 相当于switch语句中的default。在切换状态时,需要调用OnExit()方法,关闭当前状态,再赋值新状态并调用OnEnter()

在BoarPatrolState类中,LogicUpdate()继续编写切换追击状态的代码。加一个判断,判断如果发现了玩家,则切换状态

if (currentEnemy.FoundPlayer())
{
    currentEnemy.SwitchState(NPCState.Chase);
}

编写追击代码

在Enemy文件夹下,创建BoarChaseState C#脚本,用于编写追击逻辑
image

继承BaseState,并在OnEnter()中将currentEnemy进行赋值

public class BoarChaseState : BaseState
{
    public override void OnEnter(Enemy enemy)
    {
        currentEnemy = enemy;
        Debug.Log("chase");
    }
    public override void LogicUpdate()
    {
    }

    public override void PhysicsUpdate()
    {
    }

    public override void OnExit()
    {
    }
}

与此同时,在Boar类中,添加一行代码用于初始化BoarChaseState对象

protected override void Awake()
{
    base.Awake();
    patrolState = new BoarPatrolState();
    chaseState = new BoarChaseState();
}

回到BoarChaseState类,在OnEnter()中,修改当前的速度为追击速度,并开启追击动画

public override void OnEnter(Enemy enemy)
{
    currentEnemy = enemy;
    currentEnemy.currentSpeed = currentEnemy.chaseSpeed;
    currentEnemy.animator.SetBool("run", true);
}

在LogicUpdate()方法中,使野猪追击撞墙时不进行等待立即转身

public override void LogicUpdate()
{
    // 追击时,撞墙不等待并进行转身
    if (!currentEnemy.physicsCheck.isGround || (currentEnemy.physicsCheck.touchLeftWall && currentEnemy.faceDirect.x < 0) || (currentEnemy.physicsCheck.touchRightWall && currentEnemy.faceDirect.x > 0))
    {
        currentEnemy.transform.localScale = new Vector3(currentEnemy.faceDirect.x, 1, 1);
    }
}

丢失目标后切换状态

丢失目标后,进行倒计时,在一定时间内没有发现玩家则切换回巡逻状态

在Enemy中,创建两个全局变量

// 丢失目标的时间
public float lostTime;
// 丢失目标的计时器
public float lostTimeCounter;

在TimeCounter()方法内,进行一个是否发现玩家的判断,如果没有发现则扣除倒计时

if(!FoundPlayer() && lostTimeCounter > 0)
{
    lostTimeCounter -= Time.deltaTime;
} else if (FoundPlayer())
{
    lostTimeCounter = lostTime;
}

接下来在BoarChaseState类LogicUpdate()中,加一个判断。如果丢失目标的时间计时结束,则切换回巡逻状态

public override void LogicUpdate()
{
    // 丢失目标计时结束切换回巡逻状态
    if(currentEnemy.lostTimeCounter <= 0)
    {
        currentEnemy.SwitchState(NPCState.Patrol);
    }
    // 追击时,撞墙不等待并进行转身
    if (!currentEnemy.physicsCheck.isGround || (currentEnemy.physicsCheck.touchLeftWall && currentEnemy.faceDirect.x < 0) || (currentEnemy.physicsCheck.touchRightWall && currentEnemy.faceDirect.x > 0))
    {
        currentEnemy.transform.localScale = new Vector3(currentEnemy.faceDirect.x, 1, 1);
    }
}

解决正面追击玩家时,玩家攻击不造成击退位移的问题

由于野猪追击玩家时的冲力抵消了玩家的击退位移的力,所以才会导致当前的问题

在Enemy类OnTakeDamage()方法中,添加一行代码

// 受伤时,无论x轴方向当前敌人的力有多少,都会造成击退
rb.velocity = new Vector2(0, rb.velocity.y);