【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
1.状态机StateMachine
- Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public PlayerStateMachine stateMachine { get; private set; }
public PlayerIdleState idleState { get; private set; }
public PlayerMoveState moveState { get; private set; }
private void Awake()
{
//初始化状态机,创建状态
stateMachine = new PlayerStateMachine();
idleState = new PlayerIdleState(this, stateMachine,"Idle");
moveState = new PlayerMoveState(this, stateMachine, "Move");
}
private void Start()
{
//初始化状态机,设置初始状态
stateMachine.Initialize(idleState);
}
private void Update()
{
//更新状态机
stateMachine.Update();
}
}
- PlayerStateMachine.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerStateMachine
{
public PlayerState currentState { get; private set; }
//public get , private set ==> read only property
public void Initialize(PlayerState _startState)
{
currentState = _startState;
currentState.Enter();
}
public void ChangeState(PlayerState _newState)
{
currentState?.Exit(); // 如果 currentState 为 null,不会调用 Exit()
currentState = _newState;
currentState.Enter();
}
public void Update()
{
//通过当前状态对应的 Update() 方法来实现状态的切换
currentState?.Update();
}
}
- PlayerState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerState //不继承MonoBehaviour,通过Update()方法来实现状态机
{
protected Player player;
protected PlayerStateMachine stateMachine;
private string animBoolName;
//构造函数
public PlayerState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName)
{
this.player = _player;
this.stateMachine = _stateMachine;
this.animBoolName = _animBoolName;
}
public virtual void Enter()
{
Debug.Log("Enter " + animBoolName);
}
public virtual void Update()
{
Debug.Log("Update " + animBoolName);
}
public virtual void Exit()
{
Debug.Log("Exit " + animBoolName);
}
}
- PlayerIdleState PlayerMoveState 继承PlayerState,采用快速操作生成构造以及重写
- PlayerIdleState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerIdleState : PlayerState
{
public PlayerIdleState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
if (Input.GetKeyDown(KeyCode.N))
{
stateMachine.ChangeState(player.moveState);
}
}
}
- PlayerMoveState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMoveState : PlayerState
{
public PlayerMoveState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{//注意参数顺序,_player在前,_stateMachine在后,_animBoolName在最后
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
if(Input.GetKeyDown(KeyCode.N))
{
stateMachine.ChangeState(player.idleState);
}
}
}
在 C# 中,如果子类的构造函数没有显式地执行任何操作,它依然可以调用基类的构造函数。你在 PlayerMoveState
中的构造函数通过 : base(.)
语法调用了基类的构造函数,并传递了所需的参数。基类的构造函数负责初始化这个对象,因此你在子类构造函数中可以选择不写任何额外的代码。
在 C# 中,子类的实例可以被视为父类的实例,这就是所谓的 “向上转型”(Upcasting)。
当子类的实例作为父类传递并调用方法时,实际执行的是子类的实现,只要父类的方法是虚方法(virtual
)并且在子类中被重写(override
)。这体现了面向对象编程中的多态性。没被重写就用父类的。
2.动画Animation
-
创建动画:拖动对应动画素材到Animation加工处
-
设置Transition参数:参数名需要与初始化时设置的名称相同
如:
idleState = new PlayerIdleState(this, stateMachine,"Idle"); //设置参数Idle moveState = new PlayerMoveState(this, stateMachine, "Move"); //设置参数Move
-
设置Transition参数对应Bool值,比如true进入,false退出,在Animator -> make transition中也设置。
//PlayerState.cs public virtual void Enter() { player.anim.SetBool(animBoolName, true); } public virtual void Exit() { player.anim.SetBool(animBoolName, false); }
-
Player.cs中设置动画组件,Start中获取组件
#region Component public Animator anim { get; private set; } #endregion private void Start() { anim = GetComponentInChildren<Animator>(); ... }
-
PlayerState.cs中设置xInput检测键盘输入
protected float xInput; public virtual void Update() { //Debug.Log("Update " + animBoolName); xInput = Input.GetAxisRaw("Horizontal"); }
-
在对应状态中设置update检测
//PlayerIdleState.cs public override void Update() { base.Update(); //检测移动 if(xInput!= 0) { stateMachine.ChangeState(player.moveState); } } //PlayerMoveState.cs public override void Update() { base.Update(); //检测移动 if(xInput== 0) { stateMachine.ChangeState(player.idleState); } }
3.实现移动
-
添加组件
//Player.cs #region Component public Animator anim { get; private set; } public Rigidbody2D rb { get; private set; } #endregion private void Start() { rb = GetComponent<Rigidbody2D>(); ... }
-
添加公共方法,修改速度
//Player.cs [Header("Move info")] [SerializeField] public float moveSpeed ; public void SetVelocity(float x, float y) { rb.velocity = new Vector2(x, y); }
-
在PlayerState中添加rb,anim简化代码量:
//PlayerState.cs protected Rigidbody2D rb; protected Animator anim; public virtual void Enter() { rb = player.rb; anim = player.anim; ... }
-
在移动状态中对接
//PlayMoveState.cs public override void Update() { base.Update(); if (xInput == 0) { stateMachine.ChangeState(player.idleState); } player.SetVelocity(xInput * moveSpeed, rb.velocity.y); }
4.跳跃/下降
-
创建一个PlayerGroundedtate继承PlayerState, 让Player Move/Idle State继承PlayerGroundedState
public class PlayerGroundedState : PlayerState //生成构造和重写 public class PlayerIdleState : PlayerGroundedState public class PlayerMoveState : PlayerGroundedState
-
创建动画
-
Animator中设置一棵Blend Tree - JumpFall
-
将混合数自动生成的参数Blend改为yVelocity , 设置参数Jump , make transtion
-
双击JumpFall 进入, 去掉勾选Automate Thresholds, 添加Motion Field : playerJump, playerFall
-
创建PlayerAirState (fall) PlayerJumpState(jump)继承PlayerState, ,自动构造 重写
-
在Player中声明
//Player.cs public PlayerJumpState jumpState { get; private set; } public PlayerAirState airState { get; private set; } private void Awake() { jumpState = new PlayerJumpState(this, stateMachine, "Jump"); airState = new PlayerAirState(this, stateMachine, "Jump");//jump ... }
-
通过检测y速度转换状态
//PlayerState.cs public virtual void Update() { anim.SetFloat("yVelocity", rb.velocity.y); ... }
-
实现跳跃
//PlayerJumpState.cs public override void Enter() { base.Enter(); rb.velocity = new Vector2(rb.velocity.x, player.jumpForce); } public override void Update() { base.Update(); //检测移动 if(rb.velocity.y < 0) { stateMachine.ChangeState(player.airState); } } //PlayerAirState.cs public override void Update() { base.Update(); if ( player.isGrounded) { stateMachine.ChangeState(player.idleState); } }
-
地面检测,将平台/地面的标签色Tag 设置为Ground,并添加碰撞箱,刚体,composite,组件
//Player.cs public bool isGrounded { get; set; } = false; private void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.CompareTag("Ground")) { isGrounded = true; } } private void OnCollisionExit2D(Collision2D collision) { if (collision.gameObject.CompareTag("Ground")) { isGrounded = false; } }
5.翻转Flip
//Player.cs
public int facingDirection { get; set; } = 1; //默认右边1 ,左边-1
public void SetVelocity(float x, float y)
{
rb.velocity = new Vector2(x, y);
FlipController(x); //设置速度的时候就需要判断是否改变朝向。
}
public void Flip()
{
transform.Rotate(0, 180, 0);
facingDirection *= -1;
}
public void FlipController(float xInput)
{
if (facingDirection * xInput < 0)//如果按右边,角色此时也朝右边,方向就不变;如果此时角色朝左,就需要翻转。就是说按键方向和人物朝向相反时调用Flip
{
Flip();
}
}
//PlayerState.cs
public virtual void Update()
{
player.SetVelocity(xInput * player.moveSpeed, rb.velocity.y);
...
}
Comments