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

}
相关推荐
m0_748251725 小时前
Android webview 打开本地H5项目(Cocos游戏以及Unity游戏)
android·游戏·unity
benben0445 小时前
Unity3D仿星露谷物语开发7之事件创建动画
unity·游戏引擎
林枫依依8 小时前
Unity2021.3.16f1可以正常打开,但是Unity2017.3.0f3却常常打开闪退或者Unity2017编辑器运行起来就闪退掉
unity
虾球xz8 小时前
游戏引擎学习第57天
学习·游戏引擎
异次元的归来21 小时前
Unity DOTS中的share component
unity·游戏引擎
向宇it1 天前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
_oP_i1 天前
unity webgl部署到iis报错
unity
Go_Accepted1 天前
Unity全局雾效
unity
向宇it1 天前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
每日出拳老爷子1 天前
【图形渲染】【Unity Shader】【Nvidia CG】有用的参考资料链接
unity·游戏引擎·图形渲染