Bindings y actions
Los dos enganches declarados con el juego — nombres que la UI dispara y rutas que lee y escribe. No hay expresiones: todo lo dinámico de una UI de zabloo se construye con estos dos.
La tienda enseña 1.250 en su contador de oro. El jugador compra la espada de
hierro y un momento después el contador dice 1.130. Nada del envelope hizo esa
resta. Al contador se le dijo que enseñara lo que hubiera en player.gold
dentro de los datos del propio juego —eso es un
binding, el enlace entre un
dato del juego y un nodo— y pulsar Comprar le mandó al juego un nombre al que
estaba suscrito, "buy" — eso es una
named action, la acción
con nombre que la UI dispara. Esos dos enganches son toda la conexión entre una
UI de zabloo y el juego que hay detrás.
Formalmente: la IR no contiene ninguna lógica. No puede bifurcar, calcular ni llamar a nada: es datos. Lo que sí lleva son esos dos enganches declarados, y todo lo dinámico de una UI de zabloo se construye con ellos.
| Mecanismo | Dirección | Qué es |
|---|---|---|
| Named actions | UI → juego | "onClick": "buy" — un nombre al que el juego se suscribe. |
| Data bindings | juego ↔ UI | { "bind": "player.gold" } — una dirección dentro de los datos del juego. |
No hay expresiones, y es a propósito. Ni condicionales, ni formateo, ni aritmética: un valor se enseña tal cual, y todo lo que haya que decidir lo decide el juego, que después mueve un valor al que la UI está bindeada.
Las named actions
Una prop de acción es una cadena que eligió el juego, expuesta de forma idiomática en cada motor: un evento de C#, una señal, un nodo de Blueprint. La IR declara que el enganche existe; lo que ocurre nunca está en el JSON.
Qué prop dispara cuándo es fijo, porque un juego suscrito a "buy" tiene que
significar lo mismo en todos los motores:
normativeProps de acción
| Prop | Nodo | Se dispara cuando |
|---|---|---|
| onClick | Button | Se activa — toque, Enter, A del mando. |
| onChange | Toggle, Slider, TextInput | El valor cambió, lo haya causado lo que lo haya causado. |
| onCommit | Slider | Un gesto de arrastre o de tecla terminó — el valor en el que el jugador se quedó. |
| onSubmit | TextInput | El jugador confirmó el campo (Enter). |
| onDismiss | Overlay | Se pidió cerrarlo — Escape, B del mando, toque en el backdrop, autoCloseMs. |
Varios nodos pueden declarar el mismo nombre de acción; el juego recibe un solo callback en cualquier caso.
El action context
Las dos filas de la tienda tienen un botón de comprar, y las dos se
construyeron con la misma plantilla, así que las dos disparan el mismo nombre.
El action context es
lo que le dice al juego que era la espada y no la poción — sin él, "buy" no
podría decir qué fila se compró:
{ "path": "shop.items.3", "key": "sword-01", "index": 3 }
normativeActionContext
| Campo | Tipo | Descripción |
|---|---|---|
| path | string | Ruta de datos absoluta del elemento. |
| key | string | number | La clave cruda del elemento, cuando su Repeat declara una. Ausente si la identidad es posicional. |
| index | number | Su posición en el array. |
Describe el elemento más interno, y con eso basta para las listas anidadas:
path ya lleva incrustado cada índice que lo envuelve
("shop.cats.2.items.5"), así que el juego puede direccionar la cadena entera a
partir de él. Una acción disparada fuera de un
Repeat no lleva
contexto.
Las rutas de datos
Una ruta de datos es una
dirección separada por puntos dentro de los datos del juego, no una clave
opaca. Se lee igual que leerías esa misma expresión en código —recorre player,
después gold— y eso es lo que permite que un binding apunte dentro de una
estructura que el juego ya tiene, en vez de pedirle que publique un conjunto
plano de variables de UI:
player.gold
shop.items.3.name
settings.audio.master
Un segmento numérico indexa un array, y ninguna otra cosa lo hace:
"length" es un nombre de campo, no una longitud. La lectura es total y nunca
lanza. Un segmento que falta, o uno recorrido a través de algo que no es un
objeto, da ningún valor, y la UI bindeada degrada a «nada que enseñar» en vez
de romper el frame.
Implementación de referencia: readPath en @zabloo/format.
Qué se puede bindear
Cualquier prop Bindable<T> acepta { "bind": "some.path" } en lugar de un
literal. No todas las props lo hacen, y las dos listas de abajo conviene leerlas
como una: la primera son datos que entran en la UI, la segunda son datos que la
UI tiene permiso para escribir de vuelta. Todo lo que no aparezca en ninguna de
las dos acepta solo un literal.
Solo lectura — el juego empuja, la UI sigue:
normativeProps bindeables de solo lectura
| Prop | Nodo |
|---|---|
| visible | Todos los nodos |
| disabled | Todos los nodos — heredada por su subárbol |
| text | Text |
| value | ProgressBar |
| items | Repeat — siempre un binding, nunca un array literal |
Lectura/escritura — el SDK escribe además de vuelta:
normativeProps bindeables de lectura/escritura
| Prop | Nodo |
|---|---|
| checked | Toggle |
| value | Slider |
| value | TextInput |
| value | Container con group: "exclusive-check" |
Un control que es dueño de un valor escribe cada cambio en el almacén de datos
del propio SDK y avisa al juego con un callback. Eso es lo que cierra el bucle
para los formularios: un TextInput bindeado y un Text sobre la misma ruta se
mantienen sincronizados sin nada de código de juego, y el juego se entera del
valor nuevo sin ir preguntando.
Repeat.items es siempre un binding: un array literal ahí metería datos
del juego dentro del documento, y el documento lleva estructura.
Una barra que cambia de color con su valor se hace con el juego moviendo un token, no con la UI calculando uno. El diccionario es parte del envelope y un tema se puede hot-updatear por su cuenta — ver El envelope › Los tokens.
Los ámbitos de elemento
Dentro de la plantilla de un Repeat, una ruta puede empezar por el alias
del elemento y se resuelve contra el elemento actual:
{ "type": "Repeat", "items": { "bind": "shop.items" }, "as": "item", "children": [
{ "type": "Text", "text": { "bind": "item.name" } }
] }
Reglas de resolución
Una ruta dentro de la plantilla de una lista puede significar dos cosas distintas —el campo propio del elemento, o algo global— y equivocarse ahí enseña el oro del jugador equivocado en todas las filas. Estas cinco reglas lo deciden, y todos los SDK las aplican igual.
normativeresolveBinding en @zabloo/format es la implementación de referencia:
- Los ámbitos se anidan, y gana el alias coincidente más interno. Una lista
anidada que declara
as: "cat"sigue pudiendo llegar al elemento exterior por su alias, y por eso el alias se declara en vez de estar reservado. Significa también que un alias tapa una raíz de datos con el mismo nombre: elige nombres de alias que no sean raíces de tus datos. "<alias>"a secas resuelve al elemento mismo ("shop.items.3")."<alias>.resto"resuelve a"shop.items.3.resto"."<alias>.$index"—y solo esa hoja exacta— resuelve a la posición del elemento, un número que los datos no contienen. Cualquier cosa más profunda ("item.a.$index") es un segmento ordinario y sencillamente no lee ningún valor.- Una ruta que no está bajo ningún alias conocido es absoluta y pasa
intacta, que es como una fila dentro de una lista sigue pudiendo bindear
player.gold.
Ejemplo resuelto — la segunda fila de la lista de arriba. El Repeat bindea
shop.items y declara as: "item", y la fila que se está construyendo es la
del índice 1, la poción de curación. Dentro de la plantilla, item.name
resuelve a shop.items.1.name —«Poción de curación»— por la regla 3, y
item.$index da 1 por la regla 4. Un player.gold en esa misma fila no
coincide con ningún alias, así que la regla 5 lo deja pasar intacto: las dos
filas leen el mismo 1.250, y está bien, porque hay una bolsa y dos objetos.
La identidad de un elemento
Repeat.key nombra una ruta relativa al elemento que apunta a un campo
estable ("id", "meta.sku"). La identidad es lo que mantiene el estado de
ejecución de cada elemento —el foco, un Toggle marcado, un offset de scroll,
una transición en vuelo— junto a su elemento cuando el array se reordena, y es
lo que hace posible el reciclaje.
Solo una cadena no vacía o un número finito identifican a un elemento;
cualquier otra cosa cae de vuelta a la posición. Los dos espacios se mantienen
disjuntos —las identidades con clave llevan prefijo— así que un elemento
cuya clave es "0" no puede heredar nunca el estado del elemento sin clave de
la posición 0.
Implementaciones de referencia: itemKey e itemIdentity en @zabloo/format.
La otra dirección
El juego maneja la UI a través de la API del SDK, no a través del formato. Es la contraparte de las acciones que vienen hacia aquí, y está deliberadamente fuera de la IR: son operaciones de ejecución, y el documento no tiene sitio donde ponerlas.
Entran siete operaciones —SetData, SetOpen, SetSelectedTab, SetChecked,
SetValue, SetText, SetScroll— y vuelven tres callbacks: las named actions
(con el action context, cuando lo hay), los datos que cambió la UI y los
diagnósticos de la carga. Cada SDK las expone en su propio idioma, así que la
forma de escribirlas sigue las convenciones del motor mientras que las
operaciones, sus argumentos y sus efectos son los mismos en todas partes.
→ El canal del host — el contrato entero, con las firmas del target web.