【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/
本章节实现了史莱姆的分裂
Enemy_Slime.cs
1. 概览与目标
- 脚本核心目标:实现一个史莱姆敌人的行为,包括状态管理、被击晕、死亡后的分裂行为。
- 关键特性:
- 状态机管理多种行为状态。
- 支持不同类型的史莱姆(大小不同)。
- 史莱姆死亡后可以分裂为多个较小的史莱姆。
- 控制分裂史莱姆的初始速度和方向。
2. 核心字段
SlimeType
:通过枚举定义史莱姆的三种类型(big
,medium
,small
),用于区分敌人的规模和行为逻辑。slimesToCreate
和slimePrefab
:控制死亡后生成的史莱姆数量及其预制体。minCreationVelocity
和maxCreationVelocity
:定义分裂后的初速度范围,用于模拟物理效果。
3. 行为实现
(1) 被击晕逻辑
CanBeStunned()
:判断史莱姆是否可以被击晕,如果可以,切换到stunnedState
。- 实现了父类逻辑的扩展,增加状态切换能力。
(2) 死亡与分裂逻辑
Die()
:- 调用父类死亡逻辑。
- 切换到
deadState
。 - 根据史莱姆类型决定是否分裂:
small
类型不分裂,其余类型调用CreateSlimes
创建新史莱姆。
CreateSlimes()
:- 使用循环创建新史莱姆,调用
Instantiate()
生成预制体。 - 调用
SetupSlime()
初始化新史莱姆的方向和初速度。
- 使用循环创建新史莱姆,调用
(3) 分裂史莱姆的初始化
SetupSlime()
:- 根据当前史莱姆的朝向,设置新史莱姆的方向。
- 随机生成初始速度,并通过物理引擎赋予新史莱姆运动效果。
- 利用
Invoke()
方法取消初始的击退效果(knockback
)以恢复正常状态。
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using UnityEngine;
public enum SlimeType
{
big,
medium,
small
}
//2024.11.9
public class Enemy_Slime : Enemy
{
[Header("Slime spesific")]
[SerializeField] private SlimeType slimeType;
[SerializeField] private int slimesToCreate;
[SerializeField] private GameObject slimePrefab;
[SerializeField] private Vector2 minCreationVelocity;
[SerializeField] private Vector2 maxCreationVelocity;
#region States
public SlimeIdleState idleState { get; private set; }
public SlimeMoveState moveState { get; private set; }
public SlimeBattleState battleState { get; private set; }
public SlimeAttackState attackState { get; private set; }
public SlimeStunState stunnedState { get; private set; }
public SlimeDeathState deadState { get; private set; }
#endregion
protected override void Awake()
{
base.Awake();
SetupDefaultFacingDir(-1);
idleState = new SlimeIdleState(this, stateMachine, "Idle", this);
moveState = new SlimeMoveState(this, stateMachine, "Move", this);
battleState = new SlimeBattleState(this, stateMachine, "Move", this);
attackState = new SlimeAttackState(this, stateMachine, "Attack", this);
stunnedState = new SlimeStunState(this, stateMachine, "Stunned", this);
deadState = new SlimeDeathState(this, stateMachine, "Idle", 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);
if(slimeType == SlimeType.small)
return;
CreateSlimes(slimesToCreate, slimePrefab);
}
private void CreateSlimes(int _amountOfSlimes,GameObject _slimePrefab)//分裂
{
for (int i = 0; i < _amountOfSlimes; i++)
{
GameObject newSlime = Instantiate(_slimePrefab, transform.position, Quaternion.identity);
newSlime.GetComponent<Enemy_Slime>().SetupSlime(facingDir);
}
}
public void SetupSlime(int _facingDir)
{
if (_facingDir != facingDir)
Flip();
float xVelocity = Random.Range(minCreationVelocity.x, maxCreationVelocity.x);
float yVelocity = Random.Range(minCreationVelocity.y, maxCreationVelocity.y);
isKnocked = true;
GetComponent<Rigidbody2D>().velocity = new Vector2(xVelocity * facingDir, yVelocity);
Invoke("CancelKnockback", 1.5f);
}
private void CancelKnockback() => isKnocked = false;
}
Enemy.cs
using System.Collections;
using UnityEngine;
//获得相同的组件
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(CapsuleCollider2D))]
[RequireComponent(typeof(EnemyStats))]
[RequireComponent(typeof(EntityFX))]
[RequireComponent(typeof(ItemDrop))]
public class Enemy : Entity
{
[SerializeField]protected LayerMask whatIsPlayer;// LayerMask: 这是字段的类型,LayerMask 是一个用于指定物理层的结构体 ; [SerializeField] 属性使得 whatIsPlayer 字段在 Unity 编辑器中可见
[Header("眩晕参数")]//Stunned info
public float stunDuration;
public Vector2 stunDirection;
public bool canBeStunned;
[SerializeField] protected GameObject counterImage;
[Header("移动参数")]//Move info
public float moveSpeed ;
public float idleTime;
public float battleTime;
private float defaultMoveSpeed;
[Header("攻击参数")]//Attack info
public float agroDistance = 2;
public float attackDistance;
public float attackCoolDown;
public float minAttackCoolDown;//最小攻击冷却时间
public float maxAttackCoolDown;
[HideInInspector] public float lastTimeAttack;
public EnemyStateMachine stateMachine { get; private set; }
public EntityFX fx { get; private set; }
private Player player;
public string lastAnimBoolName { get; protected set; }// 存储上一个动画布尔值的名称
protected override void Start()
{
base.Start();
fx = GetComponent<EntityFX>();//使用EntityFX来控制玩家的特效
}
protected override void Awake()
{
base.Awake();
stateMachine = new EnemyStateMachine();
defaultMoveSpeed = moveSpeed;
}
protected override void Update()
{
base.Update();
stateMachine.currentState.Update();
}
public virtual void AssignLastBoolName(string _animBoolName) => lastAnimBoolName = _animBoolName;
public override void SlowEntityBy(float _slowPercentage, float _slowDuration)
{
moveSpeed = moveSpeed * (1 - _slowPercentage);
anim.speed = anim.speed * (1 - _slowPercentage);
Invoke("ReturnDefaultSpeed", _slowDuration);
}
protected override void ReturnDefaultSpeed()
{
base.ReturnDefaultSpeed();
moveSpeed = defaultMoveSpeed;
}
public virtual void FreezeTime(bool _timeFrozen)
{
if (_timeFrozen)
{
moveSpeed = 0;
anim.speed = 0;
}
else
{
moveSpeed = defaultMoveSpeed;
anim.speed = 1;
}
}
public virtual void FreezeTimeFor(float _duration) => StartCoroutine(FreezeTimerCoroutine(_duration));
protected virtual IEnumerator FreezeTimerCoroutine(float _seconds)//IEnumerator 是一个接口,用于支持迭代器的实现。迭代器允许你在集合上进行迭代操作。Unity 中的协程(Coroutine)也使用 IEnumerator 来实现异步操作。
{
FreezeTime(true);
yield return new WaitForSeconds(_seconds);//等待 _seconds 秒
FreezeTime(false);
}
#region Counter Attack Window
public virtual void OpenCounterAttackWindow()// virtual 方法可以在派生类中使用 override 关键字进行重写,以提供不同的实现。
{
canBeStunned = true;
counterImage.SetActive(true);//这行代码将 counterImage 游戏对象设置为激活状态,使其在游戏中可见。
}
public virtual void CloseCounterAttackWindow()
{
canBeStunned = false;
counterImage.SetActive(false);
}
#endregion
public virtual bool CanBeStunned()
{
if (canBeStunned)
{
CloseCounterAttackWindow();
return true;
}
return false;
}
public virtual void AnimationFinishTrigger() => stateMachine.currentState.AnimationFinishTrigger();
public virtual RaycastHit2D IsPlayerDetected() => Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, wallCheckDistance, 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));
}
}
SlimeBattleState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//2024.12.9
public class SlimeBattleState : EnemyState
{
private Enemy_Slime enemy;
private Transform player;
private int moveDir;
public SlimeBattleState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_Slime enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
this.enemy = enemy;
}
public override void Enter()
{
base.Enter();
//player = GameObject.Find("Player").transform;
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)
{
if (CanAttack())
stateMachine.ChangeState(enemy.attackState);
}
}
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 (enemy.IsPlayerDetected() && enemy.IsPlayerDetected().distance < enemy.attackDistance - .1f)
return;
enemy.SetVelocity(enemy.moveSpeed * moveDir, rb.velocity.y);
}
public override void Exit()
{
base.Exit();
}
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;
}
}
标签:enemy,stateMachine,self,Slime,private,史莱姆,void,public
From: https://blog.csdn.net/suzh1qian/article/details/144377607