Files
beyond/Assets/AI/Demon/SA_SpawnTurretSmart.cs
Szymon Miś 02bf8a9d49 Fixes
2025-08-22 11:30:41 +02:00

322 lines
9.5 KiB
C#

using Invector.vCharacterController.AI.FSMBehaviour;
using Lean.Pool;
using UnityEngine;
namespace DemonBoss.Magic
{
/// <summary>
/// StateAction for intelligent crystal turret spawning
/// Searches for optimal position in 2-6m ring from boss, prefers "behind boss" relative to player
/// </summary>
[CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Spawn Turret Smart")]
public class SA_SpawnTurretSmart : vStateAction
{
public override string categoryName => "DemonBoss/Magic";
public override string defaultName => "Spawn Turret Smart";
[Header("Turret Configuration")]
[Tooltip("Crystal prefab with CrystalShooterAI component")]
public GameObject crystalPrefab;
[Tooltip("Minimum distance from boss for crystal spawn")]
public float minSpawnDistance = 2f;
[Tooltip("Maximum distance from boss for crystal spawn")]
public float maxSpawnDistance = 6f;
[Tooltip("Collision check radius when choosing position")]
public float obstacleCheckRadius = 1f;
[Tooltip("Height above ground for raycast ground checking")]
public float groundCheckHeight = 2f;
[Tooltip("Layer mask for obstacles")]
public LayerMask obstacleLayerMask = -1;
[Tooltip("Layer mask for ground")]
public LayerMask groundLayerMask = -1;
[Tooltip("Animator bool parameter name for blocking state")]
public string animatorBlockingBool = "IsBlocking";
[Header("Smart Positioning")]
[Tooltip("Preference multiplier for positions behind boss (relative to player)")]
public float backPreferenceMultiplier = 2f;
[Tooltip("Number of attempts to find valid position")]
public int maxSpawnAttempts = 12;
[Header("Debug")]
[Tooltip("Enable debug logging")]
public bool enableDebug = false;
[Tooltip("Show gizmos in Scene View")]
public bool showGizmos = true;
private GameObject spawnedCrystal;
private Animator npcAnimator;
private Transform npcTransform;
private Transform playerTransform;
/// <summary>
/// Main action execution method called by FSM
/// </summary>
public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
{
if (executionType == vFSMComponentExecutionType.OnStateEnter)
{
OnStateEnter(fsmBehaviour);
}
else if (executionType == vFSMComponentExecutionType.OnStateExit)
{
OnStateExit(fsmBehaviour);
}
}
/// <summary>
/// Called when entering state - intelligently spawns crystal
/// </summary>
private void OnStateEnter(vIFSMBehaviourController fsmBehaviour)
{
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Starting intelligent crystal spawn");
// Store NPC references
npcTransform = fsmBehaviour.transform;
npcAnimator = npcTransform.GetComponent<Animator>();
FindPlayer(fsmBehaviour);
if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool))
{
npcAnimator.SetBool(animatorBlockingBool, true);
if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Set bool: {animatorBlockingBool} = true");
}
SpawnCrystalSmart(fsmBehaviour);
DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Turret", 12f);
}
/// <summary>
/// Called when exiting state - cleanup
/// </summary>
private void OnStateExit(vIFSMBehaviourController fsmBehaviour)
{
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Exiting turret spawn state");
if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool))
{
npcAnimator.SetBool(animatorBlockingBool, false);
if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Set bool: {animatorBlockingBool} = false");
}
}
/// <summary>
/// Finds player transform
/// </summary>
private void FindPlayer(vIFSMBehaviourController fsmBehaviour)
{
GameObject player = GameObject.FindGameObjectWithTag("Player");
if (player != null)
{
playerTransform = player.transform;
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Player found by tag");
return;
}
var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI;
if (aiController != null && aiController.currentTarget != null)
{
playerTransform = aiController.currentTarget.transform;
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Player found through AI target");
return;
}
if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Player not found!");
}
/// <summary>
/// Intelligently spawns crystal in optimal position
/// </summary>
private void SpawnCrystalSmart(vIFSMBehaviourController fsmBehaviour)
{
if (crystalPrefab == null)
{
Debug.LogError("[SA_SpawnTurretSmart] Missing crystalPrefab!");
return;
}
Vector3 bestPosition = Vector3.zero;
bool foundValidPosition = false;
float bestScore = float.MinValue;
Vector3 bossPos = npcTransform.position;
Vector3 playerDirection = Vector3.zero;
if (playerTransform != null)
{
playerDirection = (playerTransform.position - bossPos).normalized;
}
for (int i = 0; i < maxSpawnAttempts; i++)
{
float angle = (360f / maxSpawnAttempts) * i + Random.Range(-15f, 15f);
Vector3 direction = new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0, Mathf.Sin(angle * Mathf.Deg2Rad));
float distance = Random.Range(minSpawnDistance, maxSpawnDistance);
Vector3 testPosition = bossPos + direction * distance;
if (IsPositionValid(testPosition, out Vector3 groundPosition))
{
float score = EvaluatePosition(groundPosition, playerDirection, direction, bossPos);
if (score > bestScore)
{
bestScore = score;
bestPosition = groundPosition;
foundValidPosition = true;
}
}
}
if (foundValidPosition)
{
SpawnCrystal(bestPosition, fsmBehaviour);
if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Crystal spawned at position: {bestPosition} (score: {bestScore:F2})");
}
else
{
Vector3 fallbackPos = bossPos + npcTransform.forward * minSpawnDistance;
SpawnCrystal(fallbackPos, fsmBehaviour);
if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Using fallback position");
}
}
/// <summary>
/// Checks if position is valid (no obstacles, has ground)
/// </summary>
private bool IsPositionValid(Vector3 position, out Vector3 groundPosition)
{
groundPosition = position;
if (Physics.CheckSphere(position + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask))
{
return false;
}
Ray groundRay = new Ray(position + Vector3.up * groundCheckHeight, Vector3.down);
if (Physics.Raycast(groundRay, out RaycastHit hit, groundCheckHeight + 2f, groundLayerMask))
{
groundPosition = hit.point;
if (Physics.CheckSphere(groundPosition + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask))
{
return false;
}
return true;
}
return false;
}
/// <summary>
/// Evaluates position quality (higher score = better position)
/// </summary>
private float EvaluatePosition(Vector3 position, Vector3 playerDirection, Vector3 positionDirection, Vector3 bossPos)
{
float score = 0f;
if (playerTransform != null && playerDirection != Vector3.zero)
{
float angleToPlayer = Vector3.Angle(-playerDirection, positionDirection);
// The smaller the angle (closer to "behind"), the better the score
float backScore = (180f - angleToPlayer) / 180f;
score += backScore * backPreferenceMultiplier;
}
float distance = Vector3.Distance(position, bossPos);
float optimalDistance = (minSpawnDistance + maxSpawnDistance) * 0.5f;
float distanceScore = 1f - Mathf.Abs(distance - optimalDistance) / maxSpawnDistance;
score += distanceScore;
score += Random.Range(-0.1f, 0.1f);
return score;
}
/// <summary>
/// Spawns crystal at given position
/// </summary>
private void SpawnCrystal(Vector3 position, vIFSMBehaviourController fsmBehaviour)
{
Quaternion rotation = Quaternion.identity;
if (playerTransform != null)
{
Vector3 lookDirection = (playerTransform.position - position).normalized;
lookDirection.y = 0;
if (lookDirection != Vector3.zero)
{
rotation = Quaternion.LookRotation(lookDirection);
}
}
spawnedCrystal = LeanPool.Spawn(crystalPrefab, position, rotation);
var shooterAI = spawnedCrystal.GetComponent<CrystalShooterAI>();
if (shooterAI == null)
{
Debug.LogError("[SA_SpawnTurretSmart] Crystal prefab doesn't have CrystalShooterAI component!");
}
else
{
if (playerTransform != null)
{
shooterAI.SetTarget(playerTransform);
}
}
}
/// <summary>
/// Draws gizmos in Scene View for debugging
/// </summary>
private void OnDrawGizmosSelected()
{
if (!showGizmos) return;
if (npcTransform != null)
{
Vector3 pos = npcTransform.position;
// Spawn ring
Gizmos.color = Color.green;
DrawWireCircle(pos, minSpawnDistance);
Gizmos.color = Color.red;
DrawWireCircle(pos, maxSpawnDistance);
// Obstacle check radius
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(pos + Vector3.up * obstacleCheckRadius, obstacleCheckRadius);
}
}
/// <summary>
/// Helper method for drawing circles
/// </summary>
private void DrawWireCircle(Vector3 center, float radius)
{
int segments = 32;
float angle = 0f;
Vector3 prevPoint = center + new Vector3(radius, 0, 0);
for (int i = 1; i <= segments; i++)
{
angle = (float)i / segments * 360f * Mathf.Deg2Rad;
Vector3 newPoint = center + new Vector3(Mathf.Cos(angle) * radius, 0, Mathf.Sin(angle) * radius);
Gizmos.DrawLine(prevPoint, newPoint);
prevPoint = newPoint;
}
}
}
}