The wiki has been upgraded to MediaWiki 1.43. The wiki's theme is still being adapted to work with the new version, and may appear broken for the time being. --RanAS

es/Dynamic Xtreme sprite creation guide

From SnesLab
Jump to: navigation, search
English Português Español 日本語

En este artículo se explicará cómo crear sprites dinámicos utilizando Dynamic Xtreme y PIXI.

La guía recorrerá el proceso completo: preparar los gráficos, definir las poses, incluir los archivos necesarios en el sprite y utilizar el sistema para cargar y dibujar sus poses en pantalla.

Si tienes dudas sobre los registros, parámetros o valores devueltos por una llamada, consulta la API ASM. Los apartados de esta guía incluyen enlaces a las llamadas correspondientes.

Requisitos

Para seguir esta guía necesitas:

  • Tener PIXI instalado y configurado para insertar sprites en tu ROM.
  • Tener Dynamic Xtreme instalado y configurado. Si todavía no lo has hecho, consulta la guía de inicio rápido.

Archivos utilizados por un sprite dinámico

Además de los archivos utilizados normalmente por PIXI, un sprite dinámico necesita los siguientes archivos:

  • Archivo gráfico (*.bin): contiene los gráficos de las poses del sprite.
  • DynamicInfo (*.dynamicinfo): indica qué gráficos utiliza el sprite y cómo se dividen en poses.
  • DrawInfo (*.drawinfo): contiene la información necesaria para dibujar las poses.
  • Archivo ASM (*.asm): contiene el código del sprite y utiliza los recursos anteriores.

Una posible estructura consiste en guardar los archivos de Dynamic Xtreme junto al propio recurso de PIXI. Por ejemplo, un sprite normal podría organizarse de la siguiente manera:

PIXI/
└── sprites/
    └── MySprite/
        ├── MySprite.asm
        ├── MySprite.json
        └── DynamicXtreme/
            ├── MySprite.bin
            ├── MySprite.dynamicinfo
            └── MySprite.drawinfo

El archivo ASM y el archivo de configuración permanecen en la carpeta principal del sprite. Los gráficos, el DynamicInfo y el DrawInfo se guardan dentro de la subcarpeta DynamicXtreme.

En este caso, el ASM principal del sprite debe incluir el DynamicInfo y el DrawInfo mediante las siguientes directivas:

;#incdyn "DynamicXtreme/MySprite.dynamicinfo"
;#incdri "DynamicXtreme/MySprite.drawinfo"

Las rutas son relativas al archivo ASM que contiene las directivas. Si se trabaja con un cluster sprite, extended sprite u otro tipo admitido por PIXI, la carpeta completa del recurso puede organizarse de la misma manera dentro de la carpeta correspondiente de PIXI.

También es posible utilizar las carpetas tradicionales de Dynamic Xtreme. En ese caso, el archivo *.dynamicinfo se coloca en DynamicInfos, el archivo *.drawinfo en DrawInfos y los gráficos *.bin en DynamicResources. Los archivos guardados de esta forma son encontrados directamente por Dynamic Xtreme y no necesitan las directivas ;#incdyn y ;#incdri en el sprite.

La primera opción facilita distribuir el sprite con todos sus recursos dentro de una sola carpeta, mientras que la segunda mantiene la organización tradicional de Dynamic Xtreme. Para conocer la estructura y las reglas de cada formato, consulta Archivos y formatos de Dynamic Xtreme.

Durante la instalación, Dynamic Xtreme genera los defines que utilizará el código del sprite y los guarda en DXDefines.asm. Esta tabla resume el origen de los nombres utilizados en la guía:

Define de ejemplo De dónde sale Para qué se utiliza
!RenderBoxXDistanceOutOfScreenMySprite Los límites horizontales de las poses de MySprite.drawinfo. Configurar la distancia horizontal del RenderBox en el INIT.
!RenderBoxYDistanceOutOfScreenMySprite Los límites verticales de las poses de MySprite.drawinfo. Configurar la distancia vertical del RenderBox en el INIT.
!MySpritePaletteTableOffset La lista Palettes de MySprite.dynamicinfo. Indicar a la asignación de paletas qué tabla utilizar.
!DynamicPoseIDMySprite000 La primera pose de los gráficos MySprite.bin, declarados en PosesGraphics del DynamicInfo. Solicitar los gráficos de esa pose mediante !Upload. El sufijo 000 identifica la primera pose de ese archivo gráfico; 003, la cuarta.
!PoseIDMySprite_Frame0 La pose Frame0 declarada en MySprite.drawinfo. Seleccionar la pose que dibujará !DXDraw.
!PaletteEffectIDMyEffect0 El primer efecto de MyEffect.paleffect. Seleccionar el efecto de paleta que se aplicará.

Los nombres se normalizan eliminando separadores y escribiendo en mayúscula la inicial de cada palabra: por ejemplo, my_sprite se convierte en MySprite. Consulta siempre los nombres exactos generados en DXDefines.asm; la guía también recuerda su origen junto a cada uso.

