This document is about: QUANTUM 3
SWITCH TO

This page is a work in progress and could be pending updates.

Project Architecture

Introduction

This page provides an overview of the project’s overall structure. The game is designed around the idea that animation-driven motion is the central element that defines gameplay behavior. Rather than using abstract Animator Mecanim state transitions or manually scripted KCC movement, the system derives the character’s actions and movement primarily from animation-driven data.

The core gameplay systems implementing this approach are:

Animated Bone Sampler: responsible for sampling colliders motion and extracting frame-accurate position data.

Root Motion: drives character displacement and rotation directly from animation data.

FadeTo Animations: a controlled transition system that blends animations with precise timing.

This project makes extensive use of the Animator Addon, it is important to read the Animator documentation prior to exploring this sample source.
This project is under development, and structural changes may occur during the process.

Animated Bone Sampler

Some games require extracting specific information from animations so it can be used in the Quantum simulation, for example, determining the hitbox of a sword that moves quickly during an attack animation.

The technique referred to here as Animated Bone Sampler demonstrates how the Quantum Animator can be extended to extract custom data from an animation clip. It is important to note that this is not a one-size-fits-all solution, depending on the project, additional adjustments or modifications may be required to support different scenarios or more complex animation setups.

Known Issues: inconsistent results when sampling states that use Blend Trees or during transitions. Fix planed to next version.

Usage and DSL Layout

In the DSL (game state), the sampled data is stored in the dictionary contained within the AnimationBoneSamplerComponent.
The BoneSamplerType enum should be extended whenever new types of data need to be captured or processed by the sampler.

For more details on how the solution works internally, refer to the AnimationBoneSamplerSystem, which is responsible for advancing the bones data according to the animation, making it ready for being polled by custom code.
The image below illustrates how the stored data can be retrieved from the dictionary and used to perform an Shape Queries, for example, as demonstrated on this sample in the AttackSystem.

Custom Graph Baker

The AnimatorGraph’s ReferenceModel is the GameObject used to extract animation data during the bake process. To enable bone tracking, add the AnimationBoneSamplerMarker component in the hierarchy of the ReferenceModel, along with the desired position offset based on the bone/object you want to track.
The figure below shows how this configuration was applied for the sword’s collider. The Shape Config used for sampling can be of type Box, Sphere, or Capsule.

For each state defined in the Unity AnimatorController, the baking process generates an AnimationBoneSamplerSpec asset located at: Assets/QuantumUser/AnimationBoneSampler/Resources/AnimatorGraphAsset-Name.

Root Motion

This sample project includes a fully functional 3D Root Motion implementation within the MovementSystem. It also provides a motion-cancellation mechanism that interrupts the current state whenever the character collides with or stumbles over an obstacle.
How motion is applied is entirely a design choice. In this case, the decision was made to disable the KCC and move the transform directly. The code below demonstrates how the animator’s root motion is processed.

C#

  // Handles 3D root motion
  public void OnAnimatorRootMotion3D(Frame f, EntityRef entity, AnimatorFrame deltaFrame, AnimatorFrame currentFrame)
    {
      if (deltaFrame.Position == FPVector3.Zero && deltaFrame.RotationY == FP._0) return;
      if (f.Unsafe.TryGetPointer<Transform3D>(entity, out var transform))
      {
        transform->Rotate(FPVector3.Up, deltaFrame.RotationY * FP.Rad2Deg);
        Draw.Ray(transform->Position, transform->Forward);
        transform->Teleport(f, transform->Rotation);

        var currentFrameRotation = FPQuaternion.CreateFromYawPitchRoll(currentFrame.RotationY, 0, 0);
        currentFrameRotation = FPQuaternion.Inverse(currentFrameRotation);
        var newPosition = currentFrameRotation * deltaFrame.Position;
        var displacement = transform->Rotation * newPosition;

        var targetHitPosition = (displacement.XOZ.Normalized * FP._0_33 * 2) + displacement;
        var hits = f.Physics3D.RaycastAll(transform->Position, targetHitPosition.XOZ, targetHitPosition.Magnitude, -1,
          QueryOptions.HitStatics);

        if (hits.Count <= 0)
        {
          if (f.Unsafe.TryGetPointer<KCC>(entity, out var kcc))
          {
            kcc->SetActive(false);
          }

          transform->Position += displacement;

          transform->Teleport(f, transform->Position);
        }
        else
        {
          f.Signals.OnSetHitAnimation(entity);
          f.Signals.OnCharacterTryChangeStatus(entity, StatusType.HitStun);
        }
      }
    }

FadeTo Animations

This project primarily uses FadeTo transitions. This means the Animator Controller is not the main responsible for deciding which animation state should play.
Instead, state selection and transition logic are handled programmatically. For more information on how this system works, refer to the CharacterAnimationSystem.

More Quantum Samples

Visit our Samples page to discover more example projects.

Back to top