Skip to content

Value Providers

The Core Idea

Instead of hardcoding a speed, a position, or a color, make it configurable - the user picks the source in the Inspector.

A speed can be a constant number for prototyping, an animation curve that ramps up over time, an Animator parameter driven by state machines, or a shared asset that multiple objects read from. The component using the speed does not care which - it just asks for the current value and gets a number back.

This is what value providers do: they separate what a value is from where it comes from. You configure the source in the Inspector by picking from a popup menu, and the component works with any source you choose.

Example use cases:

  • A movement speed that starts constant but later switches to an animation curve for polish
  • A target position that can come from a Transform, a spline point, or a raycast hit
  • A color that reads from a gradient, a material property, or a shared palette asset

How It Looks in the Inspector

A value provider field shows the currently selected source (e.g., "Constant Float"). Click it to switch to a different source. Expand the foldout to configure that source's settings.

For a float value, you might see options like:

  • Constant Float - A single number field
  • Animation Curve Float - A curve editor and time source
  • Transform Distance - Two Transform references and a distance mode
  • Random Range - Min and max fields

Practical Example

Consider a modifier that moves an object at a configurable speed. Without value providers:

// Hardcoded — speed is always a literal number
[SerializeField] private float speed = 5f;

With value providers:

[SerializeReference] [JungleClassSelection]
private IValue<float> speed;

void Update()
{
    transform.position += transform.forward * speed.Value() * Time.deltaTime;
}

Now the user can configure speed in the Inspector to come from:

  • A constant (5.0) for simple cases
  • An AnimationCurve that ramps up over time
  • An Animator float parameter driven by animation state
  • A ScriptableObject shared across multiple objects
  • A Random range for variety

The code does not change. Only the Inspector configuration does.


Data Flow

flowchart LR
    subgraph Sources
        A[Constant]
        B[Transform]
        C[ScriptableObject]
        D[AnimationCurve]
    end
    E["IValue&lt;float&gt;"]
    F[Consumer Component]
    A --> E
    B --> E
    C --> E
    D --> E
    E -->|"Value()"| F

The consumer only knows about the value interface. The concrete source is selected by the user in the Inspector through the class selection system.


Technical Details

IValue<T>

The read-only value provider. This is the interface you will encounter most often.

public interface IValue<out T>
{
    T Value();
}
Member Description
Value() Returns the current value
V Shorthand property — equivalent to Value()
Initialize() Called once when the value source is first used in a pipeline
HasMultipleValues Whether this provider can return more than one value
GetValueAt(int index) Access individual values for multi-value providers

ISettableValue<T>

Extends IValue<T> with write access. Used for two-way bindings where the consumer needs to push values back.

public interface ISettableValue<T> : IValue<T>, IValueApplier<T>
{
    void SetValue(T value);
}

IValueApplier<T>

