【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Player : Entity
{
[Header("Attack Details")]
public Vector2[] attackMovement;//每个攻击时获得的速度组
public float counterAttackDuration = .2f;
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; }
#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 PlayerPrimaryAttackState primaryAttack { get; private set; }
public PlayerCounterAttackState counterAttack { get; private set; }
#endregion
protected override void Awake()
{
base.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 PlayerPrimaryAttackState(this, stateMachine, "Attack");
counterAttack = new PlayerCounterAttackState(this, stateMachine, "CounterAttack");
//this 就是 Player这个类本身
}//Awake初始化所以State,为所有State传入各自独有的参数,及animBool,以判断是否调用此动画(与animatoin配合完成)
protected override void Start()
{
base.Start();
stateMachine.Initialize(idleState);
}
protected override void Update()//在mano中update会自动刷新但其他没有mano的不会故,需要在这个updata中调用其他脚本中的函数stateMachine.currentState.update以实现 //stateMachine中的update
{
base.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;
yield return new WaitForSeconds(_seconds);
isBusy = false;
}//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切换设置成一个函数,使其在所以情况下都能使用
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//GroundState用于保证只有在Idle和Move这两个地面状态下才能调用某些函数,并且稍微减少一点代码量
public class PlayerGroundState : PlayerState
{
public PlayerGroundState(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.Q))//摁Q进入反击状态
{
stateMachine.ChangeState(player.counterAttack);
}
if(Input.GetKeyDown(KeyCode.Mouse0))//p38 2.从ground进入攻击状态
{
stateMachine.ChangeState(player.primaryAttack);
}
if(player.IsGroundDetected()==false)
{
stateMachine.ChangeState(player.airState);
}// 写这个是为了防止在空中直接切换为moveState了。
if (Input.GetKeyDown(KeyCode.Space) && player.IsGroundDetected())
{
stateMachine.ChangeState(player.jumpState);
}//空格切换为跳跃状态
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//从Ground里摁Q进入
public class PlayerCounterAttackState : PlayerState
{
public PlayerCounterAttackState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
stateTimer = player.counterAttackDuration;//设置Counter在没有成功的情况下可以保持的时间
player.anim.SetBool("SuccessfulCounterAttack", false);//默认失败
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
player.SetZeroVelocity();// 修复在Counter时还能移动的问题
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);//创建一个碰撞器组,保存所有圈所碰到的碰撞器
//https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Physics2D.OverlapCircleAll.html
foreach (var hit in colliders)//https://blog.csdn.net/m0_52358030/article/details/121722077
{
if (hit.GetComponent() != null)
{
if(hit.GetComponent().CanBeStunned())
{
stateTimer = 10;//只要比1大就行,防止成功反击的动画还没完就进入idleState了
player.anim.SetBool("SuccessfulCounterAttack", true);//使进入成功反击动画
}
}
}
if(stateTimer<0||triggerCalled)//当反击动画结束或反击可以持续的时间结束后回到idleState
{
stateMachine.ChangeState(player.idleState);
}
}
}
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Enemy : Entity
{
[SerializeField] protected LayerMask whatIsPlayer;
[Header("Stun Info")]
public float stunnedDuration;//stunned持续时间
public Vector2 stunnedDirection;//stunned改变后的速度
protected bool canBeStunned;//判断是否可以被反击
[SerializeField] protected GameObject counterImage;//一个代表着是否可以被反击的信号
[Header("Move Info")]
public float moveSpeed;
public float idleTime;
public float battleTime;//多久能从battle状态中退出来
[Header("Attack Info")]
public float attackDistance;
public float attackCooldown;//攻击冷却
[HideInInspector] public float lastTimeAttacked;//最后一次攻击的时间
#region 类
public EnemyStateMachine stateMachine;
#endregion
protected override void Awake()
{
base.Awake();
stateMachine = new EnemyStateMachine();
}
protected override void Start()
{
base.Start();
}
protected override void Update()
{
base.Update();
stateMachine.currentState.Update();
//Debug.Log(IsPlayerDetected().collider.gameObject.name + "I see");//这串代码会报错,可能使版本的物体,因为在没有找到Player的时候物体是空的,NULL,你想让他在控制台上显示就报错了
}
public virtual void OpenCounterAttackWindow()//打开可以反击的信号的函数
{
canBeStunned = true;
counterImage.SetActive(true);
}
public virtual void CloseCounterAttackWindow()//关闭可以反击的信号的函数
{
canBeStunned = false;
counterImage.SetActive(false);
}
public virtual bool CanBeStunned()//这里的主要目的是完成在被反击过后能立刻关闭提示窗口
{
if (canBeStunned)
{
CloseCounterAttackWindow();
return true;
}
return false;
}
public virtual void AnimationFinishTrigger() => stateMachine.currentState.AnimationFinishTrigger();//动画完成时调用的函数,与Player相同
public virtual RaycastHit2D IsPlayerDetected() => Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, 7, whatIsPlayer);//用于从射线投射获取信息的结构。
//该函数的返回值可以变,可以只返回bool,也可以是碰到的结构
protected override void OnDrawGizmos()
{
base.OnDrawGizmos();
Gizmos.color = Color.yellow;//把线改成黄色
Gizmos.DrawLine(transform.position, new Vector3(transform.position.x + attackDistance * facingDir, transform.position.y));//用来判别是否进入attackState的线
}
}
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditorInternal;
using UnityEngine;
public class Enemy_Skeleton : Enemy
{
#region 类State
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(this, stateMachine, "Idle", this);
moveState = new SkeletonMoveState(this,stateMachine, "Move", this);
battleState = new SkeletonBattleState(this, stateMachine, "Move", this);
attackState = new SkeletonAttackState(this, stateMachine, "Attack", this);
stunnedState = new SkeletonStunnedState(this, stateMachine, "Stunned", this);
}
protected override void Start()
{
base.Start();
stateMachine.Initialize(idleState);
}
protected override void Update()
{
base.Update();
}
public override bool CanBeStunned()
{
if (base.CanBeStunned())//在这里重写主要是因为只有在Skeleton里面才能调用stunnedState
{
stateMachine.ChangeState(stunnedState);
return true;
}
return false;
}
//重写后不仅能达成在完成被反击后关闭提示窗口,还能转换为stunnedState
}