Demon part (2/3) missing files
This commit is contained in:
379
Assets/AI/Demon/CrystalShooterAI.cs
Normal file
379
Assets/AI/Demon/CrystalShooterAI.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using Lean.Pool;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
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
|
||||
{
|
||||
[Header("Shooting Configuration")]
|
||||
[Tooltip("Transform point from which projectiles are fired")]
|
||||
public Transform muzzle;
|
||||
|
||||
[Tooltip("Fireball prefab")]
|
||||
public GameObject fireballPrefab;
|
||||
|
||||
[Tooltip("Fireball speed in m/s")]
|
||||
public float fireballSpeed = 28f;
|
||||
|
||||
[Tooltip("Time between shots in seconds")]
|
||||
public float fireRate = 0.7f;
|
||||
|
||||
[Tooltip("Maximum number of shots before auto-despawn")]
|
||||
public int maxShots = 10;
|
||||
|
||||
[Tooltip("Wait time after shooting before despawn")]
|
||||
public float despawnDelay = 3f;
|
||||
|
||||
[Header("Rotation Configuration")]
|
||||
[Tooltip("Target rotation speed in degrees/s")]
|
||||
public float turnSpeed = 120f;
|
||||
|
||||
[Tooltip("Idle spin speed when no target (degrees/s, 0 = disabled)")]
|
||||
public float idleSpinSpeed = 30f;
|
||||
|
||||
[Tooltip("Aiming accuracy in degrees (smaller value = more accurate)")]
|
||||
public float aimTolerance = 5f;
|
||||
|
||||
[Header("Targeting")]
|
||||
[Tooltip("Automatically find player on start")]
|
||||
public bool autoFindPlayer = true;
|
||||
|
||||
[Tooltip("Player tag to search for")]
|
||||
public string playerTag = "Player";
|
||||
|
||||
[Tooltip("Maximum shooting range")]
|
||||
public float maxShootingRange = 50f;
|
||||
|
||||
[Header("Effects")]
|
||||
[Tooltip("Particle effect at shot")]
|
||||
public GameObject muzzleFlashPrefab;
|
||||
|
||||
[Tooltip("Shoot sound")]
|
||||
public AudioClip shootSound;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("Enable debug logging")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
[Tooltip("Show gizmos in Scene View")]
|
||||
public bool showGizmos = true;
|
||||
|
||||
// Private variables
|
||||
private Transform target;
|
||||
|
||||
private AudioSource audioSource;
|
||||
private Coroutine shootingCoroutine;
|
||||
private bool isActive = false;
|
||||
private int shotsFired = 0;
|
||||
private float lastShotTime = 0f;
|
||||
|
||||
private Transform crystalTransform;
|
||||
|
||||
/// <summary>
|
||||
/// Component initialization
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
crystalTransform = transform;
|
||||
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
if (audioSource == null && shootSound != null)
|
||||
{
|
||||
audioSource = gameObject.AddComponent<AudioSource>();
|
||||
audioSource.playOnAwake = false;
|
||||
audioSource.spatialBlend = 1f;
|
||||
}
|
||||
|
||||
if (muzzle == null)
|
||||
{
|
||||
Transform muzzleChild = crystalTransform.Find("muzzle");
|
||||
if (muzzleChild != null)
|
||||
{
|
||||
muzzle = muzzleChild;
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Found muzzle child");
|
||||
}
|
||||
else
|
||||
{
|
||||
muzzle = crystalTransform;
|
||||
if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Using crystal center as muzzle");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start - begin crystal operation
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Crystal activated");
|
||||
|
||||
if (autoFindPlayer && target == null)
|
||||
{
|
||||
FindPlayer();
|
||||
}
|
||||
|
||||
StartShooting();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update - rotate crystal towards target
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (!isActive) return;
|
||||
|
||||
RotateTowardsTarget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find player automatically
|
||||
/// </summary>
|
||||
private void FindPlayer()
|
||||
{
|
||||
GameObject player = GameObject.FindGameObjectWithTag(playerTag);
|
||||
if (player != null)
|
||||
{
|
||||
SetTarget(player.transform);
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Automatically found player");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Cannot find player with tag: " + playerTag);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set target for crystal
|
||||
/// </summary>
|
||||
/// <param name="newTarget">Target transform</param>
|
||||
public void SetTarget(Transform newTarget)
|
||||
{
|
||||
target = newTarget;
|
||||
if (enableDebug && target != null)
|
||||
{
|
||||
Debug.Log($"[CrystalShooterAI] Set target: {target.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start shooting cycle
|
||||
/// </summary>
|
||||
public void StartShooting()
|
||||
{
|
||||
if (isActive) return;
|
||||
|
||||
isActive = true;
|
||||
shotsFired = 0;
|
||||
|
||||
shootingCoroutine = StartCoroutine(ShootingCoroutine());
|
||||
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Starting shooting");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop shooting
|
||||
/// </summary>
|
||||
public void StopShooting()
|
||||
{
|
||||
isActive = false;
|
||||
|
||||
if (shootingCoroutine != null)
|
||||
{
|
||||
StopCoroutine(shootingCoroutine);
|
||||
shootingCoroutine = null;
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Stopped shooting");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main coroutine handling shooting cycle
|
||||
/// </summary>
|
||||
private IEnumerator ShootingCoroutine()
|
||||
{
|
||||
while (shotsFired < maxShots && isActive)
|
||||
{
|
||||
if (CanShoot())
|
||||
{
|
||||
FireFireball();
|
||||
shotsFired++;
|
||||
lastShotTime = Time.time;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(fireRate);
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[CrystalShooterAI] Finished shooting ({shotsFired} shots)");
|
||||
|
||||
yield return new WaitForSeconds(despawnDelay);
|
||||
|
||||
DespawnCrystal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if crystal can shoot
|
||||
/// </summary>
|
||||
private bool CanShoot()
|
||||
{
|
||||
if (target == null) return false;
|
||||
|
||||
float distanceToTarget = Vector3.Distance(crystalTransform.position, target.position);
|
||||
if (distanceToTarget > maxShootingRange) return false;
|
||||
|
||||
Vector3 directionToTarget = (target.position - crystalTransform.position).normalized;
|
||||
float angleToTarget = Vector3.Angle(crystalTransform.forward, directionToTarget);
|
||||
|
||||
return angleToTarget <= aimTolerance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires fireball towards target
|
||||
/// </summary>
|
||||
private void FireFireball()
|
||||
{
|
||||
if (fireballPrefab == null || muzzle == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Missing fireball prefab or muzzle");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 shootDirection = crystalTransform.forward;
|
||||
if (target != null)
|
||||
{
|
||||
Vector3 targetCenter = target.position + Vector3.up * 1f;
|
||||
shootDirection = (targetCenter - muzzle.position).normalized;
|
||||
}
|
||||
|
||||
GameObject fireball = LeanPool.Spawn(fireballPrefab, muzzle.position,
|
||||
Quaternion.LookRotation(shootDirection));
|
||||
|
||||
Rigidbody fireballRb = fireball.GetComponent<Rigidbody>();
|
||||
if (fireballRb != null)
|
||||
{
|
||||
fireballRb.linearVelocity = shootDirection * fireballSpeed;
|
||||
}
|
||||
|
||||
PlayShootEffects();
|
||||
|
||||
if (enableDebug)
|
||||
{
|
||||
Debug.Log($"[CrystalShooterAI] Shot #{shotsFired + 1} in direction: {shootDirection}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays shooting effects
|
||||
/// </summary>
|
||||
private void PlayShootEffects()
|
||||
{
|
||||
if (muzzleFlashPrefab != null && muzzle != null)
|
||||
{
|
||||
GameObject flash = LeanPool.Spawn(muzzleFlashPrefab, muzzle.position, muzzle.rotation);
|
||||
|
||||
LeanPool.Despawn(flash, 2f);
|
||||
}
|
||||
|
||||
if (audioSource != null && shootSound != null)
|
||||
{
|
||||
audioSource.PlayOneShot(shootSound);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates crystal towards target or performs idle spin
|
||||
/// </summary>
|
||||
private void RotateTowardsTarget()
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
Vector3 directionToTarget = target.position - crystalTransform.position;
|
||||
directionToTarget.y = 0;
|
||||
|
||||
if (directionToTarget != Vector3.zero)
|
||||
{
|
||||
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
|
||||
crystalTransform.rotation = Quaternion.RotateTowards(
|
||||
crystalTransform.rotation,
|
||||
targetRotation,
|
||||
turnSpeed * Time.deltaTime
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (idleSpinSpeed > 0f)
|
||||
{
|
||||
crystalTransform.Rotate(Vector3.up, idleSpinSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns crystal from map
|
||||
/// </summary>
|
||||
public void DespawnCrystal()
|
||||
{
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Despawning crystal");
|
||||
|
||||
StopShooting();
|
||||
|
||||
LeanPool.Despawn(gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces immediate despawn (e.g. on boss death)
|
||||
/// </summary>
|
||||
public void ForceDespawn()
|
||||
{
|
||||
if (enableDebug) Debug.Log("[CrystalShooterAI] Forced despawn");
|
||||
DespawnCrystal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns crystal state information
|
||||
/// </summary>
|
||||
public bool IsActive() => isActive;
|
||||
|
||||
public int GetShotsFired() => shotsFired;
|
||||
|
||||
public int GetRemainingShots() => Mathf.Max(0, maxShots - shotsFired);
|
||||
|
||||
public float GetTimeSinceLastShot() => Time.time - lastShotTime;
|
||||
|
||||
/// <summary>
|
||||
/// Draws gizmos in Scene View
|
||||
/// </summary>
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!showGizmos) return;
|
||||
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireSphere(transform.position, maxShootingRange);
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawLine(transform.position, target.position);
|
||||
|
||||
if (muzzle != null)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Vector3 forward = transform.forward * 5f;
|
||||
Vector3 right = Quaternion.AngleAxis(aimTolerance, transform.up) * forward;
|
||||
Vector3 left = Quaternion.AngleAxis(-aimTolerance, transform.up) * forward;
|
||||
|
||||
Gizmos.DrawLine(muzzle.position, muzzle.position + right);
|
||||
Gizmos.DrawLine(muzzle.position, muzzle.position + left);
|
||||
}
|
||||
}
|
||||
|
||||
if (muzzle != null)
|
||||
{
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawWireSphere(muzzle.position, 0.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/AI/Demon/CrystalShooterAI.cs.meta
Normal file
2
Assets/AI/Demon/CrystalShooterAI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 906d915677721914dbe9708f218f574a
|
||||
14916
Assets/AI/Demon/DemonShield.prefab
Normal file
14916
Assets/AI/Demon/DemonShield.prefab
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/AI/Demon/DemonShield.prefab.meta
Normal file
7
Assets/AI/Demon/DemonShield.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 605c856178ee7eb40a38af59915d8f6c
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9656
Assets/AI/Demon/FireBall.prefab
Normal file
9656
Assets/AI/Demon/FireBall.prefab
Normal file
File diff suppressed because it is too large
Load Diff
8
Assets/AI/Demon/FireBall.prefab.meta
Normal file
8
Assets/AI/Demon/FireBall.prefab.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9591667a35466484096c6e63785e136c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 100100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
386
Assets/AI/Demon/FireballDamage.cs
Normal file
386
Assets/AI/Demon/FireballDamage.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/AI/Demon/FireballDamage.cs.meta
Normal file
2
Assets/AI/Demon/FireballDamage.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a863cb5e6092ec4a936c4eb21bb9166
|
||||
38
Assets/AI/Demon/ShieldScaleUp.cs
Normal file
38
Assets/AI/Demon/ShieldScaleUp.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class ShieldScaleUp : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Time it takes to fully grow the shield")]
|
||||
public float growDuration = 0.5f;
|
||||
|
||||
[Tooltip("Curve to control growth speed over time")]
|
||||
public AnimationCurve scaleCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||||
|
||||
private float timer = 0f;
|
||||
private Vector3 targetScale;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
targetScale = transform.localScale;
|
||||
transform.localScale = Vector3.zero;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
timer = 0f;
|
||||
transform.localScale = Vector3.zero;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (timer < growDuration)
|
||||
{
|
||||
timer += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(timer / growDuration);
|
||||
|
||||
float scaleValue = scaleCurve.Evaluate(t);
|
||||
|
||||
transform.localScale = targetScale * scaleValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/AI/Demon/ShieldScaleUp.cs.meta
Normal file
2
Assets/AI/Demon/ShieldScaleUp.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64156f4d0e036104895ef313e63c6b09
|
||||
142
Assets/AI/Demon/Turet.prefab
Normal file
142
Assets/AI/Demon/Turet.prefab
Normal file
@@ -0,0 +1,142 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &115880
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 417168}
|
||||
- component: {fileID: 3365858}
|
||||
- component: {fileID: 2323612}
|
||||
- component: {fileID: 13662188}
|
||||
- component: {fileID: 7020133711031364094}
|
||||
m_Layer: 0
|
||||
m_Name: Turet
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 4294967295
|
||||
m_IsActive: 1
|
||||
--- !u!4 &417168
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 115880}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: -3.8250632, y: -7.1673625e-17, z: 3.2278929}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &3365858
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 115880}
|
||||
m_Mesh: {fileID: 4300002, guid: dee9a5e3bb15d7740bde7911bad2ba90, type: 3}
|
||||
--- !u!23 &2323612
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 115880}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 30d0d8732a281d349a5abc48f8171e83, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!136 &13662188
|
||||
CapsuleCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 115880}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_Radius: 0.58
|
||||
m_Height: 2.64
|
||||
m_Direction: 1
|
||||
m_Center: {x: 0.0078576505, y: 1.0259461, z: -0.052128524}
|
||||
--- !u!114 &7020133711031364094
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 115880}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 906d915677721914dbe9708f218f574a, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
muzzle: {fileID: 417168}
|
||||
fireballPrefab: {fileID: 1947871717301538, guid: 9591667a35466484096c6e63785e136c,
|
||||
type: 3}
|
||||
fireballSpeed: 28
|
||||
fireRate: 0.7
|
||||
maxShots: 10
|
||||
despawnDelay: 3
|
||||
turnSpeed: 120
|
||||
idleSpinSpeed: 30
|
||||
aimTolerance: 5
|
||||
autoFindPlayer: 1
|
||||
playerTag: Player
|
||||
maxShootingRange: 50
|
||||
muzzleFlashPrefab: {fileID: 0}
|
||||
shootSound: {fileID: 8300000, guid: d44a96f293b66b74ea13a2e6fcc6c8fa, type: 3}
|
||||
enableDebug: 0
|
||||
showGizmos: 1
|
||||
8
Assets/AI/Demon/Turet.prefab.meta
Normal file
8
Assets/AI/Demon/Turet.prefab.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5eabfbed738a224284015c33b62e54b
|
||||
timeCreated: 1431698653
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user