The wiki has been upgraded to MediaWiki 1.43. The wiki's theme is still being adapted to work with the new version, and may appear broken for the time being. --RanAS

Dynamic Xtreme UberASM guide

From SnesLab
Jump to: navigation, search
English Português Español 日本語

This article explains how to use Dynamic Xtreme from UberASM to configure a level's resources and change graphics and colors during gameplay. Examples cover restricting palettes, choosing VRAM space for dynamic sprites, creating animations, and changing the player's graphics.

Requirements

  • Have UberASMTool installed and configured to insert code into your ROM.
  • Have Dynamic Xtreme installed with the features required by your chosen example. See the Quick Start guide for installation.

See the ASM API reference for call parameters and variable usage.

Each example requires the following features:

Example Required features
Setting aside palettes for other resources PalettesChange.
Choosing VRAM space for dynamic sprites GraphicsChange and DynamicPoses. Drawing these poses with Dynamic Xtreme also requires DrawingSystem.
Setting the transfer limit per frame GraphicsChange for graphics uploads or PalettesChange for palette uploads; both if you use both types.
Creating graphics animations GraphicsChange and PalettesChange, since the example uploads graphics and a palette. Add PalettesEffects if you set !PaletteGlobalEffect to 1 to use the global effect.
Changing palettes during gameplay PalettesChange and, with !PaletteGlobalEffect = 1 as in the example, PalettesEffects. To upload without the global effect, use !PaletteGlobalEffect = 0; only PalettesChange is then required.
Changing the player's graphics PlayerFeatures and its dependencies, GraphicsChange and PalettesChange.
Applying a global palette effect, fixed, cyclic, or transitional PalettesChange and PalettesEffects.

The graphics animation in this guide copies data to a fixed VRAM destination: it does not require DynamicPoses or DrawingSystem. See the API feature table for the routines and macros enabled by each feature.

Preparing the examples

Each example stands on its own. Save the level code in the appropriate UberASMTool folder and add it to the list. If you combine examples in one file, merge their load, init, and main blocks so the labels are not duplicated.

  • load prepares data while the level loads.
  • init sets options when entering the level. The palette restriction example uses this block.
  • main runs during gameplay, once per frame.

Dynamic Xtreme defines and macros must be available in UberASMTool through the integration performed during installation. Declare any additional graphics, palettes, or effects in the ASM using the corresponding includes:

;#incdyn "DynamicXtreme/MyResources.dynamicinfo"
;#incpale "DynamicXtreme/MyEffect.paleffect"

Paths are relative to the ASM file. Include only files used by your example. You can also use Dynamic Xtreme's traditional folders, as explained in Files and formats.

After adding or modifying these resources, install Dynamic Xtreme first, then UberASMTool. If you only change the ASM logic, rerun UberASMTool.

Names beginning with RAM_LIBRE_... in these examples are placeholders: replace them with free RAM addresses before inserting the code. They are not Dynamic Xtreme defines. Allocate the specified number of bytes and use separate addresses for variables that must coexist. Check that the game, Dynamic Xtreme, Lunar Magic, and other resources do not use them; an address free in the original SMW may already be in use in your ROM. You can look for addresses in the SMW Central RAM map filtered to empty space.

If you use a short SMW RAM address outside direct page, add |!addr for the appropriate LoROM or SA-1 remapping. For example, when choosing the timer address:

!AnimationTimer = RAM_LIBRE_ANIM_TIMER|!addr

Replace RAM_LIBRE_ANIM_TIMER with your chosen address and keep |!addr. Direct page addresses use the execution context's D base and do not use this remapping. If you choose a full 24-bit address, check its bank and whether the example's instructions can access it; |!addr is intended here for short addresses outside direct page.

Setting aside palettes for other resources

If an animation, HUD, or other resource needs a fixed palette, set it aside so Dynamic Xtreme does not choose it when assigning palettes to dynamic sprites.

Write $FFFE to its DX_Dynamic_Palettes_ID entry. Each entry occupies 2 bytes. For example, palette 9 uses offset $12: 9 × 2.

See Palettes and the global effect in the API for an explanation of this table.

init:
    REP #$20
    LDA #$FFFE
    STA.l DX_Dynamic_Palettes_ID+$12    ; Set aside palette 9
    SEP #$20
