113 lines
4.3 KiB
C#
113 lines
4.3 KiB
C#
using UnityEngine;
|
|
|
|
public class Projectile : MonoBehaviour
|
|
{
|
|
[Header("Projectile Settings")]
|
|
[Tooltip("How fast the projectile moves forward")]
|
|
public float speed = 50f;
|
|
|
|
[Tooltip("How long before the projectile destroys itself if it misses")]
|
|
public float lifetime = 5f;
|
|
|
|
[Tooltip("Offset for embedding the arrow into the target so it doesn't just stick on the very edge")]
|
|
public float embedDepth = 0.2f;
|
|
|
|
[Header("Loaded Offsets (Before Firing)")]
|
|
[Tooltip("Adjust this to fix how the arrow sits in the hand before it is fired")]
|
|
public Vector3 loadedPositionOffset = Vector3.zero;
|
|
public Vector3 loadedRotationOffset = Vector3.zero;
|
|
|
|
[Tooltip("How strongly gravity affects the arrow. 1 = standard Unity gravity, 0 = flies perfectly straight.")]
|
|
public float gravityScale = 1f;
|
|
|
|
private float _damage;
|
|
private LayerMask _targetLayers;
|
|
private bool _hasHit;
|
|
private Vector3 _lastPosition;
|
|
private Vector3 _startPosition;
|
|
private Vector3 _velocity;
|
|
|
|
public void Initialize(float damage, LayerMask targets, float powerMultiplier)
|
|
{
|
|
// Damage is scaled based on how long the bow was drawn
|
|
_damage = damage * powerMultiplier;
|
|
_targetLayers = targets;
|
|
|
|
// Initial velocity is scaled by power multiplier
|
|
_velocity = transform.forward * (speed * powerMultiplier);
|
|
|
|
// Destroy the arrow after a few seconds if it flies into the sky
|
|
Destroy(gameObject, lifetime);
|
|
|
|
_startPosition = transform.position;
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
_lastPosition = transform.position;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (_hasHit) return;
|
|
|
|
// Apply gravity to velocity over time (this creates the arc!)
|
|
_velocity += Physics.gravity * gravityScale * Time.deltaTime;
|
|
|
|
// Make the arrow physically point in the direction it is travelling
|
|
if (_velocity.sqrMagnitude > 0.01f)
|
|
{
|
|
transform.forward = _velocity.normalized;
|
|
}
|
|
|
|
Vector3 direction = _velocity.normalized;
|
|
float distance = _velocity.magnitude * Time.deltaTime;
|
|
transform.position += _velocity * Time.deltaTime;
|
|
|
|
// Raycast from the previous position to the new position to detect hits
|
|
// We explicitly Ignore triggers so arrows don't hit the giant Lock-On detection spheres!
|
|
if (Physics.Raycast(_lastPosition, direction, out RaycastHit hit, distance, _targetLayers, QueryTriggerInteraction.Ignore))
|
|
{
|
|
HandleHit(hit);
|
|
}
|
|
|
|
_lastPosition = transform.position;
|
|
}
|
|
|
|
private void HandleHit(RaycastHit hit)
|
|
{
|
|
_hasHit = true;
|
|
|
|
// Move the arrow right exactly to the hit point, then embed it slightly
|
|
transform.position = hit.point + (transform.forward * embedDepth);
|
|
|
|
// Parent the arrow to the object it hit so it sticks and moves with it
|
|
transform.SetParent(hit.collider.transform, true);
|
|
|
|
// If your Arrow Prefab has a Rigidbody on it, it will fall down due to gravity even after being parented!
|
|
// We must destroy the Rigidbody component so it stays perfectly glued to the target.
|
|
Rigidbody rb = GetComponent<Rigidbody>();
|
|
if (rb != null)
|
|
{
|
|
Destroy(rb);
|
|
}
|
|
|
|
// Deal damage and log distance
|
|
float distanceFlown = Vector3.Distance(_startPosition, transform.position);
|
|
Debug.Log($"Arrow hit {hit.collider.name} for {_damage:F1} damage! It flew {distanceFlown:F1} meters.");
|
|
hit.collider.GetComponentInParent<WorldObject>()?.TakeDamage(_damage);
|
|
hit.collider.GetComponentInParent<NPCController>()?.TakeDamage(_damage);
|
|
|
|
// Try to hit the combat controller first so it can process blocks/stamina
|
|
MeleeCombatLayerController playerCombat = hit.collider.GetComponentInParent<MeleeCombatLayerController>();
|
|
if (playerCombat != null) playerCombat.TakeDamage(_damage);
|
|
else hit.collider.GetComponentInParent<PlayerStats>()?.TakeDamage(_damage);
|
|
|
|
// Optional: Disable collider or script so it doesn't keep checking for hits
|
|
Collider myCollider = GetComponent<Collider>();
|
|
if (myCollider != null) myCollider.enabled = false;
|
|
|
|
this.enabled = false; // Stops the Update loop
|
|
}
|
|
}
|