We've just updated MediaWiki and its underlying software. If anything doesn't look or work quite right, please mention it to us. --RanAS

es/SpriteCreationWithDyzen: Difference between revisions

From SnesLab
Jump to: navigation, search
Line 188: Line 188:


=== Condiciones y Transiciones ===
=== Condiciones y Transiciones ===
Las transiciones permiten que bajo ciertas condiciones el estado cambie a otro, cada estado podrá cambiar a un número limitado de otros estados con el fin que el sprite tenga el comportamiento deseado. Por ejemplo, si tuviéramos un sprite que simplemente se moviera de un lado a otro sin detectar precipicios, necesitaríamos una maquina de esta manera:
[[File:Basic State Machine.png|thumb|Basic State Machine]]
Tendríamos un sprite que caminaría en linea recta hasta toparse con una muralla, luego cambiaria al estado de volteo reproduciendo esa animación y cuando termine la animación de volteo entonces volvería al estado caminar yendo hacia el otro lado.
Y en ASM esa maquina se veria de esta manera:
<pre>
StateMachine:
LDA !State,x ;A Reg = !State,x
ASL
TAX ;Transform A Reg into a index value for table States and put that value in the X Reg
JSR (States,x) ;Call Routine on the table States using X reg value.
RTS
States:
dw Walk0
dw Flip1
Walk0:
LDX !SpriteIndex ;Load Sprite index on X Register
LDA !AnimationIndex,x
CMP #$00 ;Index of the animation represented by the State
BEQ .StateLoop
.StateStart
JSR ChangeAnimationFromStart_Walk ;Change the animation to the animation with index #$XX
LDA !GlobalFlip,x
BEQ .right ;Check if is sprite direction is right or left
LDA #$E0 ;If sprite direction is left then set negative X Speed
BRA ++
.right
LDA #$20 ;If sprite direction is right then set positive X speed
++
STA !SpriteXSpeed,x
JSL $01802A|!addr ;Update Sprite position with gravity
RTS
.StateLoop
JSL $01802A|!addr ;Update Sprite position with gravity
LDA !SpriteBlockedStatus_ASB0UDLR,x
AND #$03
BEQ + ;Check if Left or Right wall are blocked
LDA #$01
STA !State,x ;If left or right are blocked then change to state flip
+
RTS
Flip1:
LDX !SpriteIndex ;Load Sprite index on X Register
LDA !AnimationIndex,x
CMP #$01 ;Index of the animation represented by the State
BEQ .StateLoop
.StateStart
JSR ChangeAnimationFromStart_Flip ;Change the animation to the animation with index #$XX
STZ !SpriteXSpeed,x ;X Speed = 0
JSL $01802A|!addr ;Update Sprite position with gravity
RTS
.StateLoop
JSL $01802A|!addr ;Update Sprite position with gravity
LDA !AnimationFrameIndex,x
CMP #$XX
BCC + ;Checks the last frame of the animation
LDA !AnimationTimer,x
BEQ + ;Check if the frame finished
LDA !GlobalFlip,x ;If animation finish
EOR #$01 ;
STA !GlobalFlip,x ;Alternate sprite direction
STZ !State,x ;State = Walk
JSR Walk0_StateStart ;Call Walk0 State Start
+
RTS
</pre>
Analicemos el código. Primero podemos notar que tiene 2 estados como se muestra en el diagrama, el primero es Walk con índice 0 y el segundo es Flip con indice 1. Iniciemos viendo el estado Walk0, este tiene en el "StateStart" la siguiente condición:
<pre>
LDA !GlobalFlip,x
BEQ .right ;Check if is sprite direction is right or left
LDA #$E0 ;If sprite direction is left then set negative X Speed
BRA ++
.right
LDA #$20 ;If sprite direction is right then set positive X speed
++
STA !SpriteXSpeed,x
</pre>
Esta condición primero revisa la variable <code>!GlobalFlip,x</code> que tiene el valor 0 si el sprite mira hacia la derecha o 1 si el sprite mira hacia la izquierda (puede ser al reves dependiendo de como se hizo el sprite en Dyzen), entonces si el sprite mira hacia la derecha (<code>!GlobalFlip,x es 0</code>) entonces carga en el registro A un valor positivo y en caso contrario carga en el registro A un valor negativo, luego de esto llama <code>STA !SpriteXSpeed,x</code> que setearia la velocidad del sprite y luego refresca la posición en pantalla del sprite con <code>JSL $01802A|!addr</code>.
Luego en la sección "StateLoop" podemos notar el siguiente chequeo:
<pre>
LDA !SpriteBlockedStatus_ASB0UDLR,x
AND #$43
BEQ + ;Check if Left or Right wall are blocked
LDA #$01
STA !State,x ;If left or right are blocked then change to state flip
+
</pre>
Este chequeo revisaría los flags de la variable <code>!SpriteBlockedStatus_ASB0UDLR,x</code>, donde:
*A: Flag que es 1 si el sprite esta tocando un bloque solido por encima en el Layer 2 (Detección de piso).
*S: Flag que es 1 si el sprite esta tocando un bloque solido por los lados en el Layer 2 (Detección de paredes).
*B: Flag que es 1 si el sprite esta tocando un bloque solido por abajo en el Layer 2 (Detección de techo).
*U: Flag que es 1 si el sprite esta tocando un bloque solido por arriba en el Layer 1 (Detección de techo).
*D: Flag que es 1 si el sprite esta tocando un bloque solido por abajo en el Layer 1 (Detección de piso).
*R: Flag que es 1 si el sprite esta tocando un bloque solido por la derecha en el Layer 1 (Detección de pared derecha).
*L: Flag que es 1 si el sprite esta tocando un bloque solido por la izquierda en el Layer 1 (Detección de pared izquierda).
Usamos el comando <code>AND</code> para que todos los flags que no necesitamos queden en 0, en este caso estamos detectando paredes asi que los flags A, B, U y D no los necesitamos, por eso se usa el valor <code>#$43</code> que en binario seria <code>#$01000011</code>. De esta manera si el registro A luego de usar el and es distinto de 0 entonces esta tocando una pared. Luego de esto cambiaria el valor de <code>!State,x</code> para que use el estado Flip.
Se debe destacar en este caso, que la rutina <code>JSL $01802A|!addr</code> actualiza el valor de <code>!SpriteBlockedStatus_ASB0UDLR,x</code>, por este motivo es llamada al inicio del "StateLoop" y no al final.
Ahora revisaremos el estado Flip. Lo primero que debemos notar es que este también actualiza la posición del sprite, un incauto podría pensar que no necesita actualizar la posición durante la rutina Flip, ya que, el sprite no deberia moverse cuando esta tocando una muralla, sin embargo esto no es cierto y se debe a que se debe cubrir el caso en que el sprite no esta tocando el piso, por lo tanto, la posición vertical si debe ser actualizada.
El "StateStart" en este caso es muy simple, solamente pone la velocidad horizontal en 0, lo interesante en este estado ocurre en el "StateLoop", aca podemos notar los siguientes chequeos:
<pre>
LDA !AnimationFrameIndex,x
CMP #$XX
BCC + ;Checks the last frame of the animation
LDA !AnimationTimer,x
BEQ + ;Check if the frame finished
LDA !GlobalFlip,x ;If animation finish
EOR #$01 ;
STA !GlobalFlip,x ;Alternate sprite direction
STZ !State,x ;State = Walk
JSR Walk0_StateStart ;Call Walk0 State Start
+
</pre>
Iniciaremos con:
<code>
LDA !AnimationFrameIndex,x
CMP #$XX
BCC +
</code>
Este revisa la variable <code>!AnimationFrameIndex,x</code>, esta variable, podemos utilizarla para saber que frame dentro de la animación, se esta reproduciendo, por lo tanto, la idea seria revisar si el frame que se reproduce es el ultimo frame de la animación. ¿Cómo podemos saber que valor poner en #$XX? pues iremos a la tabla de esa animación, en la rutina de animación hay una tabla llamada "Frames", ejemplo:
<pre>
Frames:
Animation0_Walk_Frames:
db $00,$01,$02,$03,$04,$05,$06,$07
Animation1_Flip_Frames:
db $08,$08
Animation2_Resist_Frames:
db $09
Animation3_DeathLoop_Frames:
db $0A,$0B
Animation4_Death_Frames:
db $0C,$0D,$0E,$0F,$10
</pre>
En este ejemplo, podemos notar que la animación de Flip tiene solo 2 frames, entonces el valor que tendríamos que poner es #$01, (Tamaño de la animación - 1).
Otro método para saber que número poner es ir a la tabla "AnimationLenght" que tiene el tamaño de cada animación, ejemplo:
<pre>
AnimationLenght:
dw $0008,$0002,$0001,$0002,$0005
</pre>
Podemos notar que en esta tabla se muestra el tamaño de cada una de las animaciones, en este caso:
<pre>
Walk => Tamaño 8 frames, ultimo frame #$07
Flip => Tamaño 2 frames, ultimo frame #$01
Resist => Tamaño 1 frames, ultimo frame #$00
DeathLoop => Tamaño 2 frames, ultimo frame #$01
Death => Tamaño 5 frames, ultimo frame #$04
</pre>
Una vez entendido como funciona este chequeo, iremos al siguiente:
<pre>
LDA !AnimationTimer,x
BEQ +
</pre>
Este es bastante simple, lo que indica la variable <code>!AnimationTimer,x</code> es la cantidad de game loops para que el frame cambie, por lo tanto, si tiene un valor distinto de 0, significa que el frame aun se esta reproduciendo.
Luego de estos 2 checks usamos los comandos:
<code>
LDA !GlobalFlip,x ;If animation finish
EOR #$01 ;
STA !GlobalFlip,x ;Alternate sprite direction
</code>
El comando <code>EOR</code> puede ser utilizado para alternar bits del registro A, por ejemplo, <code>EOR #$01</code> alternaría el bit de más a la derecha, entonces si ese bit era un 0 lo cambiaria a un 1 y si era un 1, lo cambiaria a 0. usamos esto para alternar la dirección del sprite.
Por ultimo se pone se vuelve al estado Walk pero además se llama al "StateStart" del estado walk, la razón de esto es que al alternar la dirección del sprite, ocurriría que por 1 frame el sprite muestre la animación volteada incorrectamente, por esto, en los estados de volteo es muy común llamar el "StateStart".
Aca podemos notar el resultado de este comportamiento:
[[File:Kritter.gif|thumb|Kritter]]