RTL

To set aside several palettes, write the same value to each entry:

Palette Offset
8 $10
9 $12
A $14
B $16
C $18
D $1A
E $1C
F $1E

This prevents automatic assignment from claiming the palette; it does not upload colors. A resource configured to use it manually can still change its colors, so do not also assign it to another resource that needs a different palette.

Choosing VRAM space for dynamic sprites

Dynamic sprites need VRAM space for their poses. Choose a region not used by the level's static graphics.

The SMW VRAM map on SNESLab shows the regions corresponding to SP1, SP2, SP3, and SP4.

For example, to allow SP4:

DynamicPoseSpaceConfig takes one of the predefined configurations.

init:
    LDA #!DynamicSpaceConfigSP4
    JSL !DynamicPoseSpaceConfig
RTL

If you need more space and the level allows it, choose !DynamicSpaceConfigSP34 for SP3 and SP4, or !DynamicSpaceConfigSP234 for SP2, SP3, and SP4. Other options include half pages and configurations that share space with the player or DSX.

The available defines are listed in DXDefines.asm and the API configuration table. Set the configuration before the level starts loading poses; changing it does not automatically remove poses already in VRAM.

Creating a custom configuration

Use DefineDynamicSpaceConfig to combine regions differently. This example makes the first half of SP3 and all of SP4 available, restricting everything else:

init:
    ; Restrict SP1 and SP2.
    %DefineDynamicSpaceConfig($00, $3F, $80)

    ; Make the first half of SP3 available.
    %DefineDynamicSpaceConfig($40, $4F, $00)

    ; Restrict the second half of SP3.
    %DefineDynamicSpaceConfig($50, $5F, $80)

    ; Make SP4 available.
    %DefineDynamicSpaceConfig($60, $7F, $00)
RTL

The first two parameters are the first and last 16×16 blocks, both inclusive. The third is $00 to allow dynamic poses or $80 to restrict the region. IDs run left to right and top to bottom, with 8 blocks per row. The macro documentation includes the full numbering table.

This replaces the preset configuration call; do not run both configurations in sequence. Adjust the ranges to cover blocks $00–$7F without overlaps, and restrict regions needed by other level graphics. Like the previous example, this requires GraphicsChange and DynamicPoses.

Setting the transfer limit per frame

DX_Dynamic_MaxDataPerFrame sets how many bytes normal Dynamic Xtreme uploads may schedule in a frame. Dynamic poses, animations, and other transfers using its macros share this limit.

See these variables in Counters and data per frame in the API.

The default is $0800, or 2048 bytes. This example increases it to $0A00, or 2560 bytes:

init:
    REP #$20
    LDA #$0A00
    STA.l DX_Dynamic_MaxDataPerFrame
    SEP #$20
RTL

Increasing it allows more uploads to be requested in one frame, but does not give the SNES more time to perform them. Too much data can cause visual problems. Adjust the value for your level and test with its sprites and animations running together.

Normal macros return carry clear if an upload does not fit within the limit. Keep the current graphics or colors and retry later. Forced variants skip the check and still add their bytes to the total.

Creating graphics animations

This example creates a graphics animation: a table gives each image's starting address, a timer controls when to change images, and the previous image stays in place if the transfer is rejected.

It uses TransferToVRAM for graphics changes, its Forced variant for the initial upload, and TransferPaletteResource for colors. Follow those links for parameters, registers, and results.

Declare the example's files in DynamicXtreme/SMMArrow.dynamicinfo:

Resources:
    SMMArrow.bin
Palettes:
    SMMArrowPal.bin
;#incdyn "DynamicXtreme/SMMArrow.dynamicinfo"

; Choose free RAM here: one separate byte per variable.
!CurrentPoseIndex = RAM_LIBRE_ANIM_POSE
!AnimationTimer = RAM_LIBRE_ANIM_TIMER
!PaletteLoaded = RAM_LIBRE_ANIM_PALETA

; Frames to wait before attempting the next change.
!TimePerPose = $03

; Bytes per image: one 8x8 4bpp tile occupies $0020.
!DataSend = $0200

; Number of images used.
!NumberOfPoses = $05

