360 lines
15 KiB
C#
360 lines
15 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace Invector.vShooter
|
|
{
|
|
using Invector.vEventSystems;
|
|
[vClassHeader("Projectile Control", "The damage value is changed from minDamage, maxDamage, DropOffStart, DropOffEnd of the ShooterWeapon", openClose = false)]
|
|
public class vProjectileControl : vMonoBehaviour
|
|
{
|
|
public vBulletLifeSettings bulletLifeSettings;
|
|
public int bulletLife = 100;
|
|
public bool debugTrajetory;
|
|
public bool debugHittedObject;
|
|
public vDamage damage;
|
|
public float forceMultiplier = 1;
|
|
public bool destroyOnCast = true;
|
|
[Tooltip("Control Trail renderer")]
|
|
public TrailRenderer trail;
|
|
public ProjectilePassDamage onPassDamage;
|
|
public ProjectileCastColliderEvent onCastCollider;
|
|
public ProjectileCastColliderEvent onDestroyProjectile;
|
|
public vProjectileInstantiateData instantiateData;
|
|
|
|
internal bool damageByDistance;
|
|
internal float velocity = 580;
|
|
internal int minDamage;
|
|
internal int maxDamage;
|
|
internal float minDamageDistance = 8f;
|
|
internal float maxDamageDistance = 50f;
|
|
internal Vector3 startPosition;
|
|
internal LayerMask hitLayer = -1;
|
|
internal List<string> ignoreTags = new List<string>();
|
|
internal Transform shooterTransform;
|
|
|
|
protected Vector3 previousPosition;
|
|
protected Rigidbody _rigidBody;
|
|
protected Color debugColor = Color.green;
|
|
protected int debugLife;
|
|
protected float castDist;
|
|
protected List<Vector3> trajectoryPositions = new List<Vector3>();
|
|
|
|
protected virtual void Start()
|
|
{
|
|
transform.SetParent(vObjectContainer.root, true);
|
|
debugLife = bulletLife;
|
|
_rigidBody = GetComponent<Rigidbody>();
|
|
startPosition = transform.position;
|
|
previousPosition = transform.position - transform.forward * 0.1f;
|
|
|
|
if (trail)
|
|
{
|
|
AddTrailPosition();
|
|
}
|
|
|
|
// Log diagnostic: Kiểm tra Layer mà đạn có thể bắn trúng
|
|
Debug.Log($"<color=cyan>PROJECTILE SPAWNED:</color> HitLayer Mask: {hitLayer.value}. Đảm bảo Layer của Enemy nằm trong mask này.");
|
|
}
|
|
|
|
protected virtual void Update()
|
|
{
|
|
RaycastHit hitInfo;
|
|
if (_rigidBody.linearVelocity.magnitude > 1)
|
|
{
|
|
transform.rotation = Quaternion.LookRotation(_rigidBody.linearVelocity.normalized, transform.up);
|
|
}
|
|
|
|
// Thực hiện raycast để kiểm tra va chạm
|
|
if (Physics.Linecast(previousPosition, transform.position + transform.forward * 0.5f, out hitInfo, hitLayer))
|
|
{
|
|
if (!hitInfo.collider)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var dist = Vector3.Distance(startPosition, transform.position) + castDist;
|
|
if (!(ignoreTags.Contains(hitInfo.collider.gameObject.tag) || (shooterTransform != null && hitInfo.collider.transform.IsChildOf(shooterTransform))))
|
|
{
|
|
if (debugHittedObject)
|
|
{
|
|
Debug.Log(hitInfo.collider.gameObject.name, hitInfo.collider);
|
|
}
|
|
|
|
onCastCollider.Invoke(hitInfo);
|
|
damage.damageValue = maxDamage;
|
|
if (damageByDistance)
|
|
{
|
|
var result = 0f;
|
|
var damageDifence = maxDamage - minDamage;
|
|
|
|
//Calc damage per distance
|
|
if (dist - minDamageDistance >= 0)
|
|
{
|
|
int percentComplete = (int)System.Math.Round((double)(100 * (dist - minDamageDistance)) / (maxDamageDistance - minDamageDistance));
|
|
result = Mathf.Clamp(percentComplete * 0.01f, 0, 1f);
|
|
damage.damageValue = maxDamage - (int)(damageDifence * result);
|
|
}
|
|
else
|
|
{
|
|
damage.damageValue = maxDamage;
|
|
}
|
|
}
|
|
damage.hitPosition = hitInfo.point;
|
|
damage.receiver = hitInfo.collider.transform;
|
|
damage.force = transform.forward * damage.damageValue * forceMultiplier;
|
|
|
|
if (damage.damageValue > 0)
|
|
{
|
|
onPassDamage.Invoke(damage);
|
|
|
|
// 1. Log khi trúng bất cứ thứ gì
|
|
Debug.Log($"<color=yellow>PROJECTILE HIT:</color> {hitInfo.collider.name} | Tag: {hitInfo.collider.tag} | Layer: {LayerMask.LayerToName(hitInfo.collider.gameObject.layer)}");
|
|
|
|
// 2. Tìm đối tượng nhận sát thương (ưu tiên tìm ở cha nếu trúng collider con)
|
|
var damageReceiver = hitInfo.collider.gameObject.GetComponentInParent<vIDamageReceiver>();
|
|
|
|
if (damageReceiver != null)
|
|
{
|
|
if (hitInfo.collider.CompareTag("Enemy") || damageReceiver.gameObject.CompareTag("Enemy"))
|
|
{
|
|
Debug.Log($"<color=green>APPLYING DAMAGE TO ENEMY:</color> {damageReceiver.gameObject.name}. Damage: {damage.damageValue}");
|
|
}
|
|
|
|
// Gửi sát thương đến đối tượng tìm thấy
|
|
damageReceiver.gameObject.ApplyDamage(damage, damage.sender ? damage.sender.GetComponent<vIMeleeFighter>() : null);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"<color=orange>NO DAMAGE RECEIVER FOUND</color> on {hitInfo.collider.name} or its parents. Đảm bảo Enemy có component vHealthController.");
|
|
}
|
|
}
|
|
|
|
var rigb = hitInfo.collider.gameObject.GetComponent<Rigidbody>();
|
|
if (rigb)
|
|
{
|
|
rigb.AddForce(transform.forward * damage.damageValue * forceMultiplier, ForceMode.Impulse);
|
|
}
|
|
|
|
startPosition = transform.position;
|
|
castDist = dist;
|
|
|
|
|
|
if (destroyOnCast)
|
|
{
|
|
if (bulletLifeSettings)
|
|
{
|
|
var bulletLifeInfo = bulletLifeSettings.GetReduceLife(hitInfo.collider.gameObject.tag, hitInfo.collider.gameObject.layer);
|
|
bulletLife -= bulletLifeInfo.lostLife;
|
|
if (debugTrajetory)
|
|
{
|
|
DrawHitPoint(hitInfo.point);
|
|
}
|
|
|
|
var crossed = false;
|
|
|
|
if (bulletLife > 0 && !bulletLifeInfo.ricochet)
|
|
{
|
|
var position = transform.position = hitInfo.point + transform.forward * 0.001f;
|
|
|
|
if (trail)
|
|
{
|
|
trail.AddPosition(transform.position);
|
|
}
|
|
|
|
if (debugTrajetory)
|
|
{
|
|
Debug.DrawLine(transform.position, previousPosition, debugColor, 10f);
|
|
}
|
|
|
|
for (float i = 0; i <= bulletLifeInfo.maxThicknessToCross; i += 0.01f)
|
|
{
|
|
|
|
var pointToCheck = position + transform.forward * (i);
|
|
if (Physics.Linecast(pointToCheck, position))
|
|
{
|
|
hitInfo.point = pointToCheck;
|
|
hitInfo.normal = transform.forward;
|
|
onCastCollider.Invoke(hitInfo);
|
|
crossed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (crossed)
|
|
{
|
|
if (trail)
|
|
{
|
|
AddTrailPosition();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!crossed && !bulletLifeInfo.ricochet)
|
|
{
|
|
bulletLife = 0;
|
|
transform.position = hitInfo.point;
|
|
if (debugTrajetory)
|
|
{
|
|
Debug.DrawLine(transform.position, previousPosition, debugColor, 10f);
|
|
}
|
|
|
|
onDestroyProjectile.Invoke(hitInfo);
|
|
if (trail && trail.gameObject != this.gameObject)
|
|
{
|
|
if (trail)
|
|
{
|
|
AddTrailPosition();
|
|
}
|
|
trail.transform.SetParent(vObjectContainer.root);
|
|
}
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
|
|
maxDamage -= (maxDamage) - ((maxDamage * bulletLifeInfo.lostDamage) / 100);
|
|
minDamage -= (minDamage) - ((minDamage * bulletLifeInfo.lostDamage) / 100);
|
|
if (maxDamage < 0)
|
|
{
|
|
maxDamage = 0;
|
|
}
|
|
|
|
if (minDamage < 0)
|
|
{
|
|
minDamage = 0;
|
|
}
|
|
|
|
var x = Random.Range(bulletLifeInfo.minChangeTrajectory, bulletLifeInfo.maxChangeTrajectory) * (Random.Range(-1, 1) >= 0 ? 1 : -1);
|
|
var y = Random.Range(bulletLifeInfo.minChangeTrajectory, bulletLifeInfo.maxChangeTrajectory) * (Random.Range(-1, 1) >= 0 ? 1 : -1);
|
|
|
|
if (y > 60 || y < -60)
|
|
{
|
|
x = Mathf.Clamp(x, -15, 15);
|
|
}
|
|
|
|
if (x != 0 || y != 0)
|
|
{
|
|
var dir = Quaternion.Euler(x, y, 0) * _rigidBody.linearVelocity;
|
|
if (dir != Vector3.zero)
|
|
{
|
|
_rigidBody.linearVelocity = dir * (bulletLifeInfo.ricochet ? -1 : 1);
|
|
|
|
transform.forward = dir * (bulletLifeInfo.ricochet ? -1 : 1);
|
|
}
|
|
}
|
|
if (debugTrajetory)
|
|
{
|
|
var lostedLifePercent = (bulletLife / (float)debugLife) * 100f;
|
|
debugColor = lostedLifePercent > 76 ? Color.green : lostedLifePercent > 51 ? Color.yellow : lostedLifePercent > 26 ? new Color(1, .5f, 0) : Color.red;
|
|
debugColor.a = 0.5f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bulletLife = 0;
|
|
}
|
|
|
|
if (bulletLife <= 0 || bulletLifeSettings == null)
|
|
{
|
|
transform.position = hitInfo.point;
|
|
if (debugTrajetory)
|
|
{
|
|
Debug.DrawLine(transform.position, previousPosition, debugColor, 10f);
|
|
}
|
|
|
|
onDestroyProjectile.Invoke(hitInfo);
|
|
if (trail && trail.gameObject != this.gameObject)
|
|
{
|
|
if (trail)
|
|
{
|
|
AddTrailPosition();
|
|
}
|
|
|
|
trail.transform.SetParent(vObjectContainer.root);
|
|
}
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
transform.position = hitInfo.point + transform.forward * 0.001f;
|
|
if (trail && trail.gameObject != this.gameObject)
|
|
{
|
|
|
|
if (trail)
|
|
{
|
|
AddTrailPosition();
|
|
}
|
|
}
|
|
if (debugTrajetory)
|
|
{
|
|
Debug.DrawLine(transform.position, previousPosition, debugColor, 10f);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (debugTrajetory)
|
|
{
|
|
Debug.DrawLine(transform.position, previousPosition, debugColor, 10f);
|
|
}
|
|
}
|
|
|
|
previousPosition = transform.position;
|
|
|
|
|
|
}
|
|
|
|
private void AddTrailPosition()
|
|
{
|
|
if (trajectoryPositions.Count > 0)
|
|
{
|
|
var lastPosition = trajectoryPositions[trajectoryPositions.Count - 1];
|
|
var distance = Vector3.Distance(lastPosition, transform.position);
|
|
var dir = transform.position - lastPosition;
|
|
var count = (int)(distance / 0.5f);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
trajectoryPositions.Add(lastPosition + dir.normalized * 0.5f);
|
|
if (debugTrajetory)
|
|
{
|
|
Debug.DrawRay(lastPosition, Vector3.up * .1f, Color.red, 10);
|
|
}
|
|
|
|
lastPosition = lastPosition + dir.normalized * 0.5f;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
trajectoryPositions.Add(transform.position);
|
|
}
|
|
|
|
trail.Clear();
|
|
var position = trajectoryPositions.ToArray();
|
|
|
|
trail.AddPositions(position);
|
|
|
|
}
|
|
|
|
void DrawHitPoint(Vector3 point)
|
|
{
|
|
Debug.DrawRay(point, -transform.forward * 0.1f, Color.red, 10f);
|
|
Debug.DrawRay(point, transform.right * 0.1f, Color.red, 10f);
|
|
Debug.DrawRay(point, -transform.right * 0.1f, Color.red, 10f);
|
|
Debug.DrawRay(point, transform.up * 0.1f, Color.red, 10f);
|
|
Debug.DrawRay(point, -transform.up * 0.1f, Color.red, 10f);
|
|
}
|
|
|
|
public void RemoveParentOfOther(Transform other)
|
|
{
|
|
other.SetParent(vObjectContainer.root, true);
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class ProjectileCastColliderEvent : UnityEngine.Events.UnityEvent<RaycastHit> { }
|
|
[System.Serializable]
|
|
public class ProjectilePassDamage : UnityEngine.Events.UnityEvent<vDamage> { }
|
|
|
|
}
|
|
} |