== Movimiento ==
== Movimiento ==

Revision as of 17:35, 22 March 2021

English Português Español 日本語

En el siguiente tutorial se explicara como crear Sprites utilizando Dyzen. Este tutorial cubre no solo el como utilizar el tool, sino que además, cubre diversos comportamientos recurrentes en la creación de sprites normales, clusters y extended.

Para este tutorial se presume que el desarrollador tiene conocimientos previos de ASM como el uso de comandos básicos, branching e indexación. Este tutorial esta basado más en como construir la lógica de un sprite.

También debe notarse que este tutorial también puede servir para sprites que no son creados con Dyzen aunque el foco este basado en este ultimo.

Creando un CFG/JSON con CFG Editor

Estructura de un sprite

Un sprite se estructura de la siguiente manera:

  1. Sprite Init: Es la rutina que ocurre cuando el sprite es creado. En Pixi esta rutina inicia con la linea:
    print "INIT ",pc
    

    Y debe terminar con un RTL.

  2. Sprite Main: Esta rutina es llamada en cada logic loop del juego y se utiliza para actualizar al sprite y su logica. En pixi esta rutina inicia con la linea:
    print "MAIN ",pc
    

    Y debe debe terminar con un RTL. Usualmente esta rutina sera de esta manera:

    print "MAIN ",pc
    	PHB
    	PHK
    	PLB
    	JSR SpriteCode
    	PLB
    RTL
    

    Llamando a una rutina llamada SpriteCode que sera el contenido real de esta rutina. Se realiza esto con el fin de mantener un mejor orden en el codigo y además setear el Program Bank para que se puedan indexar las tablas con indexación corta ($XXXX,x o $XXXX,y), en ves de usar indexación larga y asi disminuir la cantidad de ciclos en el codigo.

  3. SpriteCode: Es basicamente el contenido de la rutina Main previamente descrita. Esta rutina llamara a otras que haran la logica del sprite, separaremos estas rutinas en DynamicRoutine, GraphicRoutine, InteractionWithPlayer, InteractionWithSprites, StateMachine, AnimationRoutine, entre otras, dependiendo de que requiera el sprite. Una estructura basica de esta rutina es:
    SpriteCode:
    
    .AlwaysExecutedZone
    	JSR GraphicRoutine                  ;Calls the graphic routine and updates sprite graphics
    
    	;Here you can put code that will be excecuted each frame even if the sprite is locked
    
    	LDA !SpriteStatus,x			        
    	CMP #$08                            ;if sprite dead return
    	BNE Return	
    
    	LDA !LockAnimationFlag				    
    	BNE Return			                    ;if locked animation return.
    
    .ExecutedIfNotLocked
    	%SubOffScreen()
    
    	JSR InteractMarioSprite
    	;After this routine, if the sprite interact with mario, Carry is Set.
    
    	;Here you can write your sprite code routine
    	;This will be excecuted once per frame excepts when 
    	;the animation is locked or when sprite status is not #$08
    
    	JSR AnimationRoutine                ;Calls animation routine and decides the next frame to draw
    RTS
    Return:
    RTS
    

    Donde podemos notar 2 zonas principales:

    • .AlwaysExecutedZone que comprende el sector desde el inicio de la rutina hasta el primer chequeo LDA !SpriteStatus,x. Esta zona ocurre siempre, incluso si el juego esta en pausa o las animaciones bloqueadas, normalmente no pondremos mucho codigo en esta zona salvo por la rutina gráfica.
    • .ExecutedIfNotLocked que comprende el sector despues del chequeo LDA !LockAnimationFlag, esta zona ocurre cuando las animaciones no estan bloqueadas, o sea cuando el juego no esta pausado o el player no esta en animación de muerte o similares, tambien. Tambien debe aclararse que este sector solo es ejecutado si el !SpriteStatus,x tiene el valor 8, esto significa que el el sprite esta con el status "Rutina Normal", esta dirección de RAM corresponde con la tabla $14C8, cabe destacar que modificando el chequeo del !SpriteStatus,x se puede hacer que el sprite ejecute el codigo en otros status, siendo uno de los más comunes el 2 que ocurre cuando el sprite esta muriendo. En este sector podemos notar que se llaman a otras rutinas que iremos detallando a lo largo del tutorial, lo importante que debemos saber aca es que debajo de la del llamado JSR InteractMarioSprite es donde ejecutaremos el codigo de la lógica del sprite.

