Files cleanup, added summoner code
This commit is contained in:
430
Assets/AI/_Archer/ArcherShootingAI.cs
Normal file
430
Assets/AI/_Archer/ArcherShootingAI.cs
Normal file
@@ -0,0 +1,430 @@
|
||||
using Lean.Pool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ArcherEnemy
|
||||
{
|
||||
/// <summary>
|
||||
/// AI component for archer enemy that shoots arrows at target
|
||||
/// Should be attached to archer enemy prefab
|
||||
/// </summary>
|
||||
public class ArcherShootingAI : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[Tooltip("Transform point from which arrows are shot (usually hand bone or weapon tip)")]
|
||||
public Transform shootPoint;
|
||||
|
||||
[Tooltip("Arrow prefab with ArcherProjectile component")]
|
||||
public GameObject arrowPrefab;
|
||||
|
||||
[Tooltip("Animator for triggering shoot animation")]
|
||||
public Animator animator;
|
||||
|
||||
[Header("Targeting")]
|
||||
[Tooltip("Auto-find player on start")]
|
||||
public bool autoFindPlayer = true;
|
||||
|
||||
[Tooltip("Player tag to search for")]
|
||||
public string playerTag = "Player";
|
||||
|
||||
[Tooltip("Height offset for aiming (aim at chest/head)")]
|
||||
public float targetHeightOffset = 1.2f;
|
||||
|
||||
[Header("Shooting Parameters")]
|
||||
[Tooltip("Minimum distance to shoot from")]
|
||||
public float minShootDistance = 8f;
|
||||
|
||||
[Tooltip("Maximum distance to shoot from")]
|
||||
public float maxShootDistance = 25f;
|
||||
|
||||
[Tooltip("Time between shots (seconds)")]
|
||||
public float shootCooldown = 2f;
|
||||
|
||||
[Tooltip("Arrow launch speed (m/s)")]
|
||||
public float arrowSpeed = 30f;
|
||||
|
||||
[Tooltip("How much to lead the target (predict movement)")]
|
||||
[Range(0f, 1f)]
|
||||
public float leadTargetAmount = 0.5f;
|
||||
|
||||
[Header("Animation")]
|
||||
[Tooltip("Animator trigger parameter for shooting")]
|
||||
public string shootTriggerName = "Shoot";
|
||||
|
||||
[Tooltip("Delay after animation starts before spawning arrow")]
|
||||
public float shootAnimationDelay = 0.3f;
|
||||
|
||||
[Header("Aiming")]
|
||||
[Tooltip("How quickly archer rotates to face target (degrees/sec)")]
|
||||
public float turnSpeed = 180f;
|
||||
|
||||
[Tooltip("Angle tolerance for shooting (degrees)")]
|
||||
public float aimTolerance = 15f;
|
||||
|
||||
[Header("Effects")]
|
||||
[Tooltip("Muzzle flash effect at shoot point")]
|
||||
public GameObject muzzleFlashPrefab;
|
||||
|
||||
[Tooltip("Shoot sound")]
|
||||
public AudioClip shootSound;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("Enable debug logging")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
[Tooltip("Show aiming gizmos")]
|
||||
public bool showGizmos = true;
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private Transform target;
|
||||
private float lastShootTime = -999f;
|
||||
private bool isAiming = false;
|
||||
private AudioSource audioSource;
|
||||
private Vector3 lastKnownTargetVelocity;
|
||||
private Vector3 lastTargetPosition;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Setup audio source
|
||||
if (shootSound != null)
|
||||
{
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
if (audioSource == null)
|
||||
{
|
||||
audioSource = gameObject.AddComponent<AudioSource>();
|
||||
audioSource.playOnAwake = false;
|
||||
audioSource.spatialBlend = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
// Find animator if not assigned
|
||||
if (animator == null)
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
}
|
||||
|
||||
// Find shoot point if not assigned
|
||||
if (shootPoint == null)
|
||||
{
|
||||
// Try to find "ShootPoint" child transform
|
||||
Transform shootPointChild = transform.Find("ShootPoint");
|
||||
shootPoint = shootPointChild != null ? shootPointChild : transform;
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (autoFindPlayer)
|
||||
{
|
||||
FindPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (target != null && isAiming)
|
||||
{
|
||||
RotateTowardsTarget();
|
||||
UpdateTargetVelocity();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Unity Lifecycle
|
||||
|
||||
#region Target Management
|
||||
|
||||
/// <summary>
|
||||
/// Finds player by tag
|
||||
/// </summary>
|
||||
private void FindPlayer()
|
||||
{
|
||||
GameObject player = GameObject.FindGameObjectWithTag(playerTag);
|
||||
if (player != null)
|
||||
{
|
||||
SetTarget(player.transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target to shoot at
|
||||
/// </summary>
|
||||
public void SetTarget(Transform newTarget)
|
||||
{
|
||||
target = newTarget;
|
||||
if (target != null)
|
||||
{
|
||||
lastTargetPosition = target.position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates target velocity for prediction
|
||||
/// </summary>
|
||||
private void UpdateTargetVelocity()
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
Vector3 currentPosition = target.position;
|
||||
lastKnownTargetVelocity = (currentPosition - lastTargetPosition) / Time.deltaTime;
|
||||
lastTargetPosition = currentPosition;
|
||||
}
|
||||
|
||||
#endregion Target Management
|
||||
|
||||
#region Shooting Logic
|
||||
|
||||
/// <summary>
|
||||
/// Checks if archer can shoot at target
|
||||
/// </summary>
|
||||
public bool CanShoot()
|
||||
{
|
||||
if (target == null) return false;
|
||||
|
||||
float distance = Vector3.Distance(transform.position, target.position);
|
||||
|
||||
// Check distance range
|
||||
if (distance < minShootDistance || distance > maxShootDistance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check cooldown
|
||||
if (Time.time < lastShootTime + shootCooldown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if facing target (within tolerance)
|
||||
Vector3 directionToTarget = (target.position - transform.position).normalized;
|
||||
float angleToTarget = Vector3.Angle(transform.forward, directionToTarget);
|
||||
|
||||
if (angleToTarget > aimTolerance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates shooting sequence
|
||||
/// </summary>
|
||||
public void StartShooting()
|
||||
{
|
||||
if (!CanShoot())
|
||||
{
|
||||
if (enableDebug) Debug.Log("[ArcherShootingAI] Cannot shoot - conditions not met");
|
||||
return;
|
||||
}
|
||||
|
||||
isAiming = true;
|
||||
|
||||
// Trigger animation
|
||||
if (animator != null && !string.IsNullOrEmpty(shootTriggerName))
|
||||
{
|
||||
animator.SetTrigger(shootTriggerName);
|
||||
}
|
||||
|
||||
// Schedule arrow spawn after animation delay
|
||||
Invoke(nameof(SpawnArrow), shootAnimationDelay);
|
||||
|
||||
lastShootTime = Time.time;
|
||||
|
||||
if (enableDebug) Debug.Log($"[ArcherShootingAI] Started shooting at {target.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns arrow projectile
|
||||
/// </summary>
|
||||
private void SpawnArrow()
|
||||
{
|
||||
if (arrowPrefab == null)
|
||||
{
|
||||
Debug.LogError("[ArcherShootingAI] Arrow prefab not assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shootPoint == null)
|
||||
{
|
||||
Debug.LogError("[ArcherShootingAI] Shoot point not assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate aim point with prediction
|
||||
Vector3 aimPoint = CalculateAimPoint();
|
||||
|
||||
// Calculate shoot direction
|
||||
Vector3 shootDirection = (aimPoint - shootPoint.position).normalized;
|
||||
|
||||
// Calculate rotation for arrow
|
||||
Quaternion arrowRotation = Quaternion.LookRotation(shootDirection);
|
||||
|
||||
// Spawn arrow
|
||||
GameObject arrow = LeanPool.Spawn(arrowPrefab, shootPoint.position, arrowRotation);
|
||||
|
||||
// Configure arrow projectile
|
||||
var projectile = arrow.GetComponent<ArcherProjectile>();
|
||||
if (projectile != null)
|
||||
{
|
||||
projectile.initialSpeed = arrowSpeed;
|
||||
}
|
||||
|
||||
// Play effects
|
||||
PlayShootEffects();
|
||||
|
||||
if (enableDebug)
|
||||
Debug.Log($"[ArcherShootingAI] Arrow spawned, direction: {shootDirection}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates aim point with target prediction
|
||||
/// </summary>
|
||||
private Vector3 CalculateAimPoint()
|
||||
{
|
||||
if (target == null) return shootPoint.position + transform.forward * 10f;
|
||||
|
||||
// Base aim point
|
||||
Vector3 targetPosition = target.position + Vector3.up * targetHeightOffset;
|
||||
|
||||
// Add prediction based on target velocity
|
||||
if (leadTargetAmount > 0f && lastKnownTargetVelocity.sqrMagnitude > 0.1f)
|
||||
{
|
||||
// Estimate time to target
|
||||
float distance = Vector3.Distance(shootPoint.position, targetPosition);
|
||||
float timeToTarget = distance / arrowSpeed;
|
||||
|
||||
// Predict future position
|
||||
Vector3 predictedOffset = lastKnownTargetVelocity * timeToTarget * leadTargetAmount;
|
||||
targetPosition += predictedOffset;
|
||||
}
|
||||
|
||||
return targetPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops shooting sequence
|
||||
/// </summary>
|
||||
public void StopShooting()
|
||||
{
|
||||
isAiming = false;
|
||||
CancelInvoke(nameof(SpawnArrow));
|
||||
}
|
||||
|
||||
#endregion Shooting Logic
|
||||
|
||||
#region Effects
|
||||
|
||||
private void PlayShootEffects()
|
||||
{
|
||||
// Spawn muzzle flash
|
||||
if (muzzleFlashPrefab != null && shootPoint != null)
|
||||
{
|
||||
GameObject flash = LeanPool.Spawn(muzzleFlashPrefab, shootPoint.position, shootPoint.rotation);
|
||||
LeanPool.Despawn(flash, 2f);
|
||||
}
|
||||
|
||||
// Play shoot sound
|
||||
if (audioSource != null && shootSound != null)
|
||||
{
|
||||
audioSource.PlayOneShot(shootSound);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Effects
|
||||
|
||||
#region Rotation
|
||||
|
||||
/// <summary>
|
||||
/// Smoothly rotates archer to face target
|
||||
/// </summary>
|
||||
private void RotateTowardsTarget()
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
Vector3 directionToTarget = (target.position - transform.position);
|
||||
directionToTarget.y = 0f; // Keep rotation on Y axis only
|
||||
|
||||
if (directionToTarget.sqrMagnitude > 0.001f)
|
||||
{
|
||||
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
|
||||
transform.rotation = Quaternion.RotateTowards(
|
||||
transform.rotation,
|
||||
targetRotation,
|
||||
turnSpeed * Time.deltaTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if archer is facing target within tolerance
|
||||
/// </summary>
|
||||
public bool IsFacingTarget()
|
||||
{
|
||||
if (target == null) return false;
|
||||
|
||||
Vector3 directionToTarget = (target.position - transform.position).normalized;
|
||||
float angle = Vector3.Angle(transform.forward, directionToTarget);
|
||||
|
||||
return angle <= aimTolerance;
|
||||
}
|
||||
|
||||
#endregion Rotation
|
||||
|
||||
#region Public Query Methods
|
||||
|
||||
public Transform GetTarget() => target;
|
||||
|
||||
public bool IsAiming() => isAiming;
|
||||
|
||||
public float GetTimeSinceLastShot() => Time.time - lastShootTime;
|
||||
|
||||
public float GetDistanceToTarget() => target != null ? Vector3.Distance(transform.position, target.position) : float.MaxValue;
|
||||
|
||||
public bool IsInShootingRange() => GetDistanceToTarget() >= minShootDistance && GetDistanceToTarget() <= maxShootDistance;
|
||||
|
||||
#endregion Public Query Methods
|
||||
|
||||
#region Gizmos
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!showGizmos) return;
|
||||
|
||||
// Draw shooting range
|
||||
Gizmos.color = Color.yellow;
|
||||
UnityEditor.Handles.color = new Color(1f, 1f, 0f, 0.1f);
|
||||
UnityEditor.Handles.DrawSolidDisc(transform.position, Vector3.up, minShootDistance);
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
UnityEditor.Handles.color = new Color(0f, 1f, 0f, 0.1f);
|
||||
UnityEditor.Handles.DrawSolidDisc(transform.position, Vector3.up, maxShootDistance);
|
||||
|
||||
// Draw shoot point
|
||||
if (shootPoint != null)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireSphere(shootPoint.position, 0.1f);
|
||||
Gizmos.DrawLine(shootPoint.position, shootPoint.position + shootPoint.forward * 2f);
|
||||
}
|
||||
|
||||
// Draw aim line to target
|
||||
if (target != null && Application.isPlaying)
|
||||
{
|
||||
Vector3 aimPoint = CalculateAimPoint();
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawLine(shootPoint != null ? shootPoint.position : transform.position, aimPoint);
|
||||
Gizmos.DrawWireSphere(aimPoint, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion Gizmos
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user