1- // ProjectileVisual_.cs
1+ // ProjectileVisual_.cs — 2D pool visual
22//
3- // FIX (rotation): InitializeClientVisual now uses angle-based Z rotation for
4- // 2D visuals (dir.z ≈ 0) instead of LookRotation(forward, dir).
5- // LookRotation interprets the second argument as the UP vector, which
6- // rotated the sprite 90° incorrectly for horizontal bullets.
7- // For 3D visuals (non-zero Z component), LookRotation(dir) is used.
3+ // Inherits ProjectileVisualBase.
4+ // Handles: SpriteRenderer, TrailRenderer, 2D Z-axis rotation.
85//
9- // FIX (draw order): SpriteRenderer.sortingOrder = 1, TrailRenderer.sortingOrder = 0.
10- // The sprite now renders in front of the trail within the same sorting layer.
11- // Adjust these values in the inspector if you have multiple sorting layers.
12- //
13- // FIX (trail bug): ApplyTrailOptimised condition was checking
14- // "_cachedConfigId != _cachedConfigId" (always false). Fixed to check
15- // the _trailConfigured flag which is reset on pool return and config change.
6+ // FIXES carried forward:
7+ // + Correct 2D rotation via atan2 → Z-Euler (not LookRotation)
8+ // + _trailConfigured flag properly gates re-application on recycled objects
9+ // + SpriteRenderer.sortingOrder > TrailRenderer.sortingOrder
1610
1711using UnityEngine ;
18- using MidManStudio . Core . Logging ;
19- using MidManStudio . Core . Pools ;
2012using MidManStudio . Projectiles . Config ;
2113
2214namespace MidManStudio . Projectiles . Visuals
2315{
24- public class ProjectileVisual_ : MonoBehaviour
16+ public class ProjectileVisual_ : ProjectileVisualBase
2517 {
26- #region Serialized Fields
18+ #region Inspector
2719
28- [ Header ( "Renderers" ) ]
29- [ SerializeField ] public SpriteRenderer projectileSpriteRend ;
30- [ SerializeField ] public TrailRenderer projectileTrailRend ;
20+ [ Header ( "2D Renderers" ) ]
21+ [ SerializeField ] public SpriteRenderer projectileSpriteRend ;
22+ [ SerializeField ] public TrailRenderer projectileTrailRend ;
3123
3224 [ Header ( "Draw Order" ) ]
33- [ Tooltip ( "Sorting order for the sprite renderer. Higher = in front." ) ]
3425 [ SerializeField ] private int _spriteSortingOrder = 1 ;
35- [ Tooltip ( "Sorting order for the trail renderer. Should be lower than sprite." ) ]
3626 [ SerializeField ] private int _trailSortingOrder = 0 ;
3727
38- [ Header ( "Pool Return" ) ]
39- [ SerializeField ] private LocalPoolReturn localPoolReturn ;
40-
41- [ Header ( "Debug" ) ]
42- [ SerializeField ] private MID_LogLevel _logLevel = MID_LogLevel . None ;
43-
4428 #endregion
4529
46- #region Cached State
30+ #region State
4731
48- private ProjectileConfigSO _config ;
49- private Sprite _cachedSprite ;
50- private bool _trailConfigured ;
51- private ushort _cachedConfigId ;
52- private bool _initialised ;
32+ private Sprite _cachedSprite ;
33+ private bool _trailConfigured ;
34+ private ushort _cachedConfigId ;
35+ private bool _configInitialised ;
5336
5437 #endregion
5538
56- private void Awake ( )
39+ #region ProjectileVisualBase
40+
41+ protected override void ApplyRotation ( Vector3 dir )
5742 {
58- if ( localPoolReturn == null )
59- localPoolReturn = GetComponent < LocalPoolReturn > ( ) ;
43+ // 2D: rotate around Z so the sprite tip points along dir
44+ if ( dir . sqrMagnitude < 0.001f ) return ;
45+ float angle = Mathf . Atan2 ( dir . y , dir . x ) * Mathf . Rad2Deg ;
46+ transform . rotation = Quaternion . Euler ( 0f , 0f , angle ) ;
6047 }
6148
62- #region Public API
63-
64- public void InitializeClientVisual (
65- ushort configId ,
66- Vector3 origin ,
67- Vector3 direction ,
68- float speed )
49+ protected override void OnInitialise ( ProjectileConfigSO cfg )
6950 {
70- if ( localPoolReturn == null )
71- localPoolReturn = GetComponent < LocalPoolReturn > ( ) ;
72-
73- bool configChanged = ! _initialised || _cachedConfigId != configId ;
74-
51+ bool configChanged = ! _configInitialised || _cachedConfigId != ConfigId ;
7552 if ( configChanged )
7653 {
77- _config = ProjectileRegistry . HasInstance
78- ? ProjectileRegistry . Instance . Get ( configId )
79- : null ;
80-
81- _cachedConfigId = configId ;
82- _trailConfigured = false ;
54+ _cachedConfigId = ConfigId ;
55+ _trailConfigured = false ;
56+ _configInitialised = true ;
8357 }
8458
85- if ( _config == null )
86- MID_Logger . LogWarning ( _logLevel ,
87- $ "ProjectileVisual_: no config for id={ configId } .",
88- nameof ( ProjectileVisual_ ) ) ;
59+ ApplySpriteOptimised ( cfg ? . ProjectileSprite ) ;
60+ ApplyTrailOptimised ( cfg ) ;
61+ }
8962
90- transform . position = origin ;
63+ protected override void OnReturnToPool ( )
64+ {
65+ _configInitialised = false ;
66+ _trailConfigured = false ;
67+ _cachedSprite = null ;
9168
92- // FIX: correct 2D/3D rotation
93- if ( direction . sqrMagnitude > 0.001f )
69+ if ( projectileSpriteRend != null )
9470 {
95- if ( Mathf . Abs ( direction . z ) < 0.01f )
96- {
97- // 2D projectile — rotate around Z axis
98- float angle = Mathf . Atan2 ( direction . y , direction . x ) * Mathf . Rad2Deg ;
99- transform . rotation = Quaternion . Euler ( 0f , 0f , angle ) ;
100- }
101- else
102- {
103- // 3D projectile — align forward to travel direction
104- transform . rotation = Quaternion . LookRotation ( direction . normalized ) ;
105- }
71+ projectileSpriteRend . enabled = true ;
72+ projectileSpriteRend . sprite = null ;
73+ projectileSpriteRend . color = Color . white ;
10674 }
10775
108- ApplySpriteOptimised ( _config ? . ProjectileSprite ) ;
109- ApplyTrailOptimised ( _config ) ;
110-
111- _initialised = true ;
112-
113- MID_Logger . LogDebug ( _logLevel ,
114- $ "Initialised configId={ configId } origin={ origin } ",
115- nameof ( ProjectileVisual_ ) ) ;
116- }
117-
118- public void ReturnToPoolImmediate ( )
119- {
120- if ( this == null ) return ;
121- CleanupForPoolReturn ( ) ;
122- if ( localPoolReturn != null )
123- localPoolReturn . ReturnToPoolNow ( ) ;
76+ if ( projectileTrailRend != null )
77+ {
78+ projectileTrailRend . emitting = false ;
79+ projectileTrailRend . enabled = false ;
80+ projectileTrailRend . Clear ( ) ;
81+ }
12482 }
12583
126- public void HideProjectile ( )
84+ public override void HideProjectile ( )
12785 {
12886 if ( projectileSpriteRend != null ) projectileSpriteRend . enabled = false ;
12987 if ( projectileTrailRend != null ) projectileTrailRend . emitting = false ;
@@ -136,14 +94,11 @@ public void HideProjectile()
13694 private void ApplySpriteOptimised ( Sprite sprite )
13795 {
13896 if ( projectileSpriteRend == null ) return ;
139-
14097 bool shouldShow = sprite != null ;
141- projectileSpriteRend . enabled = shouldShow ;
142- // FIX: sprite draws in front of trail
143- projectileSpriteRend . sortingOrder = _spriteSortingOrder ;
98+ projectileSpriteRend . enabled = shouldShow ;
99+ projectileSpriteRend . sortingOrder = _spriteSortingOrder ;
144100
145101 if ( ! shouldShow ) return ;
146-
147102 if ( _cachedSprite != sprite )
148103 {
149104 projectileSpriteRend . sprite = sprite ;
@@ -163,7 +118,6 @@ private void ApplyTrailOptimised(ProjectileConfigSO cfg)
163118 return ;
164119 }
165120
166- // FIX: _trailConfigured flag correctly gates re-application on recycled objects
167121 if ( ! _trailConfigured )
168122 {
169123 if ( cfg . TrailMaterial != null )
@@ -177,20 +131,18 @@ private void ApplyTrailOptimised(ProjectileConfigSO cfg)
177131 if ( cfg . UseGradientOverride && cfg . TrailGradient != null )
178132 projectileTrailRend . colorGradient = cfg . TrailGradient ;
179133
180- projectileTrailRend . time = cfg . TrailTime ;
181- projectileTrailRend . startWidth = cfg . TrailStartWidth ;
182- projectileTrailRend . endWidth = cfg . TrailEndWidth ;
183- projectileTrailRend . numCapVertices = cfg . TrailCapVertices ;
184- projectileTrailRend . minVertexDistance = cfg . TrailMinVertexDistance ;
134+ projectileTrailRend . time = cfg . TrailTime ;
135+ projectileTrailRend . startWidth = cfg . TrailStartWidth ;
136+ projectileTrailRend . endWidth = cfg . TrailEndWidth ;
137+ projectileTrailRend . numCapVertices = cfg . TrailCapVertices ;
138+ projectileTrailRend . minVertexDistance = cfg . TrailMinVertexDistance ;
185139
186140 projectileTrailRend . shadowCastingMode = UnityEngine . Rendering . ShadowCastingMode . Off ;
187141 projectileTrailRend . receiveShadows = false ;
188142 projectileTrailRend . generateLightingData = false ;
189143 projectileTrailRend . motionVectorGenerationMode = MotionVectorGenerationMode . ForceNoMotion ;
190144 projectileTrailRend . alignment = LineAlignment . View ;
191-
192- // FIX: trail behind sprite
193- projectileTrailRend . sortingOrder = _trailSortingOrder ;
145+ projectileTrailRend . sortingOrder = _trailSortingOrder ;
194146
195147 _trailConfigured = true ;
196148 }
@@ -201,34 +153,5 @@ private void ApplyTrailOptimised(ProjectileConfigSO cfg)
201153 }
202154
203155 #endregion
204-
205- #region Pool Cleanup
206-
207- private void CleanupForPoolReturn ( )
208- {
209- _initialised = false ;
210- _config = null ;
211- _trailConfigured = false ;
212- _cachedSprite = null ;
213-
214- if ( projectileSpriteRend != null )
215- {
216- projectileSpriteRend . enabled = true ;
217- projectileSpriteRend . sprite = null ;
218- projectileSpriteRend . color = Color . white ;
219- }
220-
221- if ( projectileTrailRend != null )
222- {
223- projectileTrailRend . emitting = false ;
224- projectileTrailRend . enabled = false ;
225- projectileTrailRend . Clear ( ) ;
226- }
227-
228- MID_Logger . LogDebug ( _logLevel , "Cleaned up for pool return." ,
229- nameof ( ProjectileVisual_ ) ) ;
230- }
231-
232- #endregion
233156 }
234157}
0 commit comments