Skip to content

Commit 0cd667e

Browse files
authored
Create ProjectileVisualBase.cs
1 parent da6bd50 commit 0cd667e

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// ProjectileVisualBase.cs
2+
// Abstract base MonoBehaviour for ALL pooled projectile visuals (2D and 3D).
3+
//
4+
// WHY ABSTRACT CLASS OVER INTERFACE:
5+
// MonoBehaviour cannot be "implemented" from a pure interface in Unity;
6+
// GetComponent<IFoo>() works but you lose inheritance of Unity lifecycle
7+
// and common shared state. An abstract MonoBehaviour gives us:
8+
// • Shared pool-return logic (LocalPoolReturn)
9+
// • Common state (configId, origin, direction, speed)
10+
// • GetComponent-friendly type hierarchy
11+
// • Virtual hooks so end-users override only what they need
12+
//
13+
// END-USER EXTENSION PATTERN:
14+
// // In your game assembly:
15+
// public class MyTrailVisual : ProjectileVisualBase
16+
// {
17+
// [SerializeField] private TrailRenderer _trail;
18+
//
19+
// protected override void OnInitialise(ProjectileConfigSO cfg)
20+
// {
21+
// _trail.startColor = cfg.TrailGradient?.Evaluate(0f) ?? Color.white;
22+
// }
23+
//
24+
// protected override void OnReturnToPool()
25+
// {
26+
// _trail.Clear();
27+
// }
28+
// }
29+
//
30+
// NETWORKED vs LOCAL:
31+
// This base class is pool-based (LocalObjectPool / LocalParticlePool).
32+
// Network-owned projectile visuals that travel with a NetworkObject use
33+
// INetworkProjectileVisual (NetworkProjectileBase.cs) instead.
34+
// The two systems are intentionally separate — pool visuals are fire-and-forget
35+
// client cosmetics, network visuals are authority-driven.
36+
37+
using UnityEngine;
38+
using MidManStudio.Core.Logging;
39+
using MidManStudio.Core.Pools;
40+
using MidManStudio.Projectiles.Config;
41+
42+
namespace MidManStudio.Projectiles.Visuals
43+
{
44+
[DisallowMultipleComponent]
45+
public abstract class ProjectileVisualBase : MonoBehaviour
46+
{
47+
// ── Inspector ─────────────────────────────────────────────────────────
48+
49+
[Header("Pool Return (auto-found if null)")]
50+
[SerializeField] protected LocalPoolReturn _poolReturn;
51+
52+
[Header("Debug")]
53+
[SerializeField] protected MID_LogLevel _logLevel = MID_LogLevel.None;
54+
55+
// ── Shared state (readable by sub-classes) ────────────────────────────
56+
57+
public ushort ConfigId { get; private set; }
58+
public Vector3 Origin { get; private set; }
59+
public Vector3 Direction { get; private set; }
60+
public float Speed { get; private set; }
61+
public bool IsActive { get; private set; }
62+
63+
// ── Unity lifecycle ───────────────────────────────────────────────────
64+
65+
protected virtual void Awake()
66+
{
67+
if (_poolReturn == null)
68+
_poolReturn = GetComponent<LocalPoolReturn>();
69+
}
70+
71+
// ── Public API — called by ClientPredictionManager / RaycastHandler ──
72+
73+
/// <summary>
74+
/// Initialise this visual for a newly spawned projectile.
75+
/// Automatically resolves the config from ProjectileRegistry and calls
76+
/// the virtual <see cref="OnInitialise"/> hook for sub-class setup.
77+
/// </summary>
78+
public void InitializeClientVisual(
79+
ushort configId,
80+
Vector3 origin,
81+
Vector3 direction,
82+
float speed)
83+
{
84+
ConfigId = configId;
85+
Origin = origin;
86+
Direction = direction.sqrMagnitude > 0.001f ? direction.normalized : Vector3.forward;
87+
Speed = speed;
88+
IsActive = true;
89+
90+
transform.position = origin;
91+
ApplyRotation(Direction);
92+
93+
var cfg = ProjectileRegistry.HasInstance
94+
? ProjectileRegistry.Instance.Get(configId)
95+
: null;
96+
97+
OnInitialise(cfg);
98+
99+
MID_Logger.LogDebug(_logLevel,
100+
$"{GetType().Name} init configId={configId} origin={origin}",
101+
nameof(ProjectileVisualBase));
102+
}
103+
104+
/// <summary>
105+
/// Immediately return this visual to the object pool and reset state.
106+
/// </summary>
107+
public void ReturnToPoolImmediate()
108+
{
109+
if (this == null) return;
110+
IsActive = false;
111+
OnReturnToPool();
112+
_poolReturn?.ReturnToPoolNow();
113+
114+
MID_Logger.LogDebug(_logLevel,
115+
$"{GetType().Name} returned to pool.", nameof(ProjectileVisualBase));
116+
}
117+
118+
/// <summary>
119+
/// Hide rendering without returning to pool (e.g. during reconcile).
120+
/// </summary>
121+
public virtual void HideProjectile() { }
122+
123+
// ── Abstract / virtual hooks for sub-classes ──────────────────────────
124+
125+
/// <summary>
126+
/// Called during <see cref="InitializeClientVisual"/> after common state is set.
127+
/// Override to apply sprite, mesh, trail, particles from the config.
128+
/// <paramref name="cfg"/> may be null if the configId is not registered.
129+
/// </summary>
130+
protected abstract void OnInitialise(ProjectileConfigSO cfg);
131+
132+
/// <summary>
133+
/// Called just before the object is returned to the pool.
134+
/// Override to clear trails, stop particles, reset materials etc.
135+
/// </summary>
136+
protected abstract void OnReturnToPool();
137+
138+
/// <summary>
139+
/// Applies a world-space rotation so the visual faces its travel direction.
140+
/// Override if your visual uses a different forward convention.
141+
/// </summary>
142+
protected virtual void ApplyRotation(Vector3 dir)
143+
{
144+
if (dir.sqrMagnitude < 0.001f) return;
145+
146+
// Sub-classes override for 2D (Z-angle) vs 3D (LookRotation)
147+
// Default: identity — let sub-class handle it
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)