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