Estructura del sprite

Un sprite dinámico conserva la estructura habitual de un sprite de PIXI, pero debe preparar las variables utilizadas por Dynamic Xtreme y organizar su rutina principal según se encuentre dentro o fuera de su área de renderizado.

La estructura que se utilizará en esta guía se divide en las siguientes partes:

  • INIT: inicializa las variables del sprite y el estado utilizado por Dynamic Xtreme.
  • Short routine: ejecuta la lógica que debe continuar incluso cuando el sprite se encuentra fuera del área de renderizado.
  • Long routine: maneja las tareas que solamente son necesarias dentro del área de renderizado, como solicitar la pose y dibujarla.

Incluir los defines del tipo de sprite

Antes del código del sprite se debe incluir el archivo de defines correspondiente al tipo de recurso utilizado:

  • NormalSpriteDefines.asm para sprites normales.
  • ClusterSpriteDefines.asm para cluster sprites.
  • ExtendedSpriteDefines.asm para extended sprites.

Por ejemplo, si el ASM de un sprite normal se encuentra dentro de una subcarpeta de sprites, el include puede escribirse de la siguiente manera:

incsrc "../NormalSpriteDefines.asm"

La ruta siempre es relativa a la ubicación del archivo ASM del sprite, por lo que debe ajustarse a la estructura de carpetas utilizada por el recurso. Este archivo proporciona los aliases de tablas y rutinas correspondientes a su tipo de sprite; gracias a esto, nombres como !PoseIndex, !RenderBox y !Upload apuntan a la implementación correcta para un sprite normal, cluster o extended.

INIT

En los sprites normales, la rutina INIT debe establecer las distancias utilizadas por el RenderBox, asignar los valores iniciales del sprite e invalidar cualquier estado anterior que pueda permanecer en el mismo slot de PIXI.

El siguiente ejemplo corresponde a un sprite normal. Inicia el sprite con la pose 0 y obtiene la versión y el volteo inicial desde la configuración de PIXI:

print "INIT ",pc
    LDA.b #!RenderBoxXDistanceOutOfScreenMySprite
    STA !RenderXDistanceOutOfScreen,x
    LDA.b #!RenderBoxYDistanceOutOfScreenMySprite
    STA !RenderYDistanceOutOfScreen,x

    LDA !ExtraBits,x
    AND #$04
    LSR
    LSR
    STA !GlobalFlip,x

    LDA !ExtraByte1,x
    AND #$0F
    STA !Version,x

    LDA #$00
    STA !PoseIndex,x
    STA !LocalFlip,x
    STA !LastFlip,x

    LDA #$FF
    STA !Palette,x
    STA !LastPoseIndex,x
    STA !LastPoseHashIndex,x
    STA !LastVersion,x

RTL

Cada variable del ejemplo cumple una función concreta:

  • !RenderXDistanceOutOfScreen y !RenderYDistanceOutOfScreen guardan las distancias que utilizará !RenderBox para decidir si el sprite todavía se encuentra lo bastante cerca de la pantalla como para renderizarse. Los valores !RenderBoxXDistanceOutOfScreenMySprite y !RenderBoxYDistanceOutOfScreenMySprite son generados a partir del DrawInfo y quedan disponibles en DXDefines.asm. El nombre del recurso se normaliza para formar el define: se eliminan separadores como espacios y guiones bajos, y la inicial de cada palabra se escribe en mayúscula. Por ejemplo, my_sprite y my sprite se convierten en MySprite.
  • !PoseIndex indica la pose dinámica que el sprite desea utilizar dentro del DynamicInfo. El valor 0 selecciona la primera pose del recurso. Una rutina de animación puede asignar otro valor antes de solicitar los gráficos.
  • !Version identifica la variante actual del sprite. Una versión puede cambiar su paleta, sus gráficos o incluso su comportamiento. En los sprites normales es habitual tomarla de los cuatro bits bajos de !ExtraByte1,x, lo que permite seleccionar hasta 16 versiones distintas desde Lunar Magic.
  • !GlobalFlip representa el volteo general del sprite. En los sprites normales puede obtenerse del extra bit: 0 mantiene la orientación original y 1 aplica el volteo inicial. !LocalFlip permite aplicar un volteo adicional a la pose actual. Dynamic Xtreme combina ambos valores y conserva en !LastFlip el volteo efectivo utilizado para dibujar.
  • !Palette guarda la paleta asignada actualmente. Se inicia en $FF porque el sprite todavía no posee una paleta válida.
  • !LastPoseIndex guarda la última pose que Dynamic Xtreme pudo obtener correctamente, y !LastPoseHashIndex guarda el slot de la caché de VRAM donde se encuentra. Ambos comienzan en $FF porque todavía no existe una pose disponible.
  • !LastVersion guarda la versión procesada anteriormente por el manejo de paletas. Iniciarla en $FF hace que la primera versión del sprite sea procesada aunque !Version comience en 0.