Maquinas de Estado

Una Maquina de estados esta conformada por 2 partes principales, los estados y las transiciones entre estados. En estas maquinas, se ejecuta solo un estado al mismo tiempo y este cambia según las condiciones lógicas implementadas a los estados que son alcanzables desde este.

No se planea hacer un curso completo sobre maquinas de estados, ya que, esta materia puede tomar semestres de estudios para profundizar todas sus distintas dimensiones, sin embargo, utilizaremos este concepto para armar la lógica de nuestro sprite.

En este esquema la idea es tener una rutina central que llamaremos StateMachine que sera utilizada para llamar a la rutina correcta según el estado que es ejecutado. Para esto necesitaremos una tabla de sprites que usaremos con este proposito, se recomienda sobretodo una tabla misc, ya que, asi no necesitamos buscar freerams que pueden terminar siendo utilizadas para otros recursos, para esto podemos dirigirnos al tope del codigo y en la zona demarcada con los comentarios:

;######################################
;############## Defines ###############
;######################################

Podemos agregar la variable:

!State = !SpriteMiscTable8

Utilizando una de las tablas Misc. de sprites que pueden ser usadas para cualquier proposito que desees.

Una vez hecho lo anterior necesitaremos poner esta variable con el valor 0, usaremos el estado 0 para nuestro estado inicial, ahora no siempre podemos iniciarlo en 0, podríamos por ejemplo, hacer que a través del extra byte o el extra property, darle el estado inicial al sprite, con el fin de generar distintas versiones del mismo sprite, pero lo más usual es usar el estado 0 como estado inicial. Para esto en el el SpriteInit, agregaremos la linea:

	STZ !State,x ;!State,x = 0

