教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/
本章节传造了一个会自爆的敌人暗影殉道者
Enemy_Shady.cs
功能与逻辑
-
状态机管理:
- 定义了多个状态(如
idleState
,moveState
,deadState
,stunnedState
,battleState
),每个状态都有对应的逻辑和动画。 - 使用状态机(
stateMachine
)动态管理敌人的行为,通过调用stateMachine.ChangeState
切换状态。
- 定义了多个状态(如
-
特殊属性:
battleStateMoveSpeed
: 战斗状态的移动速度。explosivePrefab
: 特殊攻击的爆炸物预制体。growSpeed
和maxSize
: 爆炸物增长速度和最大尺寸。- 这些属性定义了暗影法师特有的战斗行为。
-
特殊功能:
- 特殊攻击 (
AnimationSepcialAttackTrigger
):- 在敌人执行特殊攻击时,会生成一个爆炸物(
explosivePrefab
),并根据暗影法师的属性(如爆炸增长速度、最大尺寸、攻击距离)进行初始化。 - 改变敌人的物理属性(禁用碰撞检测
cd.enabled
,重力影响rb.gravityScale
)。
- 在敌人执行特殊攻击时,会生成一个爆炸物(
- 死亡处理 (
Die
):- 切换到死亡状态,可能会触发死亡动画或特效。
- 被击晕 (
CanBeStunned
):- 检查敌人是否能被击晕,如果可以,则切换到
stunnedState
。
- 检查敌人是否能被击晕,如果可以,则切换到
- 自毁功能 (
SelfDestroy
):- 用于销毁自身对象,可能是在死亡动画完成后调用。
- 特殊攻击 (
cs
using System.Collections;
using UnityEngine;
public class Enemy_Shady : Enemy
{
[Header("暗影法师特殊信息")]//Shady specific info
public float battleStateMoveSpeed ;
[SerializeField] private GameObject explosivePrefab;
[SerializeField] private float growSpeed;
[SerializeField] private float maxSize;
#region States
public ShadyIdleState idleState { get; private set; }
public ShadyMoveState moveState { get; private set; }
public ShadyDeadState deadState { get; private set; }
public ShadyStunnedState stunnedState { get; private set; }
public ShadyBattleState battleState { get; private set; }
#endregion
protected override void Awake()
{
base.Awake();
idleState = new ShadyIdleState(this, stateMachine, "Idle", this);
moveState = new ShadyMoveState(this, stateMachine, "Move", this);
deadState = new ShadyDeadState(this, stateMachine, "Dead", this);
stunnedState = new ShadyStunnedState(this, stateMachine, "Stunned", this);
battleState = new ShadyBattleState(this, stateMachine, "MoveFast", this);
}
protected override void Start()
{
base.Start();
stateMachine.Initialize(idleState);
}
public override bool CanBeStunned()
{
if (base.CanBeStunned())
{
stateMachine.ChangeState(stunnedState);
return true;
}
return false;
}
public override void Die()
{
base.Die();
stateMachine.ChangeState(deadState);
}
public override void AnimationSepcialAttackTrigger()
{
GameObject newExplosive = Instantiate(explosivePrefab, transform.position, Quaternion.identity);
newExplosive.GetComponent<Explosive_Controller>().SetupExplosive(stats, growSpeed, maxSize, attackDistance);
cd.enabled = false;
rb.gravityScale = 0;
}
public void SelfDestroy() => Destroy(gameObject);
}
Explosive_Controller.cs
功能与逻辑分析
-
生长逻辑:
- 在
Update
方法中,通过Vector2.Lerp
使爆炸物逐渐放大,直到达到maxSize
。 - 当尺寸接近
maxSize
时,停止生长(canGrow = false
),并触发爆炸动画 (anim.SetTrigger("Explode")
)。
- 在
-
初始化逻辑:
SetupExplosive
方法:- 初始化爆炸物的相关参数,包括属性来源、增长速度、最大尺寸、爆炸半径。
- 设置动画组件
Animator
。
-
爆炸逻辑:
- 动画事件触发 (
AnimationExplodeEvent
方法):- 使用
Physics2D.OverlapCircleAll
检测爆炸半径内的所有碰撞体。 - 遍历检测到的对象:
- 如果对象具有
CharacterStats
组件,说明它是可以受到攻击的单位。 - 调用其
SetupKnockbackDir
方法,可能实现击退效果。 - 调用爆炸物来源的
DoDamage
方法,对目标造成伤害。
- 如果对象具有
- 使用
- 动画事件触发 (
-
销毁逻辑:
SelfDestroy
方法:- 销毁当前爆炸物对象。
- 应该在爆炸动画完成或某个条件触发后被调用。
cs
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class Explosive_Controller : MonoBehaviour
{
private Animator anim;
private CharacterStats myStats;
private float growSpeed = 15;
private float maxSize = 6;
private float explosionRadius;
private bool canGrow = true ;
private void Update()
{
if (canGrow)
{
transform.localScale = Vector2.Lerp(transform.localScale, new Vector2(maxSize, maxSize), growSpeed * Time.deltaTime);
explosionRadius = transform.localScale.x / 2; // 动态调整爆炸半径
}
if (maxSize - transform.localScale.x < .01f)
{
canGrow = false;
anim.SetTrigger("Explode");
}
}
public void SetupExplosive(CharacterStats _myStats,float _groSpeed,float _maxSize,float _radius)
{
anim = GetComponent<Animator>();
myStats = _myStats;
growSpeed = _groSpeed;
maxSize = _maxSize;
explosionRadius = _radius;
}
private void AnimationExplodeEvent()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, explosionRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<CharacterStats>() != null)
{
hit.GetComponent<Entity>().SetupKnockbackDir(transform);
myStats.DoDamage(hit.GetComponent<CharacterStats>());
}
}
}
private void SelfDestroy() => Destroy(gameObject);
}
ShadyBattleState.cs
cs
using System.Collections;
using UnityEngine;
public class ShadyBattleState : EnemyState
{
private Transform player;
private Enemy_Shady enemy;
private int moveDir;
public float defaultSpeed;
public ShadyBattleState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName,Enemy_Shady _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
enemy = _enemy;
}
public override void Enter()
{
base.Enter();
defaultSpeed = enemy.moveSpeed;
enemy.moveSpeed = enemy.battleStateMoveSpeed;
player = PlayerManager.instance.player.transform;
if (player.GetComponent<PlayerStats>().isDead)
stateMachine.ChangeState(enemy.moveState);
}
public override void Update()
{
base.Update();
if (enemy.IsPlayerDetected())
{
stateTimer = enemy.battleTime;
if (enemy.IsPlayerDetected().distance < enemy.attackDistance)
enemy.stats.KillEntity();
}
else
{
if (stateTimer < 0 || Vector2.Distance(player.transform.position, enemy.transform.position) > 10)//超过距离或者时间到了
stateMachine.ChangeState(enemy.idleState);
}
if (player.position.x > enemy.transform.position.x)
moveDir = 1;
else if (player.position.x < enemy.transform.position.x)
moveDir = -1;
if (Vector2.Distance(player.transform.position, enemy.transform.position) > 1)
enemy.SetVelocity(enemy.moveSpeed * moveDir, rb.velocity.y);
else
enemy.SetZeroVelocity();
}
public override void Exit()
{
base.Exit();
enemy.moveSpeed = defaultSpeed;
}
private bool CanAttack()
{
if (Time.time >= enemy.lastTimeAttack + enemy.attackCoolDown)
{
enemy.attackCoolDown = Random.Range(enemy.minAttackCoolDown, enemy.maxAttackCoolDown);
enemy.lastTimeAttack = Time.time;
return true;
}
else
return false;
}
}
ShadyIdleState.cs
cs
using System.Collections;
using UnityEngine;
public class ShadyIdleState : ShadyGroundedState
{
public ShadyIdleState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_Shady _enemy) : base(_enemyBase, _stateMachine, _animBoolName,_enemy)
{
}
public override void Enter()
{
base.Enter();
stateTimer = enemy.idleTime;
}
public override void Exit()
{
base.Exit();
AudioManager.instance.PlaySFX(24, enemy.transform);
}
public override void Update()
{
base.Update();
if (stateTimer < 0)
{
stateMachine.ChangeState(enemy.moveState);
}
}
}
ShadyMoveState.cs
cs
using System.Collections;
using UnityEngine;
public class ShadyMoveState : ShadyGroundedState
{
public ShadyMoveState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_Shady _enemy) : base(_enemyBase, _stateMachine, _animBoolName, _enemy)
{
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
enemy.SetVelocity(enemy.moveSpeed * enemy.facingDir, enemy.rb.velocity.y);
if (enemy.IsWallDetected() || !enemy.IsGroundDetected())//撞墙或者没有路反转
{
enemy.Flip();
stateMachine.ChangeState(enemy.idleState);
}
}
}
ShadyGroundedState.cs
cs
using System.Collections;
using UnityEngine;
public class ShadyGroundedState : EnemyState
{
protected Transform player;
protected Enemy_Shady enemy;
public ShadyGroundedState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName,Enemy_Shady _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
enemy = _enemy;
}
public override void Enter()
{
base.Enter();
player = PlayerManager.instance.player.transform;//p63 3:43改
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
if (enemy.IsPlayerDetected() || Vector2.Distance(enemy.transform.position, player.transform.position) < enemy.agroDistance)
{
stateMachine.ChangeState(enemy.battleState);
}
}
}
ShadyStunnedState.cs
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShadyStunnedState : EnemyState
{
private Enemy_Shady enemy;
public ShadyStunnedState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_Shady enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
this.enemy = enemy;
}
public override void Enter()
{
base.Enter();
enemy.fx.InvokeRepeating("RedColorBlink", 0, .1f); //这行代码使用 InvokeRepeating 方法,每隔 0.1 秒调用一次 RedColorBlink 方法。
stateTimer = enemy.stunDuration;
rb.velocity = new Vector2(-enemy.facingDir * enemy.stunDirection.x, enemy.stunDirection.y);
}
public override void Exit()
{
base.Exit();
enemy.fx.Invoke("CancelColorChange", 0);//Invoke 方法用于在指定的延迟时间后调用某个方法。在这里,延迟时间为 0
}
public override void Update()
{
base.Update();
if (stateTimer < 0)
stateMachine.ChangeState(enemy.idleState);
}
}
ShadyDeadState.cs
cs
using UnityEngine;
public class ShadyDeadState : EnemyState
{
private Enemy_Shady enemy;
public ShadyDeadState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_Shady _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
enemy = _enemy;
}
public override void Enter()
{
base.Enter();
}
public override void Update()
{
base.Update();
if(triggerCalled)
enemy.SelfDestroy();
}
}