Tanglewood: Introducing Djakk Monsters

Introducing Djakk Monsters

There’s a reason our friend Nymn is so keen to get back home before dark. That reason is a huge, snarling, fearsome beast that emerges from its cave at night, with the blood of its last victim still dripping from its teeth, and an insatiable appetite for Nymn’s face.

djakk_0Djakk monsters have been a threat to Tanglewood for as long as its inhabitants can remember. The lore speaks of an ancient tribe of hunters who saddled them up and rode them right to the edge of the forest, hunting the Djunn kind for as far as they could smell. Long gone are the bad rulers of the lands, but their big ugly pets remained.

A Djakk fight is a long and drawn out challenge, they are expert trackers and will chase Nymn to the ends of the Earth for a taste of his innards. As the player you must be quick, be forever wary of the Djakk’s presence (however much you think you’ve lost it underneath you), and never underestimate their ability to break through obstacles or foil your traps. They’ve seen it all before.

Getting rid of a Djakk will need to be done in different ways for each encounter. In the demo I’ve been working on, a precariously placed boulder is primed ready to drop on the unsuspecting beast’s head, but that’s just a set-up scenario to show off the mechanics quickly. The monster will roar on sight, then immediately give chase, but if he loses you he will wander over to the last place Nymn was spotted to sniff out your tracks. This can be used very much to your advantage, as a way to coax a Djakk into standing on a particular spot – as long as you’re cunning in your escape.

djakk_3

I have plans for some of the Djakk encounters to last entire levels, and maybe more. They provide an interesting narrative reason for keeping the forest floor out of bounds until the creature has been defeated. Flues, Fuzzls, and all other tricks and traps inhabiting the surface would be unusable, making whole sections of the game, pickups and secrets inaccessible unless Nymn figures out a way to dispose of his unwelcome companion. More importantly, Nymn is looking for somewhere to sleep to wait out the rest of the night terrors, and with this thing around it’s unlikely there will be anywhere safe to rest.

djakk_4

The Djakk posed quite an early technical challenge for the game engine; it’s the largest sprite sheet in the game so far, which is costly both in subsprite count and bandwidth to transfer animation frames. Many of the routines featured in my Mega Drive coding tutorials for sprite management were too slow and naïve in implementation, and it’s taken me quite a while to rewrite them to cope with these huge creatures. The work has certainly paid off, I’m quite proud of our Djakk so far.

Future plans for the Djakk include the ability to traverse the terrain, perhaps being able to leap across a crevasse or run up a ramp to reach higher. There’s also the possibility of weak tree branches, both as an added threat to Nynm and an advantageous point to coax a Djakk onto, to see it fall to its death. For the moment we’ll be keeping it simple, this huge sprite sheet is tricky for our artist to maintain, so we’ll squeeze what we can out of the animations available first.

See the ugly fellow in action in this short demo video:

Matt

Tanglewood: Colour Switching

Colour is everything in Tanglewood. It is a life force that can be given, shared, stolen, or used as currency.

Every animal and plant in Tanglewood has a distinct colour, and that colour may play an advantage or disadvantage to the player. The organic world may respond in a positive manner if Nymn is a matching colour. For example, a length of twisted vines blocking your way may open up for Nymn if you find a way to switch to the same colour, or even move or entice a matching coloured creature to sit next to it.

Monsters may behave differently depending if you are a matching or opposing colour, and will either give chase or hang back on the assumption that you are a member of their pack.

In Nymn’s case, his colour is his power. Born a red fellow, he can’t do many things by default, but when transformed into a blue, green or yellow creature his world is opened up to bigger and better possibilities.

In the previous article I mentioned that the humble little Fuzzls will reward Nymn for helping them back to their nests. A Fuzzl will allow Nymn to take its colour for himself, transforming Nymn to match, and allowing him to use the special ability provided by that colour.

colour_switch

This week we’ve been implementing Nymn’s first power colour – yellow. This particular colour gives Nymn the ability to glide over long distances to previously unreachable areas.

Here’s a clip:

Matt

Tanglewood: Introducing Fuzzls

Introducing Fuzzls

Tanglewood’s world is alive with the strangest of creatures, from little to large, good to evil. The Fuzzl is a neutral, furry, confused, and easily startled member of the club.

