Skip to content

00 — Attributes Quickstart

Goal: See what an attribute container is on its own — three authored rows, bounds that clamp, ids created by writing to them — and then what changes the moment you supply the one thing the framework refuses to ship for you.


What This Scene Demonstrates

Attributes splits cleanly in two, and the scene gives each half its own panel:

The container answers what numbers does this owner have. The combiner answers what do the numbers come out as.

AttributesBasicsPanel is the container. It never predicts a value: every number it shows is read back out of the real AttributeSet immediately after the write that produced it, which is the only way clamping can be observed from outside — bounds are not readable through the public API.

AttributesCombinerProvidersPanel is the seam. It builds its own owner, attaches providers to it and supplies a combiner, so it needs nothing from this scene at all. It ships two complete combiners over identical inputs, and the fact that they disagree is the point.


What Is In The Scene

Object What it is
Main Camera, Directional Light, Plane Ordinary scene furniture
Characters ▸ Player (Visual Only) A capsule carrying a StableId (Player) and the AttributeSet
Teaching Panel ▸ AttributesBasicsPanel Bound to the player's AttributeSet. Left of screen
Teaching Panel ▸ AttributesCombinerProvidersPanel Bound to nothing. Right of screen

The AttributeSet sits on the player because that is where it goes in a real project — this scene is the one that says so out loud. The panels stay off it, so the wiring you are being taught is visible as a wiring step rather than baked into the object.

Each panel is on its own GameObject. Two panels sharing one object draw into the same screen rect and open stacked and unreadable.

The three authored rows

id base min max Why this row exists
strength 10 0 100 Bounded both ends, so clamping is demonstrable at all
luck 0 — — Authored and unbounded — proof that the two are different properties
debt -50 -1000 0 Negative values and negative bounds are ordinary here

The Combiner Component slot is empty, and must stay empty. It is the shipped default, and the first thing the combining panel exists to explain.


What To Look For

On the Basics panel, drive the Writing tab first, from default state. Set strength to 500 and read the result line: it reports requested 500, stored 100, and the call still returned success. SetBaseValue promises the value was stored, not that it was stored unchanged — and reading it back is the only honest way to know which happened.

Then write to improvised, an id no row declares. It is created, and it is created unbounded — push a million into it and nothing stops you. Bounds live on the authored row, so an id with no row has nowhere to read them from. This is why a typo in an id never fails: strenght is a perfectly legal attribute that silently becomes a second stat nothing reads.

On the Combining panel, read the first tab before switching anything. Its default state has three providers attached and contributing nothing, because no combiner is selected. That is not a broken demo — it is the shipped behaviour, and it is quiet. Then switch between Sum and Flat then %: same base, same providers, same tags, two different totals. Neither is more correct than the other, which is exactly why no combiner ships.

Watch the Console once. Nothing in this scene should warn on Play. If it does — most likely "assigned as the combiner but does not implement IAttributeCombiner" — something has been dropped into the Combiner Component slot that does not belong there.