Demon Fixes
This commit is contained in:
@@ -1,15 +1,12 @@
|
||||
using Invector.vCharacterController.AI.FSMBehaviour;
|
||||
using Lean.Pool;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DemonBoss.Magic
|
||||
{
|
||||
/// <summary>
|
||||
/// FSM Action: Boss calls down a meteor.
|
||||
/// Shows a decal at the player's position, locks an impact point on ground,
|
||||
/// then spawns the MeteorProjectile prefab above that point after a delay.
|
||||
/// Cancels cleanly on state exit.
|
||||
/// Spawns a meteor behind the BOSS and launches it toward the player's position
|
||||
/// Similar mechanics to FireballProjectile but coming from above
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Call Meteor")]
|
||||
public class SA_CallMeteor : vStateAction
|
||||
@@ -21,124 +18,88 @@ namespace DemonBoss.Magic
|
||||
[Tooltip("Prefab with MeteorProjectile component")]
|
||||
public GameObject meteorPrefab;
|
||||
|
||||
[Tooltip("Visual decal prefab marking impact point")]
|
||||
public GameObject decalPrefab;
|
||||
[Tooltip("Distance behind the BOSS to spawn meteor (meters)")]
|
||||
public float behindBossDistance = 3f;
|
||||
|
||||
[Tooltip("Height above ground at which meteor spawns")]
|
||||
public float spawnHeight = 40f;
|
||||
[Tooltip("Height above the BOSS to spawn meteor (meters)")]
|
||||
public float aboveBossHeight = 8f;
|
||||
|
||||
[Tooltip("Delay before meteor spawns after decal")]
|
||||
public float castDelay = 1.5f;
|
||||
[Tooltip("Delay before meteor spawns (wind-up)")]
|
||||
public float castDelay = 0.4f;
|
||||
|
||||
[Header("Ground")]
|
||||
[Tooltip("Layer mask for ground raycast")]
|
||||
public LayerMask groundMask = -1;
|
||||
[Header("Targeting")]
|
||||
[Tooltip("Tag used to find the target (usually Player)")]
|
||||
public string targetTag = "Player";
|
||||
|
||||
[Header("Debug")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
private Transform player;
|
||||
private GameObject spawnedDecal;
|
||||
private Vector3 impactPoint;
|
||||
private Transform _boss;
|
||||
private Transform _target;
|
||||
|
||||
private Coroutine _spawnRoutine;
|
||||
private CoroutineRunner _runner;
|
||||
|
||||
/// <summary>
|
||||
/// Entry point for the FSM action, delegates to OnEnter/OnExit depending on execution type.
|
||||
/// </summary>
|
||||
public override void DoAction(vIFSMBehaviourController fsm, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
|
||||
public override void DoAction(vIFSMBehaviourController fsm, vFSMComponentExecutionType execType = vFSMComponentExecutionType.OnStateUpdate)
|
||||
{
|
||||
if (executionType == vFSMComponentExecutionType.OnStateEnter)
|
||||
if (execType == vFSMComponentExecutionType.OnStateEnter)
|
||||
{
|
||||
OnEnter(fsm);
|
||||
|
||||
if (executionType == vFSMComponentExecutionType.OnStateExit)
|
||||
OnExit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires the player, locks the impact point on the ground, shows the decal,
|
||||
/// and starts the delayed meteor spawn coroutine.
|
||||
/// </summary>
|
||||
private void OnEnter(vIFSMBehaviourController fsm)
|
||||
{
|
||||
player = GameObject.FindGameObjectWithTag("Player")?.transform;
|
||||
if (player == null)
|
||||
_boss = fsm.transform;
|
||||
_target = GameObject.FindGameObjectWithTag(targetTag)?.transform;
|
||||
|
||||
if (_target == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No player found!");
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No target found – abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// Raycast down from the player to lock the impact point
|
||||
Vector3 rayStart = player.position + Vector3.up * 5f;
|
||||
if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, 100f, groundMask, QueryTriggerInteraction.Ignore))
|
||||
impactPoint = hit.point;
|
||||
else
|
||||
impactPoint = player.position;
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Boss: {_boss.name}, Target: {_target.name}");
|
||||
|
||||
// Spawn decal
|
||||
if (decalPrefab != null)
|
||||
spawnedDecal = LeanPool.Spawn(decalPrefab, impactPoint, Quaternion.identity);
|
||||
|
||||
// Get or add a dedicated runner for coroutines
|
||||
var hostGO = fsm.transform.gameObject;
|
||||
_runner = hostGO.GetComponent<CoroutineRunner>();
|
||||
if (_runner == null) _runner = hostGO.AddComponent<CoroutineRunner>();
|
||||
|
||||
// Start delayed spawn
|
||||
_spawnRoutine = _runner.StartCoroutine(SpawnMeteorAfterDelay());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the pending spawn and cleans up the decal when exiting the state.
|
||||
/// </summary>
|
||||
private void OnExit()
|
||||
{
|
||||
if (_runner != null && _spawnRoutine != null)
|
||||
{
|
||||
_runner.StopCoroutine(_spawnRoutine);
|
||||
_spawnRoutine = null;
|
||||
}
|
||||
|
||||
if (spawnedDecal != null)
|
||||
{
|
||||
LeanPool.Despawn(spawnedDecal);
|
||||
spawnedDecal = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the configured cast delay and then spawns the meteor.
|
||||
/// </summary>
|
||||
private IEnumerator SpawnMeteorAfterDelay()
|
||||
{
|
||||
yield return new WaitForSeconds(castDelay);
|
||||
SpawnMeteor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the meteor prefab above the locked impact point and cleans up the decal.
|
||||
/// </summary>
|
||||
private void SpawnMeteor()
|
||||
{
|
||||
if (meteorPrefab == null) return;
|
||||
|
||||
Vector3 spawnPos = impactPoint + Vector3.up * spawnHeight;
|
||||
LeanPool.Spawn(meteorPrefab, spawnPos, Quaternion.identity);
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor spawned at {spawnPos}, impact={impactPoint}");
|
||||
|
||||
if (spawnedDecal != null)
|
||||
if (meteorPrefab == null)
|
||||
{
|
||||
LeanPool.Despawn(spawnedDecal);
|
||||
spawnedDecal = null;
|
||||
if (enableDebug) Debug.LogError("[SA_CallMeteor] Missing meteorPrefab");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_boss == null || _target == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogError("[SA_CallMeteor] Missing boss or target reference");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate spawn position: behind the BOSS + height
|
||||
Vector3 bossForward = _boss.forward.normalized;
|
||||
Vector3 behindBoss = _boss.position - (bossForward * behindBossDistance);
|
||||
Vector3 spawnPos = behindBoss + Vector3.up * aboveBossHeight;
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Spawning meteor at: {spawnPos}");
|
||||
|
||||
// Spawn the meteor
|
||||
var meteorGO = LeanPool.Spawn(meteorPrefab, spawnPos, Quaternion.identity);
|
||||
|
||||
// Configure the projectile to target the player
|
||||
var meteorScript = meteorGO.GetComponent<MeteorProjectile>();
|
||||
if (meteorScript != null)
|
||||
{
|
||||
// Set it to target the player's current position
|
||||
meteorScript.useOverrideImpactPoint = true;
|
||||
meteorScript.overrideImpactPoint = _target.position;
|
||||
meteorScript.snapImpactToGround = true;
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor configured to target: {_target.position}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enableDebug) Debug.LogError("[SA_CallMeteor] Meteor prefab missing MeteorProjectile component!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight helper component dedicated to running coroutines for ScriptableObject actions.
|
||||
/// </summary>
|
||||
public sealed class CoroutineRunner : MonoBehaviour
|
||||
{ }
|
||||
}
|
||||
Reference in New Issue
Block a user