Luego de esto crearemos una rutina llamada "StateMachine" que ejecutara una rutina distinta dependiendo de la variable !State,x. Esta rutina, sera la siguiente:

StateMachine:
	LDA !State,x	;A Reg = !State,x
	ASL
	TAX		;Transform A Reg into a index value for table States and put that value in the X Reg
	
	JSR (States,x)	;Call Routine on the table States using X reg value.
RTS

States:
	dw State0
	dw State1
	dw State2
	.
	.
	.

Podemos notar que esta rutina utiliza una tabla llamada "States", en esta tabla pondremos todos los estados que tenga nuestro sprite como correr, estar detenido, voltear, morir, etc. No es necesario ponerle de nombre a los estados "State0", simplemente usa el nombre que veas más conveniente, ahora normalmente se recomienda enumerarlos, ya que, sirve para recordar que valor de la variable !State,x llama a cada estado. Para aquellos que conocen algun lenguaje de programación de alto nivel, esta rutina seria el equivalente a:

switch(state)
{
	case 0:
		state0();
		break;
	case 1:
		state1();
		break;
	case 2:
		state2();
		break;
	case 3:
		state3();
		break;
	etc...
}

Esta rutina la pondremos en cualquier sector debajo de la rutina "SpriteCode", solo intenta ser ordenado, ya que, en ASM es muy fácil que el código quede hecho un desastre. Una vez creada esta rutina, la llamaremos en la zona de la rutina "SpriteCode" que sucede antes del JSR AnimationRoutine. Sin embargo, en este momento si probamos el sprite ahora mismo, no va a poder ser insertado, ya que, los labels de los estados no han sido creado asi que debemos crear cada uno de los estados.

