com.wallstop-studios.unity-helpers 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,6 +109,14 @@ namespace WallstopStudios.UnityHelpers.Tags
109
109
  /// <param name="target">The Unity Object (GameObject or Component) to check.</param>
110
110
  /// <param name="effectTag">The tag to check for.</param>
111
111
  /// <returns><c>true</c> if the target has a TagHandler with the specified tag; otherwise, <c>false</c>.</returns>
112
+ /// <example>
113
+ /// <code>
114
+ /// if (player.HasTag("Stunned"))
115
+ /// {
116
+ /// DisableMovement();
117
+ /// }
118
+ /// </code>
119
+ /// </example>
112
120
  public static bool HasTag(this Object target, string effectTag)
113
121
  {
114
122
  if (target == null)
@@ -126,6 +134,15 @@ namespace WallstopStudios.UnityHelpers.Tags
126
134
  /// <param name="target">The Unity Object (GameObject or Component) to check.</param>
127
135
  /// <param name="effectTags">The collection of tags to check for.</param>
128
136
  /// <returns><c>true</c> if the target has any of the specified tags; otherwise, <c>false</c>.</returns>
137
+ /// <example>
138
+ /// <code>
139
+ /// string[] crowdControlTags = { "Stunned", "Frozen", "KnockedDown" };
140
+ /// if (player.HasAnyTag(crowdControlTags))
141
+ /// {
142
+ /// ShowCrowdControlUI();
143
+ /// }
144
+ /// </code>
145
+ /// </example>
129
146
  public static bool HasAnyTag(this Object target, IEnumerable<string> effectTags)
130
147
  {
131
148
  if (target == null)
@@ -143,6 +160,7 @@ namespace WallstopStudios.UnityHelpers.Tags
143
160
  /// <param name="target">The Unity Object (GameObject or Component) to check.</param>
144
161
  /// <param name="effectTags">The list of tags to check for.</param>
145
162
  /// <returns><c>true</c> if the target has any of the specified tags; otherwise, <c>false</c>.</returns>
163
+ /// <remarks>Equivalent to <see cref="HasAnyTag(UnityEngine.Object,System.Collections.Generic.IEnumerable{string})"/> but optimized for indexable lists.</remarks>
146
164
  public static bool HasAnyTag(this Object target, IReadOnlyList<string> effectTags)
147
165
  {
148
166
  if (target == null)
@@ -154,6 +172,186 @@ namespace WallstopStudios.UnityHelpers.Tags
154
172
  && tagHandler.HasAnyTag(effectTags);
155
173
  }
156
174
 
175
+ /// <summary>
176
+ /// Extension method to check if a Unity Object has all of the specified tags.
177
+ /// </summary>
178
+ /// <param name="target">The Unity Object (GameObject or Component) to check.</param>
179
+ /// <param name="effectTags">The collection of tags that must all be active.</param>
180
+ /// <returns><c>true</c> if all tags are active; otherwise, <c>false</c>.</returns>
181
+ /// <example>
182
+ /// <code>
183
+ /// string[] stealthRequirements = { "Invisible", "Silenced" };
184
+ /// if (player.HasAllTags(stealthRequirements))
185
+ /// {
186
+ /// EnableBackstabBonus();
187
+ /// }
188
+ /// </code>
189
+ /// </example>
190
+ public static bool HasAllTags(this Object target, IEnumerable<string> effectTags)
191
+ {
192
+ if (target == null)
193
+ {
194
+ return false;
195
+ }
196
+
197
+ return target.TryGetComponent(out TagHandler tagHandler)
198
+ && tagHandler.HasAllTags(effectTags);
199
+ }
200
+
201
+ /// <summary>
202
+ /// Extension method to check if a Unity Object has all of the specified tags.
203
+ /// </summary>
204
+ /// <param name="target">The Unity Object (GameObject or Component) to check.</param>
205
+ /// <param name="effectTags">The list of tags that must all be active.</param>
206
+ /// <returns><c>true</c> if all tags are active; otherwise, <c>false</c>.</returns>
207
+ /// <remarks>Equivalent to <see cref="HasAllTags(UnityEngine.Object,System.Collections.Generic.IEnumerable{string})"/> but optimized for indexable lists.</remarks>
208
+ public static bool HasAllTags(this Object target, IReadOnlyList<string> effectTags)
209
+ {
210
+ if (target == null)
211
+ {
212
+ return false;
213
+ }
214
+
215
+ return target.TryGetComponent(out TagHandler tagHandler)
216
+ && tagHandler.HasAllTags(effectTags);
217
+ }
218
+
219
+ /// <summary>
220
+ /// Extension method to determine whether none of the specified tags are active on the target.
221
+ /// </summary>
222
+ /// <param name="target">The Unity Object (GameObject or Component) to check.</param>
223
+ /// <param name="effectTags">The collection of tags to inspect.</param>
224
+ /// <returns><c>true</c> if no tags in the collection are active; otherwise, <c>false</c>.</returns>
225
+ public static bool HasNoneOfTags(this Object target, IEnumerable<string> effectTags)
226
+ {
227
+ if (target == null)
228
+ {
229
+ return true;
230
+ }
231
+
232
+ return !target.TryGetComponent(out TagHandler tagHandler)
233
+ || tagHandler.HasNoneOfTags(effectTags);
234
+ }
235
+
236
+ /// <summary>
237
+ /// Extension method to determine whether none of the specified tags are active on the target.
238
+ /// </summary>
239
+ /// <param name="target">The Unity Object (GameObject or Component) to check.</param>
240
+ /// <param name="effectTags">The list of tags to inspect.</param>
241
+ /// <returns><c>true</c> if no tags in the collection are active; otherwise, <c>false</c>.</returns>
242
+ public static bool HasNoneOfTags(this Object target, IReadOnlyList<string> effectTags)
243
+ {
244
+ if (target == null)
245
+ {
246
+ return true;
247
+ }
248
+
249
+ return !target.TryGetComponent(out TagHandler tagHandler)
250
+ || tagHandler.HasNoneOfTags(effectTags);
251
+ }
252
+
253
+ /// <summary>
254
+ /// Attempts to retrieve the active count for a specific tag on the target.
255
+ /// </summary>
256
+ /// <param name="target">The Unity Object (GameObject or Component) to inspect.</param>
257
+ /// <param name="effectTag">The tag whose count should be retrieved.</param>
258
+ /// <param name="count">
259
+ /// When this method returns, contains the tag count (cast to <see cref="int"/>) if available; otherwise, zero.
260
+ /// </param>
261
+ /// <returns><c>true</c> if the target has a <see cref="TagHandler"/> and the tag is tracked; otherwise, <c>false</c>.</returns>
262
+ /// <example>
263
+ /// <code>
264
+ /// if (target.TryGetTagCount("Bleeding", out int stacks) && stacks >= 5)
265
+ /// {
266
+ /// ApplyMajorWoundPenalty();
267
+ /// }
268
+ /// </code>
269
+ /// </example>
270
+ public static bool TryGetTagCount(this Object target, string effectTag, out int count)
271
+ {
272
+ count = 0;
273
+ if (target == null)
274
+ {
275
+ return false;
276
+ }
277
+
278
+ return target.TryGetComponent(out TagHandler tagHandler)
279
+ && tagHandler.TryGetTagCount(effectTag, out count);
280
+ }
281
+
282
+ /// <summary>
283
+ /// Retrieves the active tags on the target into an optional buffer.
284
+ /// </summary>
285
+ /// <param name="target">The Unity Object (GameObject or Component) to inspect.</param>
286
+ /// <param name="buffer">
287
+ /// Optional buffer to populate. When <c>null</c>, a new list is created. The buffer is cleared before population.
288
+ /// </param>
289
+ /// <returns>The populated buffer of active tags. The buffer is empty when no tags are present or no handler exists.</returns>
290
+ /// <example>
291
+ /// <code>
292
+ /// List&lt;string&gt; activeTags = target.GetActiveTags(_tagBuffer);
293
+ /// if (activeTags.Contains("Invisible"))
294
+ /// {
295
+ /// EnableDetectionShader();
296
+ /// }
297
+ /// </code>
298
+ /// </example>
299
+ public static List<string> GetActiveTags(this Object target, List<string> buffer = null)
300
+ {
301
+ buffer ??= new List<string>();
302
+ buffer.Clear();
303
+ if (target == null)
304
+ {
305
+ return buffer;
306
+ }
307
+
308
+ if (!target.TryGetComponent(out TagHandler tagHandler))
309
+ {
310
+ return buffer;
311
+ }
312
+
313
+ return tagHandler.GetActiveTags(buffer);
314
+ }
315
+
316
+ /// <summary>
317
+ /// Retrieves all effect handles that contributed a specific tag on the target.
318
+ /// </summary>
319
+ /// <param name="target">The Unity Object (GameObject or Component) to inspect.</param>
320
+ /// <param name="effectTag">The tag to query.</param>
321
+ /// <param name="buffer">
322
+ /// Optional buffer to populate. When <c>null</c>, a new list is created. The buffer is cleared before population.
323
+ /// </param>
324
+ /// <returns>The populated buffer of effect handles whose effects contain <paramref name="effectTag"/>.</returns>
325
+ /// <example>
326
+ /// <code>
327
+ /// List&lt;EffectHandle&gt; taggedHandles = target.GetHandlesWithTag("Burning", _handleBuffer);
328
+ /// foreach (EffectHandle handle in taggedHandles)
329
+ /// {
330
+ /// target.RefreshEffect(handle);
331
+ /// }
332
+ /// </code>
333
+ /// </example>
334
+ public static List<EffectHandle> GetHandlesWithTag(
335
+ this Object target,
336
+ string effectTag,
337
+ List<EffectHandle> buffer = null
338
+ )
339
+ {
340
+ buffer ??= new List<EffectHandle>();
341
+ buffer.Clear();
342
+ if (target == null)
343
+ {
344
+ return buffer;
345
+ }
346
+
347
+ if (!target.TryGetComponent(out TagHandler tagHandler))
348
+ {
349
+ return buffer;
350
+ }
351
+
352
+ return tagHandler.GetHandlesWithTag(effectTag, buffer);
353
+ }
354
+
157
355
  /// <summary>
158
356
  /// Extension method to apply an effect to a Unity Object.
159
357
  /// Automatically adds an EffectHandler component if one doesn't exist.
@@ -161,6 +359,17 @@ namespace WallstopStudios.UnityHelpers.Tags
161
359
  /// <param name="target">The Unity Object (GameObject or Component) to apply the effect to.</param>
162
360
  /// <param name="attributeEffect">The effect to apply.</param>
163
361
  /// <returns>An EffectHandle for non-instant effects, or null for instant effects.</returns>
362
+ /// <example>
363
+ /// <code>
364
+ /// EffectHandle? handle = enemy.ApplyEffect(poisonEffect);
365
+ /// if (!handle.HasValue)
366
+ /// {
367
+ /// // Instant effects do not return a handle
368
+ /// return;
369
+ /// }
370
+ /// activeHandles.Add(handle.Value);
371
+ /// </code>
372
+ /// </example>
164
373
  public static EffectHandle? ApplyEffect(this Object target, AttributeEffect attributeEffect)
165
374
  {
166
375
  if (target == null)
@@ -172,6 +381,17 @@ namespace WallstopStudios.UnityHelpers.Tags
172
381
  return effectHandler.ApplyEffect(attributeEffect);
173
382
  }
174
383
 
384
+ /// <summary>
385
+ /// Applies a list of effects to the target without allocating a result collection.
386
+ /// </summary>
387
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
388
+ /// <param name="attributeEffects">The list of effects to apply.</param>
389
+ /// <remarks>Effects are applied sequentially; instant effects still return <c>null</c> handles internally.</remarks>
390
+ /// <example>
391
+ /// <code>
392
+ /// AttributeUtilities.ApplyEffectsNoAlloc(player, _precomputedEffects);
393
+ /// </code>
394
+ /// </example>
175
395
  public static void ApplyEffectsNoAlloc(
176
396
  this Object target,
177
397
  List<AttributeEffect> attributeEffects
@@ -193,6 +413,12 @@ namespace WallstopStudios.UnityHelpers.Tags
193
413
  }
194
414
  }
195
415
 
416
+ /// <summary>
417
+ /// Applies a sequence of effects to the target without allocations, iterating any enumerable.
418
+ /// </summary>
419
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
420
+ /// <param name="attributeEffects">The enumerable of effects to apply.</param>
421
+ /// <remarks>Use when you are streaming effects from a generator or LINQ query.</remarks>
196
422
  public static void ApplyEffectsNoAlloc(
197
423
  this Object target,
198
424
  IEnumerable<AttributeEffect> attributeEffects
@@ -210,6 +436,19 @@ namespace WallstopStudios.UnityHelpers.Tags
210
436
  }
211
437
  }