Si un sprite utiliza varios DrawInfo, las distancias deben cubrir el conjunto completo de sus gráficos. Se recomienda revisar en DXDefines.asm los valores generados para cada DrawInfo y utilizar la mayor distancia horizontal y la mayor distancia vertical. Por ejemplo, si la cabeza y el cuerpo utilizan DrawInfo diferentes, tomar solamente las distancias de la cabeza podría hacer que el sprite deje de dibujarse cuando una parte de su cuerpo todavía se encuentra dentro de la pantalla.

Los valores marcados con $FF representan estados inválidos. Además de indicar que el sprite todavía no tiene una paleta o pose disponible, evitan reutilizar información dejada por otro sprite que haya ocupado anteriormente el mismo slot de PIXI.

Inicialización de cluster y extended sprites

Los cluster sprites y extended sprites no tienen una rutina INIT propia. Para inicializarlos una sola vez se reserva una de sus tablas, por ejemplo !ClusterMiscTable1 o !ExtendedMiscTable1, y se le asigna un nombre como !Started:

!Started = !ClusterMiscTable1

Cuando se crea el sprite, !Started debe establecerse en 0 utilizando el índice del nuevo cluster o extended sprite. Esto es necesario porque el slot pudo haber sido utilizado anteriormente y todavía podría contener el valor de otro sprite.

Al comienzo de SpriteCode, antes de ejecutar !RenderBox, se comprueba esta variable:

SpriteCode:
    LDA !Started,x
    BNE +
    JSR Start
    RTS
+
    !RenderBox
    ; Resto del código del sprite

La rutina Start contiene la inicialización que un sprite normal realizaría en INIT. Al finalizar, establece !Started en 1 para que no vuelva a ejecutarse durante los frames siguientes:

Start:
    ; Aquí va la inicialización del sprite

    LDA #$01
    STA !Started,x
RTS

Como Start se llama mediante JSR, debe terminar con RTS. El código que crea el cluster o extended sprite también debe preparar cualquier dato que deba heredarse del sprite principal, como !PaletteOption, !PaletteAssignmentOption o !GlobalFlip.

PaletteOption

!PaletteOption controla la paleta solicitada y si esta será afectada por el efecto global de paleta. Su formato es el siguiente:

EPPP----
  • E indica si la paleta será afectada por el efecto global: 0 la excluye y 1 lo habilita.
  • PPP representa una paleta entre 8 y F: 000 corresponde a la paleta 8 y 111 a la paleta F.
  • Los cuatro bits bajos no forman parte de PaletteOption.

Cuando se utiliza asignación automática, PPP = 000 permite buscar un slot libre entre las ocho paletas, un valor 0XX distinto de 000 limita la búsqueda a las paletas 8-B y un valor 1XX la limita a las paletas C-F. Con asignación manual, PPP selecciona directamente la paleta que se utilizará.

En los sprites normales, !PaletteOption utiliza directamente !ExtraByte1,x. Lo habitual es aprovechar los cuatro bits restantes para seleccionar la versión del sprite, por lo que el byte completo utiliza el siguiente formato:

EPPPVVVV
  • EPPP contiene las opciones de paleta descritas anteriormente.
  • VVVV selecciona una de las 16 versiones posibles del sprite, desde 0 hasta 15.

!PaletteOption utiliza los cuatro bits altos directamente, mientras que el INIT copia los cuatro bits bajos a !Version. No es necesario copiar PaletteOption a otra tabla.

PaletteAssignmentOption

!PaletteAssignmentOption determina cómo se asignará la paleta al sprite:

--AA----
  • AA = 00: asignación automática. Dynamic Xtreme busca un slot permitido por PaletteOption y carga la paleta dinámicamente.
  • AA = 01: asignación manual. PaletteOption indica el slot exacto y Dynamic Xtreme carga la paleta dinámicamente.
  • AA = 10: asignación manual sin carga. El sprite utiliza directamente la paleta que ya existe en el nivel y Dynamic Xtreme no carga una nueva.

En los sprites normales, !PaletteAssignmentOption utiliza directamente !ExtraProp2,x. Por lo tanto, el modo se configura mediante los bits 5 y 4 del Extra Property Byte 2 y tampoco necesita copiarse a otra tabla durante el INIT.

En los cluster sprites y extended sprites, !PaletteOption y !PaletteAssignmentOption deben establecerse manualmente cuando son creados. Normalmente se copia el modo de asignación del sprite que los genera. Al copiar PaletteOption, sus cuatro bits bajos deben borrarse porque en un sprite normal contienen la versión y no forman parte de la opción de paleta.

Si el sprite generado utiliza la misma paleta que el sprite principal, puede copiar su PaletteOption después de borrar los cuatro bits bajos. Si utiliza una paleta distinta y el sprite principal emplea asignación automática, puede conservarse el mismo rango de búsqueda. Si el sprite principal emplea asignación manual o asignación manual sin carga, se suma $10 al valor enmascarado para seleccionar la paleta siguiente.