Estructura de un Estado

Para crear cada uno de los estados seguiremos la siguiente base:

StateX:
	LDX !SpriteIndex	;Load Sprite index on X Register
	LDA !AnimationIndex,x
	CMP #$XX		;Index of the animation represented by the State
	BEQ .StateLoop
.StateStart
	JSR ChangeAnimationFromStart_XXXX	;Change the animation to the animation with index #$XX
RTS
.StateLoop
RTS

Como podemos notar, esta base tiene 2 secciones:

  • .StateStart que es llamada cuando el sprite cambia desde otro estado al nuevo estado.
  • .StateLoop que es llamada cuando en cada ciclo despues que el estado ya fue iniciado.

Debemos notar que esta base de estados estaría linkeada a una y solo una animación al mismo tiempo, obviamente, se puede hacer que un estado usara más de una animación haciendo modificaciones a esta estructura, pero en la practica hacer esto no suele ser muy necesario y tiene la desventaja que genera un código más desordenado y difícil de leer, además, complica la lógica del estado, por esto, recomiendo usar 1 animación y solo una por estado a menos que sea realmente muy necesario.

Algo que podemos notar de esta estructura, es que desde fuera del estado podemos llamar tanto el "StateStart" como el "StartLoop" usando los comandos JSR StateX_StateStart y JSR StateX_StateLoop, llamar al "StateStart" puede ser útil en ciertas circunstancias donde hacemos algunas transiciones donde si o si necesitamos iniciar el estado de manera externa, esto es común sobre todo en sprites dinámicos o en animaciones de volteo, ahora llamar al "StateLoop" no es tan útil, rara vez podríamos encontrar una razón para llamar esta sección de manera externa a menos que tuviéramos otro estado que fuera casi idéntico pero con una pequeña variación.

