DynPaletteTransition - A palette transitioning function

Discussion in 'Utilities' started by giovanni.gen, Aug 2, 2023.

  1. giovanni.gen

    giovanni.gen It's still Joe-vanni, not Geo-vanni. Member

    Joined:
    Apr 16, 2015
    Messages:
    313
    Location:
    Italy
    Earlier, I've showcased a dynamic palette transitioning function on the forums. I've liked it, but with the current projects I've been working on, I haven't really been able to use it myself, outside for a few more minor purposes.

    Recently, however, I've been asked by someone for permission and assistance towards installing it on their hack. I took the bite, but in the middle of work, I had thought: "Hmm, I could just make this open source. That way, power users would be able to utilise it for their own projects without having to go through me first."

    Therefore, I made my routine open source.

    DynPaletteTransition, as suggested by its name, is a palette transitioning function that is configured through events and ran during game run time once per frame. Based on a predetermined starting palette (palette A), and a user determined ending palette (palette B), a transition from palette A to palette B is generated, without the need of intermediate palettes.

    DynPaletteTransition can be configured to work on above water, below water, and cycling palettes, even at the same time, and at variable speeds!

    DynPaletteTransition is available in a mostly clean installation of Project Sonic 1 Two-Eight. This disassembly was chosen for convenience. Feel free to use either the entire disassembly to start on a new project, or install it on your disassembly of choice!

    View here.

    Step 1: Get DynPaletteTransition from my repository

    _inc/DynPaletteTransition.asm, in my repository, linked above.

    Step 2: Add new variable equates

    Here's all of the new constants:

    v_palflags: Palette changer flags. (1 byte. Bit 0 = above water. Bit 1 = below water. Bit 2 = palette cycle. Other bits MUST stay 0.)
    v_awcount: Counter for number of above water palette entries to be changed. (1 byte)
    v_bwcount: Counter for number of below water palette entries to be changed. (1 byte)
    v_paltime: Frequency of palette changes. (1 byte)
    v_paltimecur: Actual timer used for palette transitioning. (1 byte)
    v_pcyccount: Counter for number of palette cycle entries to be changed. (1 byte)
    p_awtarget: pointer to the above water target palette. (4 bytes)
    p_bwtarget: pointer to the below water target palette. (4 bytes)
    p_awreplace: RAM pointer to the first above water palette entry to be replaced. (4 bytes)
    p_bwreplace: RAM pointer to the first below water palette entry to be replaced. (4 bytes)
    p_pcyctarget: pointer to the palette cycle target palette. (4 bytes)
    p_pcycreplace: RAM pointer to the palette cycle buffer data. Ideally, should always be set to v_palcycleram, unless you are doing very specific things. (4 bytes)
    v_palcycleram: Buffer for the palette cycling data (as much RAM as you want)
    v_paltracker: palette tracker for your level. Feel free to change the RAM address. It is recommended to use something that is reset upon level beginning. (1 byte)
    v_lamp_paltracker: copy of the above variable that is used in checkpoints. (1 byte, not to be reset by levels)

    It is up to you to set up equates. You can reference the Variables.asm file in my repository.

    Step 3: Change up level headers
    For proper checkpoint support, I make the level headers store more alternate palettes. Two for the above water levels, and two more for the underwater levels. To do this, I made use of the unused entries. Pretty much, all you'll need to do is replace your existing level headers with the modified ones found in my repository.

    Of course, just doing that won't allow the game to interpret them correctly. In my repository, I've also made edits to LevelDataLoad, which you'll need to replicate. They're in sonic.asm, and they're hard to miss if you search for the LevelDataLoad routine.

    You'll also need to ditch Level_ChkWaterPal completely, and for checkpoint support to work properly even underwater, you may need to replace Level_LoadPal with a more dynamic solution, not provided with the repository, at the current date of writing. As of right now, the solution provided is very hackish, and designed exclusively for Sonic 1. Should this change, you'll be informed.

    Step 4: Change up the checkpoint object

    Obj79 adds some more lines to store v_paltracker in v_lamp_paltracker, then loading it. Replicate the changes to Obj79.

    Step 5: Make it run

    Include the newly added DynPaletteTransition.asm file in sonic.asm, then add a call to DynPaletteTransition. Normally, I call it before PaletteCycle, in Level_MainLoop.

    Setting up events

    My repository includes a sample event for LZ1 that makes the game shift from LZ's palette to SBZ3's palette. Here it is.

    Code:
    DLE_LZ1:
            moveq    #0,d0
            move.b    (v_dle_routine).w,d0
            move.w    DLE_LZ1Routines(pc,d0.w),d0
            jmp        DLE_LZ1Routines(pc,d0.w)
    ; ===========================================================================
    DLE_LZ1Routines:    dc.w DLE_LZ1Routine0-DLE_LZ1Routines
                        dc.w DLE_LZ1Routine2-DLE_LZ1Routines
    ; ===========================================================================
    
    DLE_LZ1Routine0:
            cmpi.w  #$E80,(v_screenposx).w ; has the camera reached this point on x-axis?
            bcs.s   DLE_LZ1_Return            ; if not, return
            addq.b  #2,(v_dle_routine).w    ; go to the next routine
    
            move.b  #1,(v_paltracker).w        ; change the value of the palette tracker
            move.b    #%00000111,(v_palflags).w    ; marks above, below, and cycle palette as in need of replacement.
            move.b    #48,(v_awcount).w            ; change 48 colors above water
            move.b    #64,(v_bwcount).w            ; change 64 colors under water
            move.b    #32,(v_pcyccount).w            ; change 32 colors for the cycling palette
    
            ; above water replacement
            move.l    #v_pal_dry+$20,(p_awreplace).w    ; start from second palette line
            move.l    #Pal_SBZ3,(p_awtarget).w        ; get target palette
    
            ; below water replacement
            move.l    #v_pal_water,(p_bwreplace).w    ; start from first palette line
            move.l    #Pal_SBZ3Water,(p_bwtarget).w    ; get target palette
        
            ; cycle palette replacement
            move.l    #v_palcycleram,(p_pcycreplace).w    ; get start of buffer
            move.l    #Pal_SBZ3Cyc1,(p_pcyctarget).w        ; get target palette
    
            ; set speed of transitions
            move.b    #8,(v_paltime).w
            move.b    #8,(v_paltimecur).w
    
    DLE_LZ1Routine2:
    DLE_LZ1_Return:
            rts
    The comments should explain what each line does. DynPaletteTransition acts based on these settings.

    Ignoring palette entries

    Without additional configuration, DynPaletteTransition goes in direct conflict with palette cycling. It will try changing the individual color slots, but them being changed back by palette cycling will lead to a never ending transition.

    A workaround has been set up accordingly. You can choose which palette slots to ignore on each zone.

    In DynPaletteTransition.asm, you'll find DynPalette_AboveIgnore and DynPalette_BelowIgnore. They feed a list of palette slots in RAM that are not to be touched by the main color changing routine. Example lists were set up for LZ.

    Palette cycle

    There is one step I've neglected to inform you about so far. To work with the palette cycle data, you must move it in a buffer in RAM. I've already set up an example of how to do it in _inc/PalCycle_ToRam.asm. All you need to do to enable it is include it in the main disassembly, and call it before the level's main loop (I placed the call before LevelSizeLoad after Level_SkipTtlCard.).

    For any zone where the palette cycle is not touched by the transitional routines, you can keep running the palette cycling code by using ROM pointers. However, those that do need to be readapted accordingly. You can find an example of LZ being readapted in _inc/PaletteCycle.asm.

    Next, you have to set up an override. Basically, the palette cycle and DynPaletteTransition don't necessarily act in sync, leading to a desync in how cycled graphics (like waterfalls) look like.

    To make them act in sync, you have to set up an override cycle routine. The override cycle routine matches the original, but it is instead meant to set the color based on the current frame. An example override can be found in DynPaletteTransition.asm, and is designed for LZ.

    DynPaletteTransition is distributed as a semi-complete product. It does exactly what it sets out to do, but there may be some additional specifics that may not make it work as well as you wish for your own use case. Use DynPaletteTransition if you know what you're doing.

    Of course, If you're experiencing issues during usage, or you're having doubts even after fully understanding the above guides, feel free to inform me, and I'll make the appropriate amendments. In any case, you are free to reference the commit history of my repository, if it can be of use to you.
     
    Last edited: Aug 3, 2023
    Pacca, ProjectFM, AsuharaMoon and 6 others like this.