Unity开发2D类银河恶魔城游戏学习笔记
Unity教程(零)Unity和VS的使用相关内容
 Unity教程(一)开始学习状态机
 Unity教程(二)角色移动的实现
 Unity教程(三)角色跳跃的实现
 Unity教程(四)碰撞检测
 Unity教程(五)角色冲刺的实现
 Unity教程(六)角色滑墙的实现
 Unity教程(七)角色蹬墙跳的实现
 Unity教程(八)角色攻击的基本实现
 Unity教程(九)角色攻击的改进
Unity教程(十)Tile Palette搭建平台关卡
 Unity教程(十一)相机
 Unity教程(十二)视差背景
Unity教程(十三)敌人状态机
 
如果你更习惯用知乎
 Unity开发2D类银河恶魔城游戏学习笔记目录
文章目录
- Unity开发2D类银河恶魔城游戏学习笔记
- 前言
- 一、概述
- 二、实体类Entity的创建和继承
- (1)创建实体类
- (2)派生Player类与Enemy类
 
- 三、敌人状态基类和状态机
- (1)敌人状态基类
- (2)敌人状态机
- (3)创建SkeletonIdleState和SkeletonMoveState
 
- 四、创建骷髅小怪Enemy_Skeleton
前言
本文为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应如下所示:
 
 代码为
//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
 
//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类,
 
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)敌人状态基类
敌人状态基类和玩家状态基类也基本相似,其中具有状态可能共同用到的变量,状态的构造函数,和状态的进入更新和退出。
 
//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)敌人状态机
敌人状态机中包含当前状态,和初始化状态机和改变状态的函数
 
//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从子菜单中使用生成构造函数和生成重写。
 
 骷髅小怪的空闲状态:
//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();
    }
}
骷髅小怪的移动状态:
//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中创建两个状态空闲和移动两个状态,将空闲状态作为起始状态。
 
//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();
    }
}



















