Unity教程(十三)敌人状态机

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

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

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

Unity教程(十三)敌人状态机

Unity教程(十四)敌人空闲和移动的实现

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


文章目录


前言

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

本节实现敌人状态机。创建实体类,派生出敌人和玩家。

对应b站视频:
【Unity教程】从0编程制作类银河恶魔城游戏P47
【Unity教程】从0编程制作类银河恶魔城游戏P48
【Unity教程】从0编程制作类银河恶魔城游戏P49


一、概述

本节我们开始在游戏中添加敌人。

由于敌人很多部分与玩家相似,于是抽象出实体类Entity来复用二者都有的部分。

敌人和玩家都继承自Entity。

敌人的种类有很多,我们在敌人类Enemy的基础上,创建一个骷髅小怪Enemy_Skeleton。

此外与玩家的实现对应,我们还需要敌人状态基类EnemyState,和敌人状态机EnemyStateMechine。

二、实体类Entity的创建和继承

(1)创建实体类

先整理一下文件,把之前的玩家相关脚本放在同一文件夹Player下,把视差背景脚本拖入Scripts文件夹。

我们考虑Player和Enemy共有的必须的功能。

首先可以先包含,Awake()、 Start()、Update()三个基本函数。对于一个最简单的来回踱步的小怪,碰撞、翻转和速度设置也是它们共有的基础功能。对应共有的组件要包括刚体和动画师。

在Entity中给需要在子类中重写的函数添加virtual设置为虚函数。

Entity应如下所示:

代码为

csharp 复制代码
//Entity:实体类
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

public class Entity : MonoBehaviour
{

    [Header("Flip Info")]
    protected bool facingRight = true;
    public int facingDir { get; private set; } = 1;
    


    [Header("Collision Info")]
    [SerializeField] protected Transform groundCheck;
    [SerializeField] protected float groundCheckDistance;
    [SerializeField] protected Transform wallCheck;
    [SerializeField] protected float wallCheckDistance;
    [SerializeField] protected LayerMask whatIsGround;


    #region 组件
    public Rigidbody2D rb { get; private set; }
    public Animator anim { get; private set; }
    #endregion

    protected virtual void Awake()
    {

    }

    //获取组件
    protected virtual void Start()
    {
        rb= GetComponent<Rigidbody2D>();
        anim= GetComponentInChildren<Animator>();
    }

    // 更新
    protected virtual void Update()
    {
        
    }


    #region 速度设置
    //速度置零
    public void ZeroVelocity() => rb.velocity = new Vector2(0, 0);

    //设置速度
    public void SetVelocity(float _xVelocity, float _yVelocity)
    {
        rb.velocity = new Vector2(_xVelocity, _yVelocity);
        FlipController(_xVelocity);
    }
    #endregion

    #region 翻转
    //翻转实现
    public virtual void Flip()
    {
        facingDir = -1 * facingDir;
        facingRight = !facingRight;
        transform.Rotate(0, 180, 0);
    }
    //翻转控制
    public virtual void FlipController(float _x)
    {
        if (_x > 0 && !facingRight)
            Flip();
        else if (_x < 0 && facingRight)
            Flip();
    }
    #endregion

    #region 碰撞
    //碰撞检测
    public virtual bool isGroundDetected() => Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);
    public virtual bool isWallDetected() => Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround);


    //绘制碰撞检测
    protected virtual void OnDrawGizmos()
    {
        Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));
        Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance, wallCheck.position.y));
    }
    #endregion
}

(2)派生Player类与Enemy类

抽象出Entity后我们改为由它派生出Player