fuzzl_64_blueFuzzls began life in a tech demo for a physics game written back in my University days, and since then they’ve been crammed into almost every game design I’ve been a part of, but until now have never made it past the concept stage and to fruition. They’ve finally found their permanent home in Tanglewood, or not, seeing as they too are lost from home, and need Nymn’s help to get back to their nests.

Fuzzls sleep at night, come back during the day!

Fuzzls sleep at night, come back during the day!

Nymn can roll these little ones (if they’re awake…) back to their nests. The Fuzzls dish out handsome rewards for your efforts, but those details will come in a later post. They can also use Flues; if you drop a terrified and unsuspecting Fuzzl into one, they’ll fling sky high up into a tree branch!

Fuzzls aren’t the smartest of creatures, but they play a large role in Tanglewood, and have quite a history. They were once domestic pets of various races, including the Djun. Some of the more advanced – but now extinct – species used Fuzzls to power contraptions, as counterweights for lifts and hoists, and even to hug for stress relief, but now they’re left alone to live out their lives sleeping, or getting startled by their own shadows.

Nobody knows what they do, what they eat, how they mate, what goes on in their little minds, if they can communicate, or how they still exist as a species without being able to perform basic functions of their own. They just… are.

I’ve captured a short video showing a Fuzzl’s basic behaviour. It’s in its early prototype stages so there are a few quirks to sort out, but the basics are all working. Enjoy!

Matt

Tanglewood: Introducing Nymn

Introducing Nymn

Nymn is Tanglewood’s protagonist, a shy creature from a family-oriented species called the Djun race, who survive in packs, live underground, and only emerge during daylight. Night time is dangerous for Nymn and his fellow Djun, since many of Tanglewood’s less desirable characters emerge after dark to chase, intimidate, hunt and eat any strays who should have retreated underground.

Unfortunately, this is exactly the situation Nymn has found himself in. Lost, alone and missing his family, Nymn is desperate to find a way to get back to his underground pack before nightfall sets in and he becomes the victim of a sadistic hunting game, which will almost certainly end in his demise.

Controlling Nymn

NymnThe small teaser video from last week showed Nymn walking through the world for a small glimpse of the environment, but the gameplay itself will be faster paced; Nymn is a quick and nimble creature who can walk on two legs, and scurry along quickly on all four. The platforming experience of Tanglewood is one that I’ve spent a lot of time perfecting – it’s important to me that Nymn’s controls and physical behaviours are accessible and familiar to fans of both legacy Mega Drive titles and the modern platforming games by which Tanglewood was inspired. Since my initial platform code demos in January I’ve added a whole suite of movement features, and spent many hours tweaking and perfecting movement behaviour and response.

There are a lot of variables at play here – walk acceleration, run acceleration, max walk velocity, max run velocity, floor drag, in-air drag, gravity, critical mass, walk-to-run transition speed, forced deceleration, jump impulse, step heights, multiple jump ground heights… you get the idea. There’s a few bits left to do, too, such as a separate acceleration value for controlling Nymn’s left/right movement whilst in air; a practice that has dwindled in favour of physical realism, but was synonymous with platforming games at the time and would be sorely missed.

nym_run

Nymn’s animations are something that we are very proud of. The character design has been through several iterations, and endured many tiny tweaks in order for it to appear as smooth as the hardware allows, and we hope it shows. There’s still a lot of work left to do to iron out some of the creases, but we’re getting there.

I’ll leave you with a small sample of Nymn’s basic movements:

Matt

 

Follow Tanglewood on Twitter: @tanglewoodgame

Introducing Tanglewood – an Original Title for the SEGA Mega Drive

Introducing Tanglewood

Tanglewood is a new and original platforming title for the SEGA Genesis/Mega Drive, which lends inspiration from games like Limbo, Abe’s Oddysee, Flashback and Another World. It’s a dark and moody tale that follows the life of a creature who has gotten lost after dark – a dangerous thing to do in Tanglewood’s world – and a struggle to get back home to safety.

The game features a familiar 2D platforming experience to Mega Drive veterans, a world full of interesting characters, creatures and enemies, puzzles and traps, a day/night time cycle, and a charming story.

IMG_20151111_180414

Tanglewood is being developed in pure 68000 assembly language, using a Cross Products development kit (1993 model) and custom tools.