En este último caso, el sprite principal no puede utilizar la paleta F, porque el sprite generado necesita la paleta siguiente. Debes configurar el sprite principal con una paleta entre la 8 y la E.

Además de estas variables, el INIT puede inicializar cualquier tabla propia que necesite el comportamiento del sprite.

SpriteCode

Consulta en la API las entradas y el resultado de RenderBox.

SpriteCode comienza ejecutando !RenderBox. Esta rutina devuelve el carry activado cuando el sprite se encuentra dentro de su área de renderizado y desactivado cuando está fuera de ella. Con este resultado se decide si ejecutar la short routine o la long routine.

Una estructura base puede escribirse de la siguiente manera:

SpriteCode:
    !RenderBox
    BCS .LongRoutine

.ShortRoutine
    LDA #$00
    %SubOffScreen()
    ; Aquí va la lógica de tu sprite
RTS

.LongRoutine
    JSR PaletteManagement
    BCC ..Return

    JSR PoseManagement
    BCC ..Return

    JSR Draw
    ; Aquí va la lógica de tu sprite
..Return
RTS

Short routine

La short routine se ejecuta cuando el sprite se encuentra fuera del área calculada por !RenderBox. Aquí solamente debe ejecutarse la lógica base necesaria para mantener el comportamiento del sprite, sin solicitar gráficos dinámicos, procesar su paleta ni dibujarlo. El contenido exacto depende del sprite y puede incluir su movimiento, estado, temporizadores, interacciones o animación.

En los sprites normales se recomienda llamar a %SubOffScreen() dentro de esta parte. Recibe en A un valor entre 0 y 7 para seleccionar la configuración de eliminación fuera de pantalla; el ejemplo utiliza 0, el valor habitual. De esta manera PIXI puede eliminar el sprite cuando se aleja lo suficiente de la pantalla, sin ejecutar esta comprobación durante la parte más costosa del renderizado.

Long routine

La long routine se ejecuta cuando el sprite está dentro del área de renderizado. Esta ruta realiza el proceso completo: maneja la paleta, solicita la pose dinámica o actualiza su registro de uso si ya está en VRAM para evitar que sea sobrescrita, dibuja el sprite y finalmente ejecuta la misma lógica base de la short routine.

No es necesario llamar nuevamente a %SubOffScreen() dentro de la long routine. Como !RenderBox ya determinó que el sprite se encuentra dentro del área de renderizado, realizar allí la comprobación resulta redundante.

PaletteManagement y PoseManagement devuelven el carry activado cuando existe un recurso utilizable. Si alguna de ellas devuelve el carry desactivado, el ejemplo salta a ..Return y termina la rutina. En ese caso, no se dibuja el sprite ni se ejecuta la lógica que viene después durante ese frame.

Las rutinas de paleta, poses y dibujo se desarrollarán en los apartados siguientes. Los nombres utilizados aquí son solamente una forma de organizar el código y pueden cambiarse según la estructura del sprite.

Manejo de Paleta de Colores

Cargar una paleta dinámica

Consulta en la API los detalles de PaletteAssignment.

Para utilizar la asignación de paletas en un sprite normal, su archivo *.cfg o *.json debe tener configurado al menos un Extra Byte:

  • Extra Byte 1: proporciona !PaletteOption y, en el ejemplo de esta guía, sus cuatro bits bajos seleccionan !Version.
  • Extra Property Byte 2: configura !PaletteAssignmentOption mediante sus bits 5 y 4: 00 para asignación automática, 01 para asignación manual y 10 para asignación manual sin carga. Estos bits corresponden a los valores $00, $10 y $20, respectivamente; los demás bits pueden utilizarse para otras opciones del sprite.

La carga de una paleta dinámica se realiza mediante !PaletteAssignment. Solamente se debe cargar en A, con 16 bits, el offset de la tabla de paletas generado para el recurso:

PaletteManagement:
    REP #$20
    LDA #!MySpritePaletteTableOffset
    !PaletteAssignment
RTS

El define !MySpritePaletteTableOffset se genera a partir de las paletas declaradas en el DynamicInfo y queda disponible en DXDefines.asm. El nombre del DynamicInfo se normaliza eliminando espacios, guiones bajos y otros separadores, y escribiendo en mayúscula la inicial de cada palabra. Por ejemplo, my_sprite.dynamicinfo genera el componente MySprite.

!PaletteAssignment utiliza !Version para seleccionar una entrada de la tabla: la versión 0 utiliza la primera paleta, la versión 1 utiliza la segunda y así sucesivamente. También utiliza !PaletteOption y !PaletteAssignmentOption para decidir dónde y cómo asignarla.

La rutina devuelve el carry activado cuando existe una paleta utilizable y desactivado cuando todavía no fue posible obtenerla. Por esta razón, SpriteCode comprueba el carry antes de continuar con la carga de poses y el dibujo. !PaletteAssignment debe ejecutarse en cada frame de la long routine para conservar la asignación mientras el sprite siga utilizándola.

