Files
LowPolyBattleSim/Assets/Plugins/RaycastPro/Core/Base/BaseBullet.cs
Caleb Sandford deQuincey ecdd3e2a9e intial commit
2025-06-27 23:27:49 +01:00

314 lines
11 KiB
C#

namespace RaycastPro
{
using Bullets;
using Bullets2D;
using UnityEngine;
using System;
using UnityEngine.Events;
#if UNITY_EDITOR
using Editor;
using UnityEditor;
#endif
public enum MoveType
{
Speed,
Duration,
Curve,
}
[Serializable]
public class CasterEvent : UnityEvent<BaseCaster> { }
public abstract class BaseBullet : RaycastCore
{
#region Public Methods
public void UnParent(Transform child) => child.parent = null;
public void DetachTrail()
{
var _trail = GetComponentInChildren<TrailRenderer>();
if (_trail)
{
_trail.transform.parent = null;
}
}
public void DestroyTrail(float delay)
{
var _trail = GetComponentInChildren<TrailRenderer>();
if (_trail) Destroy(_trail.gameObject, delay);
}
public void DestroyAllTrails(float delay)
{
foreach (var _tR in GetComponentsInChildren<TrailRenderer>())
{
Destroy(_tR.gameObject, delay);
}
}
public void ClearTrail()
{
var _trail = GetComponentInChildren<TrailRenderer>();
_trail.Clear();
}
#endregion
public override bool Performed { get => false; protected set {} }
public TimeMode timeMode = TimeMode.DeltaTime;
[Tooltip("If CollisionRay is used, it is good to create a break between each detection so that the Hit calculation process does not face many repetition problems.")]
[SerializeField] protected float baseIgnoreTime = .1f;
[Tooltip("If you are using ArrayCasting, this option will help the caster to separate the bullets and respawn them if needed.")]
public string bulletID;
/// <summary>
/// Invoke on Cast
/// </summary>
public CasterEvent onCast;
/// <summary>
/// Ending Before Delay
/// </summary>
public CasterEvent onEndCast;
/// <summary>
/// Ending On Delay
/// </summary>
public CasterEvent onEnd;
public UnityEvent onPlanar;
//protected Coroutine RunCoroutine;
public MoveType moveType = MoveType.Speed;
internal float position;
/// <summary>
/// Set Bullet Position as "Clamp01" on the path.
/// </summary>
public float Position
{
get => position;
set => position = Mathf.Clamp01(position);
}
[SerializeField] private string callMethod = "OnBullet";
[SerializeField] private bool messageUpward;
/// <summary>
/// Use this variable to carry owner object reference
/// </summary>
public GameObject ownerReference;
[SerializeField] public float damage = 10;
public float speed = 6;
[Tooltip("The life of the bullet, which after the end, until the final completion, the process stops working. -1 means infinite")]
public float lifeTime = 10;
internal float life;
public float Life
{
get => life;
internal set => life = value;
}
[Tooltip("Bullet downtime before final completion. You can use it as a particle holder, keeping the bullet dead for a short time.")]
public float endDelay;
public enum EndType { Disable, Destroy }
[SerializeField]
public EndType endFunction = EndType.Destroy;
[Tooltip("If it is active after collision Ray with an object other than Planar, the life of the bullet ends.")]
public bool endOnCollide = true;
public abstract void SetCollision(bool turn);
protected abstract void CollisionRun(float delta);
#region Updates
internal abstract void RuntimeUpdate();
protected void Update()
{
if (autoUpdate != UpdateMode.Normal) return;
RuntimeUpdate();
}
protected void LateUpdate()
{
if (autoUpdate != UpdateMode.Late) return;
RuntimeUpdate();
}
protected void FixedUpdate()
{
if (autoUpdate != UpdateMode.Fixed) return;
RuntimeUpdate();
}
#endregion
internal abstract void Cast<R>(BaseCaster _caster, R raySensor);
internal bool ended;
protected void OnEndCast<B>(B _caster) where B : BaseCaster// Review
{
if (ended) return;
ended = true;
position = 1;
onEndCast?.Invoke(_caster);
StartCoroutine(DelayRun(endDelay, () =>
{
if (endFunction == EndType.Destroy) // In Normal Casting
{
Destroy(gameObject);
}
else // In Array Casting
{
gameObject.SetActive(false);
}
onEnd?.Invoke(_caster);
}));
}
/// <summary>
/// This Script need to be divided for more optimizing.
/// </summary>
/// <param name="target"></param>
protected void InvokeDamageEvent(Transform target)
{ if (callMethod == "") return;
if (messageUpward)
{
if (this is Bullet _blt)
{
target.SendMessageUpwards(callMethod, _blt, SendMessageOptions.DontRequireReceiver);
}
else
{
target.SendMessageUpwards(callMethod, this as Bullet2D, SendMessageOptions.DontRequireReceiver);
}
}
else
{
if (this is Bullet _blt)
{
target.SendMessage(callMethod, _blt, SendMessageOptions.DontRequireReceiver);
}
else
{
target.SendMessage(callMethod, this as Bullet2D, SendMessageOptions.DontRequireReceiver);
}
}
}
/// <summary>
/// Optimized if delta is calculated before run this method
/// </summary>
/// <param name="delta"></param>
protected void UpdateLifeProcess(float delta)
{
if (life >= lifeTime && lifeTime >= 0)
{
// On End Run to End Delay
if (this is Bullet bullet)
{
OnEndCast(bullet.caster);
}
else if (this is Bullet2D bullet2D)
{
OnEndCast(bullet2D.caster);
}
}
else life += delta;
}
#if UNITY_EDITOR
protected const string CPositionLerp = "Position Lerp";
protected const string CRotationLerp = "Rotation Lerp";
protected const string TPositionLerp = "This Algorithm is based on the location that will always reach the destination after a certain period, for example, I can use the Projection type of ranged heroes in Dota2 or other strategy games.";
protected const string TRotationLerp = "This Algorithm, which is based on rotation, acts like a ballistic missile and tries to change direction towards the target, but there is no guarantee that it will always hit it.";
protected void CastTypeField(SerializedProperty _moveType, SerializedProperty _speed, SerializedProperty _duration,
SerializedProperty _curve)
{
BeginVerticalBox();
PropertyEnumField(_moveType, 3, CMoveType.ToContent(TMoveType), new GUIContent[]
{
CSpeed.ToContent("By Speed"),
CDuration.ToContent("By Duration"),
CCurve.ToContent("By Animation Curve"),
});
switch (moveType)
{
case MoveType.Speed:
EditorGUILayout.PropertyField(_speed);
break;
case MoveType.Duration:
EditorGUILayout.PropertyField(_duration);
break;
case MoveType.Curve:
BeginHorizontal();
InLabelWidth(() =>
{
EditorGUILayout.CurveField(_curve, RCProEditor.Aqua, new Rect(0, 0, 1, 1), CCurve.ToContent(CCurve));
}, 80);
EditorGUILayout.PropertyField(_duration, GUIContent.none, GUILayout.Width(30));
EndHorizontal();
break;
}
EndVertical();
}
protected void EventField(SerializedObject _so)
{
EventFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(EventFoldout, CEvents.ToContent(TEvents),
RCProEditor.HeaderFoldout);
if (EventFoldout) RCProEditor.EventField(_so, events); EditorGUILayout.EndFoldoutHeaderGroup();
}
protected static readonly string[] events = new[] {nameof(onCast), nameof(onEndCast), nameof(onEnd)};
protected abstract void CollisionRayField(SerializedObject _so);
protected void BulletInfoField()
{
InformationField(() =>
{
PercentProgressField(life/lifeTime, "Life");
});
}
protected void GeneralField(SerializedObject _so)
{
BeginVerticalBox();
PropertyMaxField(_so.FindProperty(nameof(lifeTime)), -1f);
PropertyMaxField(_so.FindProperty(nameof(endDelay)));
PropertyTimeModeField(_so.FindProperty(nameof(timeMode)));
CollisionRayField(_so);
EditorGUILayout.PropertyField(_so.FindProperty(nameof(endOnCollide)));
EndVertical();
BeginVerticalBox();
BeginHorizontal();
EditorGUILayout.PropertyField(_so.FindProperty(nameof(callMethod)), "Call Method".ToContent("Calls this method on every MonoBehaviour in bullet's hit target." +
"\nex: ----------------------------------"+
"\npublic void OnBullet(Bullet _bullet)" +
"\n{" +
"\n Add_Character_Hp(-_bullet.damage);"+
"\n}" +
"\n -------------------------------------"+
"\n(Just leave it empty to cancel messaging.)"));
MiniField(_so.FindProperty(nameof(messageUpward)), "U".ToContent("Calls the method on every ancestor of the behaviour in addition to every MonoBehaviour."));
EndHorizontal();
EditorGUILayout.PropertyField(_so.FindProperty(nameof(damage)));
EndVertical();
EditorGUILayout.PropertyField(_so.FindProperty(nameof(bulletID)));
BaseField(_so);
}
#endif
}
}