Class UnityTransformApplier
Service that applies TransformDelta scale and rotation changes to Unity transforms. Analogous to UnityMaterialApplier for material properties.
Targets are registered with their TransformId and original scale/rotation are cached. When Apply(TransformDelta) is called, every REGISTERED target is re-evaluated in a single self-correcting pass — see Apply(TransformDelta)'s own remarks — the same shape UnityActivationApplier uses for activation.
Inherited Members
Namespace: Serenity.ProceduralAnimator.Infrastructure.Services
Assembly: Serenity.UnityProceduralAnimator.Infrastructure.dll
Syntax
public class UnityTransformApplier
Constructors
UnityTransformApplier()
Declaration
public UnityTransformApplier()
Properties
TargetCount
Gets the number of registered transform targets.
Declaration
public int TargetCount { get; }
Property Value
| Type | Description |
|---|---|
| int |
Methods
Apply(TransformDelta)
Applies the transform delta to every registered target in a single self-correcting pass, mirroring
Apply(ActivationDelta)'s shape. For each target in Serenity.ProceduralAnimator.Infrastructure.Services.UnityTransformApplier._targets,
resolves the desired scale — the delta's scale entry for that target if present (original scale
times the entry's multiplier), otherwise the target's cached original scale — and, independently,
the desired rotation — the delta's rotation entry's Z degrees for that target if present, otherwise
null (meaning "at original", no offset).
Scale gate: written to the transform ONLY when the resolved desired
UnityEngine.Vector3 differs from the live UnityEngine.Transform.localScale —
UnityEngine.Vector3's == operator is an approximate (small-epsilon) comparison,
which is exactly what makes this guard cheap and safe for scale: it never leaves a
visually-identical write on the table, and it skips the write entirely once nothing is animating
this target, the same rationale Apply(ActivationDelta)'s own remarks give for
its guarded UnityEngine.GameObject.SetActive(System.Boolean) call. Scale's epsilon is negligible relative to the
magnitudes scale multipliers operate at, so this comparison never masks a real change.
Rotation gate: deliberately does NOT compare against the live
UnityEngine.Transform.localRotation the way scale compares against UnityEngine.Transform.localScale.
UnityEngine.Quaternion's == operator treats two rotations as equal once their dot
product exceeds 1 - 1e-6, which — via the small-angle identity 1 - cos(x) ≈ x²/2 — tolerates
roughly 0.162° of difference. A spin slower than ~9.7°/s at 60 fps advances by less than that
per frame, so gating on localRotation != desiredRotation would silently swallow every single-frame
write: the accumulated angle keeps advancing in delta, but nothing ever gets written to
the transform until enough ticks have piled up past the tolerance, producing a stall-then-jump stutter
instead of a smooth spin. Instead, each target caches the Z-degrees offset it last WROTE in
LastAppliedZDegrees (nullable float; null = at original), and the
gate is an exact float? comparison against that cache, not the transform. Sequence math upstream
(TransformRotationSequenceLayerSO) is deterministic, so this dedupes identical writes exactly the
same as before, but any REAL change — however small — always writes, because the comparison no longer
goes through quaternion-equality's epsilon.
Trade-off: because the gate no longer reads the live transform, it can no longer detect an
external write to localRotation made by something other than this method between two Apply(TransformDelta)
calls — such a write would be silently overwritten (or left alone, if it happens to match the cache) on the
next call. Today nothing else writes rotation on these targets: HUD elements only author their base rotation
once at build time, before this applier starts driving them. If a future caller needs an external rotation
writer to coexist with this applier, this gate is the place that assumption needs revisiting.
Because every registered target is re-evaluated against its own original scale/rotation on every call, no separate per-tick reset is needed (or should be called) before this — see ResetToOriginals()'s own remarks for why that method is reserved for teardown instead.
Position gate: a THREE-way branch per target, gated first by AllowPosition
— a target registered with that flag false is never read from or written to for position at all, not
even to restore it, no matter what the delta carries for its id (see RegisterTarget(TransformId, Transform, bool)'s
own remarks on why most targets register this false). For a target that DOES allow it: an entry
present writes OriginalPosition + Offset (guarded by UnityEngine.Vector3's
live-compare, the same cheap epsilon guard the scale gate uses — position magnitudes are pixels, so
the epsilon is negligible) and sets PositionDriven true; no entry
but still driven from a PRIOR call restores OriginalPosition exactly
once and clears the latch; no entry and not driven touches nothing. That latch is THE load-bearing
piece of the position facet's safety story: an undriven target's position is never written, so this
applier never becomes a standing position authority over a layout-managed element it merely happens
to have registered.
Declaration
public void Apply(TransformDelta delta)
Parameters
| Type | Name | Description |
|---|---|---|
| TransformDelta | delta | The transform delta containing this tick's scale/rotation/position overrides. May be null or empty — in that case every target simply resolves to its cached original scale/rotation, and an allow-position target with nothing currently driving it is left untouched. |
CacheOriginalScales()
Re-caches the original scale, rotation and position for all registered targets. Call this if the base scale/rotation/position changes at runtime.
Declaration
public void CacheOriginalScales()
ClearTargets()
Clears all registered transform targets.
Declaration
public void ClearTargets()
RecachePositionBaselines()
Re-derives each DRIVEN target's OriginalPosition baseline from its
current live local position, undoing only the offset THIS APPLIER itself last wrote
(LastAppliedPositionOffset) — newBase = live - appliedOffset —
rather than re-caching the live position outright the way CacheOriginalScales() does.
Declaration
public void RecachePositionBaselines()
Remarks
Needed when something ELSE moves a driven target's parent/anchors between two Apply(TransformDelta)
calls in the same frame — e.g. UnityHudHostComponent.ApplySafeAreaIfChanged shifting the HUD
root's rect before ProceduralAnimatorComponent.Tick runs — because at that point the live
position is (old base + old offset + external shift). Simply re-caching it as-is, the way
CacheOriginalScales() does, would fold the STILL-ACTIVE offset into the new baseline,
corrupting it: the next Apply(TransformDelta) call would then write (live) + offset — the offset
applied twice — instead of snapping cleanly to where the external mover actually put it. Subtracting
the offset actually applied recovers the mover's contribution alone.
Deliberately does NOT clear PositionDriven or
LastAppliedPositionOffset: an entry still driving this target keeps
driving it, and the very next Apply(TransformDelta) call writes newBase + offset against the
corrected baseline — this method only fixes the baseline, it does not touch the latch.
A target that is not currently driven has no offset to subtract
(LastAppliedPositionOffset is null, treated as
UnityEngine.Vector3.zero), so this degrades to a plain re-cache for it — the same
outcome CacheOriginalScales() produces for position, just without touching scale/rotation
too.
Skips a target with AllowPosition false: its OriginalPosition is never read (see that field's own remarks), so re-deriving it would be pointless work.
RegisterTarget(TransformId, Transform, bool)
Registers a transform target for procedural scale/rotation/position animation. Caches the original local scale, local rotation and local position for reset.
Declaration
public void RegisterTarget(TransformId id, Transform transform, bool allowPositionOverride = false)
Parameters
| Type | Name | Description |
|---|---|---|
| TransformId | id | Unique identifier for this transform target. |
| Transform | transform | The transform to animate. |
| bool | allowPositionOverride | Whether this target accepts position-channel animation. Defaults to false — position is
layout-hostile for a FLEXBOX-parented HUD element (see TransformDelta's own remarks),
so only a caller that has confirmed the target is safe to move (today: a root-level HUD element, see
|
ResetToOriginals()
Resets every registered transform to its cached original scale and rotation. No longer called
per-tick (see Apply(TransformDelta), which is self-correcting and falls back to each target's
original scale/rotation on its own) — this is for explicit/teardown use instead, e.g. component
OnDisable/OnDestroy, so nothing is left stuck at a procedural scale/rotation once the
animator stops running. Mirrors ResetToOriginalActive()'s role.
Only writes UnityEngine.Transform.localScale/UnityEngine.Transform.localRotation when the
current value actually differs from the cached original, for the same reason Apply(TransformDelta)
does.
Declaration
public void ResetToOriginals()
UnregisterTarget(TransformId)
Unregisters a transform target.
Declaration
public void UnregisterTarget(TransformId id)
Parameters
| Type | Name | Description |
|---|---|---|
| TransformId | id | The identifier of the target to unregister. |