212
438
 
439
+ /// <summary>
440
+ /// Applies a list of effects to the target and collects any returned handles into the supplied buffer.
441
+ /// </summary>
442
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
443
+ /// <param name="attributeEffects">The list of effects to apply.</param>
444
+ /// <param name="effectHandles">Buffer that receives non-<c>null</c> handles. The buffer is not cleared automatically.</param>
445
+ /// <example>
446
+ /// <code>
447
+ /// _handles.Clear();
448
+ /// target.ApplyEffectsNoAlloc(burstEffects, _handles);
449
+ /// // _handles now contains handles for duration and infinite effects.
450
+ /// </code>
451
+ /// </example>
213
452
  public static void ApplyEffectsNoAlloc(
214
453
  this Object target,
215
454
  List<AttributeEffect> attributeEffects,
@@ -232,6 +471,18 @@ namespace WallstopStudios.UnityHelpers.Tags
232
471
  }
233
472
  }
234
473
 
474
+ /// <summary>
475
+ /// Applies a list of effects to the target and returns the collected handles.
476
+ /// </summary>
477
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
478
+ /// <param name="attributeEffects">The list of effects to apply.</param>
479
+ /// <returns>A list containing handles for every duration or infinite effect that was applied.</returns>
480
+ /// <example>
481
+ /// <code>
482
+ /// List&lt;EffectHandle&gt; handles = player.ApplyEffects(bossOpeners);
483
+ /// _activeBossEffects.AddRange(handles);
484
+ /// </code>
485
+ /// </example>
235
486
  public static List<EffectHandle> ApplyEffects(
236
487
  this Object target,
237
488
  List<AttributeEffect> attributeEffects
@@ -242,6 +493,20 @@ namespace WallstopStudios.UnityHelpers.Tags
242
493
  return handles;
243
494
  }