I’ll be revealing characters, mechanics and lore gradually, but here’s a brisk walk though the basic environment which shows off day/dusk/night cycles, falling leaves, fireflies, and the game’s protagonist, Nymn:

Follow @tanglewoodgame on Twitter for updates!

Sega Megadrive – 10: Sound Part I – The PSG Chip

Okay, despite my promises of regular updates I’ve fallen a little behind, and you can thank a short holiday, music festivals, my Real Job™, and many many videogames for that.

Welcome to the first article in the Sound series! Being a full-time audio engine programmer, game audio is a subject very close to me, so this should get quite tasty. One of the most nostalgic and fondly remembered parts of my childhood were the soundtracks accompanying my favourite games, many of which I still find technically impressive, especially considering the machine’s audio capabilities didn’t really weigh up to those of its competing consoles, requiring the sound designers to squeeze so much out of so little.

There are three chips which can create, or are involved in the creation of sound in the Megadrive:

The PSG chip

The Programmable Sound Generator, the same Texas Instruments SN76489 chip used in the Sega Master System. It’s a simple device, comprising 4 mono channels – 3 channels generate a square wave at varying frequencies, and the 4th is a white noise generator. Each channel can have its attenuation modified, and the frequency of the wave or density of the white noise.

It’s got a good trick up its sleeve – you can set the wave generators to stick at +ve, then switch the attenuation registers high and low very quickly to make it behave like a 1-bit DAC, meaning it’s capable of playing PCM data. It won’t win any sound quality awards, and with the available memory I doubt it could stream entire soundtracks, but it can pull of some short voice overs and sound effects which can’t be synthesised.

The FM chip

The Yamaha YM2612 – a stereo, 6 channel sound chip with lots to brag about. 6 FM voices with 4 operators each, Yamaha synthesizer patch compatibility, envelopes, a distortion oscillator, two built-in timers, and a DAC for playing 8-bit PCM data.

Each ‘operator’ of a channel has an input and output (from and to other operators, or the final output), a frequency and an envelope. These operators can be routed in various formations to create algorithms suited for different instrument types (harp, strings, flute, brass, xylophone, piano, organ, guitar and various percussion). It’s quite a complex beast and requires some serious effort to get anything other than basic beeps out of it, but I’ve found plenty of documentation and examples so hopefully I can get it singing.

The Z80 chip

The 8-bit Zilog Z80, which was the Sega Master System’s main CPU, made its way to the Genesis both for backwards compatibility and as a slave CPU to the 68000. Neither of the sound chips can be programmed and left to get on with it, they need their hands holding for the whole session, having data fed to them constantly. This would be quite a pain to manage on the 68k, and it would be impossible to keep up a feed of PCM data whilst trying to process an entire game at the same time, so the Z80 can be used as a secondary CPU for managing the audio. Unfortunately, it doesn’t have any knowledge of the sound chips or how to operate them by default – we need to write our own sound driver (a sequencer, and a program to feed PCM data) in Z80 assembler, generate a binary and load it on startup. This means learning another assembly language, but I’m certainly up for the challenge.

Programming the PSG

I won’t jump straight in the deep end and write a Z80 audio driver just yet, I need to get a handle on the basics. This article covers the simplest case – operating the PSG from the 68000.

The PSG has 4 channels3 square wave generators and 1 white noise generator. I’ll concentrate on just the wave generators to begin with. Each channel is controlled by 2 registers: one for the attenuation, and one for the wave generator’s counter reset. The attenuation registers are 4 bits in size, allowing 16 possible volume values, from 0x0 (no attenuation; full volume) to 0xF (full attenuation; no volume). The counter reset registers are 10 bits in size, and store the square wave’s time until the polarity of the output is flipped in clock ticks / 16 (essentially the “wave width / 2”).

So, assuming an NTSC setup with a clock frequency of 3579545 Hz, a register value of 0xFE would generate a square wave at a  frequency of 440.4 Hz (3579545  ÷ (2 x 16 x reg value)).

To program the PSG, we write to its control port at 0x00C00011, one byte at a time. The most significant bit is the latch, telling the chip that this is the first or only byte it’s expected to receive. Bits 6-5 mark the channel ID (0 – 3) that we’re about to modify. Bit 4 indicates that we’re writing either the attenuation value or wave/noise settings. That leaves 4 bits for the data, which may or may not be enough. If there wasn’t enough space, we send a second byte with the latch bit OFF, indicating it contains the remaining data and is not a new command, with the upper 6 bits of data in bits 5-0.

