Unity开发2D类银河恶魔城游戏学习笔记
Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进
Unity教程(十)Tile Palette搭建平台关卡
Unity教程(十一)相机
Unity教程(十二)视差背景
Unity教程(十三)敌人状态机
Unity教程(十四)敌人空闲和移动的实现
Unity教程(十五)敌人战斗状态的实现
Unity教程(十六)敌人攻击状态的实现
Unity教程(十七)敌人战斗状态的完善
Unity教程(十八)战斗系统 攻击逻辑
Unity教程(十九)战斗系统 受击反馈
Unity教程(二十)战斗系统 角色反击
Unity教程(二十一)技能系统
如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录
文章目录
- Unity开发2D类银河恶魔城游戏学习笔记
- 前言
- 一、概述
- 二、骷髅的眩晕状态
- (1)创建骷髅眩晕动画
- (2)创建SkeletonStunnedState
- (3)实现被击晕红色闪烁特效
- 三、反击窗口的实现
- (1)创建打开和关闭反击窗口函数
- (2)设置动画事件
- 四、玩家的反击状态
- (1)完成反击窗口检查和反击成功的操作
- (2)创建玩家反击动画
- (3)创建PlayerCounterAttackState
- (4)进入反击状态
- (5)解决反击时能移动的问题(改不改都行)
- 总结 完整代码
- SkeletonStunnedState.cs
- Enemy.cs
- EnemySkeleton.cs
- Enemy_SkeletonAnimationTriggers.cs
- EntityFX.cs
- PlayerCounterAttackState.cs
- PlayerGroundedState
- Player.cs
前言
本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。
本节实现战斗系统的角色反击部分。
Udemy课程地址
对应视频:
Counter attack - Enemy’s Stun State
Counter 's attack window
Player’s Counter Attack
一、概述
本节实现角色反击,包括骷髅被反击后的眩晕状态和角色反击状态。
骷髅眩晕状态持续期间,会播放眩晕动画并闪烁红光。
将骷髅攻击时的一段时间设置为反击窗口,并且实现了反击窗口的显示,在反击窗口内骷髅才能被反击。
玩家反击时和攻击实现一样,记录攻击范围内的敌人,并判断是否处于反击窗口中,处于则反击成功。
玩家状态转换如下:
骷髅状态转换如下:
具体如图:
二、骷髅的眩晕状态
(1)创建骷髅眩晕动画
我们创建动画skeletonStunned
层次面板中选中Enemy_skeleton下的Animator,在Animation面板中创建动画
将精灵表SkeletonHit标号3的帧拖入
动画创建的更详细讲解见Unity教程(零)Unity和VS的使用相关内容
连接状态机,并添加过渡条件Stunned,并修改过渡设置
添加bool型条件变量Stunned,并连接过渡
Entry->skeletonStunned的过渡,加条件变量
skeletonStunned->Exit的过渡,加条件变量,并更改设置
(2)创建SkeletonStunnedState
首先创建SkeletonStunnedState,它继承自EnemyState,通过菜单生成构造函数和重写。
添加Enemy_Skeleton变量,并修改构造函数中传入值
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkeletonStunnedState : EnemyState
{
private Enemy_Skeleton enemy;
public SkeletonStunnedState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy,string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
}
}
在Enemy中添加敌人被击晕的相关变量,包括被击晕的时长和方向。
[Header("Stunned Info")]
public float stunDuration = 1.0f;
public Vector2 stunDirection;
在SkeletonStunnedState的Enter函数中给计时器赋初值并设置被击晕的速度,击晕方向与骷髅面向的方向相反。
当计时器归零时,切换到空闲状态。
//SkeletonStunnedState: 眩晕状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkeletonStunnedState : EnemyState
{
private Enemy_Skeleton enemy;
public SkeletonStunnedState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy,string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
stateTimer = enemy.stunDuration;
rb.velocity = new Vector2( -enemy.facingDir * enemy.stunDirection.x, enemy.stunDirection.y);
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
if(stateTimer < 0)
stateMachine.ChangeState(enemy.idleState);
}
}
在Enemy_Skeleton中创建骷髅眩晕状态。
public SkeletonStunnedState stunnedState { get; private set; }
protected override void Awake()
{
base.Awake();
idleState = new SkeletonIdleState(stateMachine,this,this,"Idle");
moveState = new SkeletonMoveState(stateMachine, this,this, "Move");
battleState = new SkeletonBattleState(stateMachine, this, this, "Move");
attackState = new SkeletonAttackState(stateMachine, this, this, "Attack");
stunnedState = new SkeletonStunnedState(stateMachine, this, this, "Stunned");
}
然后我们先设置一个按键,按下骷髅转换到被击晕状态看一下效果。这里只是看一下效果,具体状态转换条件会在后文实现。
再Enemy_Skeleton中添加代码:
protected override void Update()
{
base.Update();
if(Input.GetKeyDown(KeyCode.U))
{
stateMachine.ChangeState(stunnedState);
}
}
在层次面板中选中Enemy_Skeleton,设置合适的被击晕时间和方向。
效果如下:
(3)实现被击晕红色闪烁特效
在实体特效类EntityFX中添加一个红色的闪烁特效,在白色与红色之间切换。
实现这个效果,改变骷髅Sprite Renderer的Color属性即可。
改变颜色为红色时效果如下:
在EntityFX中添加颜色切换的函数:
//红色闪烁特效
private void RedColorBlink()
{
if (sr.color != Color.white)
sr.color = Color.white;
sr.color = Color.red;
}
上面的函数实现了一次颜色切换,要实现闪烁效果要不断重复这个函数,我们用Invoke实现。
这里的Invoke是Unity的MonoBehaviour类中的延迟执行方法。
可参照Unity官方手册
函数 | 作用 |
---|---|
Invoke | 延迟指定时间后,调用指定函数 |
InvokeRepeating | 延迟指定时间后,调用指定函数,每隔一定时间重复执行一次 |
IsInvoking | 判断指定函数是否正被调用 |
CancelInvoke | 取消调用函数 |
几个函数调用如下:
Invoke(string methodName, float time)
InvokeRepeating(string methodName, float time, float repeatRate)
IsInvoking(string methodName)
//取消全部调用
CancelInvoke()
//取消指定方法调用
CancelInvoke(string methodName)
我们在SkeletonStunned类进入状态时使用InvokeRepeating重复调用红色闪烁特效:
public override void Enter()
{
base.Enter();
enemy.fx.InvokeRepeating("RedColorBlink", 0, 0.1f);
stateTimer = enemy.stunDuration;
rb.velocity = new Vector2( -enemy.facingDir * enemy.stunDirection.x, enemy.stunDirection.y);
}
在眩晕结束后,要取消闪烁的调用。
在EntityFX中添加取消闪烁的函数
private void CancelRedBlink()
{
CancelInvoke();
sr.color = Color.white;
}
在SkeletonStunned类退出状态时使用Invoke调用取消闪烁
public override void Exit()
{
base.Exit();
enemy.fx.Invoke("CancelRedBlink", 0);
}
效果如下:
三、反击窗口的实现
在可以进行反击时,显示一个反击窗口,在动画播放到合适的时机时触发。
(1)创建打开和关闭反击窗口函数
在Enemy的被击晕信息中,添加表示能被击晕的标志变量,和反击窗口的图像。
[Header("Stunned Info")]
public float stunDuration = 1.0f;
public Vector2 stunDirection;
protected bool canBeStunned;
[SerializeField] protected GameObject counterImage;
在Enemy中写两个函数,控制反击窗口的开启和关闭,为canBeStunned赋值和控制反击窗口图像的显示。
//打开反击窗口
protected virtual void openCounterAttackWindow()
{
canBeStunned = true;
counterImage.SetActive(true);
}
//关闭反击窗口
protected virtual void closeCounterAttackWindow()
{
canBeStunned = false;
counterImage.SetActive(false);
}
由于动画师Animator组件挂在骷髅之下,要在动画播放中设置触发事件,需要把事件写在Animator组件的脚本Enemy_SkeletonAnimationTriggers中。再由触发器调用Enemy类中的函数实现。
Enemy_SkeletonAnimationTriggers中添加代码实现:
private void openCounterWindow() => enemy.OpenCounterAttackWindow();
private void closeCounterWindow() => enemy.CloseCounterAttackWindow();
在层次面板中右击Enemy_Skeleton创建反击窗口图像
右击Enemy_Skeleton -> 2D Object -> Sprites -> Square -> 重命名为CounterImage
可以根据喜好选取攻击窗口颜色,这里我依照教程选了略透明的红色
把它拖到Stunned信息,CounterImage的位置。
(2)设置动画事件
选取骷髅拿起武器时打开反击窗口,攻击落下时关闭反击窗口。
选择动画skeletonAttack,在第4帧,骷髅拿起武器时,添加触发事件
选择触发的函数打开反击窗口
Enemy_SkeletonAnimationTriggers -> Methods -> openCounterWindow()
在第8帧,骷髅完成攻击时,添加触发事件
选择触发的函数关闭反击窗口
Enemy_SkeletonAnimationTriggers -> Methods -> closeCounterWindow()
调整CounterImage层次
调整反击窗口到合适的大小和位置
反击窗口初始状态设置为不显示,让它只在骷髅攻击时出现。
效果如下:
四、玩家的反击状态
玩家进行反击,并且反击的敌人处于可反击状态则反击成功。
反击的整体过程如下:
canBeStunned变量就代表了敌人是否处于可被反击的时间段,所以上面一系列调用本质上就是在反击时判断canBeStunned是否为真。中间这些函数只是在反击成功时,顺便完成了一些反击成功时应该进行的操作。
(1)完成反击窗口检查和反击成功的操作
在Enemy中创建虚函数CheckCanBeStunned,检查canBeStunned变量并返回。此时攻击窗口是打开的,打开和关闭攻击窗口的函数就在Enemy中,因此在canBeStunned为真时,反击成功,在其中书写关闭攻击窗口。
//检查击晕条件,关闭反击窗口
public virtual bool CheckCanBeStunned()
{
if(canBeStunned)
{
CloseCounterAttackWindow();
return true;
}
return false;
}
接着在Enemy_Skeleton中重载函数CheckCanBeStunned,调用基类函数得到canBeStunned的检查结果并返回。当检查结果为真时,添加反击成功后骷髅被击晕的状态转换。
//检查击晕条件,转到眩晕状态
public override bool CheckCanBeStunned()
{
if(base.CheckCanBeStunned())
{
stateMachine.ChangeState(stunnedState);
return true;
}
return false;
}
(2)创建玩家反击动画
我们创建动画playerCounterAttack
层次面板中选中Player下的Animator,在Animation面板中创建动画
将精灵表标号为22的帧拖入
创建动画playerSuccessfulCounterAttack
选择精灵表标号为23、24、25、18的帧拖入
这个动作稍快一点,采样率改为20
连接状态机,并添加过渡条件CounterAttack,并修改过渡设置
添加bool型条件变量CounterAttack,并连接过渡
Entry->playerCounterAttack的过渡,加条件变量
playerCounterAttack->Exit的过渡,加条件变量,并更改设置
连接状态机,并添加过渡条件SuccessfulCounterAttack,并修改过渡设置
添加bool型条件变量SuccessfulCounterAttack,并连接过渡。反击成功的动画连接与其他不同,它是由反击状态过渡过来的。
playerCounterAttack->playerSuccessfulCounterAttack的过渡,加条件变量,并更改设置
playerSuccessfulCounterAttack->Exit的过渡,加条件变量,并更改设置
注意:这里退出条件也是CounterAttack为false
反击成功的动画播放完后要退出反击状态,所以还要在反击成功最后一帧添加事件调用AnimationTrigger()
(3)创建PlayerCounterAttackState
首先创建PlayerCounterAttackState,它继承自PlayerState,通过菜单生成构造函数和重写。
在Player的攻击信息中添加变量,反击持续时间counterAttackDuration。这个值设置时建议小一点,不然会出现角色摆好反击姿势不动等着骷髅攻击就弹反的情况。
[Header("Attack details")]
public Vector2[] attackMovement;
public float counterAttackDuration = 0.2f;
public bool isBusy { get; private set; }
在玩家进入反击状态时,将计时器重置为反击持续时间,并且将反击成功的标志变量SuccessfulCounterAttack先置为false。
public override void Enter()
{
base.Enter();
stateTimer = player.counterAttackDuration;
player.anim.SetBool("SuccessfulCounterAttack", false);
}
反击过程中,要像攻击时一样,记录在攻击范围内的敌人,并检查敌人此时是否处于可被击晕的窗口期,如果处于则播放反击成功动画。
同时还要注意,stateTimer时间较短,反击成功时会播不完动画。因此在反击成功时,需要先将stateTimer改为一个较大的数值。
public override void Update()
{
base.Update();
//记录攻击范围内的敌人
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
if(hit.GetComponent<Enemy>().CheckCanBeStunned())
{
stateTimer = 10;
player.anim.SetBool("SuccessfulCounterAttack", true);
}
}
}
}
玩家退出反击状态有两种情况。
1.在反击持续时间里,反击不成功,计时器stateTimer小于0反击结束。
2.反击成功,在成功反击的动画运行到最后一帧,触发动画事件,triggerCalled为true,退出反击状态。
因此退出反击状态的条件为stateTimer<0 或 triggerCalled = true
接着在类中添加这部分代码
public override void Update()
{
base.Update();
//记录攻击范围内的敌人
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
if(hit.GetComponent<Enemy>().CheckCanBeStunned())
{
stateTimer = 10;
player.anim.SetBool("SuccessfulCounterAttack", true);
}
}
}
if(stateTimer<0 || triggerCalled)
stateMachine.ChangeState(player.idleState);
}
在Player中创建反击状态
public PlayerCounterAttackState counterAttack { get; private set; }
//创建对象
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");
counterAttack = new PlayerCounterAttackState(StateMachine, this, "CounterAttack");
}
(4)进入反击状态
在PlayerGroundedState中设置按键,切换到反击状态
//更新
public override void Update()
{
base.Update();
if (Input.GetKeyDown(KeyCode.Q))
stateMachine.ChangeState(player.counterAttack);
if (Input.GetKeyDown(KeyCode.Mouse0))
stateMachine.ChangeState(player.primaryAttack);
if(!player.isGroundDetected())
stateMachine.ChangeState(player.airState);
if (Input.GetKeyDown(KeyCode.Space)&& player.isGroundDetected())
stateMachine.ChangeState(player.jumpState);
}
反击效果如下:
不在反击窗口时反击:
处于反击窗口时反击:
(5)解决反击时能移动的问题(改不改都行)
现在反击时玩家可以移动,可以看情况修改。
只需在PlayerCounterAttackState的Update一开始设置速度为零即可。
public override void Update()
{
base.Update();
player.ZeroVelocity();
//记录攻击范围内的敌人
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
if(hit.GetComponent<Enemy>().CheckCanBeStunned())
{
stateTimer = 10;
player.anim.SetBool("SuccessfulCounterAttack", true);
}
}
}
if(stateTimer<0 || triggerCalled)
stateMachine.ChangeState(player.idleState);
}
总结 完整代码
SkeletonStunnedState.cs
设置骷髅眩晕时长和速度。设置被击晕时红色闪烁特效。
实现切换到空闲状态。
//SkeletonStunnedState: 眩晕状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkeletonStunnedState : EnemyState
{
private Enemy_Skeleton enemy;
public SkeletonStunnedState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy,string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
enemy.fx.InvokeRepeating("RedColorBlink", 0, 0.1f);
stateTimer = enemy.stunDuration;
rb.velocity = new Vector2( -enemy.facingDir * enemy.stunDirection.x, enemy.stunDirection.y);
}
public override void Exit()
{
base.Exit();
enemy.fx.Invoke("CancelRedBlink", 0);
}
public override void Update()
{
base.Update();
if(stateTimer < 0)
stateMachine.ChangeState(enemy.idleState);
}
}
Enemy.cs
添加骷髅被击晕相关变量。
创建函数控制反击窗口开关。
创建函数检查时候处于反击窗口,并在玩家反击成功时关闭反击窗口。
//Enemy:敌人基类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : Entity
{
[SerializeField] protected LayerMask WhatIsPlayer;
[Header("Stunned Info")]
public float stunDuration = 1.0f;
public Vector2 stunDirection;
protected bool canBeStunned;
[SerializeField] protected GameObject counterImage;
[Header("Move Info")]
public float moveSpeed = 1.5f;
public float idleTime = 2.0f;
public float battleTime = 4.0f;
[Header("Attack Info")]
public float attackDistance;
public float attackCoolDown;
[HideInInspector] public float lastTimeAttacked;
public EnemyStateMachine stateMachine;
protected override void Awake()
{
base.Awake();
stateMachine = new EnemyStateMachine();
}
protected override void Update()
{
base.Update();
stateMachine.currentState.Update();
}
//打开反击窗口
public virtual void OpenCounterAttackWindow()
{
canBeStunned = true;
counterImage.SetActive(true);
}
//关闭反击窗口
public virtual void CloseCounterAttackWindow()
{
canBeStunned = false;
counterImage.SetActive(false);
}
//检查击晕条件,关闭反击窗口
public virtual bool CheckCanBeStunned()
{
if(canBeStunned)
{
CloseCounterAttackWindow();
return true;
}
return false;
}
//设置触发器
public virtual void AnimationTrigger() => stateMachine.currentState.AnimationFinishTrigger();
public virtual RaycastHit2D IsPlayerDetected()=>Physics2D.Raycast(transform.position, Vector2.right * facingDir, 50 ,WhatIsPlayer);
protected override void OnDrawGizmos()
{
base.OnDrawGizmos();
Gizmos.color = Color.yellow;
Gizmos.DrawLine(transform.position, new Vector3(transform.position.x + attackDistance * facingDir, transform.position.y));
}
}
EnemySkeleton.cs
创建眩晕状态并赋值。
调用函数检查是否处于反击窗口,并在玩家反击成功时转到眩晕状态。
//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; }
public SkeletonBattleState battleState { get; private set; }
public SkeletonAttackState attackState { get; private set; }
public SkeletonStunnedState stunnedState { get; private set; }
#endregion
protected override void Awake()
{
base.Awake();
idleState = new SkeletonIdleState(stateMachine,this,this,"Idle");
moveState = new SkeletonMoveState(stateMachine, this,this, "Move");
battleState = new SkeletonBattleState(stateMachine, this, this, "Move");
attackState = new SkeletonAttackState(stateMachine, this, this, "Attack");
stunnedState = new SkeletonStunnedState(stateMachine, this, this, "Stunned");
}
protected override void Start()
{
base.Start();
stateMachine.Initialize(idleState);
}
protected override void Update()
{
base.Update();
if(Input.GetKeyDown(KeyCode.U))
{
stateMachine.ChangeState(stunnedState);
}
}
//检查击晕条件,转到眩晕状态
public override bool CheckCanBeStunned()
{
if(base.CheckCanBeStunned())
{
stateMachine.ChangeState(stunnedState);
return true;
}
return false;
}
}
Enemy_SkeletonAnimationTriggers.cs
调用函数控制反击窗口开关。
//Enemy_SkeletonAnimationTriggers:触发器组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy_SkeletonAnimationTriggers : MonoBehaviour
{
private Enemy_Skeleton enemy => GetComponentInParent<Enemy_Skeleton>();
private void AnimationTrigger()
{
enemy.AnimationTrigger();
}
private void AttackTrigger()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(enemy.attackCheck.position, enemy.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Player>() != null)
hit.GetComponent<Player>().Damage();
}
}
private void openCounterWindow() => enemy.OpenCounterAttackWindow();
private void closeCounterWindow() => enemy.CloseCounterAttackWindow();
}
EntityFX.cs
实现红色闪烁特效。
//EntityFX:实体特效
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EntityFX : MonoBehaviour
{
private SpriteRenderer sr;
[Header("Flash FX")]
[SerializeField] private Material hitMat;
private Material originalMat;
[SerializeField] private float flashDuration;
private void Start()
{
sr = GetComponentInChildren<SpriteRenderer>();
originalMat = sr.material;
}
//闪烁特效
private IEnumerator FlashFX()
{
sr.material = hitMat;
yield return new WaitForSeconds(flashDuration);
sr.material = originalMat;
}
//红色闪烁特效
private void RedColorBlink()
{
if (sr.color != Color.white)
sr.color = Color.white;
else
sr.color = Color.red;
}
private void CancelRedBlink()
{
CancelInvoke();
sr.color = Color.white;
}
}
PlayerCounterAttackState.cs
实现玩家反击状态。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCounterAttackState : PlayerState
{
public PlayerCounterAttackState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
stateTimer = player.counterAttackDuration;
player.anim.SetBool("SuccessfulCounterAttack", false);
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
player.ZeroVelocity();
//记录攻击范围内的敌人
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
if(hit.GetComponent<Enemy>().CheckCanBeStunned())
{
stateTimer = 10;
player.anim.SetBool("SuccessfulCounterAttack", true);
}
}
}
if(stateTimer<0 || triggerCalled)
stateMachine.ChangeState(player.idleState);
}
}
PlayerGroundedState
实现按Q键切换到反击状态。
//超级状态PlayerGroundedState:接地状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerGroundedState : PlayerState
{
//构造函数
public PlayerGroundedState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName)
{
}
//进入
public override void Enter()
{
base.Enter();
}
//退出
public override void Exit()
{
base.Exit();
}
//更新
public override void Update()
{
base.Update();
if (Input.GetKeyDown(KeyCode.Q))
stateMachine.ChangeState(player.counterAttack);
if (Input.GetKeyDown(KeyCode.Mouse0))
stateMachine.ChangeState(player.primaryAttack);
if(!player.isGroundDetected())
stateMachine.ChangeState(player.airState);
if (Input.GetKeyDown(KeyCode.Space)&& player.isGroundDetected())
stateMachine.ChangeState(player.jumpState);
}
}
Player.cs
创建反击状态并赋值。
//Player:玩家
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : Entity
{
[Header("Attack details")]
public Vector2[] attackMovement;
public float counterAttackDuration = 0.2f;
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; }
public PlayerCounterAttackState counterAttack { 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");
counterAttack = new PlayerCounterAttackState(StateMachine, this, "CounterAttack");
}
// 设置初始状态
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);
}
}
}