244
495
 
496
+ /// <summary>
497
+ /// Removes a previously applied effect by handle.
498
+ /// </summary>
499
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
500
+ /// <param name="effectHandle">The handle returned by <see cref="ApplyEffect(UnityEngine.Object, AttributeEffect)"/>.</param>
501
+ /// <example>
502
+ /// <code>
503
+ /// if (_slowHandle.HasValue)
504
+ /// {
505
+ /// enemy.RemoveEffect(_slowHandle.Value);
506
+ /// _slowHandle = null;
507
+ /// }
508
+ /// </code>
509
+ /// </example>
245
510
  public static void RemoveEffect(this Object target, EffectHandle effectHandle)
246
511
  {
247
512
  if (target == null)
@@ -255,6 +520,17 @@ namespace WallstopStudios.UnityHelpers.Tags
255
520
  }
256
521
  }
257
522
 
523
+ /// <summary>
524
+ /// Removes a collection of effect handles from the target.
525
+ /// </summary>
526
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
527
+ /// <param name="effectHandles">Handles to remove. The list is iterated as-is.</param>
528
+ /// <example>
529
+ /// <code>
530
+ /// target.RemoveEffects(_queuedDispels);
531
+ /// _queuedDispels.Clear();
532
+ /// </code>
533
+ /// </example>
258
534
  public static void RemoveEffects(this Object target, List<EffectHandle> effectHandles)
