using Code.Base; using Code.Lab3; using UnityEngine; namespace Code.Lab5 { public class BetterProjectileLauncher : MonoBehaviour { [Header("Projectile Settings")] public GameObject projectilePrefab; public float launchSpeed = 20f; public bool useHighTrajectory; public float fireRate = 1f; [Header("Target")] public Wander wanderingTarget; public float predictionTime = 2f; public bool useDynamicPrediction = true; [Header("Visualization")] public GameObject targetCirclePrefab; public bool showPredictedPosition = true; public float circleLifetime = 1f; [Header("Accuracy")] [Range(0f, 1f)] public float accuracy = 0.9f; public float inaccuracyRadius = 2f; private float _nextFireTime; private Transform _predictedTarget; private GameObject _predictedTargetObject; private void Start() { if (wanderingTarget == null) { Debug.LogError("Wandering target not assigned!"); return; } if (showPredictedPosition && targetCirclePrefab != null) { _predictedTargetObject = new GameObject("PredictedTarget"); _predictedTarget = _predictedTargetObject.transform; } } private void Update() { if (wanderingTarget == null) return; if (Time.time >= _nextFireTime) { TryFireAtTarget(); _nextFireTime = Time.time + 1f / fireRate; } if (showPredictedPosition && _predictedTarget != null) { UpdatePredictedTargetVisual(); } } private void TryFireAtTarget() { if (!wanderingTarget || !projectilePrefab) return; var targetPos = wanderingTarget.transform.position; var targetVelocity = GetTargetVelocity(); var predictedPos = PredictTargetPosition(targetPos, targetVelocity); var aimPosition = ApplyInaccuracy(predictedPos); if (!Projectile.IsTargetReachable(transform.position, aimPosition, launchSpeed)) { useHighTrajectory = !useHighTrajectory; if (!Projectile.IsTargetReachable(transform.position, aimPosition, launchSpeed)) { return; } } var direction = Projectile.GetFireDirection( transform.position, aimPosition, launchSpeed, useHighTrajectory ); if (direction == Vector3.zero) return; FireProjectile(direction, aimPosition); } private Vector3 GetTargetVelocity() { if (wanderingTarget) return Vector3.zero; var targetAgent = wanderingTarget.GetComponent(); if (targetAgent) { return targetAgent.velocity; } return Vector3.zero; } private Vector3 PredictTargetPosition(Vector3 currentPos, Vector3 velocity) { if (velocity.magnitude < 0.1f) return currentPos; float timeToTarget; if (useDynamicPrediction) { var toTarget = currentPos - transform.position; var distance = toTarget.magnitude; var closingSpeed = launchSpeed - Vector3.Dot(velocity, toTarget.normalized); if (closingSpeed > 0) { timeToTarget = distance / closingSpeed; } else { timeToTarget = predictionTime; } timeToTarget = Mathf.Min(timeToTarget, predictionTime); } else { timeToTarget = predictionTime; } var predictedPos = currentPos + velocity * timeToTarget; predictedPos.y = 0.1f; return predictedPos; } private Vector3 ApplyInaccuracy(Vector3 targetPos) { if (accuracy >= 1f) return targetPos; var inaccuracy = (1f - accuracy) * inaccuracyRadius; var randomOffset = Random.insideUnitCircle * inaccuracy; var inaccuratePos = targetPos; inaccuratePos.x += randomOffset.x; inaccuratePos.z += randomOffset.y; return inaccuratePos; } private void FireProjectile(Vector3 direction, Vector3 targetPos) { var projObj = Instantiate(projectilePrefab, transform.position, Quaternion.identity); var projectile = projObj.GetComponent(); projectile.Set(transform.position, direction, launchSpeed); if (showPredictedPosition && targetCirclePrefab) { SpawnTargetCircle(targetPos, "ShotTarget"); } } private void UpdatePredictedTargetVisual() { if (wanderingTarget) return; var targetPos = wanderingTarget.transform.position; var targetVelocity = GetTargetVelocity(); var predictedPos = PredictTargetPosition(targetPos, targetVelocity); _predictedTarget.position = predictedPos; Debug.DrawLine(transform.position, predictedPos, Color.cyan); Debug.DrawLine(targetPos, predictedPos, Color.yellow); } private void SpawnTargetCircle(Vector3 position, string name1 = "TargetCircle") { var circle = Instantiate(targetCirclePrefab, position, Quaternion.identity); circle.name = name1; var circlePos = circle.transform.position; circlePos.y = 0.05f; circle.transform.position = circlePos; if (circleLifetime > 0) { Destroy(circle, circleLifetime); } } public void SetAccuracy(float newAccuracy) { accuracy = Mathf.Clamp01(newAccuracy); } public void SetFireRate(float newFireRate) { fireRate = Mathf.Max(0.1f, newFireRate); } private void OnDrawGizmosSelected() { if (wanderingTarget == null) return; Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(wanderingTarget.transform.position, 0.5f); var targetAgent = wanderingTarget.GetComponent(); if (targetAgent != null && targetAgent.velocity.magnitude > 0.1f) { Gizmos.color = Color.blue; var velocityVector = targetAgent.velocity.normalized * 2f; Gizmos.DrawRay(wanderingTarget.transform.position, velocityVector); } if (Application.isPlaying) { var targetPos = wanderingTarget.transform.position; var velocity = targetAgent != null ? targetAgent.velocity : Vector3.zero; var predictedPos = PredictTargetPosition(targetPos, velocity); Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(predictedPos, 0.5f); Gizmos.DrawLine(transform.position, predictedPos); } Gizmos.color = new Color(1f, 0f, 0f, 0.2f); Gizmos.DrawWireSphere(transform.position, inaccuracyRadius); } private void OnDestroy() { if (_predictedTargetObject != null) { Destroy(_predictedTargetObject); } } } }