Seleccionar una paleta sin cambiar la versión del sprite

La paleta de un sprite puede cambiar sin que su versión o su comportamiento tengan que cambiar. Por ejemplo, un sprite puede alternar entre su paleta normal y una segunda paleta durante una animación o un estado temporal. Mantener ambas paletas como recursos independientes también permite que la segunda sea reutilizada por otros sprites.

Como !PaletteAssignment selecciona la entrada mediante !Version, puede guardarse temporalmente la versión lógica del sprite, asignar el índice de la paleta deseada y restaurar después el valor original:

PaletteManagement:
    LDA !Version,x
    PHA

    LDA #$02
    STA !Version,x

    REP #$20
    LDA #!MySpritePaletteTableOffset
    !PaletteAssignment

    PLA
    STA !Version,x
RTS

En este ejemplo, !PaletteAssignment utiliza la entrada 2 de la tabla, pero el comportamiento del sprite conserva su versión original. De esta forma, la selección de la paleta queda separada de la variante lógica del personaje. PLA y STA no alteran el carry, por lo que PaletteManagement todavía devuelve correctamente el resultado de !PaletteAssignment.

El define !MySpritePaletteTableOffset utilizado aquí se genera desde el DynamicInfo y se encuentra en DXDefines.asm. Su nombre usa la misma normalización: se eliminan separadores y se escribe en mayúscula la inicial de cada palabra, de modo que my_sprite se convierte en MySprite.

Concatenar un efecto local con el efecto global

Consulta en la API los detalles de PaletteAssignmentNoUpload, ConcatenateRGBPaletteEffectToGlobal y ConcatenateHSLPaletteEffectToGlobal.

Cuando la paleta necesita un efecto propio que además debe combinarse con el efecto global de paleta, se utiliza !PaletteAssignmentNoUpload seguido de !ConcatenateRGBPaletteEffectToGlobal o !ConcatenateHSLPaletteEffectToGlobal:

PaletteManagement:
    REP #$20
    LDA #!MySpritePaletteTableOffset
    !PaletteAssignmentNoUpload
    BCC .Return

    PHY

    LDA !Palette,x
    TAX

    LDA $14
    AND #$06
    TAY

    REP #$20
    LDA PaletteEffects,y

    PLY
    !ConcatenateRGBPaletteEffectToGlobal

.Return
RTS

PaletteEffects:
    dw !PaletteEffectIDMyEffect0
    dw !PaletteEffectIDMyEffect1
    dw !PaletteEffectIDMyEffect2
    dw !PaletteEffectIDMyEffect3

!PaletteAssignmentNoUpload asigna la paleta y prepara su dirección base sin realizar todavía la carga final. También deja en Y la información que la rutina de concatenación necesita para decidir si debe reconstruir el efecto.

En este ejemplo se utiliza $14, el contador principal de frames de Super Mario World, para seleccionar uno de cuatro efectos. Como Y se necesita temporalmente para leer la tabla PaletteEffects, primero se conserva con PHY y se restaura con PLY. El valor restaurado debe llegar sin modificaciones a la rutina de concatenación.

Antes de concatenar, X debe contener el valor de !Palette, que ya representa el índice requerido por la rutina. A debe contener, con 16 bits, el ID del efecto local. En el ejemplo, Y se utiliza como índice para cargar ese ID desde una tabla, pero el ID finalmente se entrega en A.

El define !MySpritePaletteTableOffset se genera desde el DynamicInfo y se encuentra en DXDefines.asm. Los defines !PaletteEffectIDMyEffectX son generados a partir de un archivo *.paleffect durante la instalación y se guardan en el mismo archivo. En ambos casos los nombres se normalizan eliminando espacios, guiones bajos y otros separadores, y escribiendo en mayúscula la inicial de cada palabra. Por ejemplo, my_effect.paleffect genera el componente MyEffect; X corresponde al índice del efecto dentro de la colección.

Se utiliza !ConcatenateRGBPaletteEffectToGlobal para efectos que trabajan en RGB, como PaletteTransitionRGB, y !ConcatenateHSLPaletteEffectToGlobal para efectos que trabajan en HSL, como MixH. Para combinar el efecto local con el global, el bit E de PaletteOption debe estar activado. La rutina realiza internamente las conversiones necesarias cuando ambos efectos utilizan espacios de color diferentes. Para conocer el espacio de color de cada efecto, consulta Tipos de efectos.

La concatenación devuelve el carry activado cuando existe un resultado utilizable, incluido un resultado anterior usado como respaldo, y desactivado cuando esa paleta todavía no posee ningún resultado válido. Este carry debe conservarse para que SpriteCode pueda decidir si continúa con el dibujo.

Manejo de Poses Dinámicas

Las poses dinámicas se solicitan mediante !Upload o !UploadWithoutPoseOffset. Estas rutinas buscan la pose en la caché de VRAM y, si todavía no está disponible, solicitan su carga. Deben ejecutarse en cada frame de la long routine para mantener activa la pose que utiliza el sprite.