259
535
  {
260
536
  if (target == null || effectHandles.Count <= 0)
@@ -271,6 +547,16 @@ namespace WallstopStudios.UnityHelpers.Tags
271
547
  }
272
548
  }
273
549
 
550
+ /// <summary>
551
+ /// Removes every active effect from the target.
552
+ /// </summary>
553
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
554
+ /// <example>
555
+ /// <code>
556
+ /// // Cleanse all effects when respawning the character
557
+ /// character.RemoveAllEffects();
558
+ /// </code>
559
+ /// </example>
274
560
  public static void RemoveAllEffects(this Object target)
275
561
  {
276
562
  if (target == null)
@@ -284,6 +570,185 @@ namespace WallstopStudios.UnityHelpers.Tags
284
570
  }
285
571
  }
286
572
 
573
+ /// <summary>
574
+ /// Determines whether the specified effect is currently active on the target.
575
+ /// </summary>
576
+ /// <param name="target">The Unity Object (GameObject or Component) to inspect.</param>
577
+ /// <param name="attributeEffect">The effect to query.</param>
578
+ /// <returns><c>true</c> if the effect is active; otherwise, <c>false</c>.</returns>
579
+ /// <example>
580
+ /// <code>
581
+ /// if (!enemy.IsEffectActive(slowEffect))
582
+ /// {
583
+ /// enemy.ApplyEffect(slowEffect);
584
+ /// }
585
+ /// </code>
586
+ /// </example>
587
+ public static bool IsEffectActive(this Object target, AttributeEffect attributeEffect)
588
+ {
589
+ if (target == null)
590
+ {
591
+ return false;
592
+ }
593
+
594
+ return target.TryGetComponent(out EffectHandler effectHandler)
595
+ && effectHandler.IsEffectActive(attributeEffect);
596
+ }
597
+
598
+ /// <summary>
599
+ /// Retrieves the number of active handles for the specified effect on the target.
600
+ /// </summary>
601
+ /// <param name="target">The Unity Object (GameObject or Component) to inspect.</param>
602
+ /// <param name="attributeEffect">The effect to count.</param>
603
+ /// <returns>The number of active handles; zero when inactive.</returns>
604
+ /// <example>
605
+ /// <code>
606
+ /// int stacks = target.GetEffectStackCount(bleedEffect);
607
+ /// bleedStacksLabel.text = stacks.ToString();
608
+ /// </code>
609
+ /// </example>
610
+ public static int GetEffectStackCount(this Object target, AttributeEffect attributeEffect)
611
+ {
612
+ if (target == null)
613
+ {
614
+ return 0;
615
+ }
616
+
617
+ return target.TryGetComponent(out EffectHandler effectHandler)
618
+ ? effectHandler.GetEffectStackCount(attributeEffect)
619
+ : 0;
620
+ }
621
+
622
+ /// <summary>
623
+ /// Copies all active effect handles on the target into the provided buffer.
624
+ /// </summary>
625
+ /// <param name="target">The Unity Object (GameObject or Component) to inspect.</param>
626
+ /// <param name="buffer">
627
+ /// Optional buffer to populate. When <c>null</c>, a new list is created. The buffer is cleared before population.
628
+ /// </param>
629
+ /// <returns>The populated buffer containing every active effect handle.</returns>
630
+ /// <example>
631
+ /// <code>
632
+ /// List&lt;EffectHandle&gt; handles = target.GetActiveEffects(_handleBuffer);
633
+ /// foreach (EffectHandle handle in handles)
634
+ /// {
635
+ /// Debug.Log(handle);
636
+ /// }
637
+ /// </code>
638
+ /// </example>
639
+ public static List<EffectHandle> GetActiveEffects(
640
+ this Object target,
641
+ List<EffectHandle> buffer = null
642
+ )
643
+ {
644
+ buffer ??= new List<EffectHandle>();
645
+ buffer.Clear();
646
+ if (target == null)
647
+ {
648
+ return buffer;
649
+ }
650
+
651
+ if (!target.TryGetComponent(out EffectHandler effectHandler))
652
+ {
653
+ return buffer;
654
+ }
655
+
656
+ return effectHandler.GetActiveEffects(buffer);
657
+ }
658
+
659
+ /// <summary>
660
+ /// Attempts to retrieve the remaining duration for the specified effect handle on the target.
661
+ /// </summary>
662
+ /// <param name="target">The Unity Object (GameObject or Component) to inspect.</param>
663
+ /// <param name="effectHandle">The handle to query.</param>
664
+ /// <param name="remainingDuration">When this method returns, contains the remaining time if available; otherwise, zero.</param>
665
+ /// <returns><c>true</c> if a duration was found; otherwise, <c>false</c>.</returns>
666
+ public static bool TryGetRemainingDuration(
667
+ this Object target,
668
+ EffectHandle effectHandle,
669
+ out float remainingDuration
670
+ )
671
+ {
672
+ remainingDuration = 0f;
673
+ if (target == null)
674
+ {
675
+ return false;
676
+ }
677
+
678
+ return target.TryGetComponent(out EffectHandler effectHandler)
679
+ && effectHandler.TryGetRemainingDuration(effectHandle, out remainingDuration);
680
+ }
681
+
682
+ /// <summary>
683
+ /// Ensures an effect handle exists on the target, adding an EffectHandler when necessary.
684
+ /// </summary>
685
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
686
+ /// <param name="attributeEffect">The effect to apply or refresh.</param>
687
+ /// <returns>An active handle for the effect, or <c>null</c> for instant effects.</returns>
688
+ public static EffectHandle? EnsureHandle(
689
+ this Object target,
690
+ AttributeEffect attributeEffect
691
+ )
692
+ {
693
+ return EnsureHandle(target, attributeEffect, refreshDuration: true);
694
+ }
695
+
696
+ /// <summary>
697
+ /// Ensures an effect handle exists on the target, adding an EffectHandler when necessary.
698
+ /// </summary>
699
+ /// <param name="target">The Unity Object (GameObject or Component) to modify.</param>
700
+ /// <param name="attributeEffect">The effect to apply or refresh.</param>
701
+ /// <param name="refreshDuration">
702
+ /// When <c>true</c>, attempts to refresh the duration of an existing handle if the effect supports it.
703
+ /// </param>
704
+ /// <returns>An active handle for the effect, or <c>null</c> for instant effects.</returns>
705
+ public static EffectHandle? EnsureHandle(
706
+ this Object target,
707
+ AttributeEffect attributeEffect,
708
+ bool refreshDuration
709
+ )
710
+ {
711
+ if (target == null)
712
+ {
713
+ return null;
714
+ }
715
+
716
+ EffectHandler effectHandler = target.GetGameObject().GetOrAddComponent<EffectHandler>();
717
+ return effectHandler.EnsureHandle(attributeEffect, refreshDuration);
718
+ }
719
+
720
+ /// <summary>
721
+ /// Attempts to refresh the duration of an effect handle on the target.
722
+ /// </summary>
723
+ /// <param name="target">The Unity Object (GameObject or Component) to inspect.</param>
724
+ /// <param name="effectHandle">The handle to refresh.</param>
725
+ /// <param name="ignoreReapplicationPolicy">
726
+ /// When <c>true</c>, refreshes the duration even if the effect disallows reapplication resets.
727
+ /// </param>
728
+ /// <returns><c>true</c> if the duration was refreshed; otherwise, <c>false</c>.</returns>
729
+ /// <example>
730
+ /// <code>
731
+ /// if (!target.RefreshEffect(handle))
732
+ /// {
733
+ /// target.RefreshEffect(handle, ignoreReapplicationPolicy: true);
734
+ /// }
735
+ /// </code>
736
+ /// </example>
737
+ public static bool RefreshEffect(
738
+ this Object target,
739
+ EffectHandle effectHandle,
740
+ bool ignoreReapplicationPolicy = false
741
+ )
742
+ {
743
+ if (target == null)
744
+ {
745
+ return false;
746
+ }
747
+
748
+ return target.TryGetComponent(out EffectHandler effectHandler)
749
+ && effectHandler.RefreshEffect(effectHandle, ignoreReapplicationPolicy);
750
+ }
751
+
287
752
  public static Dictionary<string, FieldInfo> GetAttributeFields(Type type)
