Saltar al contenido
zabloo

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 lleva son esos dos enganches declarados, y todo lo dinámico de una UI de zabloo se construye con ellos.

MecanismoDirecciónQué es
Named actionsUI → juego"onClick": "buy" — un nombre al que el juego se suscribe.
Data bindingsjuego ↔ 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

PropNodoSe dispara cuando
onClickButtonSe activa — toque, Enter, A del mando.
onChangeToggle, Slider, TextInputEl valor cambió, lo haya causado lo que lo haya causado.
onCommitSliderUn gesto de arrastre o de tecla terminó — el valor en el que el jugador se quedó.
onSubmitTextInputEl jugador confirmó el campo (Enter).
onDismissOverlaySe 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

CampoTipoDescripción
pathstringRuta de datos absoluta del elemento.
keystring | numberLa clave cruda del elemento, cuando su Repeat declara una. Ausente si la identidad es posicional.
indexnumberSu 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

PropNodo
visibleTodos los nodos
disabledTodos los nodos — heredada por su subárbol
textText
valueProgressBar
itemsRepeat — siempre un binding, nunca un array literal

Lectura/escritura — el SDK escribe además de vuelta:

normativeProps bindeables de lectura/escritura

PropNodo
checkedToggle
valueSlider
valueTextInput
valueContainer 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.

El estilo no es bindeable

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.

normative

resolveBinding en @zabloo/format es la implementación de referencia:

  1. 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.
  2. "<alias>" a secas resuelve al elemento mismo ("shop.items.3").
  3. "<alias>.resto" resuelve a "shop.items.3.resto".
  4. "<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.
  5. 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.

Relacionado