- Introduced comprehensive documentation for the Puffstool sprite, covering properties, core routines, and key behaviors. - Added analysis for the Sea Urchin sprite, detailing its initialization, state management, and drawing routines. - Included a thorough examination of the Thunder Ghost sprite, highlighting its dynamic health, lightning attack mechanics, and movement patterns.
12 KiB
Darknut Sprite Analysis
This document provides a detailed analysis of the darknut.asm sprite, outlining its properties, core routines, and behavioral patterns.
1. Sprite Properties
The following !SPRID constants define Darknut's fundamental characteristics:
!SPRID = Sprite_Darknut
!NbrTiles = 03 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 12 ; Number of Health the sprite have (dynamically set in _Prep)
!Damage = 00 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 12 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 00 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
Note: !Health is initially set to 12 but is dynamically determined during initialization based on Link's sword level. !Damage is 00, implying damage is handled through other means (e.g., contact with Link's sword).
2. Core Routines
2.1. Sprite_Darknut_Long (Main Loop)
This is the primary entry point for Darknut's per-frame execution, called by the game engine. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.
Sprite_Darknut_Long:
{
PHB : PHK : PLB ; Set up bank registers
JSR Sprite_Darknut_Draw ; Call drawing routine
JSL Sprite_DrawShadow ; Draw a shadow (if !Shadow is 01)
JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Check if sprite is active
JSR Sprite_Darknut_Main ; If active, run main logic
.SpriteIsNotActive
PLB ; Restore bank register
RTL ; Return from long routine
}
2.2. Sprite_Darknut_Prep (Initialization)
This routine is executed once when Darknut is first spawned. It dynamically sets Darknut's health based on Link's current sword level, initializes SprDefl (deflection timer), and SprTileDie (tile for death animation).
Sprite_Darknut_Prep:
{
PHB : PHK : PLB
LDA.l $7EF359 : TAY ; Get Link's sword level (0-3), adjust to 0-indexed
LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level
LDA.b #$80 : STA.w SprDefl, X ; Initialize deflection timer
LDA.b #%01100000 : STA.w SprTileDie, X ; Set tile for death animation
PLB
RTL
.health ; Health values for each sword level
db $04, $06, $08, $0A ; 4, 6, 8, 10 HP
}
2.3. Sprite_Darknut_Main (Behavioral Logic)
This routine manages Darknut's AI, including probe spawning, parrying, movement, and animation. It uses SprAction, X to control its facing direction and animation.
Sprite_Darknut_Main:
{
JSL GetDistance8bit_Long : CMP.b #$80 : BCS .no_probe ; Check distance to Link
JSL Sprite_SpawnProbeAlways_long ; If close, spawn a probe
.no_probe
JSL Guard_ParrySwordAttacks ; Handle parrying Link's sword attacks
JSL Sprite_Move ; Apply velocity
JSL Sprite_BounceFromTileCollision ; Handle collision with tiles
JSL Sprite_DamageFlash_Long ; Handle damage flashing
JSL Sprite_CheckIfRecoiling ; Check for recoil state
JSL Sprite_CheckDamageFromPlayer : BCC .no_dano ; Check if Link damages Darknut
LDA.b #$FF : STA.w SprTimerD, X ; If damaged, set timer D
.no_dano
LDA.w SprTimerA, X : BEQ + ; Check timer A
LDA.b #$90 : STA.w SprTimerD, X ; If timer A is not 0, set timer D
+
LDA.w SprTimerD, X : BEQ ++ ; Check timer D
LDA.b #$08 : JSL Sprite_ApplySpeedTowardsPlayer ; Apply speed towards Link
JSL Sprite_DirectionToFacePlayer ; Update facing direction
TYA
STA.w SprMiscC, X ; Store facing direction in SprMiscC
STA.w SprMiscE, X ; Store facing direction in SprMiscE
STA.w SprAction, X ; Set SprAction to facing direction
JSL Guard_ChaseLinkOnOneAxis ; Chase Link along one axis
JMP +++
++
JSR Sprite_Darknut_BasicMove ; If no timers, use basic movement
+++
JSR Goriya_HandleTileCollision ; Handle tile collision (specific to Goriya, but used here)
LDA.w SprAction, X
JSL JumpTableLocal ; Jump to animation routine based on SprAction
dw FaceRight
dw FaceLeft
dw FaceDown
dw FaceUp
FaceUp:
{
%PlayAnimation(0,1,10) ; Animate frames 0-1 every 10 frames
RTS
}
FaceDown:
{
%StartOnFrame(2)
%PlayAnimation(2,3,10) ; Animate frames 2-3 every 10 frames
RTS
}
FaceLeft:
{
%StartOnFrame(4)
%PlayAnimation(4,5,10) ; Animate frames 4-5 every 10 frames
RTS
}
FaceRight:
{
%StartOnFrame(6)
%PlayAnimation(6,7,10) ; Animate frames 6-7 every 10 frames
RTS
}
}
2.4. Sprite_Darknut_BasicMove (Basic Movement Logic)
This routine defines Darknut's basic movement patterns, which are executed when no special conditions (like being damaged or timers) are active. It uses SprAction, X to determine the current movement direction.
Sprite_Darknut_BasicMove:
{
LDA.w SprAction, X
JSL JumpTableLocal ; Jump to movement routine based on SprAction
dw MoveRight
dw MoveLeft
dw MoveDown
dw MoveUp
MoveUp:
{
LDA.b #-DarknutSpeed : STA.w SprYSpeed, X ; Set Y-speed to negative (move up)
STZ.w SprXSpeed, X ; Clear X-speed
RTS
}
MoveDown:
{
LDA.b #DarknutSpeed : STA.w SprYSpeed, X ; Set Y-speed to positive (move down)
STZ.w SprXSpeed, X ; Clear X-speed
RTS
}
MoveLeft:
{
LDA.b #-DarknutSpeed : STA.w SprXSpeed, X ; Set X-speed to negative (move left)
STZ.w SprYSpeed, X ; Clear Y-speed
RTS
}
MoveRight:
{
LDA.b #DarknutSpeed : STA.w SprXSpeed, X ; Set X-speed to positive (move right)
STZ.w SprYSpeed, X ; Clear Y-speed
RTS
}
}
2.5. Sprite_Darknut_Draw (Drawing Routine)
This routine is responsible for rendering Darknut's graphics. It uses a custom OAM (Object Attribute Memory) allocation and manipulation logic, similar to Booki, to handle its multi-tile appearance and animation. It dynamically determines the animation frame and applies offsets for each tile.
Sprite_Darknut_Draw:
{
JSL Sprite_PrepOamCoord ; Prepare OAM coordinates
JSL Sprite_OAM_AllocateDeferToPlayer ; Allocate OAM slots, deferring to player
LDA.w SprGfx, X : CLC : ADC $0D90, X : TAY ; Calculate animation frame index
LDA .start_index, Y : STA $06 ; Store start index for tiles
LDA.w SprFlash, X : STA $08 ; Store flash status
PHX
LDX .nbr_of_tiles, Y ; Load number of tiles for current frame (minus 1)
LDY.b #$00 ; Initialize Y for OAM buffer offset
.nextTile
PHX ; Save current Tile Index
TXA : CLC : ADC $06 ; Add Animation Index Offset
PHA ; Keep the value with animation index offset
ASL A : TAX ; Multiply by 2 for word access
REP #$20 ; Set A to 16-bit mode
LDA $00 : CLC : ADC .x_offsets, X : STA ($90), Y ; Store Y-coordinate with X-offset
AND.w #$0100 : STA $0E ; Check if Y-coord is off-screen (high bit)
INY
LDA $02 : CLC : ADC .y_offsets, X : STA ($90), Y ; Store X-coordinate with Y-offset
CLC : ADC #$0010 : CMP.w #$0100
SEP #$20 ; Set A to 8-bit mode
BCC .on_screen_y
LDA.b #$F0 : STA ($90), Y ; If off-screen, move sprite off-screen
STA $0E
.on_screen_y
PLX ; Restore Tile Index
INY
LDA .chr, X : STA ($90), Y ; Store character (tile) number
INY
LDA .properties, X : ORA $08 : STA ($90), Y ; Apply flash and store OAM properties
PHY
TYA : LSR #2 : TAY ; Calculate OAM buffer index for size
LDA.w .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile ; Loop for next tile
PLX ; Restore X (sprite index)
RTS
; =========================================================
; OAM Data Tables
.start_index
db $00, $03, $06, $09, $0C, $0E, $10, $12
.nbr_of_tiles
db 2, 2, 2, 2, 1, 1, 1, 1
.x_offsets
dw 0, 0, 0
dw 0, 0, 0
dw 0, 0, 0
dw 0, 0, 0
dw 0, -12
dw 0, -12
dw 0, 12
dw 0, 12
.y_offsets
dw -4, 0, -12
dw -4, 0, -12
dw 0, 12, 20
dw 0, 12, 20
dw 0, 8
dw 0, 8
dw 0, 8
dw 0, 8
.chr
db $EF, $E6, $FF
db $EF, $E6, $FF
db $E2, $EF, $FF
db $E2, $EF, $FF
db $E0, $E8
db $E4, $E8
db $E0, $E8
db $E4, $E8
.properties
db $B9, $39, $B9
db $B9, $79, $B9
db $39, $39, $39
db $79, $39, $39
db $39, $79
db $39, $79
db $79, $39
db $79, $39
.sizes
db $00, $02, $00
db $00, $02, $00
db $02, $00, $00
db $02, $00, $00
db $02, $02
db $02, $02
db $02, $02
db $02, $02
}
3. Key Behaviors and Implementation Details
- Dynamic Health: Darknut's health is determined at spawn time based on Link's current sword level, similar to Booki, allowing for dynamic difficulty scaling.
- Probe Spawning: Darknut has the ability to spawn a probe (
Sprite_SpawnProbeAlways_long) when Link is within a certain distance, adding a ranged attack or detection mechanism. - Parrying Mechanics: The
Guard_ParrySwordAttacksroutine suggests Darknut can actively defend against Link's sword attacks, potentially deflecting them or becoming temporarily invulnerable. - Chasing on One Axis: When damaged or under certain timer conditions, Darknut uses
Guard_ChaseLinkOnOneAxisto pursue Link along either the horizontal or vertical axis, making its movement more predictable but still challenging. - Basic Movement: Darknut has a set of basic directional movements (
MoveUp,MoveDown,MoveLeft,MoveRight) that it cycles through when not actively chasing or reacting to damage. - Custom OAM Drawing: Darknut utilizes a custom OAM drawing routine, similar to Booki, to handle its multi-tile sprite. This routine precisely positions and animates multiple 8x8 tiles to form the larger Darknut sprite. The use of
REP #$20andSEP #$20for 16-bit coordinate calculations is also present here. SprDeflandSprTileDie:SprDeflis used as a deflection timer, likely related to the parrying mechanic.SprTileDiespecifies a custom tile to be used during its death animation.SprMiscCandSprMiscE: These variables are used to store Darknut's facing direction, which influences both its movement and animation.SprMiscCis likely used for animation frame selection, whileSprMiscEmight be used for other directional logic.Goriya_HandleTileCollision: The use of a collision handler namedGoriya_HandleTileCollisionsuggests code reuse from another sprite, indicating a shared collision logic for certain enemy types.