csharp 复制代码
//Player:玩家
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : Entity
{
    [Header("Attack details")]
    public Vector2[] attackMovement;
    public bool isBusy { get; private set; }

    [Header("Move Info")]
    public float moveSpeed = 8f;
    public float jumpForce = 12f;


    [Header("Dash Info")]
    [SerializeField] private float dashCoolDown;
    private float dashUsageTimer;
    public float dashSpeed=25f;
    public float dashDuration=0.2f;
    public float dashDir { get; private set; }


    #region 状态
    public PlayerStateMachine StateMachine { get; private set; }
    public PlayerIdleState idleState { get; private set; }
    public PlayerMoveState moveState { get; private set; }
    public PlayerJumpState jumpState { get; private set; }
    public PlayerAirState airState { get; private set; }
    public PlayerDashState dashState { get; private set; }
    public PlayerWallSlideState wallSlideState { get; private set; }
    public PlayerWallJumpState wallJumpState { get; private set; }
    public PlayerPrimaryAttackState primaryAttack { get; private set; }  

    #endregion

    //创建对象
    protected override void Awake()
    {
        base.Awake();

        StateMachine = new PlayerStateMachine();

        idleState = new PlayerIdleState(StateMachine, this, "Idle");
        moveState = new PlayerMoveState(StateMachine, this, "Move");
        jumpState = new PlayerJumpState(StateMachine, this, "Jump");
        airState = new PlayerAirState(StateMachine, this, "Jump");
        dashState = new PlayerDashState(StateMachine, this, "Dash");
        wallSlideState = new PlayerWallSlideState(StateMachine, this, "WallSlide");
        wallJumpState = new PlayerWallJumpState(StateMachine, this, "Jump");
        primaryAttack = new PlayerPrimaryAttackState(StateMachine, this, "Attack");

    }

    // 设置初始状态
    protected override void Start()
    {
        base.Start();

        StateMachine.Initialize(idleState);
    }

    // 更新
    protected override void Update()
    {
        base.Update();

        StateMachine.currentState.Update();

        CheckForDashInput();
    }

    public IEnumerator BusyFor(float _seconds)
    {
        isBusy = true;

        yield return new WaitForSeconds(_seconds);

        isBusy = false;
    }

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

    //检查冲刺输入
    public void CheckForDashInput()
    {

        dashUsageTimer -= Time.deltaTime;

        if (Input.GetKeyDown(KeyCode.LeftShift) && dashUsageTimer<0)
        {
            dashUsageTimer = dashCoolDown;
            dashDir = Input.GetAxisRaw("Horizontal");

            if (dashDir == 0)
                dashDir = facingDir;

            StateMachine.ChangeState(dashState);
        }
    }

}

修改完后运行一下,检查是否破坏原有功能

接着我们创建敌人Enemy类,

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : Entity
{
    public EnemyStateMachine stateMachine;

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


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

三、敌人状态基类和状态机

(1)敌人状态基类

敌人状态基类和玩家状态基类也基本相似,其中具有状态可能共同用到的变量,状态的构造函数,和状态的进入更新和退出。

csharp 复制代码
//EnemyState:敌人状态基类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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

    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()
    {
        triggerCalled = false;
        enemyBase.anim.SetBool(animBoolName, true);
    }

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

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

(2)敌人状态机

敌人状态机中包含当前状态,和初始化状态机和改变状态的函数

csharp 复制代码
//EnemyStateMachine:状态机
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyStateMachine
{
    //当前状态
    public EnemyState currentState { get; private set; }

    //初始化
    public void Initialize(EnemyState _startState)
    {
        currentState = _startState;
        currentState.Enter();
    }

    //改变状态
    public void ChangeState(EnemyState _newState)
    {
        currentState.Exit();
        currentState = _newState;
        currentState.Enter();
    }
}

(3)创建SkeletonIdleState和SkeletonMoveState

我们先创建两个类用于骷髅小怪状态机的初始化,至于两个状态的具体内容,将在下一节进行具体实现。
Alt+Enter 从子菜单中使用生成构造函数和生成重写。

骷髅小怪的空闲状态:

csharp 复制代码
//SkeletonIdleState:骷髅空闲状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SkeletonIdleState : EnemyState
{
    public SkeletonIdleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
    {
    }

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

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

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

骷髅小怪的移动状态:

csharp 复制代码
//SkeletonMoveState:骷髅移动状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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

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

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

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

四、创建骷髅小怪Enemy_Skeleton

我们以Enemy为基类可以派生出各种各样的有各自特点的敌人,在本教程中我们以骷髅Enemy_Skeleton为例制作一类小怪。

参照我们起始时Player创建状态机的过程。我们在Enemy基类中创建了状态机,现在要在Skeleton_Enemy中设置骷髅小怪的起始状态。

在Skeleton_Enemy中创建两个状态空闲和移动两个状态,将空闲状态作为起始状态。

csharp 复制代码
//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; }
    #endregion

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

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

        stateMachine.Initialize(idleState);
    }

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

}
相关推荐
Sitarrrr2 小时前
【Unity】ScriptableObject的应用和3D物体跟随鼠标移动:鼠标放置物体在场景中
3d·unity
极梦网络无忧2 小时前
Unity中IK动画与布偶死亡动画切换的实现
unity·游戏引擎·lucene
逐·風9 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
_oP_i11 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
代码盗圣15 小时前
GODOT 4 不用scons编译cpp扩展的方法
游戏引擎·godot
Leoysq20 小时前
【UGUI】实现点击注册按钮跳转游戏场景
游戏·unity·游戏引擎·ugui
PandaQue1 天前
《潜行者2切尔诺贝利之心》游戏引擎介绍
游戏引擎
_oP_i1 天前
unity中 骨骼、纹理和材质关系
unity·游戏引擎·材质
Padid1 天前
Unity SRP学习笔记(二)
笔记·学习·unity·游戏引擎·图形渲染·着色器
Tp_jh2 天前
推荐一款非常好用的C/C++在线编译器
linux·c语言·c++·ide·单片机·unity·云原生