Merge branch 'NewStory' of http://185.56.209.148/beyond/beyond into NewStory
This commit is contained in:
@@ -1,59 +1,55 @@
|
|||||||
using Lean.Pool;
|
using Lean.Pool;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DemonBoss.Magic
|
namespace DemonBoss.Magic
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// AI component for crystal turret spawned by boss
|
|
||||||
/// Rotates towards player and shoots fireballs for specified time
|
|
||||||
/// </summary>
|
|
||||||
public class CrystalShooterAI : MonoBehaviour
|
public class CrystalShooterAI : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("Shooting Configuration")]
|
[Header("Shooting Configuration")]
|
||||||
[Tooltip("Transform point from which projectiles are fired")]
|
[Tooltip("Transform point from which projectiles are fired")]
|
||||||
public Transform muzzle;
|
public Transform muzzle;
|
||||||
|
|
||||||
[Tooltip("Fireball prefab")]
|
[Tooltip("Fireball prefab (projectile with its own targeting logic)")]
|
||||||
public GameObject fireballPrefab;
|
public GameObject fireballPrefab;
|
||||||
|
|
||||||
[Tooltip("Fireball speed in m/s")]
|
[Tooltip("Seconds between shots")]
|
||||||
public float fireballSpeed = 28f;
|
|
||||||
|
|
||||||
[Tooltip("Time between shots in seconds")]
|
|
||||||
public float fireRate = 0.7f;
|
public float fireRate = 0.7f;
|
||||||
|
|
||||||
[Tooltip("Maximum number of shots before auto-despawn")]
|
[Tooltip("Maximum number of shots before auto-despawn")]
|
||||||
public int maxShots = 10;
|
public int maxShots = 10;
|
||||||
|
|
||||||
[Tooltip("Wait time after shooting before despawn")]
|
[Tooltip("Wait time after last shot before despawn")]
|
||||||
public float despawnDelay = 3f;
|
public float despawnDelay = 3f;
|
||||||
|
|
||||||
[Header("Rotation Configuration")]
|
[Header("Rotation Configuration")]
|
||||||
[Tooltip("Target rotation speed in degrees/s")]
|
[Tooltip("Yaw rotation speed in degrees per second")]
|
||||||
public float turnSpeed = 120f;
|
public float turnSpeed = 120f;
|
||||||
|
|
||||||
[Tooltip("Idle spin speed when no target (degrees/s, 0 = disabled)")]
|
[Tooltip("Idle spin speed when no target (degrees/s, 0 = disabled)")]
|
||||||
public float idleSpinSpeed = 30f;
|
public float idleSpinSpeed = 30f;
|
||||||
|
|
||||||
[Tooltip("Aiming accuracy in degrees (smaller value = more accurate)")]
|
[Tooltip("Aiming accuracy in degrees (smaller = stricter)")]
|
||||||
public float aimTolerance = 5f;
|
public float aimTolerance = 5f;
|
||||||
|
|
||||||
[Header("Targeting")]
|
[Header("Targeting (Turret-Side Only)")]
|
||||||
[Tooltip("Automatically find player on start")]
|
[Tooltip("Auto-find player on start by tag")]
|
||||||
public bool autoFindPlayer = true;
|
public bool autoFindPlayer = true;
|
||||||
|
|
||||||
[Tooltip("Player tag to search for")]
|
[Tooltip("Player tag to search for")]
|
||||||
public string playerTag = "Player";
|
public string playerTag = "Player";
|
||||||
|
|
||||||
[Tooltip("Maximum shooting range")]
|
[Tooltip("Max range for allowing shots")]
|
||||||
public float maxShootingRange = 50f;
|
public float maxShootingRange = 50f;
|
||||||
|
|
||||||
[Header("Effects")]
|
[Header("Effects")]
|
||||||
[Tooltip("Particle effect at shot")]
|
[Tooltip("Enable or disable muzzle flash & sound effects when firing")]
|
||||||
|
public bool useShootEffects = true;
|
||||||
|
|
||||||
|
[Tooltip("Particle effect at shot (pooled)")]
|
||||||
public GameObject muzzleFlashPrefab;
|
public GameObject muzzleFlashPrefab;
|
||||||
|
|
||||||
[Tooltip("Shoot sound")]
|
[Tooltip("Shoot sound (played on AudioSource)")]
|
||||||
public AudioClip shootSound;
|
public AudioClip shootSound;
|
||||||
|
|
||||||
[Header("Debug")]
|
[Header("Debug")]
|
||||||
@@ -63,20 +59,14 @@ namespace DemonBoss.Magic
|
|||||||
[Tooltip("Show gizmos in Scene View")]
|
[Tooltip("Show gizmos in Scene View")]
|
||||||
public bool showGizmos = true;
|
public bool showGizmos = true;
|
||||||
|
|
||||||
// Private variables
|
|
||||||
private Transform target;
|
private Transform target;
|
||||||
|
|
||||||
private AudioSource audioSource;
|
private AudioSource audioSource;
|
||||||
private Coroutine shootingCoroutine;
|
private Coroutine shootingCoroutine;
|
||||||
private bool isActive = false;
|
private bool isActive = false;
|
||||||
private int shotsFired = 0;
|
private int shotsFired = 0;
|
||||||
private float lastShotTime = 0f;
|
private float lastShotTime = 0f;
|
||||||
|
|
||||||
private Transform crystalTransform;
|
private Transform crystalTransform;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Component initialization
|
|
||||||
/// </summary>
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
crystalTransform = transform;
|
crystalTransform = transform;
|
||||||
@@ -91,6 +81,7 @@ namespace DemonBoss.Magic
|
|||||||
|
|
||||||
if (muzzle == null)
|
if (muzzle == null)
|
||||||
{
|
{
|
||||||
|
// Try to find a child named "muzzle"; fallback to self
|
||||||
Transform muzzleChild = crystalTransform.Find("muzzle");
|
Transform muzzleChild = crystalTransform.Find("muzzle");
|
||||||
if (muzzleChild != null)
|
if (muzzleChild != null)
|
||||||
{
|
{
|
||||||
@@ -105,33 +96,27 @@ namespace DemonBoss.Magic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Start - begin crystal operation
|
|
||||||
/// </summary>
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Crystal activated");
|
if (enableDebug) Debug.Log("[CrystalShooterAI] Crystal activated");
|
||||||
|
|
||||||
if (autoFindPlayer && target == null)
|
if (autoFindPlayer && target == null)
|
||||||
{
|
|
||||||
FindPlayer();
|
FindPlayer();
|
||||||
}
|
|
||||||
|
|
||||||
StartShooting();
|
StartShooting();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update - rotate crystal towards target
|
/// Update tick: rotate towards target or idle spin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
if (!isActive) return;
|
if (!isActive) return;
|
||||||
|
|
||||||
RotateTowardsTarget();
|
RotateTowardsTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Find player automatically
|
/// Attempts to find the player by tag (for turret-only aiming).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void FindPlayer()
|
private void FindPlayer()
|
||||||
{
|
{
|
||||||
@@ -141,42 +126,37 @@ namespace DemonBoss.Magic
|
|||||||
SetTarget(player.transform);
|
SetTarget(player.transform);
|
||||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Automatically found player");
|
if (enableDebug) Debug.Log("[CrystalShooterAI] Automatically found player");
|
||||||
}
|
}
|
||||||
else
|
else if (enableDebug)
|
||||||
{
|
{
|
||||||
if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Cannot find player with tag: " + playerTag);
|
Debug.LogWarning("[CrystalShooterAI] Cannot find player with tag: " + playerTag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set target for crystal
|
/// Sets the turret's aiming target (does NOT propagate to projectiles).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newTarget">Target transform</param>
|
|
||||||
public void SetTarget(Transform newTarget)
|
public void SetTarget(Transform newTarget)
|
||||||
{
|
{
|
||||||
target = newTarget;
|
target = newTarget;
|
||||||
if (enableDebug && target != null)
|
if (enableDebug && target != null)
|
||||||
{
|
|
||||||
Debug.Log($"[CrystalShooterAI] Set target: {target.name}");
|
Debug.Log($"[CrystalShooterAI] Set target: {target.name}");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start shooting cycle
|
/// Starts the timed shooting routine (fires until maxShots, then despawns).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartShooting()
|
public void StartShooting()
|
||||||
{
|
{
|
||||||
if (isActive) return;
|
if (isActive) return;
|
||||||
|
|
||||||
isActive = true;
|
isActive = true;
|
||||||
shotsFired = 0;
|
shotsFired = 0;
|
||||||
|
|
||||||
shootingCoroutine = StartCoroutine(ShootingCoroutine());
|
shootingCoroutine = StartCoroutine(ShootingCoroutine());
|
||||||
|
|
||||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Starting shooting");
|
if (enableDebug) Debug.Log("[CrystalShooterAI] Starting shooting");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop shooting
|
/// Stops the shooting routine immediately.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopShooting()
|
public void StopShooting()
|
||||||
{
|
{
|
||||||
@@ -192,7 +172,8 @@ namespace DemonBoss.Magic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Main coroutine handling shooting cycle
|
/// Main shooting loop: checks aim/range → spawns fireball → waits fireRate.
|
||||||
|
/// After finishing, waits a short delay and despawns the turret.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerator ShootingCoroutine()
|
private IEnumerator ShootingCoroutine()
|
||||||
{
|
{
|
||||||
@@ -211,12 +192,11 @@ namespace DemonBoss.Magic
|
|||||||
if (enableDebug) Debug.Log($"[CrystalShooterAI] Finished shooting ({shotsFired} shots)");
|
if (enableDebug) Debug.Log($"[CrystalShooterAI] Finished shooting ({shotsFired} shots)");
|
||||||
|
|
||||||
yield return new WaitForSeconds(despawnDelay);
|
yield return new WaitForSeconds(despawnDelay);
|
||||||
|
|
||||||
DespawnCrystal();
|
DespawnCrystal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if crystal can shoot
|
/// Aiming/range gate for firing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool CanShoot()
|
private bool CanShoot()
|
||||||
{
|
{
|
||||||
@@ -232,7 +212,7 @@ namespace DemonBoss.Magic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires fireball towards target
|
/// Spawns a fireball oriented towards the turret's current aim direction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void FireFireball()
|
private void FireFireball()
|
||||||
{
|
{
|
||||||
@@ -242,39 +222,41 @@ namespace DemonBoss.Magic
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 shootDirection = crystalTransform.forward;
|
Vector3 shootDirection;
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
Vector3 targetCenter = target.position + Vector3.up * 1f;
|
Vector3 targetCenter = target.position + Vector3.up * 1f;
|
||||||
shootDirection = (targetCenter - muzzle.position).normalized;
|
shootDirection = (targetCenter - muzzle.position).normalized;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
GameObject fireball = LeanPool.Spawn(fireballPrefab, muzzle.position,
|
|
||||||
Quaternion.LookRotation(shootDirection));
|
|
||||||
|
|
||||||
Rigidbody fireballRb = fireball.GetComponent<Rigidbody>();
|
|
||||||
if (fireballRb != null)
|
|
||||||
{
|
{
|
||||||
fireballRb.linearVelocity = shootDirection * fireballSpeed;
|
shootDirection = crystalTransform.forward;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3 spawnPosition = muzzle.position;
|
||||||
|
Quaternion spawnRotation = Quaternion.LookRotation(shootDirection);
|
||||||
|
|
||||||
|
LeanPool.Spawn(fireballPrefab, spawnPosition, spawnRotation);
|
||||||
|
|
||||||
PlayShootEffects();
|
PlayShootEffects();
|
||||||
|
|
||||||
if (enableDebug)
|
if (enableDebug)
|
||||||
{
|
{
|
||||||
Debug.Log($"[CrystalShooterAI] Shot #{shotsFired + 1} in direction: {shootDirection}");
|
Debug.Log($"[CrystalShooterAI] Shot #{shotsFired + 1} at {spawnPosition} dir: {shootDirection}");
|
||||||
|
Debug.DrawRay(spawnPosition, shootDirection * 8f, Color.red, 2f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plays shooting effects
|
/// Plays muzzle VFX and shoot SFX (if enabled).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void PlayShootEffects()
|
private void PlayShootEffects()
|
||||||
{
|
{
|
||||||
|
if (!useShootEffects) return;
|
||||||
|
|
||||||
if (muzzleFlashPrefab != null && muzzle != null)
|
if (muzzleFlashPrefab != null && muzzle != null)
|
||||||
{
|
{
|
||||||
GameObject flash = LeanPool.Spawn(muzzleFlashPrefab, muzzle.position, muzzle.rotation);
|
GameObject flash = LeanPool.Spawn(muzzleFlashPrefab, muzzle.position, muzzle.rotation);
|
||||||
|
|
||||||
LeanPool.Despawn(flash, 2f);
|
LeanPool.Despawn(flash, 2f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,14 +267,14 @@ namespace DemonBoss.Magic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rotates crystal towards target or performs idle spin
|
/// Smooth yaw rotation towards target; idles by spinning when no target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void RotateTowardsTarget()
|
private void RotateTowardsTarget()
|
||||||
{
|
{
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
Vector3 directionToTarget = target.position - crystalTransform.position;
|
Vector3 directionToTarget = target.position - crystalTransform.position;
|
||||||
directionToTarget.y = 0;
|
directionToTarget.y = 0f;
|
||||||
|
|
||||||
if (directionToTarget != Vector3.zero)
|
if (directionToTarget != Vector3.zero)
|
||||||
{
|
{
|
||||||
@@ -311,19 +293,17 @@ namespace DemonBoss.Magic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Despawns crystal from map
|
/// Despawns the turret via Lean Pool.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DespawnCrystal()
|
public void DespawnCrystal()
|
||||||
{
|
{
|
||||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Despawning crystal");
|
if (enableDebug) Debug.Log("[CrystalShooterAI] Despawning crystal");
|
||||||
|
|
||||||
StopShooting();
|
StopShooting();
|
||||||
|
|
||||||
LeanPool.Despawn(gameObject);
|
LeanPool.Despawn(gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Forces immediate despawn (e.g. on boss death)
|
/// Forces immediate despawn (e.g., boss death).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ForceDespawn()
|
public void ForceDespawn()
|
||||||
{
|
{
|
||||||
@@ -331,9 +311,7 @@ namespace DemonBoss.Magic
|
|||||||
DespawnCrystal();
|
DespawnCrystal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> Returns crystal state information. </summary>
|
||||||
/// Returns crystal state information
|
|
||||||
/// </summary>
|
|
||||||
public bool IsActive() => isActive;
|
public bool IsActive() => isActive;
|
||||||
|
|
||||||
public int GetShotsFired() => shotsFired;
|
public int GetShotsFired() => shotsFired;
|
||||||
@@ -343,7 +321,7 @@ namespace DemonBoss.Magic
|
|||||||
public float GetTimeSinceLastShot() => Time.time - lastShotTime;
|
public float GetTimeSinceLastShot() => Time.time - lastShotTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draws gizmos in Scene View
|
/// Gizmos for range and aim visualization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnDrawGizmosSelected()
|
private void OnDrawGizmosSelected()
|
||||||
{
|
{
|
||||||
@@ -366,6 +344,7 @@ namespace DemonBoss.Magic
|
|||||||
|
|
||||||
Gizmos.DrawLine(muzzle.position, muzzle.position + right);
|
Gizmos.DrawLine(muzzle.position, muzzle.position + right);
|
||||||
Gizmos.DrawLine(muzzle.position, muzzle.position + left);
|
Gizmos.DrawLine(muzzle.position, muzzle.position + left);
|
||||||
|
Gizmos.DrawLine(muzzle.position, muzzle.position + forward);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ GameObject:
|
|||||||
- component: {fileID: 4577187839491108}
|
- component: {fileID: 4577187839491108}
|
||||||
- component: {fileID: 198086061384069858}
|
- component: {fileID: 198086061384069858}
|
||||||
- component: {fileID: 199127982807948490}
|
- component: {fileID: 199127982807948490}
|
||||||
m_Layer: 0
|
m_Layer: 2
|
||||||
m_Name: FireEmbers (4)
|
m_Name: FireEmbers (4)
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
@@ -4703,7 +4703,6 @@ ParticleSystemRenderer:
|
|||||||
m_RendererPriority: 0
|
m_RendererPriority: 0
|
||||||
m_Materials:
|
m_Materials:
|
||||||
- {fileID: 2100000, guid: 776677ab3818fb249adaca05f4e04545, type: 2}
|
- {fileID: 2100000, guid: 776677ab3818fb249adaca05f4e04545, type: 2}
|
||||||
- {fileID: 0}
|
|
||||||
m_StaticBatchInfo:
|
m_StaticBatchInfo:
|
||||||
firstSubMesh: 0
|
firstSubMesh: 0
|
||||||
subMeshCount: 0
|
subMeshCount: 0
|
||||||
@@ -4786,7 +4785,7 @@ Transform:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: -0.7071068, y: 0, z: 0, w: 0.7071068}
|
m_LocalRotation: {x: -0.7071068, y: 0, z: 0, w: 0.7071068}
|
||||||
m_LocalPosition: {x: 6.67, y: 1, z: -53.73}
|
m_LocalPosition: {x: 6.67, y: 1, z: -53.73}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 0.25, y: 0.25, z: 0.25}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 4577187839491108}
|
- {fileID: 4577187839491108}
|
||||||
@@ -9552,7 +9551,6 @@ ParticleSystemRenderer:
|
|||||||
m_RendererPriority: 0
|
m_RendererPriority: 0
|
||||||
m_Materials:
|
m_Materials:
|
||||||
- {fileID: 2100000, guid: 3349fc02d104e2b458470ec533f51c16, type: 2}
|
- {fileID: 2100000, guid: 3349fc02d104e2b458470ec533f51c16, type: 2}
|
||||||
- {fileID: 0}
|
|
||||||
m_StaticBatchInfo:
|
m_StaticBatchInfo:
|
||||||
firstSubMesh: 0
|
firstSubMesh: 0
|
||||||
subMeshCount: 0
|
subMeshCount: 0
|
||||||
@@ -9620,14 +9618,14 @@ CapsuleCollider:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_Bits: 0
|
m_Bits: 0
|
||||||
m_LayerOverridePriority: 0
|
m_LayerOverridePriority: 0
|
||||||
m_IsTrigger: 0
|
m_IsTrigger: 1
|
||||||
m_ProvidesContacts: 0
|
m_ProvidesContacts: 0
|
||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_Radius: 0.5
|
m_Radius: 0.5
|
||||||
m_Height: 1
|
m_Height: 0.1
|
||||||
m_Direction: 1
|
m_Direction: 1
|
||||||
m_Center: {x: 0, y: 0, z: 0}
|
m_Center: {x: 0.31801516, y: 0, z: 0}
|
||||||
--- !u!114 &6785567375430979834
|
--- !u!114 &6785567375430979834
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -9640,17 +9638,11 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 1a863cb5e6092ec4a936c4eb21bb9166, type: 3}
|
m_Script: {fileID: 11500000, guid: 1a863cb5e6092ec4a936c4eb21bb9166, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
|
targetTag: Player
|
||||||
|
speed: 6
|
||||||
|
lockTime: 15
|
||||||
|
maxLifeTime: 30
|
||||||
|
arrivalTolerance: 0.25
|
||||||
damage: 25
|
damage: 25
|
||||||
damageOnce: 1
|
|
||||||
targetLayerMask:
|
|
||||||
serializedVersion: 2
|
|
||||||
m_Bits: 1410334711
|
|
||||||
impactEffectPrefab: {fileID: 132858, guid: c5cabec71c815b64ab6e2547b594ec81, type: 3}
|
|
||||||
impactSound: {fileID: 8300000, guid: 5dd60193d2ea89e47ae309eec2c3852e, type: 3}
|
|
||||||
knockbackForce: 5
|
knockbackForce: 5
|
||||||
maxLifetime: 5
|
|
||||||
destroyOnTerrain: 1
|
|
||||||
terrainLayerMask:
|
|
||||||
serializedVersion: 2
|
|
||||||
m_Bits: 1
|
|
||||||
enableDebug: 0
|
enableDebug: 0
|
||||||
|
|||||||
@@ -1,386 +0,0 @@
|
|||||||
using Invector;
|
|
||||||
using Invector.vCharacterController;
|
|
||||||
using Lean.Pool;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace DemonBoss.Magic
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Component handling damage and collisions for fireball fired by crystals
|
|
||||||
/// Deals damage on collision with player and automatically despawns
|
|
||||||
/// </summary>
|
|
||||||
[RequireComponent(typeof(Collider))]
|
|
||||||
public class FireballDamage : MonoBehaviour
|
|
||||||
{
|
|
||||||
[Header("Damage Configuration")]
|
|
||||||
[Tooltip("Damage dealt by fireball")]
|
|
||||||
public float damage = 25f;
|
|
||||||
|
|
||||||
[Tooltip("Whether fireball can deal damage only once")]
|
|
||||||
public bool damageOnce = true;
|
|
||||||
|
|
||||||
[Tooltip("Layer mask for targets that can be hit")]
|
|
||||||
public LayerMask targetLayerMask = -1;
|
|
||||||
|
|
||||||
[Header("Impact Effects")]
|
|
||||||
[Tooltip("Explosion effect prefab on hit")]
|
|
||||||
public GameObject impactEffectPrefab;
|
|
||||||
|
|
||||||
[Tooltip("Impact sound")]
|
|
||||||
public AudioClip impactSound;
|
|
||||||
|
|
||||||
[Tooltip("Knockback force on hit")]
|
|
||||||
public float knockbackForce = 5f;
|
|
||||||
|
|
||||||
[Header("Lifetime")]
|
|
||||||
[Tooltip("Maximum fireball lifetime in seconds (failsafe)")]
|
|
||||||
public float maxLifetime = 5f;
|
|
||||||
|
|
||||||
[Tooltip("Whether to destroy fireball on terrain collision")]
|
|
||||||
public bool destroyOnTerrain = true;
|
|
||||||
|
|
||||||
[Tooltip("Layer mask for terrain/obstacles")]
|
|
||||||
public LayerMask terrainLayerMask = 1;
|
|
||||||
|
|
||||||
[Header("Debug")]
|
|
||||||
[Tooltip("Enable debug logging")]
|
|
||||||
public bool enableDebug = false;
|
|
||||||
|
|
||||||
// Private variables
|
|
||||||
private bool hasDealtDamage = false;
|
|
||||||
|
|
||||||
private float spawnTime;
|
|
||||||
private AudioSource audioSource;
|
|
||||||
private Collider fireballCollider;
|
|
||||||
private Rigidbody fireballRigidbody;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Component initialization
|
|
||||||
/// </summary>
|
|
||||||
private void Awake()
|
|
||||||
{
|
|
||||||
fireballCollider = GetComponent<Collider>();
|
|
||||||
fireballRigidbody = GetComponent<Rigidbody>();
|
|
||||||
|
|
||||||
if (fireballCollider != null && !fireballCollider.isTrigger)
|
|
||||||
{
|
|
||||||
fireballCollider.isTrigger = true;
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Set collider as trigger");
|
|
||||||
}
|
|
||||||
|
|
||||||
audioSource = GetComponent<AudioSource>();
|
|
||||||
if (audioSource == null && impactSound != null)
|
|
||||||
{
|
|
||||||
audioSource = gameObject.AddComponent<AudioSource>();
|
|
||||||
audioSource.playOnAwake = false;
|
|
||||||
audioSource.spatialBlend = 1f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Start - note spawn time and start failsafe timer
|
|
||||||
/// </summary>
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
spawnTime = Time.time;
|
|
||||||
hasDealtDamage = false;
|
|
||||||
|
|
||||||
Invoke(nameof(FailsafeDespawn), maxLifetime);
|
|
||||||
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Fireball initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Trigger collision handling - dealing damage
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">Collider that fireball collides with</param>
|
|
||||||
private void OnTriggerEnter(Collider other)
|
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log($"[FireballDamage] Collision with: {other.name}");
|
|
||||||
|
|
||||||
if (IsValidTarget(other))
|
|
||||||
{
|
|
||||||
DealDamageToTarget(other);
|
|
||||||
CreateImpactEffect(other.transform.position);
|
|
||||||
DespawnFireball();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destroyOnTerrain && IsTerrainCollision(other))
|
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Terrain collision");
|
|
||||||
CreateImpactEffect(transform.position);
|
|
||||||
DespawnFireball();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if collider is valid target
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">Collider to check</param>
|
|
||||||
/// <returns>True if it's valid target</returns>
|
|
||||||
private bool IsValidTarget(Collider other)
|
|
||||||
{
|
|
||||||
if ((targetLayerMask.value & (1 << other.gameObject.layer)) == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (damageOnce && hasDealtDamage)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var damageReceiver = other.GetComponent<vIDamageReceiver>();
|
|
||||||
var healthController = other.GetComponent<vHealthController>();
|
|
||||||
var character = other.GetComponent<vCharacter>();
|
|
||||||
var thirdPersonController = other.GetComponent<vThirdPersonController>();
|
|
||||||
|
|
||||||
if (damageReceiver == null) damageReceiver = other.GetComponentInParent<vIDamageReceiver>();
|
|
||||||
if (healthController == null) healthController = other.GetComponentInParent<vHealthController>();
|
|
||||||
if (character == null) character = other.GetComponentInParent<vCharacter>();
|
|
||||||
if (thirdPersonController == null) thirdPersonController = other.GetComponentInParent<vThirdPersonController>();
|
|
||||||
|
|
||||||
return damageReceiver != null || healthController != null || character != null || thirdPersonController != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if it's terrain collision
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">Collider to check</param>
|
|
||||||
/// <returns>True if it's terrain</returns>
|
|
||||||
private bool IsTerrainCollision(Collider other)
|
|
||||||
{
|
|
||||||
return (terrainLayerMask.value & (1 << other.gameObject.layer)) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deals damage to target - ulepszona wersja
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="targetCollider">Target collider</param>
|
|
||||||
private void DealDamageToTarget(Collider targetCollider)
|
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log($"[FireballDamage] Dealing {damage} damage to: {targetCollider.name}");
|
|
||||||
|
|
||||||
Vector3 hitPoint = GetClosestPointOnCollider(targetCollider);
|
|
||||||
Vector3 hitDirection = GetHitDirection(targetCollider);
|
|
||||||
|
|
||||||
vDamage damageInfo = new vDamage(Mathf.RoundToInt(damage));
|
|
||||||
damageInfo.sender = transform;
|
|
||||||
damageInfo.hitPosition = hitPoint;
|
|
||||||
|
|
||||||
if (knockbackForce > 0f)
|
|
||||||
{
|
|
||||||
damageInfo.force = hitDirection * knockbackForce;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool damageDealt = false;
|
|
||||||
|
|
||||||
var damageReceiver = targetCollider.GetComponent<vIDamageReceiver>();
|
|
||||||
if (damageReceiver == null) damageReceiver = targetCollider.GetComponentInParent<vIDamageReceiver>();
|
|
||||||
|
|
||||||
if (damageReceiver != null && !damageDealt)
|
|
||||||
{
|
|
||||||
damageReceiver.TakeDamage(damageInfo);
|
|
||||||
damageDealt = true;
|
|
||||||
hasDealtDamage = true;
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vIDamageReceiver");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!damageDealt)
|
|
||||||
{
|
|
||||||
var healthController = targetCollider.GetComponent<vHealthController>();
|
|
||||||
if (healthController == null) healthController = targetCollider.GetComponentInParent<vHealthController>();
|
|
||||||
|
|
||||||
if (healthController != null)
|
|
||||||
{
|
|
||||||
healthController.TakeDamage(damageInfo);
|
|
||||||
damageDealt = true;
|
|
||||||
hasDealtDamage = true;
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vHealthController");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!damageDealt)
|
|
||||||
{
|
|
||||||
var thirdPersonController = targetCollider.GetComponent<vThirdPersonController>();
|
|
||||||
if (thirdPersonController == null) thirdPersonController = targetCollider.GetComponentInParent<vThirdPersonController>();
|
|
||||||
|
|
||||||
if (thirdPersonController != null)
|
|
||||||
{
|
|
||||||
if (thirdPersonController is Beyond.bThirdPersonController beyondController)
|
|
||||||
{
|
|
||||||
if (!beyondController.GodMode && !beyondController.isImmortal)
|
|
||||||
{
|
|
||||||
thirdPersonController.TakeDamage(damageInfo);
|
|
||||||
damageDealt = true;
|
|
||||||
hasDealtDamage = true;
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through bThirdPersonController");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Player is immortal or in God Mode - no damage dealt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
thirdPersonController.TakeDamage(damageInfo);
|
|
||||||
damageDealt = true;
|
|
||||||
hasDealtDamage = true;
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vThirdPersonController");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!damageDealt)
|
|
||||||
{
|
|
||||||
if (enableDebug) Debug.LogWarning("[FireballDamage] Could not deal damage - no valid damage receiver found!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes the closest point on the collider as the point of impact
|
|
||||||
/// </summary>
|
|
||||||
private Vector3 GetClosestPointOnCollider(Collider targetCollider)
|
|
||||||
{
|
|
||||||
return targetCollider.ClosestPoint(transform.position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the direction of impact for knockback
|
|
||||||
/// </summary>
|
|
||||||
private Vector3 GetHitDirection(Collider targetCollider)
|
|
||||||
{
|
|
||||||
Vector3 direction;
|
|
||||||
|
|
||||||
if (fireballRigidbody != null && fireballRigidbody.linearVelocity.magnitude > 0.1f)
|
|
||||||
{
|
|
||||||
direction = fireballRigidbody.linearVelocity.normalized;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
direction = (targetCollider.transform.position - transform.position).normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
direction.y = Mathf.Max(0.2f, direction.y);
|
|
||||||
return direction.normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates impact effect
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="impactPosition">Impact position</param>
|
|
||||||
private void CreateImpactEffect(Vector3 impactPosition)
|
|
||||||
{
|
|
||||||
if (impactEffectPrefab != null)
|
|
||||||
{
|
|
||||||
GameObject impact = LeanPool.Spawn(impactEffectPrefab, impactPosition, Quaternion.identity);
|
|
||||||
|
|
||||||
LeanPool.Despawn(impact, 3f);
|
|
||||||
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Impact effect created");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioSource != null && impactSound != null)
|
|
||||||
{
|
|
||||||
audioSource.PlayOneShot(impactSound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Despawns fireball from map
|
|
||||||
/// </summary>
|
|
||||||
private void DespawnFireball()
|
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Despawning fireball");
|
|
||||||
|
|
||||||
CancelInvoke(nameof(FailsafeDespawn));
|
|
||||||
|
|
||||||
LeanPool.Despawn(gameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Failsafe despawn - removes fireball after maximum lifetime
|
|
||||||
/// </summary>
|
|
||||||
private void FailsafeDespawn()
|
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Failsafe despawn - lifetime exceeded");
|
|
||||||
DespawnFireball();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Forces immediate despawn
|
|
||||||
/// </summary>
|
|
||||||
public void ForceDespawn()
|
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Forced despawn");
|
|
||||||
DespawnFireball();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if fireball has already dealt damage
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if damage was already dealt</returns>
|
|
||||||
public bool HasDealtDamage()
|
|
||||||
{
|
|
||||||
return hasDealtDamage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns fireball lifetime
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Time in seconds since spawn</returns>
|
|
||||||
public float GetLifetime()
|
|
||||||
{
|
|
||||||
return Time.time - spawnTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets new damage (e.g. for different difficulty levels)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newDamage">New damage</param>
|
|
||||||
public void SetDamage(float newDamage)
|
|
||||||
{
|
|
||||||
damage = newDamage;
|
|
||||||
if (enableDebug) Debug.Log($"[FireballDamage] Set new damage: {damage}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets new knockback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newKnockback">New knockback force</param>
|
|
||||||
public void SetKnockback(float newKnockback)
|
|
||||||
{
|
|
||||||
knockbackForce = newKnockback;
|
|
||||||
if (enableDebug) Debug.Log($"[FireballDamage] Set new knockback: {knockbackForce}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resets fireball state (useful for pooling)
|
|
||||||
/// </summary>
|
|
||||||
public void ResetFireball()
|
|
||||||
{
|
|
||||||
hasDealtDamage = false;
|
|
||||||
spawnTime = Time.time;
|
|
||||||
|
|
||||||
CancelInvoke();
|
|
||||||
|
|
||||||
Invoke(nameof(FailsafeDespawn), maxLifetime);
|
|
||||||
|
|
||||||
if (enableDebug) Debug.Log("[FireballDamage] Fireball reset");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called on spawn by Lean Pool
|
|
||||||
/// </summary>
|
|
||||||
private void OnSpawn()
|
|
||||||
{
|
|
||||||
ResetFireball();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called on despawn by Lean Pool
|
|
||||||
/// </summary>
|
|
||||||
private void OnDespawn()
|
|
||||||
{
|
|
||||||
CancelInvoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
314
Assets/AI/Demon/FireballProjectile.cs
Normal file
314
Assets/AI/Demon/FireballProjectile.cs
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
using Invector;
|
||||||
|
using Invector.vCharacterController;
|
||||||
|
using Lean.Pool;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace DemonBoss.Magic
|
||||||
|
{
|
||||||
|
public class FireballProjectile : MonoBehaviour
|
||||||
|
{
|
||||||
|
#region Configuration
|
||||||
|
|
||||||
|
[Header("Targeting")]
|
||||||
|
[Tooltip("Tag of the target to chase while tracking phase is active.")]
|
||||||
|
public string targetTag = "Player";
|
||||||
|
|
||||||
|
[Tooltip("Vertical offset added to the target position (e.g., aim at chest/head instead of feet).")]
|
||||||
|
public float targetHeightOffset = 1.0f;
|
||||||
|
|
||||||
|
[Header("Movement")]
|
||||||
|
[Tooltip("Units per second.")]
|
||||||
|
public float speed = 6f;
|
||||||
|
|
||||||
|
[Tooltip("Seconds to track the player before locking to last known position.")]
|
||||||
|
public float lockTime = 15f;
|
||||||
|
|
||||||
|
[Tooltip("Seconds before the projectile auto-despawns regardless of state.")]
|
||||||
|
public float maxLifeTime = 30f;
|
||||||
|
|
||||||
|
[Tooltip("Distance threshold to consider we arrived at the locked position.")]
|
||||||
|
public float arrivalTolerance = 0.25f;
|
||||||
|
|
||||||
|
[Header("Damage")]
|
||||||
|
[Tooltip("Base damage dealt to the target.")]
|
||||||
|
public int damage = 20;
|
||||||
|
|
||||||
|
[Tooltip("Optional knockback impulse magnitude applied along hit direction.")]
|
||||||
|
public float knockbackForce = 0f;
|
||||||
|
|
||||||
|
[Header("Debug")]
|
||||||
|
[Tooltip("Enable verbose debug logs.")]
|
||||||
|
public bool enableDebug = false;
|
||||||
|
|
||||||
|
#endregion Configuration
|
||||||
|
|
||||||
|
#region Runtime State
|
||||||
|
|
||||||
|
private Transform player;
|
||||||
|
private Vector3 lockedTargetPos;
|
||||||
|
private bool isLocked = false;
|
||||||
|
private bool hasDealtDamage = false;
|
||||||
|
private float timer = 0f;
|
||||||
|
|
||||||
|
#endregion Runtime State
|
||||||
|
|
||||||
|
#region Unity Lifecycle
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the object becomes enabled (including when spawned from a pool).
|
||||||
|
/// Resets runtime state and acquires the target by tag.
|
||||||
|
/// </summary>
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
ResetRuntimeState();
|
||||||
|
AcquireTargetByTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per-frame update: handle tracking/lock movement and lifetime/despawn conditions.
|
||||||
|
/// </summary>
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
timer += Time.deltaTime;
|
||||||
|
|
||||||
|
// Transition to 'locked to last position' after lockTime
|
||||||
|
if (!isLocked && timer >= lockTime && player != null)
|
||||||
|
{
|
||||||
|
lockedTargetPos = player.position + Vector3.up * targetHeightOffset;
|
||||||
|
isLocked = true;
|
||||||
|
if (enableDebug) Debug.Log($"[Fireball] Locked to last known player position: {lockedTargetPos}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine current aim point
|
||||||
|
Vector3 aimPoint;
|
||||||
|
if (!isLocked && player != null)
|
||||||
|
{
|
||||||
|
// Tracking phase: follow live player position
|
||||||
|
aimPoint = player.position + Vector3.up * targetHeightOffset;
|
||||||
|
}
|
||||||
|
else if (isLocked)
|
||||||
|
{
|
||||||
|
// Locked phase: fly to memorized position
|
||||||
|
aimPoint = lockedTargetPos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fallback: no player found, keep moving forward
|
||||||
|
aimPoint = transform.position + transform.forward * 1000f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move towards aim point
|
||||||
|
Vector3 toTarget = aimPoint - transform.position;
|
||||||
|
Vector3 step = toTarget.normalized * speed * Time.deltaTime;
|
||||||
|
transform.position += step;
|
||||||
|
|
||||||
|
// Face movement direction (optional but nice for VFX)
|
||||||
|
if (step.sqrMagnitude > 0.0001f)
|
||||||
|
transform.forward = step.normalized;
|
||||||
|
|
||||||
|
// If locked and close enough to the locked point → despawn
|
||||||
|
if (isLocked && toTarget.magnitude <= arrivalTolerance)
|
||||||
|
{
|
||||||
|
if (enableDebug) Debug.Log("[Fireball] Arrived at locked position → Despawn");
|
||||||
|
Despawn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hard cap lifetime
|
||||||
|
if (timer >= maxLifeTime)
|
||||||
|
{
|
||||||
|
if (enableDebug) Debug.Log("[Fireball] Max lifetime reached → Despawn");
|
||||||
|
Despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trigger hit handler – applies damage when colliding with the intended target tag and then despawns.
|
||||||
|
/// </summary>
|
||||||
|
private void OnTriggerEnter(Collider other)
|
||||||
|
{
|
||||||
|
if (hasDealtDamage) return; // guard against multiple triggers in a single frame
|
||||||
|
|
||||||
|
if (other.CompareTag(targetTag))
|
||||||
|
{
|
||||||
|
DealDamageToTarget(other);
|
||||||
|
Despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Unity Lifecycle
|
||||||
|
|
||||||
|
#region Target Acquisition
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the target by tag (e.g., "Player"). Called on enable and can be retried if needed.
|
||||||
|
/// </summary>
|
||||||
|
private void AcquireTargetByTag()
|
||||||
|
{
|
||||||
|
var playerObj = GameObject.FindGameObjectWithTag(targetTag);
|
||||||
|
player = playerObj ? playerObj.transform : null;
|
||||||
|
|
||||||
|
if (enableDebug)
|
||||||
|
{
|
||||||
|
if (player != null) Debug.Log($"[Fireball] Target found by tag '{targetTag}'.");
|
||||||
|
else Debug.LogWarning($"[Fireball] No target with tag '{targetTag}' found – moving forward fallback.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Target Acquisition
|
||||||
|
|
||||||
|
#region Damage Pipeline (Invector-style)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies damage using available receivers on the hit collider or its parents:
|
||||||
|
/// vIDamageReceiver → vHealthController → vThirdPersonController (with Beyond variant checks).
|
||||||
|
/// </summary>
|
||||||
|
private void DealDamageToTarget(Collider targetCollider)
|
||||||
|
{
|
||||||
|
if (enableDebug) Debug.Log($"[FireballDamage] Dealing {damage} damage to: {targetCollider.name}");
|
||||||
|
|
||||||
|
Vector3 hitPoint = GetClosestPointOnCollider(targetCollider);
|
||||||
|
Vector3 hitDirection = GetHitDirection(targetCollider);
|
||||||
|
|
||||||
|
// Build vDamage payload (Invector)
|
||||||
|
vDamage damageInfo = new vDamage(Mathf.RoundToInt(damage))
|
||||||
|
{
|
||||||
|
sender = transform,
|
||||||
|
hitPosition = hitPoint
|
||||||
|
};
|
||||||
|
|
||||||
|
if (knockbackForce > 0f)
|
||||||
|
damageInfo.force = hitDirection * knockbackForce;
|
||||||
|
|
||||||
|
bool damageDealt = false;
|
||||||
|
|
||||||
|
// 1) Try generic vIDamageReceiver (collider or parent)
|
||||||
|
var damageReceiver = targetCollider.GetComponent<vIDamageReceiver>() ??
|
||||||
|
targetCollider.GetComponentInParent<vIDamageReceiver>();
|
||||||
|
|
||||||
|
if (damageReceiver != null && !damageDealt)
|
||||||
|
{
|
||||||
|
damageReceiver.TakeDamage(damageInfo);
|
||||||
|
damageDealt = true;
|
||||||
|
hasDealtDamage = true;
|
||||||
|
if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vIDamageReceiver");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Fallback to vHealthController
|
||||||
|
if (!damageDealt)
|
||||||
|
{
|
||||||
|
var healthController = targetCollider.GetComponent<vHealthController>() ??
|
||||||
|
targetCollider.GetComponentInParent<vHealthController>();
|
||||||
|
|
||||||
|
if (healthController != null)
|
||||||
|
{
|
||||||
|
healthController.TakeDamage(damageInfo);
|
||||||
|
damageDealt = true;
|
||||||
|
hasDealtDamage = true;
|
||||||
|
if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vHealthController");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Fallback to vThirdPersonController (handle Beyond variant)
|
||||||
|
if (!damageDealt)
|
||||||
|
{
|
||||||
|
var tpc = targetCollider.GetComponent<vThirdPersonController>() ??
|
||||||
|
targetCollider.GetComponentInParent<vThirdPersonController>();
|
||||||
|
|
||||||
|
if (tpc != null)
|
||||||
|
{
|
||||||
|
if (tpc is Beyond.bThirdPersonController beyond)
|
||||||
|
{
|
||||||
|
if (!beyond.GodMode && !beyond.isImmortal)
|
||||||
|
{
|
||||||
|
tpc.TakeDamage(damageInfo);
|
||||||
|
damageDealt = true;
|
||||||
|
hasDealtDamage = true;
|
||||||
|
if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through bThirdPersonController");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (enableDebug) Debug.Log("[FireballDamage] Target is immortal / GodMode – no damage dealt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tpc.TakeDamage(damageInfo);
|
||||||
|
damageDealt = true;
|
||||||
|
hasDealtDamage = true;
|
||||||
|
if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vThirdPersonController");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!damageDealt && enableDebug)
|
||||||
|
Debug.LogWarning("[FireballDamage] Could not deal damage – no valid damage receiver found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the closest point on the target collider to this projectile position.
|
||||||
|
/// </summary>
|
||||||
|
private Vector3 GetClosestPointOnCollider(Collider col)
|
||||||
|
{
|
||||||
|
return col.ClosestPoint(transform.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a reasonable hit direction from projectile to target center (fallbacks to forward).
|
||||||
|
/// </summary>
|
||||||
|
private Vector3 GetHitDirection(Collider col)
|
||||||
|
{
|
||||||
|
Vector3 dir = (col.bounds.center - transform.position).normalized;
|
||||||
|
return dir.sqrMagnitude > 0.0001f ? dir : transform.forward;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Damage Pipeline (Invector-style)
|
||||||
|
|
||||||
|
#region Pooling & Utilities
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns this projectile to the Lean Pool (safe to call on non-pooled too).
|
||||||
|
/// </summary>
|
||||||
|
private void Despawn()
|
||||||
|
{
|
||||||
|
if (enableDebug) Debug.Log("[Fireball] Despawn via LeanPool");
|
||||||
|
LeanPool.Despawn(gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears/initializes runtime variables so pooled instances behave like fresh spawns.
|
||||||
|
/// </summary>
|
||||||
|
private void ResetRuntimeState()
|
||||||
|
{
|
||||||
|
timer = 0f;
|
||||||
|
isLocked = false;
|
||||||
|
hasDealtDamage = false;
|
||||||
|
lockedTargetPos = Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Pooling & Utilities
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional gizmo: shows lock radius and direction for quick debugging.
|
||||||
|
/// </summary>
|
||||||
|
private void OnDrawGizmosSelected()
|
||||||
|
{
|
||||||
|
Gizmos.color = Color.cyan;
|
||||||
|
Gizmos.DrawWireSphere(transform.position, 0.25f);
|
||||||
|
|
||||||
|
// Draw a small forward arrow
|
||||||
|
Gizmos.DrawLine(transform.position, transform.position + transform.forward * 1.0f);
|
||||||
|
|
||||||
|
// Arrival tolerance sphere (only meaningful when locked)
|
||||||
|
if (isLocked)
|
||||||
|
{
|
||||||
|
Gizmos.color = Color.yellow;
|
||||||
|
Gizmos.DrawWireSphere(lockedTargetPos, arrivalTolerance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,185 +1,187 @@
|
|||||||
using Invector.vCharacterController.AI.FSMBehaviour;
|
using Invector.vCharacterController.AI.FSMBehaviour;
|
||||||
using Lean.Pool;
|
using Lean.Pool;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DemonBoss.Magic
|
namespace DemonBoss.Magic
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// StateAction for Magic Shield spell - boss casts magical shield for 5 seconds
|
/// StateAction for Magic Shield spell - boss casts magical shield for 5 seconds
|
||||||
/// During casting boss stands still, after completion returns to Combat
|
/// During casting boss stands still, after completion returns to Combat
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Cast Shield")]
|
[CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Cast Shield")]
|
||||||
public class SA_CastShield : vStateAction
|
public class SA_CastShield : vStateAction
|
||||||
{
|
{
|
||||||
public override string categoryName => "DemonBoss/Magic";
|
public override string categoryName => "DemonBoss/Magic";
|
||||||
public override string defaultName => "Cast Shield";
|
public override string defaultName => "Cast Shield";
|
||||||
|
|
||||||
[Header("Shield Configuration")]
|
[Header("Shield Configuration")]
|
||||||
[Tooltip("Prefab with magical shield particle effect")]
|
[Tooltip("Prefab with magical shield particle effect")]
|
||||||
public GameObject shieldFXPrefab;
|
public GameObject shieldFXPrefab;
|
||||||
|
|
||||||
[Tooltip("Transform where shield should appear (usually boss center)")]
|
[Tooltip("Shield duration in seconds")]
|
||||||
public Transform shieldSpawnPoint;
|
public float shieldDuration = 5f;
|
||||||
|
|
||||||
[Tooltip("Shield duration in seconds")]
|
[Tooltip("Animator bool parameter name for blocking state")]
|
||||||
public float shieldDuration = 5f;
|
public string animatorBlockingBool = "IsBlocking";
|
||||||
|
|
||||||
[Tooltip("Animator trigger name for shield casting animation")]
|
[Header("Debug")]
|
||||||
public string animatorTrigger = "CastShield";
|
[Tooltip("Enable debug logging")]
|
||||||
|
public bool enableDebug = false;
|
||||||
[Header("Debug")]
|
|
||||||
[Tooltip("Enable debug logging")]
|
private GameObject spawnedShield;
|
||||||
public bool enableDebug = false;
|
private Animator npcAnimator;
|
||||||
|
private Transform npcTransform;
|
||||||
private GameObject spawnedShield;
|
private float shieldStartTime;
|
||||||
|
private bool shieldActive = false;
|
||||||
private float shieldStartTime;
|
|
||||||
private bool shieldActive = false;
|
/// <summary>
|
||||||
|
/// Main action execution method called by FSM
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Main action execution method called by FSM
|
public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
|
||||||
/// </summary>
|
{
|
||||||
public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
|
if (executionType == vFSMComponentExecutionType.OnStateEnter)
|
||||||
{
|
{
|
||||||
if (executionType == vFSMComponentExecutionType.OnStateEnter)
|
OnStateEnter(fsmBehaviour);
|
||||||
{
|
}
|
||||||
OnStateEnter(fsmBehaviour);
|
else if (executionType == vFSMComponentExecutionType.OnStateUpdate)
|
||||||
}
|
{
|
||||||
else if (executionType == vFSMComponentExecutionType.OnStateUpdate)
|
OnStateUpdate(fsmBehaviour);
|
||||||
{
|
}
|
||||||
OnStateUpdate(fsmBehaviour);
|
else if (executionType == vFSMComponentExecutionType.OnStateExit)
|
||||||
}
|
{
|
||||||
else if (executionType == vFSMComponentExecutionType.OnStateExit)
|
OnStateExit(fsmBehaviour);
|
||||||
{
|
}
|
||||||
OnStateExit(fsmBehaviour);
|
}
|
||||||
}
|
|
||||||
}
|
/// <summary>
|
||||||
|
/// Called when entering state - starts shield casting
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Called when entering state - starts shield casting
|
private void OnStateEnter(vIFSMBehaviourController fsmBehaviour)
|
||||||
/// </summary>
|
{
|
||||||
private void OnStateEnter(vIFSMBehaviourController fsmBehaviour)
|
if (enableDebug) Debug.Log("[SA_CastShield] Entering shield casting state");
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log("[SA_CastShield] Entering shield casting state");
|
// Store NPC references
|
||||||
|
npcTransform = fsmBehaviour.transform;
|
||||||
var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI;
|
npcAnimator = npcTransform.GetComponent<Animator>();
|
||||||
if (aiController != null)
|
|
||||||
{
|
var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI;
|
||||||
aiController.Stop();
|
if (aiController != null)
|
||||||
if (enableDebug) Debug.Log("[SA_CastShield] AI stopped");
|
{
|
||||||
}
|
aiController.Stop();
|
||||||
|
if (enableDebug) Debug.Log("[SA_CastShield] AI stopped");
|
||||||
var animator = fsmBehaviour.transform.GetComponent<Animator>();
|
}
|
||||||
if (animator != null && !string.IsNullOrEmpty(animatorTrigger))
|
|
||||||
{
|
if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool))
|
||||||
animator.SetTrigger(animatorTrigger);
|
{
|
||||||
if (enableDebug) Debug.Log($"[SA_CastShield] Set trigger: {animatorTrigger}");
|
npcAnimator.SetBool(animatorBlockingBool, true);
|
||||||
}
|
if (enableDebug) Debug.Log($"[SA_CastShield] Set bool: {animatorBlockingBool} = true");
|
||||||
|
}
|
||||||
SpawnShieldEffect(fsmBehaviour);
|
|
||||||
|
SpawnShieldEffect(fsmBehaviour);
|
||||||
shieldStartTime = Time.time;
|
|
||||||
shieldActive = true;
|
shieldStartTime = Time.time;
|
||||||
}
|
shieldActive = true;
|
||||||
|
}
|
||||||
/// <summary>
|
|
||||||
/// Called every frame during state duration
|
/// <summary>
|
||||||
/// </summary>
|
/// Called every frame during state duration
|
||||||
private void OnStateUpdate(vIFSMBehaviourController fsmBehaviour)
|
/// </summary>
|
||||||
{
|
private void OnStateUpdate(vIFSMBehaviourController fsmBehaviour)
|
||||||
if (shieldActive && Time.time - shieldStartTime >= shieldDuration)
|
{
|
||||||
{
|
if (shieldActive && Time.time - shieldStartTime >= shieldDuration)
|
||||||
if (enableDebug) Debug.Log("[SA_CastShield] Shield time passed, finishing state");
|
{
|
||||||
FinishShield(fsmBehaviour);
|
if (enableDebug) Debug.Log("[SA_CastShield] Shield time passed, finishing state");
|
||||||
}
|
FinishShield(fsmBehaviour);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
|
||||||
/// Called when exiting state - cleanup
|
/// <summary>
|
||||||
/// </summary>
|
/// Called when exiting state - cleanup
|
||||||
private void OnStateExit(vIFSMBehaviourController fsmBehaviour)
|
/// </summary>
|
||||||
{
|
private void OnStateExit(vIFSMBehaviourController fsmBehaviour)
|
||||||
if (enableDebug) Debug.Log("[SA_CastShield] Exiting shield state");
|
{
|
||||||
|
if (enableDebug) Debug.Log("[SA_CastShield] Exiting shield state");
|
||||||
if (shieldActive)
|
|
||||||
{
|
if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool))
|
||||||
CleanupShield();
|
{
|
||||||
}
|
npcAnimator.SetBool(animatorBlockingBool, false);
|
||||||
|
if (enableDebug) Debug.Log($"[SA_CastShield] Set bool: {animatorBlockingBool} = false");
|
||||||
var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI;
|
}
|
||||||
if (aiController != null)
|
|
||||||
{
|
if (shieldActive)
|
||||||
if (enableDebug) Debug.Log("[SA_CastShield] AI resumed");
|
{
|
||||||
}
|
CleanupShield();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI;
|
||||||
/// Spawns magical shield particle effect
|
if (aiController != null)
|
||||||
/// </summary>
|
{
|
||||||
private void SpawnShieldEffect(vIFSMBehaviourController fsmBehaviour)
|
if (enableDebug) Debug.Log("[SA_CastShield] AI resumed");
|
||||||
{
|
}
|
||||||
if (shieldFXPrefab == null)
|
}
|
||||||
{
|
|
||||||
Debug.LogWarning("[SA_CastShield] Missing shieldFXPrefab!");
|
/// <summary>
|
||||||
return;
|
/// Spawns magical shield particle effect
|
||||||
}
|
/// </summary>
|
||||||
|
private void SpawnShieldEffect(vIFSMBehaviourController fsmBehaviour)
|
||||||
Vector3 spawnPosition = shieldSpawnPoint != null ?
|
{
|
||||||
shieldSpawnPoint.position : fsmBehaviour.transform.position;
|
if (shieldFXPrefab == null)
|
||||||
|
{
|
||||||
spawnedShield = LeanPool.Spawn(shieldFXPrefab, spawnPosition,
|
Debug.LogWarning("[SA_CastShield] Missing shieldFXPrefab!");
|
||||||
shieldSpawnPoint != null ? shieldSpawnPoint.rotation : fsmBehaviour.transform.rotation);
|
return;
|
||||||
|
}
|
||||||
if (enableDebug) Debug.Log($"[SA_CastShield] Shield spawned at position: {spawnPosition}");
|
|
||||||
|
// Spawn shield at NPC's position and rotation
|
||||||
if (spawnedShield != null && shieldSpawnPoint != null)
|
Vector3 spawnPosition = npcTransform.position;
|
||||||
{
|
Quaternion spawnRotation = npcTransform.rotation;
|
||||||
spawnedShield.transform.SetParent(shieldSpawnPoint);
|
|
||||||
}
|
spawnedShield = LeanPool.Spawn(shieldFXPrefab, spawnPosition, spawnRotation);
|
||||||
}
|
|
||||||
|
if (enableDebug) Debug.Log($"[SA_CastShield] Shield spawned at NPC position: {spawnPosition}");
|
||||||
/// <summary>
|
}
|
||||||
/// Finishes shield operation and transitions to next state
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
private void FinishShield(vIFSMBehaviourController fsmBehaviour)
|
/// Finishes shield operation and transitions to next state
|
||||||
{
|
/// </summary>
|
||||||
shieldActive = false;
|
private void FinishShield(vIFSMBehaviourController fsmBehaviour)
|
||||||
CleanupShield();
|
{
|
||||||
|
shieldActive = false;
|
||||||
DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Shield", 15f);
|
CleanupShield();
|
||||||
|
|
||||||
// End state - FSM will transition to next state
|
DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Shield", 15f);
|
||||||
// FYI: In Invector FSM, state completion is handled automatically
|
|
||||||
}
|
// End state - FSM will transition to next state
|
||||||
|
// FYI: In Invector FSM, state completion is handled automatically
|
||||||
/// <summary>
|
}
|
||||||
/// Cleans up spawned shield
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
private void CleanupShield()
|
/// Cleans up spawned shield
|
||||||
{
|
/// </summary>
|
||||||
if (spawnedShield != null)
|
private void CleanupShield()
|
||||||
{
|
{
|
||||||
LeanPool.Despawn(spawnedShield);
|
if (spawnedShield != null)
|
||||||
spawnedShield = null;
|
{
|
||||||
if (enableDebug) Debug.Log("[SA_CastShield] Shield despawned");
|
LeanPool.Despawn(spawnedShield);
|
||||||
}
|
spawnedShield = null;
|
||||||
}
|
if (enableDebug) Debug.Log("[SA_CastShield] Shield despawned");
|
||||||
|
}
|
||||||
/// <summary>
|
}
|
||||||
/// Checks if shield is currently active
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
public bool IsShieldActive()
|
/// Checks if shield is currently active
|
||||||
{
|
/// </summary>
|
||||||
return shieldActive;
|
public bool IsShieldActive()
|
||||||
}
|
{
|
||||||
|
return shieldActive;
|
||||||
/// <summary>
|
}
|
||||||
/// Returns remaining shield time
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
public float GetRemainingShieldTime()
|
/// Returns remaining shield time
|
||||||
{
|
/// </summary>
|
||||||
if (!shieldActive) return 0f;
|
public float GetRemainingShieldTime()
|
||||||
return Mathf.Max(0f, shieldDuration - (Time.time - shieldStartTime));
|
{
|
||||||
}
|
if (!shieldActive) return 0f;
|
||||||
}
|
return Mathf.Max(0f, shieldDuration - (Time.time - shieldStartTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,298 +1,322 @@
|
|||||||
using Invector.vCharacterController.AI.FSMBehaviour;
|
using Invector.vCharacterController.AI.FSMBehaviour;
|
||||||
using Lean.Pool;
|
using Lean.Pool;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace DemonBoss.Magic
|
namespace DemonBoss.Magic
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// StateAction for intelligent crystal turret spawning
|
/// StateAction for intelligent crystal turret spawning
|
||||||
/// Searches for optimal position in 2-6m ring from boss, prefers "behind boss" relative to player
|
/// Searches for optimal position in 2-6m ring from boss, prefers "behind boss" relative to player
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Spawn Turret Smart")]
|
[CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Spawn Turret Smart")]
|
||||||
public class SA_SpawnTurretSmart : vStateAction
|
public class SA_SpawnTurretSmart : vStateAction
|
||||||
{
|
{
|
||||||
public override string categoryName => "DemonBoss/Magic";
|
public override string categoryName => "DemonBoss/Magic";
|
||||||
public override string defaultName => "Spawn Turret Smart";
|
public override string defaultName => "Spawn Turret Smart";
|
||||||
|
|
||||||
[Header("Turret Configuration")]
|
[Header("Turret Configuration")]
|
||||||
[Tooltip("Crystal prefab with CrystalShooterAI component")]
|
[Tooltip("Crystal prefab with CrystalShooterAI component")]
|
||||||
public GameObject crystalPrefab;
|
public GameObject crystalPrefab;
|
||||||
|
|
||||||
[Tooltip("Minimum distance from boss for crystal spawn")]
|
[Tooltip("Minimum distance from boss for crystal spawn")]
|
||||||
public float minSpawnDistance = 2f;
|
public float minSpawnDistance = 2f;
|
||||||
|
|
||||||
[Tooltip("Maximum distance from boss for crystal spawn")]
|
[Tooltip("Maximum distance from boss for crystal spawn")]
|
||||||
public float maxSpawnDistance = 6f;
|
public float maxSpawnDistance = 6f;
|
||||||
|
|
||||||
[Tooltip("Collision check radius when choosing position")]
|
[Tooltip("Collision check radius when choosing position")]
|
||||||
public float obstacleCheckRadius = 1f;
|
public float obstacleCheckRadius = 1f;
|
||||||
|
|
||||||
[Tooltip("Height above ground for raycast ground checking")]
|
[Tooltip("Height above ground for raycast ground checking")]
|
||||||
public float groundCheckHeight = 2f;
|
public float groundCheckHeight = 2f;
|
||||||
|
|
||||||
[Tooltip("Layer mask for obstacles")]
|
[Tooltip("Layer mask for obstacles")]
|
||||||
public LayerMask obstacleLayerMask = -1;
|
public LayerMask obstacleLayerMask = -1;
|
||||||
|
|
||||||
[Tooltip("Layer mask for ground")]
|
[Tooltip("Layer mask for ground")]
|
||||||
public LayerMask groundLayerMask = -1;
|
public LayerMask groundLayerMask = -1;
|
||||||
|
|
||||||
[Tooltip("Animator trigger name for crystal casting animation")]
|
[Tooltip("Animator bool parameter name for blocking state")]
|
||||||
public string animatorTrigger = "CastCrystal";
|
public string animatorBlockingBool = "IsBlocking";
|
||||||
|
|
||||||
[Header("Smart Positioning")]
|
[Header("Smart Positioning")]
|
||||||
[Tooltip("Preference multiplier for positions behind boss (relative to player)")]
|
[Tooltip("Preference multiplier for positions behind boss (relative to player)")]
|
||||||
public float backPreferenceMultiplier = 2f;
|
public float backPreferenceMultiplier = 2f;
|
||||||
|
|
||||||
[Tooltip("Number of attempts to find valid position")]
|
[Tooltip("Number of attempts to find valid position")]
|
||||||
public int maxSpawnAttempts = 12;
|
public int maxSpawnAttempts = 12;
|
||||||
|
|
||||||
[Header("Debug")]
|
[Header("Debug")]
|
||||||
[Tooltip("Enable debug logging")]
|
[Tooltip("Enable debug logging")]
|
||||||
public bool enableDebug = false;
|
public bool enableDebug = false;
|
||||||
|
|
||||||
[Tooltip("Show gizmos in Scene View")]
|
[Tooltip("Show gizmos in Scene View")]
|
||||||
public bool showGizmos = true;
|
public bool showGizmos = true;
|
||||||
|
|
||||||
private GameObject spawnedCrystal;
|
private GameObject spawnedCrystal;
|
||||||
|
private Animator npcAnimator;
|
||||||
private Transform playerTransform;
|
private Transform npcTransform;
|
||||||
|
private Transform playerTransform;
|
||||||
/// <summary>
|
|
||||||
/// Main action execution method called by FSM
|
/// <summary>
|
||||||
/// </summary>
|
/// Main action execution method called by FSM
|
||||||
public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
|
/// </summary>
|
||||||
{
|
public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
|
||||||
if (executionType == vFSMComponentExecutionType.OnStateEnter)
|
{
|
||||||
{
|
if (executionType == vFSMComponentExecutionType.OnStateEnter)
|
||||||
OnStateEnter(fsmBehaviour);
|
{
|
||||||
}
|
OnStateEnter(fsmBehaviour);
|
||||||
}
|
}
|
||||||
|
else if (executionType == vFSMComponentExecutionType.OnStateExit)
|
||||||
/// <summary>
|
{
|
||||||
/// Called when entering state - intelligently spawns crystal
|
OnStateExit(fsmBehaviour);
|
||||||
/// </summary>
|
}
|
||||||
private void OnStateEnter(vIFSMBehaviourController fsmBehaviour)
|
}
|
||||||
{
|
|
||||||
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Starting intelligent crystal spawn");
|
/// <summary>
|
||||||
|
/// Called when entering state - intelligently spawns crystal
|
||||||
FindPlayer(fsmBehaviour);
|
/// </summary>
|
||||||
|
private void OnStateEnter(vIFSMBehaviourController fsmBehaviour)
|
||||||
var animator = fsmBehaviour.transform.GetComponent<Animator>();
|
{
|
||||||
if (animator != null && !string.IsNullOrEmpty(animatorTrigger))
|
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Starting intelligent crystal spawn");
|
||||||
{
|
|
||||||
animator.SetTrigger(animatorTrigger);
|
// Store NPC references
|
||||||
if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Set trigger: {animatorTrigger}");
|
npcTransform = fsmBehaviour.transform;
|
||||||
}
|
npcAnimator = npcTransform.GetComponent<Animator>();
|
||||||
|
|
||||||
SpawnCrystalSmart(fsmBehaviour);
|
FindPlayer(fsmBehaviour);
|
||||||
|
|
||||||
DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Turret", 12f);
|
if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool))
|
||||||
}
|
{
|
||||||
|
npcAnimator.SetBool(animatorBlockingBool, true);
|
||||||
/// <summary>
|
if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Set bool: {animatorBlockingBool} = true");
|
||||||
/// Finds player transform
|
}
|
||||||
/// </summary>
|
|
||||||
private void FindPlayer(vIFSMBehaviourController fsmBehaviour)
|
SpawnCrystalSmart(fsmBehaviour);
|
||||||
{
|
|
||||||
GameObject player = GameObject.FindGameObjectWithTag("Player");
|
DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Turret", 12f);
|
||||||
if (player != null)
|
}
|
||||||
{
|
|
||||||
playerTransform = player.transform;
|
/// <summary>
|
||||||
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Player found by tag");
|
/// Called when exiting state - cleanup
|
||||||
return;
|
/// </summary>
|
||||||
}
|
private void OnStateExit(vIFSMBehaviourController fsmBehaviour)
|
||||||
|
{
|
||||||
var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI;
|
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Exiting turret spawn state");
|
||||||
if (aiController != null && aiController.currentTarget != null)
|
|
||||||
{
|
if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool))
|
||||||
playerTransform = aiController.currentTarget.transform;
|
{
|
||||||
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Player found through AI target");
|
npcAnimator.SetBool(animatorBlockingBool, false);
|
||||||
return;
|
if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Set bool: {animatorBlockingBool} = false");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Player not found!");
|
|
||||||
}
|
/// <summary>
|
||||||
|
/// Finds player transform
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Intelligently spawns crystal in optimal position
|
private void FindPlayer(vIFSMBehaviourController fsmBehaviour)
|
||||||
/// </summary>
|
{
|
||||||
private void SpawnCrystalSmart(vIFSMBehaviourController fsmBehaviour)
|
GameObject player = GameObject.FindGameObjectWithTag("Player");
|
||||||
{
|
if (player != null)
|
||||||
if (crystalPrefab == null)
|
{
|
||||||
{
|
playerTransform = player.transform;
|
||||||
Debug.LogError("[SA_SpawnTurretSmart] Missing crystalPrefab!");
|
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Player found by tag");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 bestPosition = Vector3.zero;
|
var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI;
|
||||||
bool foundValidPosition = false;
|
if (aiController != null && aiController.currentTarget != null)
|
||||||
float bestScore = float.MinValue;
|
{
|
||||||
|
playerTransform = aiController.currentTarget.transform;
|
||||||
Vector3 bossPos = fsmBehaviour.transform.position;
|
if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Player found through AI target");
|
||||||
Vector3 playerDirection = Vector3.zero;
|
return;
|
||||||
|
}
|
||||||
if (playerTransform != null)
|
|
||||||
{
|
if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Player not found!");
|
||||||
playerDirection = (playerTransform.position - bossPos).normalized;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
for (int i = 0; i < maxSpawnAttempts; i++)
|
/// Intelligently spawns crystal in optimal position
|
||||||
{
|
/// </summary>
|
||||||
float angle = (360f / maxSpawnAttempts) * i + Random.Range(-15f, 15f);
|
private void SpawnCrystalSmart(vIFSMBehaviourController fsmBehaviour)
|
||||||
Vector3 direction = new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0, Mathf.Sin(angle * Mathf.Deg2Rad));
|
{
|
||||||
|
if (crystalPrefab == null)
|
||||||
float distance = Random.Range(minSpawnDistance, maxSpawnDistance);
|
{
|
||||||
Vector3 testPosition = bossPos + direction * distance;
|
Debug.LogError("[SA_SpawnTurretSmart] Missing crystalPrefab!");
|
||||||
|
return;
|
||||||
if (IsPositionValid(testPosition, out Vector3 groundPosition))
|
}
|
||||||
{
|
|
||||||
float score = EvaluatePosition(groundPosition, playerDirection, direction);
|
Vector3 bestPosition = Vector3.zero;
|
||||||
|
bool foundValidPosition = false;
|
||||||
if (score > bestScore)
|
float bestScore = float.MinValue;
|
||||||
{
|
|
||||||
bestScore = score;
|
Vector3 bossPos = npcTransform.position;
|
||||||
bestPosition = groundPosition;
|
Vector3 playerDirection = Vector3.zero;
|
||||||
foundValidPosition = true;
|
|
||||||
}
|
if (playerTransform != null)
|
||||||
}
|
{
|
||||||
}
|
playerDirection = (playerTransform.position - bossPos).normalized;
|
||||||
|
}
|
||||||
if (foundValidPosition)
|
|
||||||
{
|
for (int i = 0; i < maxSpawnAttempts; i++)
|
||||||
SpawnCrystal(bestPosition, fsmBehaviour);
|
{
|
||||||
if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Crystal spawned at position: {bestPosition} (score: {bestScore:F2})");
|
float angle = (360f / maxSpawnAttempts) * i + Random.Range(-15f, 15f);
|
||||||
}
|
Vector3 direction = new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0, Mathf.Sin(angle * Mathf.Deg2Rad));
|
||||||
else
|
|
||||||
{
|
float distance = Random.Range(minSpawnDistance, maxSpawnDistance);
|
||||||
Vector3 fallbackPos = bossPos + fsmBehaviour.transform.forward * minSpawnDistance;
|
Vector3 testPosition = bossPos + direction * distance;
|
||||||
SpawnCrystal(fallbackPos, fsmBehaviour);
|
|
||||||
if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Using fallback position");
|
if (IsPositionValid(testPosition, out Vector3 groundPosition))
|
||||||
}
|
{
|
||||||
}
|
float score = EvaluatePosition(groundPosition, playerDirection, direction, bossPos);
|
||||||
|
|
||||||
/// <summary>
|
if (score > bestScore)
|
||||||
/// Checks if position is valid (no obstacles, has ground)
|
{
|
||||||
/// </summary>
|
bestScore = score;
|
||||||
private bool IsPositionValid(Vector3 position, out Vector3 groundPosition)
|
bestPosition = groundPosition;
|
||||||
{
|
foundValidPosition = true;
|
||||||
groundPosition = position;
|
}
|
||||||
|
}
|
||||||
if (Physics.CheckSphere(position + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask))
|
}
|
||||||
{
|
|
||||||
return false;
|
if (foundValidPosition)
|
||||||
}
|
{
|
||||||
|
SpawnCrystal(bestPosition, fsmBehaviour);
|
||||||
Ray groundRay = new Ray(position + Vector3.up * groundCheckHeight, Vector3.down);
|
if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Crystal spawned at position: {bestPosition} (score: {bestScore:F2})");
|
||||||
if (Physics.Raycast(groundRay, out RaycastHit hit, groundCheckHeight + 2f, groundLayerMask))
|
}
|
||||||
{
|
else
|
||||||
groundPosition = hit.point;
|
{
|
||||||
|
Vector3 fallbackPos = bossPos + npcTransform.forward * minSpawnDistance;
|
||||||
if (Physics.CheckSphere(groundPosition + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask))
|
SpawnCrystal(fallbackPos, fsmBehaviour);
|
||||||
{
|
if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Using fallback position");
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
/// <summary>
|
||||||
}
|
/// Checks if position is valid (no obstacles, has ground)
|
||||||
|
/// </summary>
|
||||||
return false;
|
private bool IsPositionValid(Vector3 position, out Vector3 groundPosition)
|
||||||
}
|
{
|
||||||
|
groundPosition = position;
|
||||||
/// <summary>
|
|
||||||
/// Evaluates position quality (higher score = better position)
|
if (Physics.CheckSphere(position + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask))
|
||||||
/// </summary>
|
{
|
||||||
private float EvaluatePosition(Vector3 position, Vector3 playerDirection, Vector3 positionDirection)
|
return false;
|
||||||
{
|
}
|
||||||
float score = 0f;
|
|
||||||
|
Ray groundRay = new Ray(position + Vector3.up * groundCheckHeight, Vector3.down);
|
||||||
if (playerTransform != null && playerDirection != Vector3.zero)
|
if (Physics.Raycast(groundRay, out RaycastHit hit, groundCheckHeight + 2f, groundLayerMask))
|
||||||
{
|
{
|
||||||
float angleToPlayer = Vector3.Angle(-playerDirection, positionDirection);
|
groundPosition = hit.point;
|
||||||
|
|
||||||
// The smaller the angle (closer to "behind"), the better the score
|
if (Physics.CheckSphere(groundPosition + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask))
|
||||||
float backScore = (180f - angleToPlayer) / 180f;
|
{
|
||||||
score += backScore * backPreferenceMultiplier;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 bossPos = new Vector3();
|
return true;
|
||||||
float distance = Vector3.Distance(position, bossPos);
|
}
|
||||||
float optimalDistance = (minSpawnDistance + maxSpawnDistance) * 0.5f;
|
|
||||||
float distanceScore = 1f - Mathf.Abs(distance - optimalDistance) / maxSpawnDistance;
|
return false;
|
||||||
score += distanceScore;
|
}
|
||||||
|
|
||||||
score += Random.Range(-0.1f, 0.1f);
|
/// <summary>
|
||||||
|
/// Evaluates position quality (higher score = better position)
|
||||||
return score;
|
/// </summary>
|
||||||
}
|
private float EvaluatePosition(Vector3 position, Vector3 playerDirection, Vector3 positionDirection, Vector3 bossPos)
|
||||||
|
{
|
||||||
/// <summary>
|
float score = 0f;
|
||||||
/// Spawns crystal at given position
|
|
||||||
/// </summary>
|
if (playerTransform != null && playerDirection != Vector3.zero)
|
||||||
private void SpawnCrystal(Vector3 position, vIFSMBehaviourController fsmBehaviour)
|
{
|
||||||
{
|
float angleToPlayer = Vector3.Angle(-playerDirection, positionDirection);
|
||||||
Quaternion rotation = Quaternion.identity;
|
|
||||||
if (playerTransform != null)
|
// The smaller the angle (closer to "behind"), the better the score
|
||||||
{
|
float backScore = (180f - angleToPlayer) / 180f;
|
||||||
Vector3 lookDirection = (playerTransform.position - position).normalized;
|
score += backScore * backPreferenceMultiplier;
|
||||||
lookDirection.y = 0;
|
}
|
||||||
if (lookDirection != Vector3.zero)
|
|
||||||
{
|
float distance = Vector3.Distance(position, bossPos);
|
||||||
rotation = Quaternion.LookRotation(lookDirection);
|
float optimalDistance = (minSpawnDistance + maxSpawnDistance) * 0.5f;
|
||||||
}
|
float distanceScore = 1f - Mathf.Abs(distance - optimalDistance) / maxSpawnDistance;
|
||||||
}
|
score += distanceScore;
|
||||||
|
|
||||||
spawnedCrystal = LeanPool.Spawn(crystalPrefab, position, rotation);
|
score += Random.Range(-0.1f, 0.1f);
|
||||||
|
|
||||||
var shooterAI = spawnedCrystal.GetComponent<CrystalShooterAI>();
|
return score;
|
||||||
if (shooterAI == null)
|
}
|
||||||
{
|
|
||||||
Debug.LogError("[SA_SpawnTurretSmart] Crystal prefab doesn't have CrystalShooterAI component!");
|
/// <summary>
|
||||||
}
|
/// Spawns crystal at given position
|
||||||
else
|
/// </summary>
|
||||||
{
|
private void SpawnCrystal(Vector3 position, vIFSMBehaviourController fsmBehaviour)
|
||||||
if (playerTransform != null)
|
{
|
||||||
{
|
Quaternion rotation = Quaternion.identity;
|
||||||
shooterAI.SetTarget(playerTransform);
|
if (playerTransform != null)
|
||||||
}
|
{
|
||||||
}
|
Vector3 lookDirection = (playerTransform.position - position).normalized;
|
||||||
}
|
lookDirection.y = 0;
|
||||||
|
if (lookDirection != Vector3.zero)
|
||||||
/// <summary>
|
{
|
||||||
/// Draws gizmos in Scene View for debugging
|
rotation = Quaternion.LookRotation(lookDirection);
|
||||||
/// </summary>
|
}
|
||||||
private void OnDrawGizmosSelected()
|
}
|
||||||
{
|
|
||||||
if (!showGizmos) return;
|
spawnedCrystal = LeanPool.Spawn(crystalPrefab, position, rotation);
|
||||||
|
|
||||||
Vector3 pos = new Vector3();
|
var shooterAI = spawnedCrystal.GetComponent<CrystalShooterAI>();
|
||||||
|
if (shooterAI == null)
|
||||||
// Spawn ring
|
{
|
||||||
Gizmos.color = Color.green;
|
Debug.LogError("[SA_SpawnTurretSmart] Crystal prefab doesn't have CrystalShooterAI component!");
|
||||||
DrawWireCircle(pos, minSpawnDistance);
|
}
|
||||||
Gizmos.color = Color.red;
|
else
|
||||||
DrawWireCircle(pos, maxSpawnDistance);
|
{
|
||||||
|
if (playerTransform != null)
|
||||||
// Obstacle check radius
|
{
|
||||||
Gizmos.color = Color.yellow;
|
shooterAI.SetTarget(playerTransform);
|
||||||
Gizmos.DrawWireSphere(pos + Vector3.up * obstacleCheckRadius, obstacleCheckRadius);
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
|
||||||
/// Helper method for drawing circles
|
/// <summary>
|
||||||
/// </summary>
|
/// Draws gizmos in Scene View for debugging
|
||||||
private void DrawWireCircle(Vector3 center, float radius)
|
/// </summary>
|
||||||
{
|
private void OnDrawGizmosSelected()
|
||||||
int segments = 32;
|
{
|
||||||
float angle = 0f;
|
if (!showGizmos) return;
|
||||||
Vector3 prevPoint = center + new Vector3(radius, 0, 0);
|
|
||||||
|
if (npcTransform != null)
|
||||||
for (int i = 1; i <= segments; i++)
|
{
|
||||||
{
|
Vector3 pos = npcTransform.position;
|
||||||
angle = (float)i / segments * 360f * Mathf.Deg2Rad;
|
|
||||||
Vector3 newPoint = center + new Vector3(Mathf.Cos(angle) * radius, 0, Mathf.Sin(angle) * radius);
|
// Spawn ring
|
||||||
Gizmos.DrawLine(prevPoint, newPoint);
|
Gizmos.color = Color.green;
|
||||||
prevPoint = newPoint;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ GameObject:
|
|||||||
- component: {fileID: 2323612}
|
- component: {fileID: 2323612}
|
||||||
- component: {fileID: 13662188}
|
- component: {fileID: 13662188}
|
||||||
- component: {fileID: 7020133711031364094}
|
- component: {fileID: 7020133711031364094}
|
||||||
m_Layer: 0
|
m_Layer: 26
|
||||||
m_Name: Turet
|
m_Name: Turet
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
@@ -32,7 +32,8 @@ Transform:
|
|||||||
m_LocalPosition: {x: -3.8250632, y: -7.1673625e-17, z: 3.2278929}
|
m_LocalPosition: {x: -3.8250632, y: -7.1673625e-17, z: 3.2278929}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children:
|
||||||
|
- {fileID: 8411050209285398990}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!33 &3365858
|
--- !u!33 &3365858
|
||||||
@@ -123,20 +124,51 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 906d915677721914dbe9708f218f574a, type: 3}
|
m_Script: {fileID: 11500000, guid: 906d915677721914dbe9708f218f574a, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
muzzle: {fileID: 417168}
|
muzzle: {fileID: 8411050209285398990}
|
||||||
fireballPrefab: {fileID: 1947871717301538, guid: 9591667a35466484096c6e63785e136c,
|
fireballPrefab: {fileID: 1947871717301538, guid: 9591667a35466484096c6e63785e136c,
|
||||||
type: 3}
|
type: 3}
|
||||||
fireballSpeed: 28
|
fireRate: 5
|
||||||
fireRate: 0.7
|
maxShots: 3
|
||||||
maxShots: 10
|
despawnDelay: 10
|
||||||
despawnDelay: 3
|
|
||||||
turnSpeed: 120
|
turnSpeed: 120
|
||||||
idleSpinSpeed: 30
|
idleSpinSpeed: 30
|
||||||
aimTolerance: 5
|
aimTolerance: 5
|
||||||
autoFindPlayer: 1
|
autoFindPlayer: 1
|
||||||
playerTag: Player
|
playerTag: Player
|
||||||
maxShootingRange: 50
|
maxShootingRange: 50
|
||||||
|
useShootEffects: 0
|
||||||
muzzleFlashPrefab: {fileID: 0}
|
muzzleFlashPrefab: {fileID: 0}
|
||||||
shootSound: {fileID: 8300000, guid: d44a96f293b66b74ea13a2e6fcc6c8fa, type: 3}
|
shootSound: {fileID: 8300000, guid: d44a96f293b66b74ea13a2e6fcc6c8fa, type: 3}
|
||||||
enableDebug: 0
|
enableDebug: 0
|
||||||
showGizmos: 1
|
showGizmos: 1
|
||||||
|
--- !u!1 &6534933121460188189
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 8411050209285398990}
|
||||||
|
m_Layer: 26
|
||||||
|
m_Name: Spawnpoint
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &8411050209285398990
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 6534933121460188189}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 3.114, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 417168}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1640
Assets/AI/FSM/FSM_Giant.asset
Normal file
1640
Assets/AI/FSM/FSM_Giant.asset
Normal file
File diff suppressed because it is too large
Load Diff
8
Assets/AI/FSM/FSM_Giant.asset.meta
Normal file
8
Assets/AI/FSM/FSM_Giant.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8a6b7d8e31ffda843b06eaa079b5ed5d
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/AI/Gigant.meta
Normal file
8
Assets/AI/Gigant.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b9f57b9ef3a51cd4285f9c4514b79802
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
5566
Assets/AI/Gigant/Giant.prefab
Normal file
5566
Assets/AI/Gigant/Giant.prefab
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/AI/Gigant/Giant.prefab.meta
Normal file
7
Assets/AI/Gigant/Giant.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 05db168aa73c6d7489b4930e8ac0f2ce
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
23
Assets/AI/Gigant/LookAtPlayerInitialization.cs
Normal file
23
Assets/AI/Gigant/LookAtPlayerInitialization.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using RootMotion.FinalIK;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class LookAtPlayerInitialization : MonoBehaviour
|
||||||
|
{
|
||||||
|
public LookAtIK lookAtIK;
|
||||||
|
public string playerTag = "Player";
|
||||||
|
private Transform player;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
GameObject playerObj = GameObject.FindGameObjectWithTag(playerTag);
|
||||||
|
if (playerObj != null)
|
||||||
|
{
|
||||||
|
player = playerObj.transform;
|
||||||
|
if (lookAtIK != null)
|
||||||
|
{
|
||||||
|
lookAtIK.solver.target = player;
|
||||||
|
lookAtIK.solver.IKPositionWeight = 1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/AI/Gigant/LookAtPlayerInitialization.cs.meta
Normal file
2
Assets/AI/Gigant/LookAtPlayerInitialization.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 529e37587f83e4b4893a5814cd0cb915
|
||||||
@@ -15,7 +15,7 @@ MonoBehaviour:
|
|||||||
m_DefaultGroup: bc074d00982114f0388e9c98de91d44f
|
m_DefaultGroup: bc074d00982114f0388e9c98de91d44f
|
||||||
m_currentHash:
|
m_currentHash:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
Hash: 1efe8cf9686e6b90ba179faa51fb53d2
|
Hash: ef672e941747c2eb6b19db828d740a6a
|
||||||
m_OptimizeCatalogSize: 0
|
m_OptimizeCatalogSize: 0
|
||||||
m_BuildRemoteCatalog: 0
|
m_BuildRemoteCatalog: 0
|
||||||
m_CatalogRequestsTimeout: 0
|
m_CatalogRequestsTimeout: 0
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ Material:
|
|||||||
- _CastShadows: 1
|
- _CastShadows: 1
|
||||||
- _ClearCoatMask: 0
|
- _ClearCoatMask: 0
|
||||||
- _ClearCoatSmoothness: 0
|
- _ClearCoatSmoothness: 0
|
||||||
- _Cull: 2
|
- _Cull: 0
|
||||||
- _Cutoff: 0.5
|
- _Cutoff: 0.5
|
||||||
- _DetailAlbedoMapScale: 1
|
- _DetailAlbedoMapScale: 1
|
||||||
- _DetailNormalMapScale: 1
|
- _DetailNormalMapScale: 1
|
||||||
@@ -156,7 +156,7 @@ Material:
|
|||||||
- _WorkflowMode: 1
|
- _WorkflowMode: 1
|
||||||
- _ZTest: 4
|
- _ZTest: 4
|
||||||
- _ZWrite: 1
|
- _ZWrite: 1
|
||||||
- _ZWriteControl: 0
|
- _ZWriteControl: 1
|
||||||
m_Colors:
|
m_Colors:
|
||||||
- BaseColor: {r: 0.7830189, g: 0.6195203, b: 0.5798772, a: 0}
|
- BaseColor: {r: 0.7830189, g: 0.6195203, b: 0.5798772, a: 0}
|
||||||
- Color_613d1588816440ec9b17710effb7528b: {r: 0, g: 13.98681, b: 714.8679, a: 0}
|
- Color_613d1588816440ec9b17710effb7528b: {r: 0, g: 13.98681, b: 714.8679, a: 0}
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user