Thurs, 22nd Oct 2020
Writing P8C-BUN I was fairly determined not to fight with PICO-8 too much since I was so new to it and because I wasn't too concerned about what came out the other end.
I ended up doing some things that might be a bit out of the ordinary and thought somebody might find it interesting - even if it's just future me - so while I can remember, I thought I'd note some of them down. A lot of them may be rather stupid. I guess this is also a kind of post-mortem/retrospective. It's not exactly the world's most complicated, important or clever game project and there are some far more impressive things out there, but hey - maybe somebody will learn from it.
Update and Drawing Routines
In terms of structure of the code and for navigating around I don't like having a single _update and _draw function for every game mode and using an extended if/elseif and even a gamemode variable was getting me down. There's no switch statements in Lua either. Instead I set up a couple of tables with function pointers for output and draw routines. By the end of development, partly to save tokens, but mostly to save sanity, these had become 2 global variables that I pointed to the appropriate update and draw functions at a given point in time. Usually this was done by my fade routine (see below).
The actual _update and _draw routines did the bare minimum other than call these functions through the globals.
function _draw() pal() dr_func() fade() endand
function _update60() if jbox.change and stat(24)!=jbox.pat then jbox.new_track() end pulse,fr=(pulse+1)%192,(pulse\8)%3 upd_func() end
The pulse global is used by almost all of the animation in conjunction with the integer divide operator and modulus (\ and %) to govern the frame rate and number of frames for each animation. Some of this uses separate graphics, some of it uses palette cycling.
Before I go into that more I'll mention the "jukebox" stat call is to try and make the music change "track" in time i.e. at the end of a pattern. When a new track is requested it stores the current pattern number and only changes when the current pattern no longer matches it. As I can only check every 60th of a second it doesn't always work very well (it's amazing how precise timing in music has to be), but I left it in for the times it does. The idea was to convey how Pac-bun always keeps running. Deeper meaning. Stuff.
Animation and Palettes
The fr variable dictates which frame the current bun is drawn with for each of their animations and for the fox (and ghost). Three key frames of animation can do an awful lot (two is pushing it usually). Given the characters are 8x8, tweening isn't really necessary or possible. When I needed a different number of frames I derived the number directly from the pulse variable. On reflection, I could either have used more "fr"s e.g. fr2, fr3, fr4 and saved some tokens or not used fr at all.
I cut my teeth on palette manipulation with the Atari ST, which also allowed 16 colours on screen (more or less), but had far more colours to choose from. The classic demo of this was the Neochrome Waterfall (glorious isn't it? 1985... also see these even more impressive palette cycling pictures). I couldn't resist putting some water into the P8C-BUN levels using a little palette cycling of my own.
I tried out frame-based animation for water too and in the end both techniques are present in the game. The rivers have animation frames, the pond/sea are entirely palette cycling. The water levels are where I first started utilising the extra colours in PICO-8 (the extra blue: 140). These are dropped in every now and then, but I stuck to the original 16 most of the time.
The Christmas lights, flames, generally anything flashing is done with palette cycling.
Less obvious palette switching occurs to draw the bunnies. Originally I had 4 rabbits, but I'd already been toying with the idea of adding our own, real, live, eating, sleeping and pooing pets to the game (see here). So I added their markings in rather odd colours and mapped those colours to something more representative at run-time.
After that, all 9 (yes, 9) rabbits used exactly the same sprites, just with palette switching. Pac-bun, Brownie, Oreo and Macaroon are still done this way, for instance.
Mapping each of the colours in the bun sprites to different output colours with the pal() command allowed the sprites to be recycled for multiple characters. You can see the 12 total frames, four animations of three frames, for both the buns and the fox as well.
Palette switching is the only way I could see to get the fades between screens to work as well. I set up a mapping from each colour to the next darker colour (for all 32 colours) in a table then run the current palette through that mapping for different values of "dark". So doing it 4 or more times gets every colour to black. The same technique could be used to fade to white or any other colour. Using the extended palette is fine since 16 colours are only ever mapped to 16 others.
Well, at least until you want to fade the screen a bit and still have yellow text on top (the paws screen). Then you realise you've got different colours on different levels and can't just have a special bright colour without ruining the effect. So for the paws effect I scan the whole screen replacing colour 15 with another one before using that for the text. It looks fine. It kills the frame rate and is the only place where P8C-BUN drops below 60fps. Slow motion looks good for a pause screen - right?
Just to underline: the pal() command is over-powered and over-fun in PICO-8 and almost worth the price of entry by itself; palette switching for what PICO-8 calls the "screen palette" was pretty straightforward back in the day (at least for the ST) and gave you more colours to play with, but the equivalent of the "draw palette" mode of the command was never so easily available, complete or cheap (that I was aware of at least). Every drawing command (sprites, lines, shapes etc.) obeys the draw palette without further work from the programmer. It's also completely obsolete, irrelevant to and largely unavailable on modern devices that have more direct colour systems and don't use palettes so reasonably you could argue "when else are you going to get to play with this?" :)
Anyway, I showed my girlfriend at about this point and she immediately pointed out that Dandelion is a lop-eared rabbit and only raises one ear at most at a time (no good deed ever goes unpunished). I grumbled about memory, performance, the alignment of the heavens, tokens and such, hoping she and I would forget it, but it rankled me.
It was about this point where I got a bit more adventurous, in general.
I realised that 16 levels were going to fit very conveniently into the remaining map data and got interested in the specifics of the PICO-8 memory layout. I'd made such progress that I decided to treat P8C-BUN as a full project that I needed to finish (I know, right?).
I dug into performance metrics and found the little graphs available when pressing Ctl-P/Cmd-P. And found I was using almost none of the processing power of the PICO-8. At least from my experience, the idea that PICO-8 is "8-bit" is selling it rather short - I'd say it's processor is more powerful than many 16bit machines (esp when balanced against the screen resolution, cartridge size and high level language commands - try getting an 8bit machine to run Doom using BASIC). Oh to get access to the underlying machine code... ;).
Of course, in my mind, that immediately raised the Bar of Acceptable Quality and little imperfections that had been fine before began to annoy me. And things that I had no intention of bothering with suddenly became important. Like a logo. You've got to have a logo! Everyone knows that!
But I didn't want to eat into the remaining sprites available to me in PICO-8 and I'd just been reading about the relative allowances of text characters, cartridge memory, tokens etc. Besides I liked the idea of drawing a logo, encoding it and rendering it with non-standard colours. I grabbed a sprite editor (Pixen) and digital-fingerpainted the logo at the top of the page. I cranked up Python (and SDL2) to write a little CL tool to encode the PNG produced into a string and then pasted the output into PICO-8. Then I made PICO-8 extract it and draw it. It's been through a few iterations to make it a bit more efficient and works fairly nicely now. I drew the line at writing actual compression routines. Maybe I'll talk about my "Bad Bad Apple" project sometime.
Eventually I knew I had to do something about Dandelion.
This is Dandelion, our oldest rabbit, although you wouldn't know it. She loves to explore so much that she can open doors that are even the slightest bit ajar and is constantly getting out of where she lives with Brownie in our kitchen. She is very cuddly, likes long, snoring afternoon naps and is very good at games and toys.
Building on the "triumph" of the logo I altered the same Python code to encode a new set of rabbit sprites, this time with lop ears and pasted that in the game code.
Dandelion is pulled out of the resulting string into memory when she is going to be drawn (it's literally the same function that sets up the buns' colours) and stays until the original is reloaded from the cartridge when another bun is needed. This works well enough that I added more "custom buns" for Pinkie, Blue and Bowie. I considered doing every rabbit this way, but it didn't seem necessary. An obvious optimisation to try would be to keep the "standard" bunny sprites in memory as well instead of reloading, but I'd begun running out of tokens at this point and was nowhere near hitting performance trouble. Perhaps for P8C-BUN 2?
The same trick is used for the custom sprites for other levels: the witch and the flying saucer. Except for the ghost on level 9 which is just another sprite. I did this really early on and it never broke so I never felt like changing it.
Perhaps interestingly, this is one part of what makes the title screen the slowest view in the game - well, apart from the horrendous paws screen. Drawing all of the bunnies in one frame involves a bunch of memcpys and reloads. The other slow bits are the mini-level and the outlining.
Dandelion is a very grey bunny. If she's out in the garden and it begins to get dark she quickly becomes nearly invisible against the grass. Brownie is not a great deal better. In the game, Dandelion was pretty hard to spot unless your colour vision was _really_ good. I tried making her lighter (didn't look like Dandy and still wasn't easy to see), darker (even harder to see) and even tried drawing here alternate shades of grey each frame (some monitors looked fine, others flickered very badly).
One of the very last things to be added to the game were the outlines to the game objects. By this point, I'd run out of sprites and really didn't want to have to redraw the bunnies, the fox and the rest with outlines - I had no idea how I'd fit the bigger resulting sprites into the sprite sheet nor how I'd draw them without big changes that wouldn't fit in the remaining tokens (approx 0).
I sent the game to some friends who had struggled with it, partly because picking out the objects that affected play was difficult. I'd read a few threads where other developers had added outlines to their sprites programmatically. And been horrified - the mentioned algorithm seemed to be to set the draw palette to black (ok), then draw the sprite shifted up a pixel, left a pixel etc. until 8 copies of the sprite were drawn(?!?) and then draw the sprite as normal at the original coordinates(ok) - hey presto an outline. I hope this isn't something that is done routinely by anyone since it implies a truly horrendous amount of overdraw (background, 8 layers of black, sprite). I also couldn't see why there was any need to draw the diagonals.
In desperation I tried a compromise (that stuck). All interacting elements in P8C-BUN (e.g. buns, fox, nanas, skins, kites) are drawn with an outline created by drawing a blacked out sprite a pixel in each direction (i.e. 4 times) and then the actual sprite. The result is terrible for performance - it takes a big chunk out of what I had to spare, but since I had so much left it's stayed.
And suddenly, Dandelion could be seen.
Drawing the Maps
The third reason why the title screen is the worst performing was drawing the mini-map and this is also where a huge chunk of the tokens disappeared to for P8C-BUN as a whole.
I wanted to have black items on the maps. But black is transparent by default and that had been kinda handy for most of what I'd already drawn. So I thought I'd mark all the sprites that needed black with the sprite flags that PICO-8 provides and use a different colour "pal-ed" to black. I added an if/then statement and forgot about it. Then I wanted animated background tiles. Then I was drawing the easter eggs for the Easter Island level (yes, it's a bad pun) and didn't really fancy drawing the same thing in different colours and using up multiple sprites just for that. So I took my function pointer trick and set up a table with multiple versions of the sprite command for drawing the map. And I thought I'd do the same for the mini map - so I added an if/else into each of these functions.
There's 20+ different ways to draw tiles in the map each governed by the flags on a particular sprite. This is how the fairy lights are done for the Christmas level. It's how the multi-coloured flowers are done, the dancing ghosts etc. The coordinates passed are used to shift some of the sprites in a way to break up the "grid-ness" a bit as well. On reflection, I should have kept the mini-map drawn in a single way as it would have been "good enough" and that would have saved me a huge amount of tokens for other things. I might still change that if I feel very inspired to add more to the game in the future.
Music and Sound
I'd already written tracks for the other, non-PICO-8 version of P8C-BUN so I set about making PICO-8 versions. It's pretty hairy, isn't it? I'm fairly happy with what came out although it certainly isn't the easiest music composition environment I've ever used. There are some severe limitations to the sequencer that I'm not overly enamoured of and I swear I found a bug in that a custom instrument made from another custom instrument only partly works - kinda feels like it really should be works fine or not at all. I actually prefer some of the bass lines in the PICO-8 versions to what I'd done in Logic earlier. Of course, I can port those back very easily where adding more to the PICO-8 version would be problematic.
Making silly sounds was quite fun. I think they're okay.
On the whole, it works and the code got better and more uniform as I went. I think if I kept going it would be even prettier, almost to the point of not being spaghetti. That's what it is though. Making the code pretty isn't the point. If I really want to add some functionality I might feel more inclined to change things in the future. I suspect the first bit to go will be the mini map sprite routines.
What would I do differently or change now?
- The mini-map and menu on the title screen. I could make it prettier and use fewer tokens.
- Spend less time worrying about the game being too easy. I had to make it easier in a couple of ways.
- That said, I'd be tempted to add another enemy or different types of obstacles. I wanted to have frogger style logs on the water, but I started running out of tokens and sprites and they didn't seem v necessary to the game.
- Making some of the hacks to add extra sprites and effects to work could be done a lot more neatly
- More random creatures would be nice - I wanted froggies, but ran out of space
- I never cracked the "turn before the corners" feature mentioned on pages like this. TBH I didn't fancy messing with the bunny code once it seemed to work.
Wed, 14 Oct 2020
I'd seen PICO-8 mentioned a bit on Twitter in the past couple of years without really getting it. I couldn't quite see the point of developing for a fantasy console when real 8-bit and 16-bit consoles (and computers) exist from back in the day. I'd been considering the difficulties of distributing Pac-Bun and how hard it is to get games running on multiple platforms (or even multiple machines with the same OS, but that's another story). For PICO8 there's a web player for making embeddable versions of your games inside a page. And it's easy to use. And you can make standalone "native" versions as easily for Windows, Linux and Mac.
And I thought about the tooling for developing on old platforms - luxurious compared to actually developing when these machines were state-of-the-art (DevPac 3 and it's free!?! When I were lad we had nowt but DevPac 1 and only after several years off a cover disk with no manual...), but still a bit awkward.
Then I realised I'd actually purchased PICO-8 in a bundle a few months ago and so the remaining excuses not to have a go had been eliminated.
I thought I'd take an afternoon's shot at it. I spent half that afternoon trying to get the installation to be portable and I still don't like the compromise I came to (see below). I was worried.
But then within a couple of hours I'd implemented a huge chunk of Pac-Bun. By the end of the day there was more:
And by the end of 48 hours:
Of course the first 90% is the easy bit. It's the last 99% that's hard. So it's been most of a month since then:
PICO-8 is a very well realised idea. The restrictions stop "rabbit-holing" too much without getting in the way of actually implementing features. Pac-Bun happened to be a pretty good fit, which doesn't hurt and I consciously decided to not fight the PICO-8 (well, at least not until near the end) and to not worry too much about how to do things. The resulting code is not a work of art. But at the same time, it could be so much worse.
The Lua implementation is fine. I'd messed with Love2D a bit at one point, but on the whole I'm new to Lua. There's a few things I don't like, the major one being indexing from 1 instead of 0 - but that's Lua's fault not PICO-8. And there's plenty who would disagree with me (and be wrong).
The built-in dev tools seem horrible at 128x128 until you start using them - and quickly you realise they're effective and refreshingly lean. I've since found BBedit more handy for code editing and begun using external editors for some sprites, but for the most part what comes in PICO-8 suffices and is highly convenient.
Music was... interesting - I get the impression this is the trickiest aspect of PICO-8, but my rather eclectic music experience seems to have helped me a lot here. And the excellent tutorials from Gruber.
- PICO-8 has way more processing power than real older systems, especially when you consider that it's running a high level language like Lua. It runs rings around most 16bit systems, never mind 8bit ones.
- PICO-8 has way more RAM to play with than older systems: the 2MB for Lua alone was reserved for exotic business machines like the Atari TT back in the day.
- The screen resolution is much smaller than most real 8bit or 16bit machines which underlines the comparatively huge processing power and RAM available.
- The cartridge size is tiny (esp when compared to the 2MB of RAM). I haven't played around with using multiple carts much yet, but the (artificial) lag from loading off another cart is noticeable and may be more of a problem than I'd like. I'm contemplating filling up memory at start-up for a new project - will have to see how that goes.
- 16 colours on screen (with some caveats) is fine. Only having 32 colours to choose those 16 from is a bit irksome, especially when the "hidden" colours aren't all particularly useful. It does stop a lot of wasting time selecting a palette, I guess. A less arbitrary available palette would be welcome for me at least, though.
- It does mean that the pal() command and associated tricks are so much fun to play about with, though.
In general, I've really enjoyed PICO-8 so far and I'd encourage others to have a go - if you've coded in the past it's fun to chuck away pretensions and hack. If you've never coded before it's a lovely little sandbox to get your hands dirty in and build some rather nice sand castles. It's very forgiving and that's always a good thing.
Minor moan - Portable PICO-8 for Mac - or not so much
Warning: please don't be put off PICO-8 just by this
I blame Apple as much as anyone - ~/Library/Application Support is a stupid place to hide data for applications at the best of times, but when it's where the documents, in this case source code, are supposed to live it's ridiculous. It's literally inside a hidden folder. How is this helpful to beginner coders? I kinda thought these were at least part of the target demographics.
The solution is to edit the config file in this location to point PICO-8 to look somewhere else for its file system (and hence your source code), but then you need to make the same edit on every machine you use it on.
Give me a config file in the application directory or in the app bundle itself or a setting within the application itself. And choose a better default.
Thu, 27 Aug 2020
Despite PacBun being a fairly small and simple game, I'm using it as a dry-run for implementing something more advanced in the (near?) future. That means I'm heavily straying into the dangerous territory of over-engineering.
For example, I've been working over the last week to make the game less hard-coded and a lot more data-driven. The aim is to allow most of the game to be put together with content defined in data files read by the game and leave the "kernel" of it as a much more generic framework or, dare I say, "engine" (perhaps "motor" might be a more accurate term).
There's big reasons to do this (I hope):
- it allows me to create the game without rebuilding (or even restarting) the main part of the program
- content creation is defining rather than coding - should be simpler
- it forces modularisation so that changing one bit of the game is much less likely to break another part
- it means the "motor" should be more flexible
- it opens the possibility of others being able to add content to my game(s) - i.e. mods. I'll essentially be modding my own game and it's in my interest to make that experience as easy as possible
I've obviously had to provide a bunch of test or placeholder content to work with the framework and it's very rough just now. You could say that I'm deep down a rabbit hole right now...
Pac-Bun has a lot of placeholders right now. A bit like this gerbil masquerading as a rabbit.
Fri, 21 Aug 2020
Pac-Bun likes to poo...
PacBun is a small rabbit that likes to poo. Everywhere. PacBun and his friends delight in leaving a small, perfectly formed excretion at every point in a series of mazes. It's "reverse PacMan" - no consumption and clearing of pills here; production and spread of poo is the aim of the game.
PacBun is an "intermediate" game. It's a "finish what you started" game. A journeyman's test. It's not "triple-A", nor even single "A". "B" would be nice, but maybe "P" would do. But it will be a fully realised, complete product. And hopefully it'll be fun with it.
I've written a lot of scraps of games. It's the recommended first step for any game developer. There's even lists suggesting the correct series of games, escalating in complexity, that a novice developer should write in order to gain the correct skills to attempt something more ambitious. From what I can tell, few people "graduate" to writing their ambitious magnum opus - PacBun is my stepping stone to it. I hope I don't slip and fall!