; Source: installed resource. You can also use !AN2Buffer2.
!Source = !ResourceSMMArrow

; VRAM destination address.
!Destination = !BG2Line0

; Installed palette and first destination color.
!Palette = "SMMArrowPal"
!PaletteDestination = $71

; 0 = no global effect; 1 = apply the global effect.
!PaletteGlobalEffect = 0

init:
    STZ !CurrentPoseIndex
    LDA #!TimePerPose
    STA !AnimationTimer
    STZ !PaletteLoaded

    %ForcedTransferToVRAM(#!Destination, #!Source, #((!Source>>16)&$FF), #!DataSend)
    %TransferPaletteResource(!Palette, #!PaletteDestination, !PaletteGlobalEffect)
    BCC .Return
    LDA #$01
    STA !PaletteLoaded
.Return
RTL

main:
    ; Retry the palette if init's upload was rejected.
    LDA !PaletteLoaded
    BNE .Animate
    %TransferPaletteResource(!Palette, #!PaletteDestination, !PaletteGlobalEffect)
    BCS .PaletteReady
    RTL
.PaletteReady
    LDA #$01
    STA !PaletteLoaded

.Animate
    LDA !AnimationTimer
    BEQ .NextPose
    DEC !AnimationTimer
RTL

.NextPose
    LDA !CurrentPoseIndex
    INC A
    CMP #!NumberOfPoses
    BCC .SelectPose
    LDA #$00
.SelectPose
    STA $05
    ASL
    TAX

    REP #$20
    LDA.l PosesTable,x
    CLC
    ADC.w #!Source
    STA $00
    SEP #$20

    %TransferToVRAM(#!Destination, $00, #((!Source>>16)&$FF), #!DataSend)
    BCC .Return

    LDA $05
    STA !CurrentPoseIndex
    LDA #!TimePerPose
    STA !AnimationTimer
.Return
RTL

PosesTable:
    dw $0000
    dw !DataSend*$01
    dw !DataSend*$02
    dw !DataSend*$03
    dw !DataSend*$04

What you can customize

  • !TimePerPose: wait time. With $03, a change is attempted every four frames: three to count down the timer and one to make the change.
  • !DataSend: bytes per image. $0200 is 16 8×8 tiles in 4bpp.
  • !NumberOfPoses and PosesTable: image count and offsets within the source. The example uses the first five images in SMMArrow.bin. Extend the table if you add more.
  • !Source: graphics address. The example reads !ResourceSMMArrow directly; use !AN2Buffer2 to animate graphics already prepared by Lunar Magic.
  • !Destination: VRAM region to update. Place tiles in the level that use this region, and check that another resource does not overwrite it.
  • !Palette, !PaletteDestination, and !PaletteGlobalEffect: palette resource, first destination color, and participation in the global effect.

Replace the three RAM placeholders at the start with addresses available throughout the level: each variable needs its own byte. These are not temporary scratch bytes. !PaletteLoaded lets the code retry a rejected palette upload without repeating it once accepted.

!ResourceSMMArrow, !PaletteSMMArrowPal, and !PaletteSMMArrowPalSize are generated when installing the DynamicInfo and written to DXDefines.asm. Their names are normalized to PascalCase by removing separators; here, the capitalization in SMMArrow and SMMArrowPal is preserved.

All images used must be in the same source bank. The initial graphics upload uses Forced; later changes respect the frame's data limit. If an upload is rejected, the code keeps the previous image and retries next frame without advancing the index or resetting the timer.

The timer advances each time main runs. To stop the animation while paused or while the action is frozen, check $13D4|!addr and $9D before counting it down.

Changing palettes during gameplay

TransferPalette macros upload colors while keeping SMW's and Dynamic Xtreme's palette copies up to date. They also prepare palette effects, so you do not need to copy buffers or clear flags manually.

This example uses TransferPaletteResource; see its API entry for the parameters and carry results.

The example uses SMMArrowPal.bin, a 15-color file ($001E bytes), and changes colors 1–F of palette 7. Color 0 is left unchanged.

In DynamicXtreme/SMMArrow.dynamicinfo, declare the graphics as a general resource and the palette under Palettes:

Resources:
    SMMArrow.bin
Palettes:
    SMMArrowPal.bin

In the level ASM:

;#incdyn "DynamicXtreme/SMMArrow.dynamicinfo"

; Choose one byte of free RAM here.
!PaletteChangePending = RAM_LIBRE_CAMBIO_PALETA

!Palette = "SMMArrowPal"
!PaletteDestination = $71
!PaletteGlobalEffect = 1

init:
    LDA #$01
    STA !PaletteChangePending
    ; Continue to the first upload attempt.

main:
    LDA !PaletteChangePending
    BNE .TryTransfer
    RTL

.TryTransfer
    %TransferPaletteResource(!Palette, #!PaletteDestination, !PaletteGlobalEffect)
    BCC .Return

    ; Confirm the change only if the call was accepted.
    STZ !PaletteChangePending
.Return
RTL

Replace RAM_LIBRE_CAMBIO_PALETA with one byte of free RAM available throughout the level. !PaletteChangePending is set to 1 when a change is requested and returns to 0 only when the macro returns carry set. If the call fails, the change remains pending and is retried next frame. Once accepted, the upload is not repeated.

!Palette selects the resource, and !PaletteDestination selects the first destination color. !PaletteGlobalEffect, the macro's last parameter, controls the upload:

  • 1: prepares the original colors so they receive the global effect before being displayed. Also prepares the palette if no effect has been selected yet.
  • 0: uploads the colors without the global effect and excludes that palette from it. Also updates SMW's buffers, which prevents palette corruption during the goal victory animation.

To upload the same palette without the global effect, change only this define and keep the carry check:

!PaletteGlobalEffect = 0

If effects are disabled in the installation, both options upload directly. When effects are enabled and you use 1, the system uploads the processed result; it does not display an unaffected palette for one frame first.

SMMArrowPal uses !PaletteSMMArrowPal and !PaletteSMMArrowPalSize, generated from the DynamicInfo's Palettes section and written to DXDefines.asm. Names are normalized to PascalCase by removing separators and capitalizing each word. In this case, SMMArrowPal stays as written.

To request another change during gameplay, set !PaletteChangePending to 1 when your chosen condition is met. Do not set it every frame if the palette is unchanged. For a direct upload, carry set means the transfer was scheduled; with the global effect, it means the original colors were prepared, not that the result is already visible.

The effect option applies to the entire 16-color palette. When uploading colors for a global effect, prepare all visible colors, as with the 15 in this example; do not change just a few colors expecting to preserve a base you have not prepared yet.

If you use a fixed sprite palette from 8–F, set it aside first as described at the start of this article. These macros do not perform automatic assignment. To upload from an address, pointer, or table, see Uploading colors with TransferPalette in the API.

Changing the player's graphics

This example switches between the original player and Geno when you press up. Its defines let you choose the graphics and four palettes: Mario, Luigi, Fire Mario, and Fire Luigi. It also updates the selected palette when the player's state changes.

Requires PlayerFeatures. Custom graphics must use the same pose layout as the originals; changing the source does not adapt a spritesheet with a different layout.

The code uses the player variables and macros DivW, TransferPaletteFromPointer, and TransferPaletteFromAddress. Their API entries explain each call's parameters, registers, and results.

Declare the following in DynamicXtreme/Geno.dynamicinfo:

Resources:
    Geno.bin
Palettes:
    GenoPal.bin
    GenoPalPlayer2.bin
    FireGenoPal.bin
    FireGenoPalPlayer2.bin
;#incdyn "DynamicXtreme/Geno.dynamicinfo"

; Choose two consecutive bytes of free RAM here.
!Last0D82 = RAM_LIBRE_JUGADOR_2_BYTES

; Custom player graphics.
!PlayerGFXResource = !ResourceGeno

; Palettes: player 1, player 2, and their fire variants.
!PlayerGFXPalette = !PaletteGenoPal
!PlayerGFXPalettePlayer2 = !PaletteGenoPalPlayer2
!PlayerGFXFirePalette = !PaletteFireGenoPal
!PlayerGFXFirePalettePlayer2 = !PaletteFireGenoPalPlayer2

; Switch button: $08 = up.
!ChangeButton = $08

init:
    REP #$20
    LDA $0D82|!addr
    STA !Last0D82
    SEP #$20

main:
    LDA !ButtonDown_BYETUDLR
    AND #!ChangeButton
    BNE .Change

    REP #$20
    LDA $0D82|!addr
    CMP !Last0D82
    BNE .Reload
    SEP #$20
RTL

.Change
    LDA.l DX_Dynamic_Player_GFX_BNK
    CMP.b #!GFX32Buffer>>16
    BEQ .ChooseGeno
    JMP .Mario
.ChooseGeno
    JMP .Geno

.Reload
    SEP #$20
    LDA.l DX_Dynamic_Player_GFX_BNK
    CMP.b #!GFX32Buffer>>16
    BNE .Geno
    JMP .Mario

.Geno
    ; Get 0, 1, 2, or 3 based on the player and fire state.
    REP #$20
    LDA $0D82|!addr
    SEC
    SBC #!PlayerBasePalette
    STA $00
    SEP #$20
    %DivW($01, $00, #$14)

    ; Each address in GenoPals occupies three bytes.
    LDA !DivisionResult
    ASL
    CLC
    ADC !DivisionResult
    TAX

    ; Keep the selected source and prepare colors 2-5.
    REP #$20
    LDA.l GenoPals,x
    STA $03
    CLC
    ADC #$0002
    STA $00
    SEP #$20
    LDA.l GenoPals+2,x
    STA $02
    STA $05

    %TransferPaletteFromPointer($00, #$82, $0008, 0)
    BCS .ApplyGeno
    RTL

.ApplyGeno
    REP #$20
    LDA.w #!PlayerGFXResource
    STA.l DX_Dynamic_Player_GFX_Addr
    LDA.w #(!PlayerGFXResource>>16)&$00FF
    STA.l DX_Dynamic_Player_GFX_BNK

    ; The player routine uploads colors 6-F.
    LDA $03
    CLC
    ADC #$000A
    STA.l DX_Dynamic_Player_Palette_Addr
    SEP #$20
    LDA $05
    STA.l DX_Dynamic_Player_Palette_BNK
    JMP .ConfirmSelection

.Mario
    %TransferPaletteFromAddress(!PlayerPaletteColor2To5, #$82, $0008, 0)
    BCS .ApplyMario
    RTL

.ApplyMario
    REP #$20
    LDA.w #!GFX32Buffer
    STA.l DX_Dynamic_Player_GFX_Addr
    LDA $0D82|!addr
    STA.l DX_Dynamic_Player_Palette_Addr
    LDA.w #(!GFX32Buffer>>16)&$00FF
    STA.l DX_Dynamic_Player_GFX_BNK
    LDA #$0000
    STA.l DX_Dynamic_Player_Palette_BNK
    SEP #$20

.ConfirmSelection
    REP #$20
    LDA $0D82|!addr
    STA !Last0D82
    SEP #$20
RTL

GenoPals:
    dl !PlayerGFXPalette
    dl !PlayerGFXPalettePlayer2
    dl !PlayerGFXFirePalette
    dl !PlayerGFXFirePalettePlayer2

What you can customize

  • !PlayerGFXResource: custom character graphics.
  • The four !PlayerGFX...Palette... defines: palette addresses, in the order used by GenoPals.
  • !ChangeButton: button used to switch characters. Uses the controller bits BYETUDLR; $08 is up.
  • !Last0D82: replace RAM_LIBRE_JUGADOR_2_BYTES with two consecutive free RAM bytes. This stores the game's palette selection; neither byte may be used by other code.

!ResourceGeno, !PaletteGenoPal, !PaletteGenoPalPlayer2, !PaletteFireGenoPal, and !PaletteFireGenoPalPlayer2 are generated from the DynamicInfo and written to DXDefines.asm. Names use PascalCase, with spaces, hyphens, and underscores removed and existing capitals preserved.

Each palette file contains colors 1–F. Its first color is skipped to copy colors 2–5; the final ten start at +$0A and are used by the player routine.

Colors 2–5 are uploaded with TransferPalette, which also updates SMW's buffers. The example does not confirm a graphics change if that transfer is rejected. A pending player-state change is retried while it differs from !Last0D82; if a button-requested change is rejected, press the button again.

The code identifies the original player by the bank of !GFX32Buffer. If you adapt it to switch between several custom characters in ROM, you will need another way to track the selected character.

This example uploads the player's palette without the global effect. Changing a four-color upload's parameter to 1 is not enough to add an effect: you must prepare all visible colors of palette 8 and account for its special upload handling.

Applying a global palette effect

These examples apply a fixed effect, cycle through effects, or run a transition that stops when finished. You can select each palette separately and include the fixed color. The cycle and transition examples also let you set the speed with !Delay.

All three configure the global effect variables and the fixed color and first palette color variables. Their API entries explain each variable.

In *.paleffect files, EffectType selects the effect type. Ratios range from 0 to 127, and Channel values range from 0 to 31.

Choosing the affected palettes

In each example, set !P0 through !PF to 1 to include the corresponding palette or 0 to exclude it. !EnableFixedColor controls the fixed color, and !EnableColor00 includes color 0,0.

The masks !EnableBG, !EnableSP, and !EnableColors are calculated from these settings. You do not need to edit them manually. In the current API, the lowest bit corresponds to the first palette in each group:

Bit value Background palette Sprite palette
$01 0 8
$02 1 9
$04 2 A
$08 3 B
$10 4 C
$20 5 D
$40 6 E
$80 7 F

The fixed color and color 0,0 use DX_PPU_ColorEffect_Enable: bits $01 and $02, respectively.

For sprites with dynamic assignment, also configure the E bit in PaletteOption. When using TransferPalette, its last parameter controls whether those palettes participate in the global effect. Avoid having other code overwrite the masks without accounting for this configuration.

Applying a fixed effect

This example applies the effect selected by !PaletteEffetID. The configuration shown enables all palettes and the fixed color, and selects the last effect in DecreaseSaturation.

Save the following in DynamicXtreme/DecreaseSaturation.paleffect:

{
  "Effects": [
    {"Channel2": 0, "Ratio2": 4, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 8, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 12, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 16, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 20, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 25, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 29, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 33, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 37, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 41, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 45, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 49, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 53, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 57, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 61, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 66, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 70, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 74, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 78, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 82, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 86, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 90, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 94, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 98, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 102, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 107, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 111, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 115, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 119, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 123, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 127, "EffectType": 11},
    {"Channel2": 0, "Ratio2": 127, "EffectType": 11}
  ]
}

The file defines 32 effects. EffectType = 11 selects MixS; Channel2 = 0 reduces saturation, and Ratio2 controls how much. The last effect uses 127, applying the full change.

The ASM code is:

;#incpale "DynamicXtreme/DecreaseSaturation.paleffect"

; 1 = include the palette in the effect; 0 = exclude it.
!P0 = 1
!P1 = 1
!P2 = 1
!P3 = 1
!P4 = 1
!P5 = 1
!P6 = 1
!P7 = 1

!P8 = 1
!P9 = 1
!PA = 1
!PB = 1
!PC = 1
!PD = 1
!PE = 1
!PF = 1

!EnableFixedColor = 1
!EnableColor00 = 0

; Effect applied when entering the level.
!PaletteEffetID = !PaletteEffectIDDecreaseSaturation31

; Calculated masks: no manual changes needed.
!EnableBG = !P0|(!P1<<1)|(!P2<<2)|(!P3<<3)|(!P4<<4)|(!P5<<5)|(!P6<<6)|(!P7<<7)
!EnableSP = !P8|(!P9<<1)|(!PA<<2)|(!PB<<3)|(!PC<<4)|(!PD<<5)|(!PE<<6)|(!PF<<7)
!EnableColors = !EnableFixedColor|(!EnableColor00<<1)

init:
    LDA #!EnableBG
    STA.l DX_Dynamic_Palettes_GlobalBGEnable
    LDA #!EnableSP
    STA.l DX_Dynamic_Palettes_GlobalSPEnable
    LDA #!EnableColors
    STA.l DX_PPU_ColorEffect_Enable

    REP #$20
    LDA #!PaletteEffetID
    STA.l DX_Dynamic_Palettes_GlobalEffectID
    SEP #$20
RTL

What you can customize

  • !P0 through !PF: affected palettes.
  • !EnableFixedColor and !EnableColor00: whether these colors are affected.
  • !PaletteEffetID: selected effect. You can use any ID generated from your effects file, not just this example's final effect.

Defines !PaletteEffectIDDecreaseSaturation0 through !PaletteEffectIDDecreaseSaturation31 are generated from the *.paleffect and written to DXDefines.asm. Names use PascalCase, with spaces, hyphens, and underscores removed and existing capitals preserved.

The effect is selected in init; you do not need to repeat this every frame. To remove it, write 0 to the global effect ID and leave the palettes enabled so the system restores their original colors:

    REP #$20
    LDA #$0000
    STA.l DX_Dynamic_Palettes_GlobalEffectID
    SEP #$20

Cycling through effects

This example runs through a sequence of effects and returns to the first. You can configure the speed, first and last IDs, and affected palettes. The settings shown include only palette 0 and the fixed color.

Save the following in DynamicXtreme/Hue.paleffect:

{
  "Effects": [
    {"Channel1": 1, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 2, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 3, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 4, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 5, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 6, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 7, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 8, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 9, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 10, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 11, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 12, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 13, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 14, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 15, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 16, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 17, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 18, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 19, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 20, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 21, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 22, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 23, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 24, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 25, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 26, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 27, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 28, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 29, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 30, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 31, "Ratio1": 127, "EffectType": 9},
    {"Channel1": 31, "Ratio1": 127, "EffectType": 9}
  ]
}

The file defines 32 hue effects. EffectType = 9 selects MixH, and Ratio1 = 127 fully applies the selected hue.

;#incpale "DynamicXtreme/Hue.paleffect"

; 1 = include the palette in the effect; 0 = exclude it.
!P0 = 1
!P1 = 0
!P2 = 0
!P3 = 0
!P4 = 0
!P5 = 0
!P6 = 0
!P7 = 0

!P8 = 0
!P9 = 0
!PA = 0
!PB = 0
!PC = 0
!PD = 0
!PE = 0
!PF = 0

!EnableFixedColor = 1
!EnableColor00 = 0

; Allowed values: $00, $01, $03, $07, $0F, $1F, $3F, $7F, and $FF.
!Delay = $03

!StartingPaletteEffetID = !PaletteEffectIDHue0
!EndPaletteEffetID = !PaletteEffectIDHue31

; Calculated masks: no manual changes needed.
!EnableBG = !P0|(!P1<<1)|(!P2<<2)|(!P3<<3)|(!P4<<4)|(!P5<<5)|(!P6<<6)|(!P7<<7)
!EnableSP = !P8|(!P9<<1)|(!PA<<2)|(!PB<<3)|(!PC<<4)|(!PD<<5)|(!PE<<6)|(!PF<<7)
!EnableColors = !EnableFixedColor|(!EnableColor00<<1)

init:
    LDA #!EnableBG
    STA.l DX_Dynamic_Palettes_GlobalBGEnable
    LDA #!EnableSP
    STA.l DX_Dynamic_Palettes_GlobalSPEnable
    LDA #!EnableColors
    STA.l DX_PPU_ColorEffect_Enable

    REP #$20
    LDA #!StartingPaletteEffetID
    STA.l DX_Dynamic_Palettes_GlobalEffectID
    SEP #$20
RTL

main:
    LDA $14
    AND #!Delay
    BNE .Return

    REP #$20
    LDA.l DX_Dynamic_Palettes_GlobalEffectID
    CMP #!EndPaletteEffetID
    BCS .Restart
    INC A
    BRA .Store
.Restart
    LDA #!StartingPaletteEffetID
.Store
    STA.l DX_Dynamic_Palettes_GlobalEffectID
    SEP #$20
.Return
RTL

What you can customize

  • !P0 through !PF, !EnableFixedColor, and !EnableColor00: affected colors.
  • !Delay: interval between changes. $00 changes every frame, $01 every two frames, $03 every four, and $07 every eight. Use only the values listed in the comment.
  • !StartingPaletteEffetID and !EndPaletteEffetID: first and last effects in the cycle. They must form a consecutive ID range, with the start less than or equal to the end.

The cycle displays the last ID before returning to the first. It uses $14 to time changes, so the first interval depends on that counter's value when entering the level. To stop it while the action is frozen, also check $9D and $13D4|!addr at the start of main.

Defines !PaletteEffectIDHue0 through !PaletteEffectIDHue31 are generated from the *.paleffect and written to DXDefines.asm. Names are normalized to PascalCase by removing separators; here, Hue stays unchanged.

Running a transition that stops when complete

This example advances from the first effect to the last and keeps the final result. You can configure the affected palettes, fixed color, speed, and ID range.

Save the following in DynamicXtreme/Night.paleffect:

{
  "Effects": [
    {"Channel1": 0, "Channel2": 0, "Ratio1": 0, "Ratio2": 0, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 4, "Ratio2": 4, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 8, "Ratio2": 8, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 12, "Ratio2": 12, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 16, "Ratio2": 16, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 20, "Ratio2": 20, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 25, "Ratio2": 25, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 29, "Ratio2": 29, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 33, "Ratio2": 33, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 37, "Ratio2": 37, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 41, "Ratio2": 41, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 45, "Ratio2": 45, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 49, "Ratio2": 49, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 53, "Ratio2": 53, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 57, "Ratio2": 57, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 61, "Ratio2": 61, "EffectType": 2},
    {"Channel1": 0, "Channel2": 0, "Ratio1": 66, "Ratio2": 66, "EffectType": 2}
  ]
}

The transition has 17 steps. EffectType = 2 selects MixRG: it reduces red and green without changing blue. Ratios increase from 0 to 66 to darken the colors gradually without completely removing red and green.

;#incpale "DynamicXtreme/Night.paleffect"

; 1 = include the palette in the effect; 0 = exclude it.
!P0 = 1
!P1 = 1
!P2 = 1
!P3 = 1
!P4 = 1
!P5 = 1
!P6 = 1
!P7 = 1

!P8 = 1
!P9 = 1
!PA = 1
!PB = 1
!PC = 1
!PD = 1
!PE = 1
!PF = 1

!EnableFixedColor = 1
!EnableColor00 = 0

; Allowed values: $00, $01, $03, $07, $0F, $1F, $3F, $7F, and $FF.
!Delay = $03

!StartingPaletteEffetID = !PaletteEffectIDNight0
!EndPaletteEffetID = !PaletteEffectIDNight16

; Calculated masks: no manual changes needed.
!EnableBG = !P0|(!P1<<1)|(!P2<<2)|(!P3<<3)|(!P4<<4)|(!P5<<5)|(!P6<<6)|(!P7<<7)
!EnableSP = !P8|(!P9<<1)|(!PA<<2)|(!PB<<3)|(!PC<<4)|(!PD<<5)|(!PE<<6)|(!PF<<7)
!EnableColors = !EnableFixedColor|(!EnableColor00<<1)

init:
    LDA #!EnableBG
    STA.l DX_Dynamic_Palettes_GlobalBGEnable
    LDA #!EnableSP
    STA.l DX_Dynamic_Palettes_GlobalSPEnable
    LDA #!EnableColors
    STA.l DX_PPU_ColorEffect_Enable

    REP #$20
    LDA #!StartingPaletteEffetID
    STA.l DX_Dynamic_Palettes_GlobalEffectID
    SEP #$20
RTL

main:
    LDA $14
    AND #!Delay
    BNE .Return

    REP #$20
    LDA.l DX_Dynamic_Palettes_GlobalEffectID
    CMP #!EndPaletteEffetID
    BCS .Finished
    INC A
    STA.l DX_Dynamic_Palettes_GlobalEffectID
.Finished
    SEP #$20
.Return
RTL

What you can customize

  • !P0 through !PF, !EnableFixedColor, and !EnableColor00: affected colors.
  • !Delay: interval between changes, using the same allowed values as the cycle example.
  • !StartingPaletteEffetID and !EndPaletteEffetID: first and last effects. IDs must be consecutive and in ascending order.

Defines !PaletteEffectIDNight0 through !PaletteEffectIDNight16 are generated from Night.paleffect and written to DXDefines.asm. Names use PascalCase with separators removed; here, Night stays unchanged.


This example moves between mixing effects. To transition toward another complete palette, use PalTransitionRGB or PalTransitionHSL, described in Files and formats.