Upload

Consulta en la API los detalles de Upload.

!Upload recibe en A, con 16 bits, el ID dinámico base del recurso y le suma el valor de !PoseIndex:

PoseManagement:
    REP #$20
    LDA #!DynamicPoseIDMySprite000
    !Upload
RTS

En este ejemplo, si !PoseIndex contiene 0 se solicita la primera pose; si contiene 1 se solicita la pose siguiente, y así sucesivamente. Esto permite que la rutina de animación cambie solamente !PoseIndex mientras PoseManagement conserva el mismo ID base.

El define !DynamicPoseIDMySprite000 se genera a partir del DynamicInfo durante la instalación de Dynamic Xtreme y queda disponible en DXDefines.asm. El nombre se normaliza eliminando espacios, guiones bajos y otros separadores, y escribiendo en mayúscula la inicial de cada palabra. Por ejemplo, my_sprite.dynamicinfo genera el componente MySprite.

!Upload devuelve el carry activado cuando existe una pose utilizable. Esta puede ser la pose solicitada durante el frame actual o la última pose válida del sprite, utilizada como respaldo mientras la nueva termina de cargarse. El carry se devuelve desactivado solamente cuando el sprite todavía no posee ninguna pose que pueda utilizar.

Cuando la solicitud tiene éxito, !LastPoseIndex guarda la pose realmente disponible y !LastPoseHashIndex guarda su posición dentro de la caché de VRAM. Por esta razón, el dibujo debe utilizar el estado de la última pose disponible y no asumir que !PoseIndex ya fue cargado.

UploadWithoutPoseOffset

Consulta en la API los detalles de UploadWithoutPoseOffset.

!UploadWithoutPoseOffset también recibe un ID dinámico en A con 16 bits, pero lo utiliza directamente y no le suma !PoseIndex:

PoseManagement:
    REP #$20
    LDA #!DynamicPoseIDMySprite003
    !UploadWithoutPoseOffset
RTS

Esta variante es útil cuando el código ya seleccionó el ID dinámico exacto, por ejemplo mediante una tabla o una condición propia. Su carry tiene el mismo significado que en !Upload: activado cuando existe una pose utilizable y desactivado cuando todavía no existe ninguna.

El define !DynamicPoseIDMySprite003 también es generado desde el DynamicInfo y se encuentra en DXDefines.asm. Su nombre utiliza la misma normalización de estilo CamelCase: se eliminan los separadores y se escribe en mayúscula la inicial de cada palabra.

Actualmente !UploadWithoutPoseOffset está publicado por los archivos de defines para sprites normales y cluster sprites. Los extended sprites deben utilizar !Upload junto con !PoseIndex mientras su archivo de defines no publique este alias.

Los defines !DynamicPoseID... pertenecen al sistema de carga dinámica. No deben confundirse con los defines !PoseID... del DrawInfo, que se utilizan posteriormente para dibujar la pose.

Dibujar Poses en Pantalla

Una vez que PoseManagement ha obtenido una pose utilizable, esta puede dibujarse mediante !DXDraw o !DXDrawWithOffset. Ambas rutinas utilizan la posición, el volteo, la paleta y la pose dinámica que Dynamic Xtreme conserva para el sprite.

Antes de llamar a cualquiera de las dos rutinas deben prepararse los siguientes valores:

  • A, con 16 bits, contiene el ID de la pose de dibujo.
  • !PropParam contiene propiedades adicionales con el formato YXCC----.
  • !MaxTilePriorityParam indica la prioridad máxima que pueden utilizar los tiles de la pose.

Los valores admitidos por !MaxTilePriorityParam son:

  • $00: prioridad máxima.
  • $01: prioridad alta.
  • $02: prioridad propia del tipo de sprite.
  • $03: prioridad mínima.

DXDraw

Consulta en la API los detalles de DXDraw.

!DXDraw dibuja la pose tomando como origen la posición normal del sprite. Una rutina de dibujo básica puede escribirse de la siguiente manera:

Draw:
    LDA #$20
    STA !PropParam

    LDA #$02
    STA !MaxTilePriorityParam

    LDA !LastPoseIndex,x
    REP #$21
    AND #$00FF
    ADC #!PoseIDMySprite_Frame0
    !DXDraw
RTS

El valor de !LastPoseIndex se suma al primer ID de dibujo para seleccionar la pose que realmente se encuentra disponible. Es importante utilizar !LastPoseIndex y no !PoseIndex: mientras una pose nueva termina de cargarse, Dynamic Xtreme puede conservar la pose anterior como respaldo.

Este ejemplo supone que las poses del DrawInfo siguen el mismo orden que las poses del DynamicInfo. Si el orden es diferente, el código debe convertir !LastPoseIndex al ID de dibujo correspondiente, por ejemplo mediante una tabla.

El define !PoseIDMySprite_Frame0 es generado a partir del DrawInfo durante la instalación y queda disponible en DXDefines.asm. Los nombres generados se normalizan eliminando espacios, guiones bajos y otros separadores, y escribiendo en mayúscula la inicial de cada palabra. Por ejemplo, my_sprite genera el componente MySprite.