So, a quick recap:

First byte:
Bit 7    : Latch. ON indicates this is the first (or only) byte being written
Bits 6-5 : Channel ID (0-3)
Bit 4    : Data type. ON if data bits contain attenuation value, OFF if they contain the square wave counter reset
Bits 3-0 : The data. Either all 4 bits of the attenuation value, or the lower 4 bits of counter reset value

Second byte:
Bit 7    : Latch. OFF indicates this is the second byte, and will only contain the remainder of data
Bit 6    : Unused
Bits 5-0 : Upper 6 bits of data

So, let’s make channel 0 produce 440.4hz. The first byte needs the latch ON (to indicate it’s the first byte), the channel ID, the data type bit OFF (to indicate we’re writing a counter reset value), and the lower 4 bits of the value 254. The second byte needs the latch OFF (it’s the second byte), and the upper 6 bits of the value 254.

They’re written to the PSG control port at address 0x00C00011:

move.b #%10001110, 0x00C00011 ; Latch ON, channel 0, counter data type, lower 4 bits of data
move.b #%00001111, 0x00C00011 ; Latch OFF, upper 6 bits of data

The channel will have been initialised fully attenuated, so we need to turn the volume up to hear anything. The attenuation is specified in 4 bits, where 0 is fully attenuated and 16 is full volume, so we can fit the command and data in a single byte. Latch needs to be ON, channel ID is 0, data type bit ON to indicate attenuation data, followed by the 4-bit value:

move.b #%10010000, 0x00C00011 ; Latch OFF, channel 0, attenuation data type, 4 bits of data

Immediately after writing, the PSG will emit a constant tone of 440.4hz, at full volume.

A poor man’s sequencer

Even with just one feature of the PSG covered, it’s enough to make a basic tune. By defining an array of counter reset values, with sustain times, we can iterate over them and play the notes. Here’s some example data – it’s one crudely written line from Scott Joplin’s The Entertainer. Each “note” is 32 bits in size, the first word is the sustain time in vsync frames, and the second word is the counter reset value:

 chan0_notes:
   dc.w 0x0010, 0x02f8, 0x0010, 0x02cd, 0x0010, 0x02a5, 0x0010, 0x01aa, 0x0008, 0x0000 ; D3 D#3 E3 C4 .
   dc.w 0x0010, 0x02a5, 0x0010, 0x01aa, 0x0008, 0x0000 ; E3 C4 .
   dc.w 0x0010, 0x02a5, 0x0010, 0x01aa, 0x0010, 0x0000 ; E3 C4 .
   dc.w 0x0010, 0x01aa, 0x0010, 0x0193, 0x0010, 0x017c, 0x0010, 0x0152, 0x0010, 0x01aa, 0x0010, 0x017c, 0x0010, 0x0152, 0x0008, 0x0000 ; C4 C#4 D4 E4 C4 D4 E4 .
   dc.w 0x0010, 0x01c4, 0x0010, 0x017c, 0x0008, 0x0000 ; B3 D4 .
   dc.w 0x0010, 0x01aa ; C4
 chan0_notes_end
 chan0_notes_len equ chan0_notes_end-chan0_notes
 chan0_notes_count equ chan0_notes_len/4

In order to play it, we loop over the notes, apply the counter reset value to channel 0, and wait for the defined amount of frames. It’s pretty simple:

 move.b #%10010000, psg_control ; Channel 0 full volume

 lea chan0_notes, a0            ; Notes to a0
 move.l #chan0_notes_count, d1  ; Number of notes to d1
 subi.l #0x1, d1                ; -1 for counter

 @NextNote:

 move.w (a0)+, d0       ; Delay to d0 and inc. pointer
 move.w (a0)+, d2       ; Counter reset to d2 and inc. pointer
 move.b d2, d3          ; Lower byte of counter reset to d3
 and.b #%00001111, d3   ; Clear top nybble (leave lower 4 bits)
 or.b #%10000000, d3    ; Latch bit (7) on, chan 0 (6-5), tone data bit (4) off
 move.b d3, psg_control ; Write to PSG port

 move.w d2, d3          ; Counter reset to d3 again
 ror.w #0x4, d3         ; Shift right 4 bits
 and.b #%00111111, d3   ; Only need bits 5-0 (upper 6 bits of the 10 bit value)
 move.b d3, psg_control ; Write to PSG port

 move.l d1, -(sp)       ; Backup d1
 jsr WaitFrames         ; Delay frames is in d0
 move.l (sp)+, d1       ; Restore d1

 dbra d1, @NextNote     ; Branch back up to play the next note

 move.b #%10011111, psg_control ; Finished - silence channel 0

