Files cleanup, added summoner code
This commit is contained in:
105
Assets/AI/_Summoner/DEC_CanSpawnMinions.cs
Normal file
105
Assets/AI/_Summoner/DEC_CanSpawnMinions.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Invector.vCharacterController.AI.FSMBehaviour;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DemonBoss.Summoner
|
||||
{
|
||||
/// <summary>
|
||||
/// FSM Decision checking if summoner can spawn minions
|
||||
/// Checks: not already spawning, minion count below max, cooldown passed
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Invector/FSM/Decisions/Summoner/Can Spawn Minions")]
|
||||
public class DEC_CanSpawnMinions : vStateDecision
|
||||
{
|
||||
public override string categoryName => "Summoner";
|
||||
public override string defaultName => "Can Spawn Minions";
|
||||
|
||||
[Header("Spawn Conditions")]
|
||||
[Tooltip("Check if health is below threshold")]
|
||||
public bool checkHealthThreshold = true;
|
||||
|
||||
[Tooltip("Check if minions are below max count")]
|
||||
public bool checkMinionCount = true;
|
||||
|
||||
[Tooltip("Check if cooldown has passed")]
|
||||
public bool checkCooldown = true;
|
||||
|
||||
[Tooltip("Cooldown between summon attempts (seconds)")]
|
||||
public float cooldownTime = 15f;
|
||||
|
||||
[Header("Distance Check")]
|
||||
[Tooltip("Only spawn if player is within this distance (0 = disabled)")]
|
||||
public float maxDistanceToPlayer = 0f;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("Enable debug logging")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
private string cooldownKey = "SummonMinions";
|
||||
|
||||
public override bool Decide(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
var summoner = fsmBehaviour.GetComponent<SummonerAI>();
|
||||
if (summoner == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[DEC_CanSpawnMinions] No SummonerAI component found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if already spawning
|
||||
if (summoner.IsSpawning)
|
||||
{
|
||||
if (enableDebug) Debug.Log("[DEC_CanSpawnMinions] Already spawning - FALSE");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check minion count
|
||||
if (checkMinionCount && !summoner.CanSpawnMinions)
|
||||
{
|
||||
if (enableDebug) Debug.Log($"[DEC_CanSpawnMinions] Max minions reached ({summoner.ActiveMinionCount}) - FALSE");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check health threshold
|
||||
if (checkHealthThreshold && !summoner.ShouldSummonByHealth())
|
||||
{
|
||||
if (enableDebug) Debug.Log("[DEC_CanSpawnMinions] Health threshold not met - FALSE");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check distance to player
|
||||
if (maxDistanceToPlayer > 0f)
|
||||
{
|
||||
float distance = summoner.GetDistanceToPlayer();
|
||||
if (distance > maxDistanceToPlayer)
|
||||
{
|
||||
if (enableDebug) Debug.Log($"[DEC_CanSpawnMinions] Player too far ({distance:F1}m) - FALSE");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check cooldown
|
||||
if (checkCooldown)
|
||||
{
|
||||
string timerKey = "cooldown_" + cooldownKey;
|
||||
|
||||
if (fsmBehaviour.HasTimer(timerKey))
|
||||
{
|
||||
float lastUsedTime = fsmBehaviour.GetTimer(timerKey);
|
||||
float timeSinceLastUse = Time.time - lastUsedTime;
|
||||
|
||||
if (timeSinceLastUse < cooldownTime)
|
||||
{
|
||||
if (enableDebug) Debug.Log($"[DEC_CanSpawnMinions] On cooldown - {cooldownTime - timeSinceLastUse:F1}s remaining - FALSE");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set cooldown for next use
|
||||
fsmBehaviour.SetTimer(timerKey, Time.time);
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log("[DEC_CanSpawnMinions] All conditions met - TRUE");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/AI/_Summoner/DEC_CanSpawnMinions.cs.meta
Normal file
2
Assets/AI/_Summoner/DEC_CanSpawnMinions.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6932f86375a52954785f89df47a2d61e
|
||||
16
Assets/AI/_Summoner/DEC_CheckDistanceToPlayer.cs
Normal file
16
Assets/AI/_Summoner/DEC_CheckDistanceToPlayer.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class DEC_CheckDistanceToPlayer : MonoBehaviour
|
||||
{
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/AI/_Summoner/DEC_CheckDistanceToPlayer.cs.meta
Normal file
2
Assets/AI/_Summoner/DEC_CheckDistanceToPlayer.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40326c8e0d34db64d9aec176f7d5bc97
|
||||
16
Assets/AI/_Summoner/DEC_HasActiveMinions.cs
Normal file
16
Assets/AI/_Summoner/DEC_HasActiveMinions.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class DEC_HasActiveMinions : MonoBehaviour
|
||||
{
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/AI/_Summoner/DEC_HasActiveMinions.cs.meta
Normal file
2
Assets/AI/_Summoner/DEC_HasActiveMinions.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a537cd142b23fa4b8285f3d22cda409
|
||||
74
Assets/AI/_Summoner/DEC_ShouldMeleeAttack.cs
Normal file
74
Assets/AI/_Summoner/DEC_ShouldMeleeAttack.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Invector.vCharacterController.AI.FSMBehaviour;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DemonBoss.Summoner
|
||||
{
|
||||
/// <summary>
|
||||
/// FSM Decision checking if summoner should engage in melee combat
|
||||
/// Checks: player distance, minion status, combat settings
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Invector/FSM/Decisions/Summoner/Should Melee Attack")]
|
||||
public class DEC_ShouldMeleeAttack : vStateDecision
|
||||
{
|
||||
public override string categoryName => "Summoner";
|
||||
public override string defaultName => "Should Melee Attack";
|
||||
|
||||
[Header("Distance Configuration")]
|
||||
[Tooltip("Minimum distance to engage melee")]
|
||||
public float minMeleeDistance = 0f;
|
||||
|
||||
[Tooltip("Maximum distance to engage melee")]
|
||||
public float maxMeleeDistance = 3f;
|
||||
|
||||
[Header("Behavior")]
|
||||
[Tooltip("Attack even when minions are alive")]
|
||||
public bool attackWithMinions = false;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("Enable debug logging")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
public override bool Decide(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
var summoner = fsmBehaviour.gameObject.GetComponent<SummonerAI>();
|
||||
if (summoner == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[DEC_ShouldMeleeAttack] No SummonerAI component found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't attack while spawning
|
||||
if (summoner.IsSpawning)
|
||||
{
|
||||
if (enableDebug) Debug.Log("[DEC_ShouldMeleeAttack] Currently spawning - FALSE");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if has minions and shouldn't attack with them
|
||||
if (!attackWithMinions && summoner.HasActiveMinions)
|
||||
{
|
||||
if (enableDebug) Debug.Log($"[DEC_ShouldMeleeAttack] Has {summoner.ActiveMinionCount} minions and attackWithMinions=false - FALSE");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check distance to player
|
||||
float distance = summoner.GetDistanceToPlayer();
|
||||
|
||||
bool inRange = distance >= minMeleeDistance && distance <= maxMeleeDistance;
|
||||
|
||||
if (enableDebug)
|
||||
{
|
||||
if (inRange)
|
||||
{
|
||||
Debug.Log($"[DEC_ShouldMeleeAttack] Player in melee range ({distance:F1}m) - TRUE");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[DEC_ShouldMeleeAttack] Player not in melee range ({distance:F1}m, need {minMeleeDistance:F1}-{maxMeleeDistance:F1}m) - FALSE");
|
||||
}
|
||||
}
|
||||
|
||||
return inRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/AI/_Summoner/DEC_ShouldMeleeAttack.cs.meta
Normal file
2
Assets/AI/_Summoner/DEC_ShouldMeleeAttack.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8abf79055070ed47a11a57e0cc5aea3
|
||||
125
Assets/AI/_Summoner/SA_SpawnMinions.cs
Normal file
125
Assets/AI/_Summoner/SA_SpawnMinions.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Invector.vCharacterController.AI.FSMBehaviour;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DemonBoss.Summoner
|
||||
{
|
||||
/// <summary>
|
||||
/// FSM State Action that triggers minion spawning
|
||||
/// Calls SummonerAI.StartSpawning() on state enter
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Invector/FSM/Actions/Summoner/Spawn Minions")]
|
||||
public class SA_SpawnMinions : vStateAction
|
||||
{
|
||||
public override string categoryName => "Summoner";
|
||||
public override string defaultName => "Spawn Minions";
|
||||
|
||||
[Header("Animation")]
|
||||
[Tooltip("Animator trigger parameter for summoning animation")]
|
||||
public string summonTriggerName = "Summon";
|
||||
|
||||
[Tooltip("Animator bool for summoning state")]
|
||||
public string summoningBoolName = "IsSummoning";
|
||||
|
||||
[Header("Behavior")]
|
||||
[Tooltip("Wait for spawning to complete before allowing state exit")]
|
||||
public bool waitForCompletion = true;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("Enable debug logging")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
private SummonerAI summoner;
|
||||
private Animator animator;
|
||||
private bool hasStartedSpawning = false;
|
||||
|
||||
public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
|
||||
{
|
||||
if (executionType == vFSMComponentExecutionType.OnStateEnter)
|
||||
{
|
||||
OnEnter(fsmBehaviour);
|
||||
}
|
||||
else if (executionType == vFSMComponentExecutionType.OnStateUpdate)
|
||||
{
|
||||
OnUpdate(fsmBehaviour);
|
||||
}
|
||||
else if (executionType == vFSMComponentExecutionType.OnStateExit)
|
||||
{
|
||||
OnExit(fsmBehaviour);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnter(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
summoner = fsmBehaviour.GetComponent<SummonerAI>();
|
||||
animator = fsmBehaviour.GetComponent<Animator>();
|
||||
|
||||
if (summoner == null)
|
||||
{
|
||||
Debug.LogError("[SA_SpawnMinions] No SummonerAI component found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger animation
|
||||
if (animator != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(summonTriggerName))
|
||||
{
|
||||
animator.SetTrigger(summonTriggerName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(summoningBoolName))
|
||||
{
|
||||
animator.SetBool(summoningBoolName, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Start spawning minions
|
||||
summoner.StartSpawning();
|
||||
hasStartedSpawning = true;
|
||||
|
||||
if (enableDebug) Debug.Log("[SA_SpawnMinions] Started spawning minions");
|
||||
}
|
||||
|
||||
private void OnUpdate(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
// If waiting for completion, keep state active until spawning is done
|
||||
if (waitForCompletion && summoner != null && summoner.IsSpawning)
|
||||
{
|
||||
// State will continue until spawning is complete
|
||||
if (enableDebug && Time.frameCount % 60 == 0) // Log once per second
|
||||
{
|
||||
Debug.Log("[SA_SpawnMinions] Waiting for spawning to complete...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExit(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
// Reset animation bool
|
||||
if (animator != null && !string.IsNullOrEmpty(summoningBoolName))
|
||||
{
|
||||
animator.SetBool(summoningBoolName, false);
|
||||
}
|
||||
|
||||
// If spawning was interrupted, stop it
|
||||
if (summoner != null && summoner.IsSpawning)
|
||||
{
|
||||
summoner.StopSpawning();
|
||||
if (enableDebug) Debug.Log("[SA_SpawnMinions] Spawning interrupted on state exit");
|
||||
}
|
||||
|
||||
hasStartedSpawning = false;
|
||||
|
||||
if (enableDebug) Debug.Log("[SA_SpawnMinions] State exited");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if spawning is complete (for FSM decision nodes)
|
||||
/// </summary>
|
||||
public bool IsSpawningComplete()
|
||||
{
|
||||
if (summoner == null) return true;
|
||||
return hasStartedSpawning && !summoner.IsSpawning;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/AI/_Summoner/SA_SpawnMinions.cs.meta
Normal file
2
Assets/AI/_Summoner/SA_SpawnMinions.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad57378f916739249891d117d50e333b
|
||||
359
Assets/AI/_Summoner/SummonerAI.cs
Normal file
359
Assets/AI/_Summoner/SummonerAI.cs
Normal file
@@ -0,0 +1,359 @@
|
||||
using Invector;
|
||||
using Invector.vCharacterController.AI;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DemonBoss.Summoner
|
||||
{
|
||||
/// <summary>
|
||||
/// Main AI controller for Summoner enemy
|
||||
/// Manages spawned minions and provides combat state tracking
|
||||
/// Attach to Summoner character along with vControlAI
|
||||
/// </summary>
|
||||
public class SummonerAI : MonoBehaviour
|
||||
{
|
||||
[Header("Minion Management")]
|
||||
[Tooltip("Prefab of minion to spawn")]
|
||||
public GameObject minionPrefab;
|
||||
|
||||
[Tooltip("Maximum number of minions alive at once")]
|
||||
public int maxActiveMinions = 3;
|
||||
|
||||
[Tooltip("Distance from summoner to spawn minions")]
|
||||
public float spawnRadius = 5f;
|
||||
|
||||
[Tooltip("Height offset for spawn position")]
|
||||
public float spawnHeightOffset = 0f;
|
||||
|
||||
[Tooltip("Should minions look at summoner after spawn?")]
|
||||
public bool minionsLookAtCenter = false;
|
||||
|
||||
[Header("Spawn Configuration")]
|
||||
[Tooltip("Number of minions to spawn per summon action")]
|
||||
public int minionsPerSummon = 3;
|
||||
|
||||
[Tooltip("Delay before first minion spawns")]
|
||||
public float initialSpawnDelay = 0.5f;
|
||||
|
||||
[Tooltip("Time between spawning each minion")]
|
||||
public float timeBetweenSpawns = 0.3f;
|
||||
|
||||
[Header("Combat Behavior")]
|
||||
[Tooltip("Minimum distance to player before engaging in melee")]
|
||||
public float meleeEngageDistance = 3f;
|
||||
|
||||
[Tooltip("Should summoner fight when minions are alive?")]
|
||||
public bool fightWithMinions = false;
|
||||
|
||||
[Tooltip("Health percentage threshold to spawn minions (0-1)")]
|
||||
[Range(0f, 1f)]
|
||||
public float healthThresholdForSummon = 0.7f;
|
||||
|
||||
[Header("Targeting")]
|
||||
[Tooltip("Tag to find player")]
|
||||
public string playerTag = "Player";
|
||||
|
||||
[Header("Effects")]
|
||||
[Tooltip("Particle effect at spawn location")]
|
||||
public GameObject spawnEffectPrefab;
|
||||
|
||||
[Tooltip("Sound played when spawning minions")]
|
||||
public AudioClip summonSound;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("Enable debug logging")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
[Tooltip("Show gizmos in Scene View")]
|
||||
public bool showGizmos = true;
|
||||
|
||||
// Runtime state
|
||||
private List<GameObject> activeMinions = new List<GameObject>();
|
||||
|
||||
private Transform playerTransform;
|
||||
private AudioSource audioSource;
|
||||
private vHealthController healthController;
|
||||
private bool isSpawning = false;
|
||||
private Coroutine spawnCoroutine;
|
||||
|
||||
// Public properties for FSM decisions
|
||||
public bool IsSpawning => isSpawning;
|
||||
|
||||
public int ActiveMinionCount => activeMinions.Count;
|
||||
public bool CanSpawnMinions => activeMinions.Count < maxActiveMinions && !isSpawning;
|
||||
public bool HasActiveMinions => activeMinions.Count > 0;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
healthController = GetComponent<vHealthController>();
|
||||
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
if (audioSource == null && summonSound != null)
|
||||
{
|
||||
audioSource = gameObject.AddComponent<AudioSource>();
|
||||
audioSource.playOnAwake = false;
|
||||
audioSource.spatialBlend = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
FindPlayer();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
CleanupDeadMinions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find player by tag
|
||||
/// </summary>
|
||||
private void FindPlayer()
|
||||
{
|
||||
GameObject player = GameObject.FindGameObjectWithTag(playerTag);
|
||||
if (player != null)
|
||||
{
|
||||
playerTransform = player.transform;
|
||||
if (enableDebug) Debug.Log("[SummonerAI] Player found: " + player.name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start spawning minions
|
||||
/// </summary>
|
||||
public void StartSpawning()
|
||||
{
|
||||
if (isSpawning)
|
||||
{
|
||||
if (enableDebug) Debug.Log("[SummonerAI] Already spawning minions");
|
||||
return;
|
||||
}
|
||||
|
||||
if (minionPrefab == null)
|
||||
{
|
||||
Debug.LogError("[SummonerAI] No minion prefab assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
spawnCoroutine = StartCoroutine(SpawnMinionsCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop spawning minions immediately
|
||||
/// </summary>
|
||||
public void StopSpawning()
|
||||
{
|
||||
if (spawnCoroutine != null)
|
||||
{
|
||||
StopCoroutine(spawnCoroutine);
|
||||
spawnCoroutine = null;
|
||||
}
|
||||
isSpawning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that spawns minions with delays
|
||||
/// </summary>
|
||||
private IEnumerator SpawnMinionsCoroutine()
|
||||
{
|
||||
isSpawning = true;
|
||||
|
||||
if (enableDebug) Debug.Log($"[SummonerAI] Starting to spawn {minionsPerSummon} minions");
|
||||
|
||||
// Initial delay
|
||||
yield return new WaitForSeconds(initialSpawnDelay);
|
||||
|
||||
// Play summon sound
|
||||
if (audioSource != null && summonSound != null)
|
||||
{
|
||||
audioSource.PlayOneShot(summonSound);
|
||||
}
|
||||
|
||||
// Spawn minions
|
||||
int spawned = 0;
|
||||
for (int i = 0; i < minionsPerSummon && activeMinions.Count < maxActiveMinions; i++)
|
||||
{
|
||||
SpawnSingleMinion();
|
||||
spawned++;
|
||||
|
||||
// Wait between spawns (except after last one)
|
||||
if (i < minionsPerSummon - 1)
|
||||
{
|
||||
yield return new WaitForSeconds(timeBetweenSpawns);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[SummonerAI] Finished spawning {spawned} minions. Total active: {activeMinions.Count}");
|
||||
|
||||
isSpawning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn a single minion at random position around summoner
|
||||
/// </summary>
|
||||
private void SpawnSingleMinion()
|
||||
{
|
||||
// Calculate random spawn position
|
||||
float angle = Random.Range(0f, 360f) * Mathf.Deg2Rad;
|
||||
float x = transform.position.x + spawnRadius * Mathf.Cos(angle);
|
||||
float z = transform.position.z + spawnRadius * Mathf.Sin(angle);
|
||||
Vector3 spawnPosition = new Vector3(x, transform.position.y + spawnHeightOffset, z);
|
||||
|
||||
// Spawn minion
|
||||
GameObject minion = Instantiate(minionPrefab, spawnPosition, Quaternion.identity);
|
||||
|
||||
// Set rotation
|
||||
if (minionsLookAtCenter)
|
||||
{
|
||||
minion.transform.LookAt(transform.position);
|
||||
}
|
||||
else if (playerTransform != null)
|
||||
{
|
||||
// Make minion face player
|
||||
Vector3 directionToPlayer = (playerTransform.position - minion.transform.position).normalized;
|
||||
directionToPlayer.y = 0;
|
||||
if (directionToPlayer != Vector3.zero)
|
||||
{
|
||||
minion.transform.rotation = Quaternion.LookRotation(directionToPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
// Configure minion AI to target player
|
||||
var minionAI = minion.GetComponent<vControlAI>();
|
||||
if (minionAI != null && playerTransform != null)
|
||||
{
|
||||
// Set player as target through AI system
|
||||
minionAI.SetCurrentTarget(playerTransform);
|
||||
}
|
||||
|
||||
// Add to active minions list
|
||||
activeMinions.Add(minion);
|
||||
|
||||
// Spawn visual effect
|
||||
if (spawnEffectPrefab != null)
|
||||
{
|
||||
GameObject effect = Instantiate(spawnEffectPrefab, spawnPosition, Quaternion.identity);
|
||||
Destroy(effect, 3f);
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[SummonerAI] Spawned minion at {spawnPosition}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove destroyed/null minions from list
|
||||
/// </summary>
|
||||
private void CleanupDeadMinions()
|
||||
{
|
||||
activeMinions.RemoveAll(minion => minion == null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if summoner should spawn minions based on health
|
||||
/// </summary>
|
||||
public bool ShouldSummonByHealth()
|
||||
{
|
||||
if (healthController == null) return false;
|
||||
|
||||
float healthPercent = healthController.currentHealth / healthController.MaxHealth;
|
||||
return healthPercent <= healthThresholdForSummon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get distance to player
|
||||
/// </summary>
|
||||
public float GetDistanceToPlayer()
|
||||
{
|
||||
if (playerTransform == null)
|
||||
{
|
||||
FindPlayer();
|
||||
if (playerTransform == null) return float.MaxValue;
|
||||
}
|
||||
|
||||
return Vector3.Distance(transform.position, playerTransform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if player is in melee range
|
||||
/// </summary>
|
||||
public bool IsPlayerInMeleeRange()
|
||||
{
|
||||
return GetDistanceToPlayer() <= meleeEngageDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if summoner should engage in melee combat
|
||||
/// </summary>
|
||||
public bool ShouldEngageMelee()
|
||||
{
|
||||
if (!IsPlayerInMeleeRange()) return false;
|
||||
if (fightWithMinions) return true;
|
||||
return !HasActiveMinions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy all active minions (e.g., when summoner dies)
|
||||
/// </summary>
|
||||
public void DestroyAllMinions()
|
||||
{
|
||||
foreach (GameObject minion in activeMinions)
|
||||
{
|
||||
if (minion != null)
|
||||
{
|
||||
Destroy(minion);
|
||||
}
|
||||
}
|
||||
activeMinions.Clear();
|
||||
|
||||
if (enableDebug) Debug.Log("[SummonerAI] All minions destroyed");
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Cleanup minions when summoner dies
|
||||
DestroyAllMinions();
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!showGizmos) return;
|
||||
|
||||
// Draw spawn radius
|
||||
Gizmos.color = Color.cyan;
|
||||
DrawCircle(transform.position, spawnRadius, 32);
|
||||
|
||||
// Draw melee range
|
||||
Gizmos.color = Color.red;
|
||||
DrawCircle(transform.position, meleeEngageDistance, 16);
|
||||
|
||||
// Draw lines to active minions
|
||||
Gizmos.color = Color.green;
|
||||
foreach (GameObject minion in activeMinions)
|
||||
{
|
||||
if (minion != null)
|
||||
{
|
||||
Gizmos.DrawLine(transform.position + Vector3.up, minion.transform.position + Vector3.up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCircle(Vector3 center, float radius, int segments)
|
||||
{
|
||||
float angleStep = 360f / segments;
|
||||
Vector3 previousPoint = center + new Vector3(radius, 0, 0);
|
||||
|
||||
for (int i = 1; i <= segments; i++)
|
||||
{
|
||||
float angle = i * angleStep * Mathf.Deg2Rad;
|
||||
Vector3 newPoint = center + new Vector3(
|
||||
Mathf.Cos(angle) * radius,
|
||||
0,
|
||||
Mathf.Sin(angle) * radius
|
||||
);
|
||||
Gizmos.DrawLine(previousPoint, newPoint);
|
||||
previousPoint = newPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/AI/_Summoner/SummonerAI.cs.meta
Normal file
2
Assets/AI/_Summoner/SummonerAI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab91807f36ca9204a8515cfaffac091c
|
||||
Reference in New Issue
Block a user