288
753
  {
289
754
  return AttributeFields.GetOrAdd(
@@ -48,7 +48,7 @@ namespace WallstopStudios.UnityHelpers.Tags
48
48
  /// </summary>
49
49
  public event Action<string, float, float> OnAttributeModified;
50
50
 
51
- private readonly Dictionary<string, Func<object, Attribute>> _attributeFieldGetters;
51
+ private Dictionary<string, Func<object, Attribute>> _attributeFieldGetters;
52
52
  private readonly HashSet<EffectHandle> _effectHandles;
53
53
 
54
54
  [SiblingComponent]
@@ -62,7 +62,6 @@ namespace WallstopStudios.UnityHelpers.Tags
62
62
  /// </summary>
63
63
  protected AttributesComponent()
64
64
  {
65
- _attributeFieldGetters = AttributeUtilities.GetOptimizedAttributeFields(GetType());
66
65
  _effectHandles = new HashSet<EffectHandle>();
67
66
  }
68
67
 
@@ -72,6 +71,7 @@ namespace WallstopStudios.UnityHelpers.Tags
72
71
  /// </summary>
73
72
  protected virtual void Awake()
74
73
  {
74
+ EnsureAttributeFieldGettersInitialized();
75
75
  this.AssignSiblingComponents();
76
76
  _effectHandler.Register(this);
77
77
  }
@@ -217,8 +217,9 @@ namespace WallstopStudios.UnityHelpers.Tags
217
217
  }
218
218
  }
219
219
 
220
- private bool TryGetAttribute(string attributeName, out Attribute attribute)
220
+ protected bool TryGetAttribute(string attributeName, out Attribute attribute)
221
221
  {
222
+ EnsureAttributeFieldGettersInitialized();
222
223
  if (
223
224
  !_attributeFieldGetters.TryGetValue(
224
225
  attributeName,
@@ -233,5 +234,15 @@ namespace WallstopStudios.UnityHelpers.Tags
233
234
  attribute = getter(this);
234
235
  return true;
235
236
  }
237
+
238
+ protected void EnsureAttributeFieldGettersInitialized()
239
+ {
240
+ if (_attributeFieldGetters != null)
241
+ {
242
+ return;
243
+ }
244
+
245
+ _attributeFieldGetters = AttributeUtilities.GetOptimizedAttributeFields(GetType());
246
+ }
236
247
  }
237
248
  }