It’s a good start, but it will need many improvements. At the very least, it should support all three square wave channels, and a higher frequency timer. Also, each note should come with a “note on” time, so we don’t need to create “empty” notes for silence, and instead have arbitrary start times. To make it more advanced, we could implement software ADSR envelopes to bring some life to each note with a softer attack and a fade out.

I’ll be building on my sequencer with each new sound feature I learn, and I have plans to write an authoring tool to compose music on a PC, instead of looking up the frequency of each note, converting it to a counter reset value and manually typing it into a text editor! The sequencer will eventually need translating to Z80 assembly language, too.

Well, this was quite a short article, I’ll try and pick up the pace again soon. I’ve missed out the white noise generator, but to really show it off I need to add some finely tuned support to my sequencer for it, so perhaps it deserves – along with many sequencer improvements – a post of its own.

Matt.

Source

Assemble with:

asm68k.exe /p soundtest.asm,soundtest.bin

References

Sega Megadrive – 9: Maps and scrolling planes

Okay! Things are going smoothly, I’ve been managing to write up a article once every three weeks so I’ll try to keep that pace going. Here’s the next couple of things I’ve been dabbling in: loading plane data from maps, then scrolling them vertically and horizontally.

First, some corrections: I made a few errors in the source of the last post, mainly to do with the number of bytes copied when updating a sprite animation frame (basic maths, typical me), so the source at the bottom should clear up the mess.

The VDP’s plane A and B memory space can fit in more tile descriptors than there are cells – the surplus is hidden offscreen ready for scrolling. Typically, the data describing the entire scrolling field would come from a map of sorts, which would contain the tile IDs, palette IDs and flipping info for every cell in the entire level. This data would usually be generated from some map designing program to make it easier. I found several tools to do the job, alas they were very much DOS based and I had difficulties getting them to run under Windows 7, even using DOSbox. I settled on MMM, a very basic (but good enough for my needs) Genesis map editor which is able to import tilesets from BMP2Tile, my tool of choice for converting BMP images, which is perfect.

First, I need some test data. I spent an evening hand crafting a basic tileset which could be used for a simple platform game – four rounded corners, green grassy top, with various soil tiles and even two plants. Not bad for the world’s worst artist, I think:

The idea is to forward plan the re-use of each tile. Ignoring the flower for a minute: the 4 border tiles either side can only be used at the sides of one platform island, but the two centre columns of soil/grass can be placed many times in the middle to make the island longer or shorter. The one odd soil tile (two from the bottom, two from the right) can be dotted around the island to make large areas of soil less repetitive. Well, that’s the idea, if it was executed better and perhaps with a bigger tileset, but I’m keeping it simple for the learning process. The flower can either be very tall, as presented in the tileset itself, or just the top two tiles could be used to make a short flower. The two stem tiles themselves could be used alone to make a small headless plant, too. The possibilities are… not exactly endless… but with only 20 tiles we can now make something which would pass as a platformer. Notice the very first tile has been left empty, this is to ensure the whole screen doesn’t get filled with some data when the tileset is copied to VRAM, as well as defining the “blank” tile for MMM.

It could have been optimised further; I didn’t realise MMM supported flipping until after I’d finished my test map. This would have allowed to me to do the same thing with just a 4×4 tileset, since I could get away with flipping the first column to create the last column.

Making sure to convert the image to indexed colour, save as a BMP, and then open it up in BMP2Tile, the process goes something like this:

  • Press the * key to select the whole region
  • File -> Save Palette -> In ASM
  • File -> Save Tiles -> In ASM
  • File -> Export TLE -> New

The palette and tile data can be tidied up and included as assets in code, but for now we’re just interested in the TLE file. This is the format MMM is able to import for designing maps. BMP2Tile has another option – Save Map – but I can’t figure out how it works or where it gets the map data from. I might have another shot at this later, since MMM exports its final map in binary format, and up until now I’ve been trying to keep everything in text format so I can add metrics to it. It wouldn’t be that difficult to convert it back to a block of dc.b text, it’s just a load of bytes after all.

