es/Dynamic Xtreme sprite creation guide
| 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.asmpara sprites normales.ClusterSpriteDefines.asmpara cluster sprites.ExtendedSpriteDefines.asmpara 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:
!RenderXDistanceOutOfScreeny!RenderYDistanceOutOfScreenguardan las distancias que utilizará!RenderBoxpara decidir si el sprite todavía se encuentra lo bastante cerca de la pantalla como para renderizarse. Los valores!RenderBoxXDistanceOutOfScreenMySpritey!RenderBoxYDistanceOutOfScreenMySpriteson generados a partir del DrawInfo y quedan disponibles enDXDefines.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_spriteymy spritese convierten enMySprite.!PoseIndexindica 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.!Versionidentifica 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.!GlobalFliprepresenta 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.!LocalFlippermite aplicar un volteo adicional a la pose actual. Dynamic Xtreme combina ambos valores y conserva en!LastFlipel volteo efectivo utilizado para dibujar.!Paletteguarda la paleta asignada actualmente. Se inicia en$FFporque el sprite todavía no posee una paleta válida.!LastPoseIndexguarda la última pose que Dynamic Xtreme pudo obtener correctamente, y!LastPoseHashIndexguarda el slot de la caché de VRAM donde se encuentra. Ambos comienzan en$FFporque todavía no existe una pose disponible.!LastVersionguarda la versión procesada anteriormente por el manejo de paletas. Iniciarla en$FFhace que la primera versión del sprite sea procesada aunque!Versioncomience 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----
Eindica si la paleta será afectada por el efecto global: 0 la excluye y 1 lo habilita.PPPrepresenta una paleta entre 8 y F:000corresponde a la paleta 8 y111a 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
EPPPcontiene las opciones de paleta descritas anteriormente.VVVVselecciona 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 porPaletteOptiony carga la paleta dinámicamente.AA = 01: asignación manual.PaletteOptionindica 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
!PaletteOptiony, en el ejemplo de esta guía, sus cuatro bits bajos seleccionan!Version. - Extra Property Byte 2: configura
!PaletteAssignmentOptionmediante sus bits 5 y 4:00para asignación automática,01para asignación manual y10para asignación manual sin carga. Estos bits corresponden a los valores$00,$10y$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.
!PropParamcontiene propiedades adicionales con el formatoYXCC----.!MaxTilePriorityParamindica 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.