Applies a value to a target. Used by pipelines to write the final computed value to its destination (e.g., setting a Transform's position).

public interface IValueApplier<T>
{
    T CurrentValue { get; }
    void Apply(T value);
    void Initialize();
    void Complete();
}

Common Value Types

Value providers exist for every common Unity type, spread across Jungle packages:

Type Package Example Sources
float jungle.math Constant, AnimationCurve, Animator parameter, Random range
int jungle.math Constant, counter, database lookup
double jungle.math Constant, high-precision calculations
Vector3 jungle.spatial Constant, Transform position, spline point
Quaternion jungle.spatial Constant, Transform rotation
Color jungle.graphics Constant, material property, gradient sample
bool jungle.core Constant, toggle state
string jungle.core Constant, localization key
GameObject jungle.objects Direct reference, find by tag/name
Component jungle.objects Direct reference, GetComponent lookup
Rigidbody jungle.physics Direct reference, velocity accessor

Value Sources

For any given type, multiple source implementations exist. Here are the common patterns:

Constant

The simplest provider. Returns a serialized value directly. Good for prototyping or when the value truly is fixed.

From Transform

Reads a value from a Transform component — position, rotation, scale, or a derived quantity like forward direction.

From Component Property

Reads a property from any component. For example, a Rigidbody's velocity magnitude as a float value.

From ScriptableObject

Reads from a shared ScriptableObject asset. Useful when multiple objects need to reference the same configurable value.

From Function

Computes the value dynamically. Wraps a delegate or expression — useful for math operations, remapping, or aggregating other values.


Contextual Values

Some places in Jungle evaluate a value chain with an implicit target — for example, a GameObjectFilter is asked "is this object valid?" and runs its serialized chain to decide. The object being checked isn't a serialized field anywhere; it's passed at runtime.

Contextual values let designers reach that runtime target from inside the chain.

The pattern

The classic example is Filtered Object: an IGameObjectValue that resolves to whichever object the surrounding filter is currently testing.

PhysicsOverlapFilter
  └─ sphereRadius (SmartFloat)
       └─ Bounds Sphere Radius
            └─ Local GameObject Bounds
                 └─ gameObject  ← Filtered Object

When the filter is asked about an object, every node in that chain ultimately reads from "the object being filtered" — so the sphere overlap is sized to that specific object's bounds, automatically. The same filter can be reused on objects of any size without per-object tuning.

When to use it

Use a contextual value when:

  • The same filter (or condition, or action) is reused across many different runtime targets, and
  • A node deep in the chain needs to read a property of whichever target is currently being evaluated.

If the target is fixed at edit time, use a normal serialized field instead — Local GameObject Bounds already takes a regular IGameObjectValue field, so for a static target you wire it to a literal reference.

How the editor surfaces them

The picker is context-aware: contextual values only appear in slots whose surrounding chain provides matching context. Inside a GameObjectFilter, the picker for any IGameObjectValue slot offers Filtered Object; outside one, it doesn't appear at all. This means most mis-wirings can't be authored in the first place.

ScriptableObject assets are an exception — values authored inside an SO can be referenced from anywhere, so the picker is permissive there. Inside an SO, contextual values are offered without a guaranteed provider; they resolve at the consuming site at runtime.

For plugin authors

An evaluation root declares what context it provides via an attribute on its base class:

[ProvidesEvaluationContext(typeof(FilteredObjectContext),
    "the object currently being evaluated by this filter")]
public abstract class GameObjectFilter : IGameObjectFilter { ... }

A contextual value declares what context it reads:

[ConsumesEvaluationContext(typeof(FilteredObjectContext))]
public sealed class FilteredObject : IGameObjectValue
{
    public GameObject Value()
        => JungleEvaluationContext.Get<FilteredObjectContext, GameObject>();
}

The runtime push/pop is on the provider — typically a one-line using block in the entry method:

public bool IsGameObjectValid(GameObject go)
{
    if (!go) return false;
    using (JungleEvaluationContext.Scope<FilteredObjectContext>(go))
        return Evaluate(go);
}

Once both attributes and the push are in place, the editor picker filters the value into matching slots automatically — no further wiring needed.


Value References

Value references are reactive, scene-side value holders. Where a value source provides a value for a consumer to read, a value reference holds a settable value and fires configured processes whenever it changes. This makes them useful as binding points in a scene: other code writes to the reference, and downstream logic reacts automatically.

ValueReference<T, TValue>

The abstract base class. It is a MonoBehaviour that wraps an ISettableValue<T> backing field and a list of IProcess instances.

public abstract class ValueReference<T, TValue> : MonoBehaviour
    where TValue : class, ISettableValue<T>
{
    public T Value { get; }
    public void SetValue(T value);
    public void Clear();
}
Member Description
Value Returns the current value from the backing settable value
SetValue(T value) Writes a new value. If the value changed, all configured processes are fired
Clear() Resets the held value to its default

The backing ISettableValue<T> and the process list are both configured in the Inspector through the standard class selection system.

GameObjectValueReference

A concrete implementation: ValueReference<GameObject, ISettableGameObjectValue>. Attach it to a GameObject in your scene to create a reactive binding point — other components or commands write a GameObject into it, and the configured processes fire in response.

ComponentReference<TComponent, TValue>

A generic base for referencing a Component that directly implements a value interface. It implements ISettableValue<TValue>, bridging a live component reference into the value system so it can be used anywhere a settable value is expected.