Next, open up MMM, and select File -> New. I plan to make a horizontally scrolling platform map, so I set the metrics to 64×16, and imported the TLE file dumped out by BMP2Tile:

In the bottom-left hand corner is the tileset, and above it a preview for whichever tile is currently selected. On the right-hand side is the play field. It works pretty simply – select the tile to be placed, then the draw icon, and then click in the playfield to place it. After some messing around, here’s what I came up with:

There. Some islands of varying dimentions, and some plants of varying heights. Then File -> Export Binary to get it out of there.

Copying to VRAM

The dumped BIN file should now contain ready-prepared plane data which can be copied to VRAM in one blast; each word in the file contains the tile ID, palette ID and flipping bits for each cell in a 128×16 field, in a VDP friendly format. However, the tile IDs begin at zero, and it assumes the first palette, so if we were to upload the tileset to any location other than 0x0000 or use an alternative palette slot, we would see incorrect results. Also, I didn’t plan for my map to begin at the top of the screen, it represents a platform for the player to walk on so it needs to be moved further down. So, whilst copying the data we need to modify each word to suit our environment:

LoadMapPlaneA:
 ; a0 (l) - Map address (ROM)
 ; d0 (b) - Size in words
 ; d1 (b) - Y offset
 ; d2(w) - First tile ID
 ; d3 (b) - Palette ID

 mulu.w  #0x0040, d1            ; Multiply Y offset by line width (in words)
 swap    d1                     ; Shift to upper word
 add.l   #vdp_write_plane_a, d1 ; Add PlaneA write cmd + address
 move.l  d1, vdp_control        ; Move dest address to VDP control port

 rol.l   #0x08, d3              ; Shift palette ID to bits 14-15
 rol.l   #0x05, d3              ; Can only rol 8 bits at a time

 subq.b  #0x01, d0              ; Num words in d0, minus 1 for counter

 @Copy:
 move.w  (a0)+, d4              ; Move tile ID from map data to lower d4
 and.l   #%0011111111111111, d4 ; Mask out original palette ID
 or.l    d3, d4                 ; Replace with our own
 add.w   d2, d4                 ; Add first tile offset to d4
 move.w  d4, vdp_data           ; Move to VRAM
 dbra    d0, @Copy              ; Loop

 rts

If the map’s width doesn’t match the VDP’s setup, I’ll need to modify this to copy the data line by line and offset the address after each one, but it’ll do for now.

We have the tile data and palette in ASM files already, the metrics just need adding to it as per previous articles. The map data itself is in a BIN file, so in order to add some sizes it’ll need wrapping up:

Level1Map:

; Include data from .bin file
incbin    "assets\maps\level1_map.bin"

Level1MapEnd
Level1MapSizeB: equ (Level1MapEnd-Level1Map) ; Map size in bytes
Level1MapSizeW: equ (Level1MapSizeB/2)       ; Map size in words
Level1MapSizeL: equ (Level1MapSizeB/4)       ; Map size in longs

Level1MapDimentions: equ 0x4010              ; Dimentions (W/H)

We also need to tell the VDP the dimentions of our map. VDP register 16 controls the scrolling field size, and is set up using the following bit pattern:

00VV00HH

where:

  • 00 = 32 cells
  • 01 = 64 cells
  • 11 = 128 cells

so for a 128×64 playfield (the smallest we can go to support my 128×16 map), register 16 needs initialising to 0x01.

After setting up the size and adding the three assets to the asset map, it’s ready for testing:

; ************************************
; Load palette
; ************************************
lea Level1Palette, a0 ; Palette source address (ROM) to a0
move.l #0x0, d0       ; Palette slot ID to d0
jsr LoadPalette       ; Jump to subroutine

; ************************************
; Load map tiles
; ************************************
lea      Level1Tiles, a0       ; Tileset source address (ROM) to a0
move.l   #Level1TilesVRAM, d0  ; VRAM dest address to d0
move.l   #Level1TilesSizeT, d1 ; Number of tiles in tileset to d1
jsr      LoadTiles             ; Jump to subroutine

