- Introduced detailed analysis for the Minecart system, highlighting its state machine, track system, and areas for improvement. - Created an NPCs analysis document, summarizing various NPC sprites and their functionalities. - Added an Objects analysis document, covering interactive elements like collectibles, ice blocks, and minecarts. - Documented the Overlord sprite system, detailing its role in spawning other sprites and managing events. - Compiled a Dungeons & Indoor Areas document, outlining custom room tags, enhanced mechanics, and advanced collision systems. - Developed an Overworld Systems Analysis, focusing on the ZSCustomOverworld architecture and its core features. - Added a Time System document, explaining the in-game clock and day/night cycle management. - Documented the ZScream Custom Overworld, detailing its data-driven approach and key features.
4.9 KiB
Sprite Creation Guide
This guide provides a step-by-step walkthrough for creating a new custom sprite in Oracle of Secrets using the project's modern sprite system.
1. File Setup
-
Create the Sprite File: Create a new
.asmfile for your sprite in the appropriate subdirectory ofSprites/(e.g.,Sprites/Enemies/MyNewEnemy.asm). -
Include the File: Open
Sprites/all_sprites.asmand add anincsrcdirective to include your new file. Make sure to place it in the correct bank section (e.g., Bank 30, 31, or 32) to ensure it gets compiled into the ROM.; In Sprites/all_sprites.asm org $318000 ; Bank 31 ... incsrc "Sprites/Enemies/MyNewEnemy.asm"
2. Sprite Properties
At the top of your new sprite file, define its core properties using the provided template. These ! constants are used by the %Set_Sprite_Properties macro to automatically configure the sprite's behavior and integrate it into the game.
; Properties for MyNewEnemy
!SPRID = $XX ; CHOOSE AN UNUSED SPRITE ID!
!NbrTiles = 02 ; Number of 8x8 tiles used in the largest frame
!Health = 10 ; Health points
!Damage = 04 ; Damage dealt to Link on contact (04 = half a heart)
!Harmless = 00 ; 00 = Harmful, 01 = Harmless
!Hitbox = 08 ; Hitbox size (0-31)
!ImperviousAll = 00 ; 01 = All attacks clink harmlessly
!Statue = 00 ; 01 = Behaves like a solid statue
!Prize = 01 ; Prize pack dropped on death (0-15)
; ... and so on for all properties ...
; This macro MUST be called after the properties
%Set_Sprite_Properties(Sprite_MyNewEnemy_Prep, Sprite_MyNewEnemy_Long)
3. Main Structure (_Long routine)
This is the main entry point for your sprite, called by the game engine every frame. Its primary job is to call the drawing and logic routines.
Sprite_MyNewEnemy_Long:
{
PHB : PHK : PLB ; Set up bank registers
JSR Sprite_MyNewEnemy_Draw
JSL Sprite_DrawShadow ; Optional: Draw a shadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Only run logic if active
JSR Sprite_MyNewEnemy_Main
.SpriteIsNotActive
PLB ; Restore bank register
RTL ; Return from long routine
}
4. Initialization (_Prep routine)
This routine runs once when the sprite is first spawned. Use it to set initial values for timers, its action state, and any other properties.
Sprite_MyNewEnemy_Prep:
{
PHB : PHK : PLB
%GotoAction(0) ; Set the initial state to the first one in the jump table
%SetTimerA(120) ; Set a general-purpose timer to 120 frames (2 seconds)
PLB
RTL
}
5. Main Logic & State Machine (_Main routine)
This is the heart of your sprite. Use the %SpriteJumpTable macro to create a state machine. The sprite's current state is stored in SprAction, X.
Sprite_MyNewEnemy_Main:
{
%SpriteJumpTable(State_Idle, State_Attacking, State_Hurt)
State_Idle:
{
%PlayAnimation(0, 1, 15) ; Animate between frames 0 and 1 every 15 game frames
; Check distance to player. If less than 80 pixels, switch to attacking state.
JSL GetDistance8bit_Long : CMP.b #$50 : BCS .player_is_far
%GotoAction(1) ; Switch to State_Attacking
.player_is_far
RTS
}
State_Attacking:
{
%PlayAnimation(2, 3, 8)
%MoveTowardPlayer(12) ; Move toward the player with speed 12
%DoDamageToPlayerSameLayerOnContact()
; Check if the player has hit the sprite
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
%GotoAction(2) ; Switch to State_Hurt
.no_damage
RTS
}
State_Hurt:
{
; Sprite was hit, flash and get knocked back
JSL Sprite_DamageFlash_Long
RTS
}
}
6. Drawing (_Draw routine)
This routine renders your sprite's graphics. The easiest method is to use the %DrawSprite() macro, which reads from a set of data tables you define.
Sprite_MyNewEnemy_Draw:
{
%DrawSprite()
; --- OAM Data Tables ---
.start_index ; Starting index in the tables for each animation frame
db $00, $02, $04, $06
.nbr_of_tiles ; Number of tiles to draw for each frame (minus 1)
db 1, 1, 1, 1
.x_offsets ; X-position offset for each tile
dw -8, 8, -8, 8, -8, 8, -8, 8
.y_offsets ; Y-position offset for each tile
dw -8, -8, -8, -8, -8, -8, -8, -8
.chr ; The character (tile) number from the graphics sheet
db $C0, $C2, $C4, $C6, $C8, $CA, $CC, $CE
.properties ; OAM properties (palette, priority, flips)
db $3B, $7B, $3B, $7B, $3B, $7B, $3B, $7B
.sizes ; Size of each tile (e.g., $02 for 16x16)
db $02, $02, $02, $02, $02, $02, $02, $02
}
7. Final Integration
The %Set_Sprite_Properties() macro you added in Step 2 handles the final integration. It automatically adds your sprite's _Prep and _Long routines to the game's sprite tables in Core/sprite_new_table.asm. Your sprite is now ready to be placed in the game world!