Otro detalle importante es notar que el estado al inicio tiene un LDX !SpriteIndex, esto se debe a que los sprites utilizan tablas para sus variables y estas tablas usan el índice del Sprite almacenado en el registro X para podes acceder al valor correcto de la tabla, sin embargo, cuando llamamos la rutina "StateMachine", el valor del registro X fue modificado y perdimos el índice del sprite en el registro X, así que usamos este comando para restaurarlo.

Por ultimo, cabe la duda ¿Cómo se el índice de la animación utilizada en el estado?, esta se responde yendo a la sección del código donde están las rutinas ChangeAnimationFromStart, ahí podremos encontrar algo como esto:

ChangeAnimationFromStart_Walk:
	STZ !AnimationIndex,x
	JMP ChangeAnimationFromStart
ChangeAnimationFromStart_Flip:
	LDA #$01
	STA !AnimationIndex,x
	JMP ChangeAnimationFromStart
ChangeAnimationFromStart_Resist:
	LDA #$02
	STA !AnimationIndex,x
	JMP ChangeAnimationFromStart
ChangeAnimationFromStart_DeathLoop:
	LDA #$03
	STA !AnimationIndex,x
	JMP ChangeAnimationFromStart
ChangeAnimationFromStart_Death:
	LDA #$04
	STA !AnimationIndex,x

Donde el valor cargado en A seria el índice de la animación correspondiente, en este ejemplo:

Walk => !AnimationIndex,x = 0
Flip => !AnimationIndex,x = 1
Resist => !AnimationIndex,x = 2
DeathLoop => !AnimationIndex,x = 3
Death => !AnimationIndex,x = 4

Condiciones y Transiciones

Las transiciones permiten que bajo ciertas condiciones el estado cambie a otro, cada estado podrá cambiar a un número limitado de otros estados con el fin que el sprite tenga el comportamiento deseado. Por ejemplo, si tuviéramos un sprite que simplemente se moviera de un lado a otro sin detectar precipicios, necesitaríamos una maquina de esta manera:

Basic State Machine

Tendríamos un sprite que caminaría en linea recta hasta toparse con una muralla, luego cambiaria al estado de volteo reproduciendo esa animación y cuando termine la animación de volteo entonces volvería al estado caminar yendo hacia el otro lado. Y en ASM esa maquina se veria de esta manera:

StateMachine:
	LDA !State,x	;A Reg = !State,x
	ASL
	TAX		;Transform A Reg into a index value for table States and put that value in the X Reg
	
	JSR (States,x)	;Call Routine on the table States using X reg value.
RTS

States:
	dw Walk0
	dw Flip1

Walk0:
	LDX !SpriteIndex	;Load Sprite index on X Register
	LDA !AnimationIndex,x
	CMP #$00		;Index of the animation represented by the State
	BEQ .StateLoop
.StateStart
	JSR ChangeAnimationFromStart_Walk	;Change the animation to the animation with index #$XX
	
	LDA !GlobalFlip,x
	BEQ .right		;Check if is sprite direction is right or left
	LDA #$E0		;If sprite direction is left then set negative X Speed
	BRA ++
.right
	LDA #$20		;If sprite direction is right then set positive X speed
++
	STA !SpriteXSpeed,x
	JSL $01802A|!addr			;Update Sprite position with gravity
RTS
.StateLoop
	JSL $01802A|!addr	;Update Sprite position with gravity
	
	LDA !SpriteBlockedStatus_ASB0UDLR,x
	AND #$03
	BEQ +					;Check if Left or Right wall are blocked
	LDA #$01
	STA !State,x				;If left or right are blocked then change to state flip
+
RTS

Flip1:
	LDX !SpriteIndex	;Load Sprite index on X Register
	LDA !AnimationIndex,x
	CMP #$01		;Index of the animation represented by the State
	BEQ .StateLoop
.StateStart
	JSR ChangeAnimationFromStart_Flip	;Change the animation to the animation with index #$XX
	STZ !SpriteXSpeed,x			;X Speed = 0
	JSL $01802A|!addr			;Update Sprite position with gravity
