教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/
完结撒花!!!!!!
本章节制作了精英怪物死亡领主,拥有高额的伤害,和传送技能,召唤死亡触手技能
攻击有概率触发传送,之后召唤触手
Enemy_DeathBringer.cs
1. 传送功能
- 目的:让 Boss 在指定范围内进行随机传送。
- 逻辑:
- 在传送区域 (
arena
) 内随机生成一个位置。 - 检查该位置是否满足:
- 在地面上(通过射线检测
GroundBelow()
)。 - 周围没有玩家或障碍物(
SomethingIsAround()
)。
- 在地面上(通过射线检测
- 如果位置不合适,递归重新查找。
- 在传送区域 (
- 传送概率控制:
- 使用
CanTeleport()
函数判断是否可以传送,基于随机概率决定。 - 成功传送后,重置传送概率为默认值。
- 使用
2. 施法功能
- 目的:生成施法技能攻击玩家。
- 逻辑:
- 获取玩家当前位置,并计算偏移量。
- 在玩家位置附近生成施法技能(
spellPrefab
)。 - 通过冷却时间控制施法频率,使用
CanDoSpellCast()
函数判断是否可以施法。
using System.Collections;
using UnityEngine;
//2024.12.16
//完结撒花
public class Enemy_DeathBringer : Enemy
{
public bool bossFightBegun;
[Header("施法信息")]//Spell cast details
[SerializeField] private GameObject spellPrefab;
public int amountOfSpells;
public float spellCooldown;
public float lastTimeCast;
[SerializeField] private float spellStateCooldown;
[SerializeField] private Vector2 spellOffset;
[Header("传送信息")]//Teleport details
[SerializeField] private BoxCollider2D arena;
[SerializeField] private Vector2 surroundingCheckSize;
public float chanceToteleport;
public float defaultChanceToTeleport =25 ;
#region States
public DeathBringerBattleState battleState { get; private set; }
public DeathBringerAttackState attackState { get; private set; }
public DeathBringerIdleState idleState { get; private set; }
public DeathBringerDeadState deadState { get; private set; }
public DeathBringerSpellCastState spellCastState { get; private set; }
public DeathBringerTeleportState teleportState { get; private set; }
#endregion
protected override void Awake()
{
base.Awake();
SetupDefaultFacingDir(-1);
idleState = new DeathBringerIdleState(this, stateMachine, "Idle", this);
battleState = new DeathBringerBattleState(this, stateMachine, "Move", this);
attackState = new DeathBringerAttackState(this, stateMachine, "Attack", this);
deadState = new DeathBringerDeadState(this, stateMachine, "Idle", this);
spellCastState = new DeathBringerSpellCastState(this, stateMachine, "SpellCast", this);
teleportState = new DeathBringerTeleportState(this, stateMachine, "Teleport", this);
}
protected override void Start()
{
base.Start();
stateMachine.Initialize(idleState);
}
public override void Die()
{
base.Die();
stateMachine.ChangeState(deadState);
}
public void CastSpell()
{
Player player = PlayerManager.instance.player;
float xOffset = 0;
if(player.rb.velocity.x!= 0)
xOffset = player.facingDir * spellOffset.x;
Vector3 spellPostion = new Vector3(player.transform.position.x + player.facingDir * 2, player.transform.position.y + spellOffset.y);
GameObject newSpell = Instantiate(spellPrefab ,spellPostion,Quaternion.identity);
newSpell.GetComponent<DeathBringerSpell_Controller>().SetupSpell(stats);
}
public void FindPosition()
{
float x = Random.Range(arena.bounds.min.x + 3, arena.bounds.max.x - 3);
float y = Random.Range(arena.bounds.min.y + 3, arena.bounds.max.y - 3);
transform.position = new Vector3(x, y);
transform.position = new Vector3(transform.position.x, transform.position.y - GroungdBelow().distance + (cd.size.y / 2));
if(!GroungdBelow() || SomethingIsAround())
{
Debug.Log("寻找新的位置");
FindPosition();
}
}
private RaycastHit2D GroungdBelow() => Physics2D.Raycast(transform.position, Vector2.down, 100, whatIsGround);
private bool SomethingIsAround() => Physics2D.BoxCast(transform.position, surroundingCheckSize, 0, Vector2.zero, 0, whatIsPlayer);
protected override void OnDrawGizmos()
{
base.OnDrawGizmos();
Gizmos.DrawWireCube(transform.position, surroundingCheckSize);
}
public bool CanTeleport()
{
if (Random.Range(0, 100) <= chanceToteleport)
{
chanceToteleport = defaultChanceToTeleport;
return true;
}
return false;
}
public bool CanDoSpellCast()
{
if (Time.time >= lastTimeCast + spellStateCooldown)
{
return true;
}
else
return false;
}
}
DeathBringerSpell_Controller.cs
核心功能
1. 施法技能碰撞检测
- 使用
Physics2D.OverlapBoxAll()
方法,基于一个 指定区域(check.position
和boxSize
)检测是否有玩家碰到技能。 - 检测的对象由
whatIsPlayer
层级掩码指定,确保只对玩家进行碰撞检测。
2. 施加伤害和击退效果
- 遍历检测到的碰撞对象,如果对象包含
CharacterStats
组件:- 调用
Entity.SetupKnockbackDir()
,设置击退方向。 - 使用
myStats.DoDamage()
对玩家施加伤害。
- 调用
3. 自我销毁
- 当动画播放到特定帧时,通过
SelfDestory()
函数销毁技能对象,避免资源浪费。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DeathBringerSpell_Controller : MonoBehaviour
{
[SerializeField] Transform check;
[SerializeField] Vector2 boxSize;
[SerializeField] private LayerMask whatIsPlayer;
private CharacterStats myStats;
public void SetupSpell(CharacterStats _stats)=> myStats = _stats;
private void AnimationTrigger()
{
Collider2D[] colliders = Physics2D.OverlapBoxAll(check.position,boxSize,whatIsPlayer);
foreach (var hit in colliders)
{
if (hit.GetComponent<CharacterStats>() != null)
{
hit.GetComponent<Entity>().SetupKnockbackDir(transform);
myStats.DoDamage(hit.GetComponent<CharacterStats>());
}
}
}
private void OnDrawGizmos()=> Gizmos.DrawWireCube(check.position, boxSize);
private void SelfDestory() => Destroy(gameObject);
}
DeathBringerAttackState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DeathBringerAttackState : EnemyState
{
private Enemy_DeathBringer enemy;
public DeathBringerAttackState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
enemy.chanceToteleport += 5;//攻击增加传送概率
}
public override void Exit()
{
base.Exit();
enemy.lastTimeAttack = Time.time;
}
public override void Update()
{
base.Update();
enemy.SetZeroVelocity();
if (triggerCalled)
{
if (enemy.CanTeleport())
stateMachine.ChangeState(enemy.teleportState);
else
stateMachine.ChangeState(enemy.battleState);
}
}
}
DeathBringerBattleState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DeathBringerBattleState : EnemyState
{
private Enemy_DeathBringer enemy;
private Transform player;
private int moveDir;
public DeathBringerBattleState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
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
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;
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();
}
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;
}
}
DeathBringerSpellCastState.cs
using System.Collections;
using UnityEngine;
public class DeathBringerSpellCastState : EnemyState
{
private Enemy_DeathBringer enemy;
private int amountOfSpells;
private float spellTimer;
public DeathBringerSpellCastState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
amountOfSpells = enemy.amountOfSpells;
spellTimer = .5f;
}
public override void Update()
{
base.Update();
spellTimer -= Time.deltaTime;
if(CanCast())
enemy.CastSpell();
if (amountOfSpells <= 0)
stateMachine.ChangeState(enemy.teleportState);
}
public override void Exit()
{
base.Exit();
enemy.lastTimeCast = Time.time;
}
private bool CanCast()
{
if(amountOfSpells > 0 && spellTimer < 0)
{
amountOfSpells--;
spellTimer = enemy.spellCooldown;
return true;
}
return false;
}
}
DeathBringerIdleState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DeathBringerIdleState : EnemyState
{
private Enemy_DeathBringer enemy;
private Transform player;
public DeathBringerIdleState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
stateTimer = enemy.idleTime;
player = PlayerManager.instance.player.transform;
}
public override void Exit()
{
base.Exit();
AudioManager.instance.PlaySFX(24, enemy.transform);
}
public override void Update()
{
base.Update();
if( Vector2.Distance(player.transform.position, enemy.transform.position) < 7)
enemy.bossFightBegun = true;
if (Input.GetKeyDown(KeyCode.V))
stateMachine.ChangeState(enemy.teleportState);
if(stateTimer < 0 && enemy.bossFightBegun)
stateMachine.ChangeState(enemy.battleState);
}
}
Enemy_DeathBringerTrigger.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy_DeathBringerTrigger : Enemy_AnimationTriggers
{
private Enemy_DeathBringer enemydeathBringer => GetComponentInParent<Enemy_DeathBringer>();
private void Relocate() => enemydeathBringer.FindPosition();
private void MakeInvisivle() => enemydeathBringer.fx.MakeTransprent(true);
private void MakeVisivle() => enemydeathBringer.fx.MakeTransprent(false);
}
DeathBringerTeleportState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DeathBringerTeleportState : EnemyState
{
private Enemy_DeathBringer enemy;
public DeathBringerTeleportState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
enemy.stats.MakeInvincible(true);
}
public override void Update()
{
base.Update();
if(triggerCalled)
{
if(enemy.CanDoSpellCast())
stateMachine.ChangeState(enemy.spellCastState);
else
stateMachine.ChangeState(enemy.battleState);
}
}
public override void Exit()
{
base.Exit();
enemy.stats.MakeInvincible(false);
}
}
DeathBringerDeadState.cs
using UnityEngine;
public class DeathBringerDeadState : EnemyState
{
private Enemy_DeathBringer enemy;
public DeathBringerDeadState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
{
this.enemy = _enemy;
}
public override void Enter()
{
base.Enter();
enemy.anim.SetBool(enemy.lastAnimBoolName, true);
enemy.anim.speed = 0;
enemy.cd.enabled = false;//禁止启动碰撞器,死后升天然后掉到地下
stateTimer = .18f;//死的时候升天的时间
}
public override void Update()
{
base.Update();
if (stateTimer > 0)
rb.velocity = new Vector2(0, 10);//死后升天
}
}