I need to say, that's awesome! Great art and fun to play, congratulations, success!
Devlog: Tech and Design of the Flag Editor
- High level implementation - How does it work at the core?
- UI Design - A Full Image Editor in only 64x64 pixels
- PICO-8 Trick - Mouse-based Button Input
- Bonus - Undo
How does it work at the core?
At its core - the image editor is really simple. I can break it down into just a few steps:
- Draw the contents of the sprite sheet onto the screen -- spr()
- If left-click: Draw a circle at the mouse position -- circfill()
- Copy the contents of the screen back onto the sprite sheet - memcpy()
- Draw the mouse afterwards, so it isn’t included
Using these simple steps, it’s way easier to make an image editor in PICO-8 than it has any right to be! This works because of how memory works in PICO-8: The screen and the sprite sheet both represent 128x128 images, and store pixel data the same way between them. If we wanted, we could memcpy() the entire sprite sheet onto the screen, and it would be exactly the same!
Critically, though, this allows us to do so much, so easily! Not only does circfill work, but it means we can use all of PICO-8’s graphic functions to great effect! The line, circle, stamp, and brush tools all work this way, which means they can share a lot of code!
There is one important caveat with this method, though: It doesn’t really allow for scaling any operations up or down. Here, we have a canvas in screen space which directly corresponds to a region in the sprite sheet. If we tried to sever this relationship, we would need one (or more) of these systems to be more complicated.
A Full Image Editor in only 64x64 pixels
The flag we’re editing is only 56 pixels wide, and 32 pixels tall, and now we can’t scale it up at all. This really limits the interfaces we can have. I decided to use PICO-8’s 64x64 mode, because I wanted the flag to be the most prominent element. But, it meant the rest of the interface had to be condensed into a tiny, tiny space.
This was tricky to work out. I knew, at minimum, I would need a color selection, a tools selection, and some contextual information. I wanted them to be as easy to use as possible, so I wanted this all to be accessible directly. I was able to achieve this! From the editing page, you can get to any other action or information with one click!
In the end, though, that was about all I was able to fit. Each line of text is 5 pixels tall, so explaining things is expensive. I had to let these constraints inform the design: The contextual information had to fit in one line, and if it had any options (like size or pattern) then I had to be able to represent it with a number or symbol. After some experimentation and feedback, that’s how I came to the small set of tools available.
With all of the functionality available in only 3 “toolbars,” I had just a little but of space left. I used it to make a pretty header, and added “tabs” which allowed me to have a little bit of a help section, and throw in some extra features I desperately wanted but couldn’t fit in the contextual space.
Mouse-based Button Input
For mouse-based games, I like to follow a pattern of predominantly using button-based interactions. This approach is kind of object-oriented: You can think of each button as a table, containing information on how it should behave and how it should be rendered. Then, I can use one function, button_render, across all button objects!
- Calculate if the mouse is within the bounds of this button
- Call the button’s specific draw function
- If the mouse is within bounds, call the button’s specific hover function
- If the mouse is within bounds and there’s a click input, call it’s specific click function
- We also return true here, which lets us create generic buttons that have the same graphics but different behavior - in which case, there is no specific click function. This is used for various increment/decrement or “continue” buttons
This is a simple but powerful system. A button can be drawn as text, or a shape, or a sprite, and it will behave the same. You can create buttons on the fly, or create arrays of buttons which all share similar behavior easily. And it’s all pretty intuitive to the end user because “point and click” is more accessible than “use arrow keys and z/x”.
There are a few weaknesses to this: It mixes game logic with draw logic, which could get messy. It’s more upkeep, and more tokens, than just checking for key presses. For arrays of buttons, you may end up cross-referencing tables with each other. If you stick to only buttons, you miss out on stuff like dragging or right clicks. But, I’ve found them to be pretty useful.
Undo is pretty straightforward! Take a look at the sprite sheet: I’ve labelled four 7x4 regions, which represent the displayed flag, as well as three slots for edit history. This allocated space actually extends into the map region as well, giving us five total history slots!
Any time you start making a change, data from each region is cascaded down to the next one. For this, I’m using the same function that copies the data from the flag to the sprite sheet. Yay for code reuse! If you need to undo, we do the same thing in the opposite direction. Additionally, we keep track of how many undo’s are available, so you can’t undo before actually doing.
Come back next week for another devlog, on a special case - the the palette editor tool!
Get Flags For Friends
Log in with itch.io to leave a comment.
oh, I like your undo method! and the drawing / saving to spritesheet thing; those are some nice tips. (I’ve been making a pico-8 drawing program recently and went full programmer on those problems, which was fun but maybe too much work haha)
I assume you know what an “Immediate-mode GUI” is, since that’s exactly what you’re describing with your button system? but just in case you (or other readers) haven’t heard the term before, well, now you have. I haven’t used that sort of system myself yet, but maybe this will finally spur me into trying it.
thanks for the post!
Thanks! I made a drawing app before, but it was only for 8x12 sprites, so I could get away with only using sset. For this, I needed something more sophisticated.
This is a pretty easy undo, but the downside is it takes up a lot of space, which could be a dealbreaker based on your needs. What I don't mention here is that the undo's also continue further into memory, into the map region - which is otherwise unused.
I haven't heard of that term, actually! It's good to know. The buttons were actually mostly based off a tweet I saw.