; ************************************
; Load map
; ************************************
lea      Level1Map, a0           ; Map data to a0
move.w   #Level1MapSizeW, d0     ; Size (words) to d0
move.l   #0x18, d1               ; Y offset to d1
move.w   #Level1TilesTileID, d2  ; First tile ID to d2
move.l   #0x0, d3                ; Palette ID to d3
jsr      LoadMapPlaneA           ; Jump to subroutine

Since I’ve now separated palettes into their own asset files, I knocked up a quick LoadPalette routine. It’s not much to look at, see the source if interested.

Anyway, here’s the result:

Somewhere along the way I’ve lost all the black pixels, most likely it’s using colour 0 in the palette (transparency), and my flower doesn’t look how I designed it. I’ll go back and correct it at some point, perhaps the palette can be manually messed with in GIMP before saving.

Scrolling horizontally

The rest of the map is in VRAM ready to scroll to the right to see it. For horizontal scrolling there are three scrolling modespixel, cell and screen. The first mode allows us to scroll each line of pixels individually, the second allows us to scroll each line of cells individually, and the latter allows us to scroll the entire plane in one go. For vertical scrolling, there are just two modes – 2-cell and screen, which either scrolls sets of two cells or the entire plane. For both scroll directions, the scrolling can be nudged in pixel steps.

For horizontal scrolling, the data to describe each line or tile scroll value is stored in VRAM, at the address space defined in VDP register 13 (currently set to 0xD000), but for vertical scrolling the VDP has an entirely separate area of memory called VSRAM. I’m not quite sure why the two are separated, perhaps this chip model didn’t originally have support for vertical scrolling, and it was tacked on at a later date, requiring a new area of memory for the job. Of course, that’s complete guesswork, but I think it’s good guesswork.

I’ll go ahead and assume (perhaps dangerously?) that scrolling vertically is done in the same way as scrolling horizontally, so I’ll just concentrate on horizontal for now. I’m also not too worried about scrolling individual lines of pixels or tiles yet, until I get round to doing wavy water effects, so for simplicity I’ll just scroll the entire screen. First, the scrolling mode needs to be set. This is defined – for both scroll directions – in VDP register 11:

  • Bits 0-1: Horizontal scroll mode (0=screen, 1=pixel, 2=cell)
  • Bit 2: Vertical scroll mode (0=screen, 1=two-cell)

In pixel mode every word in the scroll memory area of VRAM describes the scroll value (in pixels) for a row of pixels. In cell mode every 8th word describes the scroll value (in pixels) for a row of cells. In screen mode, the very first word describes the scroll value (in pixels) for the entire plane.

So, after a lengthy introduction, we simply need to ensure horizontal scrolling is set to mode 0 (0x00 in VDP register 11) and move the scroll value to 0xD000 in VRAM:

move.l  #0x50000003, vdp_control
move.w  #0x0010, vdp_data

This should shift the whole of plane A 16 pixels to the left. To make it a little more useful, let’s scroll it using the joypad:

 move.l #0x0, d6              ; HScroll value

; ************************************
; Main game loop
; ************************************
GameLoop:

; ************************************
; Read gamepad input
; ************************************
 jsr ReadPad1 ; Read pad 1 state, result in d0

 btst   #pad_button_right, d0 ; Check right button
 bne    @NoRight              ; Branch if button off
 subi   #0x1, d6              ; Update H scroll value
 @NoRight:

 btst   #pad_button_left, d0  ; Check left button
 bne    @NoLeft               ; Branch if button off
 addi   #0x1, d6              ; Update H scroll value
 @NoLeft:

; ************************************
; Update scrolling during vblank
; ************************************

 jsr WaitVBlankStart   ; Wait for start of vblank

 move.l  #vdp_write_hscroll, vdp_control ; Update H scrolling (scroll value in d6)
 move.w  d6, vdp_data

 jsr     WaitVBlankEnd ; Wait for end of vblank
 jmp     GameLoop      ; Back to the top

This is all very primitive, though. It’s a relatively tiny map when compared to most scrolling Megadrive games, and since 128 tiles is the widest we can go then we’d need some method of streaming in new map data offscreen whilst scrolling. I’d also like to mess with per-pixel line scrolling and create a water effect at some point, but this will do for now.

Matt.

Source

Assemble with:

asm68k.exe /p scrolltest.asm,scrolltest.bin
References