RTS
.StateLoop
	JSL $01802A|!addr			;Update Sprite position with gravity

	LDA !AnimationFrameIndex,x		
	CMP #$XX
	BCC +					;Checks the last frame of the animation
	
	LDA !AnimationTimer,x
	BEQ +					;Check if the frame finished

	LDA !GlobalFlip,x			;If animation finish
	EOR #$01				;
	STA !GlobalFlip,x			;Alternate sprite direction
	
	STZ !State,x				;State = Walk
	JSR Walk0_StateStart			;Call Walk0 State Start
+
RTS

Analicemos el código. Primero podemos notar que tiene 2 estados como se muestra en el diagrama, el primero es Walk con índice 0 y el segundo es Flip con indice 1. Iniciemos viendo el estado Walk0, este tiene en el "StateStart" la siguiente condición:

	LDA !GlobalFlip,x
	BEQ .right		;Check if is sprite direction is right or left
	LDA #$E0		;If sprite direction is left then set negative X Speed
	BRA ++
.right
	LDA #$20		;If sprite direction is right then set positive X speed
++
	STA !SpriteXSpeed,x

Esta condición primero revisa la variable !GlobalFlip,x que tiene el valor 0 si el sprite mira hacia la derecha o 1 si el sprite mira hacia la izquierda (puede ser al reves dependiendo de como se hizo el sprite en Dyzen), entonces si el sprite mira hacia la derecha (!GlobalFlip,x es 0) entonces carga en el registro A un valor positivo y en caso contrario carga en el registro A un valor negativo, luego de esto llama STA !SpriteXSpeed,x que setearia la velocidad del sprite y luego refresca la posición en pantalla del sprite con JSL $01802A|!addr.

Luego en la sección "StateLoop" podemos notar el siguiente chequeo:

	LDA !SpriteBlockedStatus_ASB0UDLR,x
	AND #$43
	BEQ +					;Check if Left or Right wall are blocked
	LDA #$01
	STA !State,x				;If left or right are blocked then change to state flip
+

Este chequeo revisaría los flags de la variable !SpriteBlockedStatus_ASB0UDLR,x, donde:

  • A: Flag que es 1 si el sprite esta tocando un bloque solido por encima en el Layer 2 (Detección de piso).
  • S: Flag que es 1 si el sprite esta tocando un bloque solido por los lados en el Layer 2 (Detección de paredes).
  • B: Flag que es 1 si el sprite esta tocando un bloque solido por abajo en el Layer 2 (Detección de techo).
  • U: Flag que es 1 si el sprite esta tocando un bloque solido por arriba en el Layer 1 (Detección de techo).
  • D: Flag que es 1 si el sprite esta tocando un bloque solido por abajo en el Layer 1 (Detección de piso).
  • R: Flag que es 1 si el sprite esta tocando un bloque solido por la derecha en el Layer 1 (Detección de pared derecha).
  • L: Flag que es 1 si el sprite esta tocando un bloque solido por la izquierda en el Layer 1 (Detección de pared izquierda).

Usamos el comando AND para que todos los flags que no necesitamos queden en 0, en este caso estamos detectando paredes asi que los flags A, B, U y D no los necesitamos, por eso se usa el valor #$43 que en binario seria #$01000011. De esta manera si el registro A luego de usar el and es distinto de 0 entonces esta tocando una pared. Luego de esto cambiaria el valor de !State,x para que use el estado Flip.

Se debe destacar en este caso, que la rutina JSL $01802A|!addr actualiza el valor de !SpriteBlockedStatus_ASB0UDLR,x, por este motivo es llamada al inicio del "StateLoop" y no al final.

Ahora revisaremos el estado Flip. Lo primero que debemos notar es que este también actualiza la posición del sprite, un incauto podría pensar que no necesita actualizar la posición durante la rutina Flip, ya que, el sprite no deberia moverse cuando esta tocando una muralla, sin embargo esto no es cierto y se debe a que se debe cubrir el caso en que el sprite no esta tocando el piso, por lo tanto, la posición vertical si debe ser actualizada.

