-
Notifications
You must be signed in to change notification settings - Fork 831
Use GS SGB palettes for maps
The Super Game Boy (SGB) was an adapter that let you play Game Boy games on a SNES, taking advantage of the console's hardware to add more colors than a Game Boy (DMG), but fewer than a Game Boy Color (CGB). Pokémon Gold and Silver supported all three systems, but Crystal was GBC-only. However, it has leftover unused code from G/S for SGB colors. This tutorial will apply the SGB colors to overworld maps, giving them a "retro" look.
A palette consists of four shades: white, light, dark, and black.
The DMG (from the Game Boy's internal codename "Dot Matrix Game") was monochrome; its games had no concept of color, and the display hardware itself was a greenish monochrome. However, it supported three shade assignments at once: BGP for background tiles, and OBP0 and OBP1 for objects (aka sprites). G/S were designed to look good even with these limitations.
The SGB supported four different palettes at once, and was backwards-compatible with DMG shade assignments. G/S, like Yellow, took advantage of this to give each city its own colors, as well as unique colors for different interfaces like the Pokédex, slot machines, and so on.
The CGB supported sixteen palettes: eight usable by background tiles, and eight by objects.
It also doubled the amount of video RAM, which Crystal needed for its extra graphics (like improved maps and in-battle sprite animations). That's why Crystal couldn't have supported the DMG or SGB:
Despite only supporting the CGB, Crystal still has leftover SGB palette data from G/S. The gfx/sgb/predef.pal table defines most of the individual palettes, which were selectively loaded with the GetPredefPal
routine in engine/gfx/color.asm and applied to regions of the screen by LoadSGBLayout
in engine/gfx/sgb_layouts.asm. (Since the SGB used an SNES and TV screen to display the game, that data was sent in "packets" from the Game Boy to the SNES for processing.)
With that background knowledge, we can apply some SGB palettes to Crystal. It won't literally be playable on a Super Game Boy, but it will look like one. (At least the overworld maps will.)
Edit engine/gfx/cgb_layouts.asm:
_CGB_MapPals:
- call LoadMapPals
+; Get SGB palette
+ call LoadSGBLayout.GetMapPalsIndex
+ call GetPredefPal
+ ld de, wBGPals1
+; Copy 7 BG palettes
+ ld b, 7
+.bg_loop
+ call .LoadHLBGPaletteIntoDE
+ dec b
+ jr nz, .bg_loop
+; Copy PAL_BG_TEXT and 6 OB palettes
+ ld b, 7
+.ob_loop
+ call .LoadHLOBPaletteIntoDE
+ dec b
+ jr nz, .ob_loop
+; Copy PAL_OW_TREE and PAL_OW_ROCK
+ call .LoadHLBGPaletteIntoDE
+ call .LoadHLBGPaletteIntoDE
ld a, SCGB_MAPPALS
ld [wSGBPredef], a
ret
+
+.LoadHLBGPaletteIntoDE:
+; morn/day: shades 0, 1, 2, 3 -> 0, 1, 2, 3
+; nite: shades 0, 1, 2, 3 -> 1, 2, 2, 3
+ push hl
+ ld a, [wTimeOfDayPal]
+ cp NITE_F
+ jr c, .bg_morn_day
+ inc hl
+ inc hl
+ call .LoadHLColorIntoDE
+ call .LoadHLColorIntoDE
+ dec hl
+ dec hl
+ call .LoadHLColorIntoDE
+ call .LoadHLColorIntoDE
+.bg_done
+ pop hl
+ ret
+
+.bg_morn_day
+ call LoadHLPaletteIntoDE
+ jr .bg_done
+
+.LoadHLOBPaletteIntoDE:
+; shades 0, 1, 2, 3 -> 0, 0, 1, 3
+ push hl
+ call .LoadHLColorIntoDE
+ dec hl
+ dec hl
+ call .LoadHLColorIntoDE
+ call .LoadHLColorIntoDE
+ inc hl
+ inc hl
+ call .LoadHLColorIntoDE
+ pop hl
+ ret
+
+.LoadHLColorIntoDE:
+ ldh a, [rSVBK]
+ push af
+ ld a, BANK(wBGPals1)
+ ldh [rSVBK], a
+rept PAL_COLOR_SIZE
+ ld a, [hli]
+ ld [de], a
+ inc de
+endr
+ pop af
+ ldh [rSVBK], a
+ ret
The LoadMapPals
routine in engine/gfx/color.asm was responsible for loading the palettes from gfx/tilesets/bg_tiles.pal and gfx/overworld/npc_sprites.pal based on the environment and time of day. (Crystal also made it call LoadSpecialMapPalette
in engine/tilesets/tileset_palettes.asm to load new unique palettes like gfx/tilesets/ice_path.pal.)
Anyway, we've replaced all of that with a call to LoadSGBLayout.GetMapPalsIndex
in engine/gfx/sgb_layouts.asm, which gets a single palette from gfx/sgb/predef.pal based on the environment and time of day. The rest of the code is applying the colors for that one palette to all sixteen CGB palettes, giving different shade assignments to background tiles and to objects.
Edit engine/tilesets/tileset_anims.asm:
AnimateFlowerTile:
; No parameters.
; Save sp in bc (see WriteTile).
ld hl, sp+0
ld b, h
ld c, l
; Alternate tile graphic every other frame
ld a, [wTileAnimationTimer]
and %10
-
-; CGB has different color mappings for flowers.
- ld e, a
- ldh a, [hCGB]
- and 1
- add e
-
swap a
ld e, a
ld d, 0
ld hl, FlowerTileFrames
add hl, de
ld sp, hl
ld hl, vTiles2 tile $03
jp WriteTile
FlowerTileFrames:
INCBIN "gfx/tilesets/flower/dmg_1.2bpp"
INCBIN "gfx/tilesets/flower/cgb_1.2bpp"
INCBIN "gfx/tilesets/flower/dmg_2.2bpp"
INCBIN "gfx/tilesets/flower/cgb_2.2bpp"
As you can see in the screenshots in step 1, the waving flowers have different shading.
AnimateWaterPalette:
; Transition between color values 0-2 for color 0 in palette 3.
; No palette changes on DMG.
- ldh a, [hCGB]
- and a
- ret z
-
-; We don't want to mess with non-standard palettes.
- ldh a, [rBGP] ; BGP
- cp %11100100
- ret nz
-
-; Only update on even frames.
- ld a, [wTileAnimationTimer]
- ld l, a
- and 1 ; odd
- ret nz
-
-; Ready for BGPD input...
-
- ld a, (1 << rBGPI_AUTO_INCREMENT) palette PAL_BG_WATER
- ldh [rBGPI], a
-
- ldh a, [rSVBK]
- push af
- ld a, BANK(wBGPals1)
- ldh [rSVBK], a
-
-; Update color 0 in order 0 1 2 1
- ld a, l
- and %110 ; frames 0 2 4 6
- jr z, .color0
- cp %100 ; frame 4
- jr z, .color2
-
-.color1
- ld hl, wBGPals1 palette PAL_BG_WATER color 1
- ld a, [hli]
- ldh [rBGPD], a
- ld a, [hli]
- ldh [rBGPD], a
- jr .end
-
-.color0
- ld hl, wBGPals1 palette PAL_BG_WATER color 0
- ld a, [hli]
- ldh [rBGPD], a
- ld a, [hli]
- ldh [rBGPD], a
- jr .end
-
-.color2
- ld hl, wBGPals1 palette PAL_BG_WATER color 2
- ld a, [hli]
- ldh [rBGPD], a
- ld a, [hli]
- ldh [rBGPD], a
-
-.end
- pop af
- ldh [rSVBK], a
ret
CGB animated the "white" shade of PAL_BG_WATER
, cycling it between the white, light, and dark shades. This was arguably a design flaw: it prevents that palette from being used for general-purpose blue things, and when it cycles to the dark shade, water ends up looking like solid dark blue. Here we've fixed the problem.
FlickeringCaveEntrancePalette:
; No palette changes on DMG.
- ldh a, [hCGB]
- and a
- ret z
-; We don't want to mess with non-standard palettes.
- ldh a, [rBGP]
- cp %11100100
- ret nz
-; We only want to be here if we're in a dark cave.
- ld a, [wTimeOfDayPalset]
- cp %11111111 ; 3,3,3,3
- ret nz
-
- ldh a, [rSVBK]
- push af
- ld a, BANK(wBGPals1)
- ldh [rSVBK], a
-; Ready for BGPD input...
- ld a, (1 << rBGPI_AUTO_INCREMENT) palette PAL_BG_YELLOW
- ldh [rBGPI], a
- ldh a, [hVBlankCounter]
- and %10
- jr nz, .bit1set
- ld hl, wBGPals1 palette PAL_BG_YELLOW
- jr .okay
-
-.bit1set
- ld hl, wBGPals1 palette PAL_BG_YELLOW color 1
-
-.okay
- ld a, [hli]
- ldh [rBGPD], a
- ld a, [hli]
- ldh [rBGPD], a
-
- pop af
- ldh [rSVBK], a
ret
CGB also animated PAL_BG_YELLOW
inside of dark caves, giving their entrances a flickering look. This would not have been possible on the SGB, since every tile used the same BGP palette. For a more accurate retro look, we've disabled it.
That's all it takes to make the overworld maps look like an SGB game:
Adapting more SGB palettes—the Pokédex, slot machines, Pokégear, trainer card, and so on—is left as an exercise for the reader. ;)