Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考
此代码仅为较上一P有所改变的代码
Player.cs
cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Player : MonoBehaviour
{
[Header("Attack Details")]
public Vector2[] attackMovement;//每个攻击时获得的速度组
public bool isBusy{ get; private set; }//防止在攻击间隔中进入move
//
[Header("Move Info")]
public float moveSpeed;//定义速度,与xInput相乘控制速度的大小
public float jumpForce;
[Header("Dash Info")]
[SerializeField] private float dashCooldown;
private float dashUsageTimer;//为dash设置冷却时间,在一定时间内不能连续使用
public float dashSpeed;//冲刺速度
public float dashDuration;//持续时间
public float dashDir { get; private set; }
[Header("Collision Info")]
[SerializeField] private Transform groundCheck;//transform类,代表的时物体的位置,后面会来定位子组件的位置
[SerializeField] private float groundCheckDistance;
[SerializeField] private Transform wallCheck;//transform类,代表的时物体的位置,后面会来定位子组件的位置
[SerializeField] private float wallCheckDistance;
[SerializeField] private LayerMask whatIsGround;//LayerMask类,与Raycast配合,https://docs.unity3d.com/cn/current/ScriptReference/Physics.Raycast.html
public int facingDir { get; private set; } = 1;
private bool facingRight = true;//判断是否朝右
#region 定义Unity组件
public Animator anim { get; private set; }//这样才能配合着拿到自己身上的animator的控制权
public Rigidbody2D rb { get; private set; }//配合拿到身上的Rigidbody2D组件控制权
#endregion
#region 定义States
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 wallSlide { get; private set; }
public PlayerWallJumpState wallJump { get; private set; }
public PlayerPrimaryAttack primaryAttack { get; private set; }
#endregion
private void Awake()
{
stateMachine = new PlayerStateMachine();
//通过构造函数,在构造时传递信息
idleState = new PlayerIdleState(this, stateMachine, "Idle");
moveState = new PlayerMoveState(this, stateMachine, "Move");
jumpState = new PlayerJumpState(this, stateMachine, "Jump");
airState = new PlayerAirState(this, stateMachine, "Jump");
dashState = new PlayerDashState(this, stateMachine, "Dash");
wallSlide = new PlayerWallSlideState(this, stateMachine, "WallSlide");
wallJump = new PlayerWallJumpState(this, stateMachine, "Jump");//wallJump也是Jump动画
primaryAttack = new PlayerPrimaryAttack(this, stateMachine, "Attack");
//this 就是 Player这个类本身
}//Awake初始化所以State,为所有State传入各自独有的参数,及animBool,以判断是否调用此动画(与animatoin配合完成)
private void Start()
{
anim = GetComponentInChildren<Animator>();//拿到自己身上的animator的控制权
rb = GetComponent<Rigidbody2D>();
stateMachine.Initialize(idleState);
}
private void Update()//在mano中update会自动刷新但其他没有mano的不会故,需要在这个updata中调用其他脚本中的函数stateMachine.currentState.update以实现 //stateMachine中的update
{
stateMachine.currentState.Update();//反复调用CurrentState的Update函数
CheckForDashInput();
}
public IEnumerator BusyFor(float _seconds)//https://www.zhihu.com/tardis/bd/art/504607545?source_id=1001
{
isBusy = true;
Debug.Log("IsBusy");
yield return new WaitForSeconds(_seconds);
isBusy = false;
Debug.Log("NotBusy");
}//p39 4.防止在攻击间隔中进入move,通过设置busy值,在使用某些状态时,使其为busy为true,抑制其进入其他state
//IEnumertor本质就是将一个函数分块执行,只有满足某些条件才能执行下一段代码,此函数有StartCoroutine调用
public void AnimationTrigger() => stateMachine.currentState.AnimationFinishTrigger();
//从当前状态拿到AnimationTrigger进行调用的函数
public void CheckForDashInput()
{
dashUsageTimer -= Time.deltaTime;//给dash上冷却时间
if (IsWallDetected())
{
return;
}//修复在wallslide可以dash的BUG
if (Input.GetKeyDown(KeyCode.LeftShift) && dashUsageTimer < 0)
{
dashUsageTimer = dashCooldown;
dashDir = Input.GetAxisRaw("Horizontal");//设置一个值,可以将dash的方向改为你想要的方向而不是你的朝向
if (dashDir == 0)
{
dashDir = facingDir;//只有当玩家没有控制方向时才使用默认朝向
}
stateMachine.ChangeState(dashState);
}
}//将Dash切换设置成一个函数,使其在所以情况下都能使用
#region 速度函数Velocity
public void ZeroVelocity()
{
rb.velocity = new Vector2(0, 0);
}//设置速度为0函数
public void SetVelocity(float _xVelocity, float _yVelocity)
{
rb.velocity = new Vector2(_xVelocity, _yVelocity);//将rb的velocity属性设置为对应的想要的二维向量。因为2D游戏的速度就是二维向量
FlipController(_xVelocity);//在其他设置速度的时候调用翻转控制器
}//控制速度的函数,此函数在其他State中可能会使用,但仅能通过player.SeVelocity调用
#endregion
#region 碰撞函数Collision
public bool IsGroundDetected()
{
return Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);
}//通过RayCast检测是否挨着地面,https://docs.unity3d.com/cn/current/ScriptReference/Physics.Raycast.html
//xxxxxxxx() => xxxxxxxx == xxxxxxxxxx() return xxxxxxxxx;
public bool IsWallDetected()
{
return Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround);
}//通过RayCast检测是否挨着地面,https://docs.unity3d.com/cn/current/ScriptReference/Physics.Raycast.html
//xxxxxxxx() => xxxxxxxx == xxxxxxxxxx() return xxxxxxxxx;
private void OnDrawGizmos()
{
Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));//绘制一条从 from(前面的) 开始到 to(后面的) 的线。
Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance, wallCheck.position.y));//绘制一条从 from(前面的) 开始到 to(后面的) 的线。
}//画线函数
#endregion
#region 翻转函数Flip
public void Flip()
{
facingDir = facingDir * -1;
facingRight = !facingRight;
transform.Rotate(0, 180, 0);//旋转函数,transform不需要额外定义,因为他是自带的
}//翻转函数
public void FlipController(float _x)//目前设置x,目的时能在空中时也能转身
{
if (_x > 0 && !facingRight)//当速度大于0且没有朝右时,翻转
{
Flip();
}
else if (_x < 0 && facingRight)
{
Flip();
}
}
#endregion
}
PlayerIdleState.cs
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerIdleState : PlayerGroundState//继承函数,获得PlayerState里的所有函数和参数
{
public PlayerIdleState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}//构造函数,用于传递信息。
//当外补New出对象时,New出的对象里传入参数
public override void Enter()
{
base.Enter();
player.ZeroVelocity();//因为速度是没在wallJump里改,所以进入idle写死速度为0
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
//-------------------------------
if (player.IsWallDetected())
{
if (xInput != 0 && xInput != player.facingDir)
{
stateMachine.ChangeState(player.moveState);//在这里我们能使用Player里的东西,主要是因为我们通过构造函数传过来Player实体,如果不传,则无法使用已经存在了的Player实体。
}
}
else
{
if (xInput != 0 && !player.isBusy)//设置BusyBool值,在一定时间内使其无法从idle-》move
{
stateMachine.ChangeState(player.moveState);
}
}
//--------------------------------
//以上代码为自我改良版,改良了在碰墙时,idle和move会交替卡住的状况
}
}
PlayerPrimaryAttack.cs
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerPrimaryAttack : PlayerState
{
//p38 2.从ground进入
private int comboCounter;
private float lastTimeAttacked;//距离上一次攻击的时间
private float comboWindow = 2;//可以间隔的时间
public PlayerPrimaryAttack(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
if(comboCounter >2||Time.time>comboWindow+lastTimeAttacked)//当计数器超过2和间隔时间大于window时,进入第一个攻击动作
{
comboCounter = 0;
}
Debug.Log(comboCounter);
player.anim.SetInteger("ComboCounter", comboCounter);//设置animtor里的comboCounter
player.SetVelocity(player.attackMovement[comboCounter].x * player.facingDir, player.attackMovement[comboCounter].y);//给角色初速度,让角色在攻击触发时移动一点
stateTimer = .1f;
}
public override void Exit()
{
base.Exit();
player.StartCoroutine("BusyFor", .15f);
comboCounter++;
lastTimeAttacked = Time.time;
}
public override void Update()
{
base.Update();
if(stateTimer<0)
{
player.ZeroVelocity();
}//1.修改移动时攻击时后可以移动的BUG
//2.但给了点时间模拟惯性可以动一点
if (triggerCalled)
{
stateMachine.ChangeState(player.idleState);
}
}
}