Los defines !PoseID... identifican poses de dibujo declaradas en un DrawInfo. No deben confundirse con los defines !DynamicPoseID... del DynamicInfo, que se utilizan con !Upload para solicitar los gráficos dinámicos.

!DXDraw combina las propiedades indicadas en !PropParam con !LastFlip, la paleta asignada y las propiedades propias de la pose. También utiliza !LastPoseHashIndex para localizar sus gráficos en la caché de VRAM. Si este valor contiene $FF, la rutina no dibuja nada.

DXDrawWithOffset

Consulta en la API los detalles de DXDrawWithOffset.

!DXDrawWithOffset funciona de la misma forma, pero permite desplazar el dibujo con respecto a la posición del sprite. Los desplazamientos se entregan como valores de 8 bits con signo mediante !XOffsetParam y !YOffsetParam:

Draw:
    LDA #$08
    STA !XOffsetParam

    LDA #$F8
    STA !YOffsetParam

    LDA #$20
    STA !PropParam

    LDA #$02
    STA !MaxTilePriorityParam

    LDA !LastPoseIndex,x
    REP #$21
    AND #$00FF
    ADC #!PoseIDMySprite_Frame0
    !DXDrawWithOffset
RTS

En este ejemplo, el dibujo se desplaza 8 píxeles hacia la derecha y 8 píxeles hacia arriba. Un valor positivo en X desplaza la pose hacia la derecha y uno negativo hacia la izquierda; en Y, un valor positivo la desplaza hacia abajo y uno negativo hacia arriba.

El offset solamente modifica la posición donde se dibuja la pose. No cambia la posición real del sprite, su hitbox ni sus interacciones. Esto resulta útil para alinear gráficos, aplicar movimientos visuales o dibujar varias partes de una entidad desde una misma posición lógica.

Al igual que en el ejemplo anterior, !PoseIDMySprite_Frame0 es un define generado desde el DrawInfo y guardado en DXDefines.asm. Su nombre utiliza la normalización de Dynamic Xtreme: elimina separadores como espacios y guiones bajos, y escribe en mayúscula la inicial de cada palabra.

DrawStatic

Consulta en la API los detalles de DrawStatic.

El sistema de dibujo de Dynamic Xtreme también puede utilizarse con poses estáticas. Estas poses se declaran mediante .IsStatic en el DrawInfo y se dibujan con la macro %DrawStatic(). Como sus gráficos ya se encuentran disponibles en la VRAM, no necesitan !Upload, !LastPoseIndex ni !LastPoseHashIndex.

Cuando una pose estática utiliza el tile predeterminado (0) para todos sus componentes, el dibujo toma el número de tile desde !Tile, que representa la scratch RAM $05. Esto permite reutilizar una misma pose, por ejemplo una de un solo tile, para dibujar distintos gráficos. El sprite debe preparar el valor antes de llamar a %DrawStatic():

    LDA #$80            ; Número de tile que se desea dibujar
    STA !Tile           ; Scratch RAM $05

Este valor debe prepararse en cada llamada de dibujo que lo necesite; no basta con establecerlo durante el INIT, porque la scratch RAM se comparte con otras rutinas.

En las poses dinámicas, !DXDraw y !DXDrawWithOffset obtienen el tile desde la ubicación de los gráficos en la caché de VRAM. En ese caso no es necesario preparar !Tile manualmente. Si una pose estática declara tiles distintos del predeterminado en su DrawInfo, el dibujo utiliza esos valores directamente.

El siguiente ejemplo dibuja una pose estática en la posición de un sprite normal, sin volteo y utilizando la paleta A:

Draw:
    LDA #$20
    STA !PropParam

    LDA #$02
    STA !MaxTilePriorityParam

    REP #$21
    LDA #!PoseIDMyStaticSprite_Frame0
    %DrawStatic(0, 1, #$00, #$04, "!SpriteXLow,x", "!SpriteXHigh,x", "!SpriteYLow,x", "!SpriteYHigh,x")

El primer parámetro indica si se utilizarán offsets adicionales: 0 dibuja en la posición entregada y 1 aplica también !XOffsetParam y !YOffsetParam. Los parámetros siguientes proporcionan el contexto del dibujo, el volteo, la paleta y las posiciones X e Y de 16 bits. Las posiciones indexadas se escriben entre comillas para que la coma de ,x no sea interpretada como un separador de parámetros de la macro.

%DrawStatic() finaliza la rutina mediante RTS, por lo que no se coloca otro RTS después de la llamada en este ejemplo.

En el ejemplo, #$00 indica que la pose no se voltea y #$04 selecciona la paleta A. Estos argumentos también pueden utilizar tablas o variables cuando el sprite necesite cambiar su orientación o paleta.

El define !PoseIDMyStaticSprite_Frame0 se genera desde el DrawInfo durante la instalación y queda disponible en DXDefines.asm. Su nombre se normaliza en PascalCase: se eliminan espacios, guiones bajos, guiones y otros separadores, y se escribe en mayúscula la inicial de cada palabra.

Para obtener más información sobre la declaración de poses estáticas, consulta Archivos y formatos de Dynamic Xtreme.

Instalar y probar el sprite

Cada vez que se modifique alguno de los recursos procesados por Dynamic Xtreme, se debe reinstalar primero Dynamic Xtreme y después volver a insertar los sprites con PIXI. Esto incluye cambios en archivos *.dynamicinfo, *.drawinfo, gráficos *.bin, efectos *.paleffect y cualquier otro recurso dinámico utilizado por el sprite.

El orden es importante: Dynamic Xtreme debe procesar primero los recursos y regenerar sus datos y defines; después PIXI puede ensamblar el sprite utilizando la información actualizada.

Si añades, eliminas o modificas los includes de recursos ;#incdyn, ;#incdri o ;#incpale dentro del ASM, también debes reinstalar primero Dynamic Xtreme y después PIXI.

Si solo modificas el código *.asm del sprite, sin cambiar esos includes, no es necesario reinstalar Dynamic Xtreme. En ese caso basta con ejecutar PIXI nuevamente.

Para conocer el proceso completo de instalación, configuración y reinstalación de recursos, consulta la guía de inicio rápido de Dynamic Xtreme.

Después de cada instalación, prueba el sprite en el juego y comprueba que sus poses, paletas, volteos y desplazamientos se muestren correctamente durante sus diferentes estados y animaciones.

Errores comunes

Utilizar un nombre sin normalizar en un define generado

Los nombres incluidos en los defines generados por Dynamic Xtreme deben escribirse con la normalización utilizada por la herramienta. Los espacios, guiones bajos y guiones se eliminan, y la primera letra de cada palabra se escribe en mayúscula siguiendo el estilo PascalCase.

Por ejemplo, una pose llamada my_sprite-pose utiliza el componente normalizado MySpritePose dentro de un define como el siguiente:

!DynamicPoseIDMySpritePose

Escribir el nombre original con sus separadores, utilizar una combinación distinta de mayúsculas o confundir un define !DynamicPoseID... con un !PoseID... hará que Asar no encuentre el símbolo. Ante una duda, revisa el nombre exacto generado en DXDefines.asm.

Inicializar incorrectamente las variables del sprite

Las variables utilizadas por Dynamic Xtreme deben recibir valores válidos cada vez que se crea un sprite. Cuando se utiliza !PaletteAssignment, !Palette, !LastPoseIndex, !LastPoseHashIndex y !LastVersion deben comenzar en $FF, mientras que variables como !PoseIndex, !LocalFlip y !LastFlip normalmente comienzan en 0. También deben configurarse las distancias utilizadas por !RenderBox.

Si estas tablas no se inicializan correctamente, el sprite puede reutilizar una paleta, una pose o información dejada por otro sprite que ocupó anteriormente el mismo slot. En cluster sprites y extended sprites, esta inicialización debe realizarse mediante la rutina de inicio simulada explicada anteriormente.

No reservar un Extra Byte para la paleta

Un sprite normal que utilice carga de paletas debe tener disponible al menos un Extra Byte en su archivo *.cfg o *.json. Dynamic Xtreme utiliza Extra Byte 1 como !PaletteOption y, normalmente, también aprovecha sus cuatro bits bajos para seleccionar !Version.

Si el sprite no declara ningún Extra Byte, !ExtraByte1,x no contendrá la configuración necesaria para seleccionar correctamente la paleta y la versión del sprite.

No restaurar una paleta fija en la short routine

Un sprite puede utilizar una paleta fija que ya se encuentre cargada en el nivel en lugar de llamar a !PaletteAssignment. En este caso todavía es necesario asignar manualmente a !Palette,x el valor de la paleta que utilizará el dibujo.

Los valores avanzan de dos en dos y corresponden a las paletas de sprites 8-F:

  • $00: paleta 8.
  • $02: paleta 9.
  • $04: paleta A.
  • $06: paleta B.
  • $08: paleta C.
  • $0A: paleta D.
  • $0C: paleta E.
  • $0E: paleta F.

El valor debe asignarse durante el INIT y también dentro de la short routine. Cuando !RenderBox envía el sprite a la short routine, invalida !Palette,x; por eso la paleta fija debe restaurarse en esa ruta:

; Dentro de INIT
    LDA #$04            ; Paleta A
    STA !Palette,x

; Dentro de SpriteCode
.ShortRoutine
    LDA #$04            ; Paleta A
    STA !Palette,x

    LDA #$00
    %SubOffScreen()
    ; Aquí va la lógica de tu sprite
RTS

Esta asignación manual solamente corresponde a sprites que utilizan una paleta fija y no llaman a !PaletteAssignment. Los sprites con carga dinámica deben conservar el flujo de asignación explicado en la sección de manejo de paletas.