El "StateStart" en este caso es muy simple, solamente pone la velocidad horizontal en 0, lo interesante en este estado ocurre en el "StateLoop", aca podemos notar los siguientes chequeos:

	LDA !AnimationFrameIndex,x		
	CMP #$XX
	BCC +					;Checks the last frame of the animation
	
	LDA !AnimationTimer,x
	BEQ +					;Check if the frame finished

	LDA !GlobalFlip,x			;If animation finish
	EOR #$01				;
	STA !GlobalFlip,x			;Alternate sprite direction
	
	STZ !State,x				;State = Walk
	JSR Walk0_StateStart			;Call Walk0 State Start
+

Iniciaremos con: LDA !AnimationFrameIndex,x CMP #$XX BCC +

Este revisa la variable !AnimationFrameIndex,x, esta variable, podemos utilizarla para saber que frame dentro de la animación, se esta reproduciendo, por lo tanto, la idea seria revisar si el frame que se reproduce es el ultimo frame de la animación. ¿Cómo podemos saber que valor poner en #$XX? pues iremos a la tabla de esa animación, en la rutina de animación hay una tabla llamada "Frames", ejemplo:

Frames:
Animation0_Walk_Frames:
	db $00,$01,$02,$03,$04,$05,$06,$07
Animation1_Flip_Frames:
	db $08,$08
Animation2_Resist_Frames:
	db $09
Animation3_DeathLoop_Frames:
	db $0A,$0B
Animation4_Death_Frames:
	db $0C,$0D,$0E,$0F,$10

En este ejemplo, podemos notar que la animación de Flip tiene solo 2 frames, entonces el valor que tendríamos que poner es #$01, (Tamaño de la animación - 1).

Otro método para saber que número poner es ir a la tabla "AnimationLenght" que tiene el tamaño de cada animación, ejemplo:

AnimationLenght:
	dw $0008,$0002,$0001,$0002,$0005

Podemos notar que en esta tabla se muestra el tamaño de cada una de las animaciones, en este caso:

Walk => Tamaño 8 frames, ultimo frame #$07
Flip => Tamaño 2 frames, ultimo frame #$01
Resist => Tamaño 1 frames, ultimo frame #$00
DeathLoop => Tamaño 2 frames, ultimo frame #$01
Death => Tamaño 5 frames, ultimo frame #$04

Una vez entendido como funciona este chequeo, iremos al siguiente:

	LDA !AnimationTimer,x
	BEQ +	

Este es bastante simple, lo que indica la variable !AnimationTimer,x es la cantidad de game loops para que el frame cambie, por lo tanto, si tiene un valor distinto de 0, significa que el frame aun se esta reproduciendo.

Luego de estos 2 checks usamos los comandos: LDA !GlobalFlip,x ;If animation finish EOR #$01 ; STA !GlobalFlip,x ;Alternate sprite direction El comando EOR puede ser utilizado para alternar bits del registro A, por ejemplo, EOR #$01 alternaría el bit de más a la derecha, entonces si ese bit era un 0 lo cambiaria a un 1 y si era un 1, lo cambiaria a 0. usamos esto para alternar la dirección del sprite.

Por ultimo se pone se vuelve al estado Walk pero además se llama al "StateStart" del estado walk, la razón de esto es que al alternar la dirección del sprite, ocurriría que por 1 frame el sprite muestre la animación volteada incorrectamente, por esto, en los estados de volteo es muy común llamar el "StateStart".

Aca podemos notar el resultado de este comportamiento:

Kritter

Movimiento

Movimiento Básico

Movimiento Avanzado

Interacción

Interacción con Player

Vanilla

Custom

Interacción con Dyzen

Interacción con Otros Sprites

Vanilla

Custom

Interacción con Dyzen

Rutina Gráfica

Trucos en la Rutina Gráfica

Animación

Trucos en la Rutina de Animación

Sonido y Música

Comportamientos Comunes

Comportamientos durante el Funcionamiento Normal

Comportamientos durante la Interacción con el Player

Comportamientos durante la Interacción con otros Sprites

Creación de Clusters y Extended Sprites

Sprites Dinamicos

Instalación

Utilizando DRAdder

Cambios en la Animación

Trucos con paletas de colores