logo SERENITY
Game Systems Foundation
Search Results for

    Show / Hide Table of Contents

    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.

    Inheritance
    object
    UnityTransformApplier
    Inherited Members
    object.ToString()
    object.Equals(object)
    object.Equals(object, object)
    object.ReferenceEquals(object, object)
    object.GetHashCode()
    object.GetType()
    object.MemberwiseClone()
    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 UnityHudHostComponent.BuildAnimator) should pass true.

    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.

    In this article
    © 2026 Serenity. All Rights Reserved