Files
oracle-of-secrets/Docs/SpriteCreationGuide.md
scawful eeeb9ffeb4 Add detailed documentation for memory mapping, quest flow, and sprite creation
- Created MemoryMap.md to outline WRAM and SRAM structures, including key variables and custom regions.
- Added QuestFlow.md to document main quest progression and major side-quests, detailing triggers, events, rewards, and flags.
- Introduced SpriteCreationGuide.md to provide a comprehensive guide for creating custom sprites, including file setup, properties, main structure, initialization, logic, and drawing routines.
- Updated oracle.org to include new documentation links and improve project organization with infrastructure suggestions.
2025-10-01 22:40:58 -04:00

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

  1. Create the Sprite File: Create a new .asm file for your sprite in the appropriate subdirectory of Sprites/ (e.g., Sprites/Enemies/MyNewEnemy.asm).

  2. Include the File: Open Sprites/all_sprites.asm and add an incsrc directive 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!