Log In  

Cart #pm_demo-0 | 2021-11-08 | Code ▽ | Embed ▽ | No License

Cart #picomap-4 | 2021-11-08 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA


Version 0.40 (Work-in-progress): Added autotile mapping type, Added dedicated object types for eraser and scroll barrier, editor now accurately previews just a single layer of tiles, significantly decreased CPU usage when rendering large levels....

Version 0.30: Added support for parallax scrolling backgrounds, added eraser tile functionality, made repeating pattern size adjustable for bordered objects, revamped architecture to handle future added features, sprite flags now export along with sprite and map data, various optimizations and bug fixes.

Version 0.21: Updated appearance and changed name to something simpler and (hopefully) more appealing, misc. bug fixes.

Version 0.20: Reconfigured repeating pattern object types for adjustable pattern size, added level delete option to editor menu, and changed a few design elements to improve usability.

PicoMap is a graphical map editor which allows creation of game worlds hundreds of screens in size that fit in a single Pico-8 cart. It does this using a version of metatiles, a compression technique used commonly with retro systems like the NES. Basically, levels are stored more efficiently by being built from groups of tiles instead of single tiles, just as images are stored more efficiently when built from tiles rather than single pixels.

To give you an idea of how efficient the scheme is, I built a playable demo of all Super Mario Bros. level maps with an earlier version, and they fit in just 3.9KB, about 1/3 of the storage space the system makes available. https://www.lexaloffle.com/bbs/?tid=39469

I've worked hard to minimize the system's footprint so it's easy to add to game carts. It's very CPU-efficient and well-suited to 60fps games since map decompression is done only when loading a new level, and the core functions take about 670 tokens and 9% of compressed file size with comments, or about 6% without. You can use PicoMap in any project you want and modify the code to fit your application, just be sure to include attribution somewhere in your cart. Hopefully this will enable people to make some cool larger-scale games like platformers and action-adventures.

System outline

PicoMap lets users create levels from metatile "objects", like a tree, a hill, or a building, each of which is basically a reference to a rectangle of tiles in Pico 8's map memory. In the editor, levels are stored as lists of objects in string form, but when exported to a separate cart, these are condensed into raw binary data and stored in its spritesheet and map areas. Meanwhile, three strings are output to the clipboard-- compressed sprite and map memory data strings, and a string-based lookup table for the location of each level's data in cart memory.

When the destination cart is run, the binary level data is transferred to a table in Lua RAM and the spritesheet and map memory data are decompressed to the proper areas in cart memory. On loading a level, a second table is created to contain its map, and this is filled using instructions from the main table. A portion of the level table slightly larger than one screen is then copied to the top right corner of Pico-8's map memory once each frame and mapped to the display. This allows each level to be any width or height, and up to 256 screens in size.



Left Mouse btn.-----Select or place object/bring up object placement preview
Right Mouse btn.---Click and hold to drag view (turns off object placement preview)
Directional keys----Move view
Scroll Wheel--------Select object number
Z--------------------Toggle object editor screen
X--------------------Delete object when mousing over its top left corner (a white 'x' will appear)

Editor shortcut keys

V-------------------Change variance type (when in obj.editing mode)
M-------------------Change mapping pattern type (when in obj.editing mode)
G-------------------Toggle grid
S-------------------New level string submenu
Q-------------------Previous level map
W-------------------Next level map
E-------------------Export level map to cartridge
A-------------------Export all level maps to cartridge
H-------------------Reset current map to Home position (screen 1,1)

Viewer Cartridge

Directional keys---Move camera
Z-------------------Previous level map (optional)
X-------------------Next level map (optional)

Using the editor

Creating and managing maps

Once you've started the editor, if you want to create a new map string, open the menu and click on the 'new level map' icon. Select your desired background color, level type (work in progress), level width and height in screens, and press the checkmark icon to create the new string. To delete a map, go to it using either the left and right arrows in the menu, or the q and w keys on your keyboard. Then click the "delete" icon. If you delete a map by mistake, just click the Undo icon.

Defining Objects

Since your maps will be made from tile patterns in Pico-8's map memory, you'll need to draw each object type there before you can use it. Due to the way the system works, you'll need to keep a few limitations in mind.

Additionally, the top left tile in map memory (location 000,000) should be left blank, as it's the default selection for all undefined objects.

To create objects to populate your level map, select the object category using the icons on the top right, then the object number either by clicking the thumbnail and selecting a slot from the drop down menu, or by using the mouse wheel. To create an object, go into object editing mode by pressing 'Z' and outline the rectangle of tiles that defines the object's default (minimum) size.

Object settings

Each object type can be made to have a variable size. This is done by changing two properties, its variance and mapping pattern. These are represented by icons in the lower right corner of the editor, next to the object's ID number.

An object's variance type tells how its size can be altered when it's being placed in a level.The box that's highlighted defines the default (or minimum) size of an object, but if its size is variable along an axis, the size can be extended up to 15 tiles larger than the default size in that direction. Fixed-size objects take 2 bytes each, while ones whose size can vary on one or two axes take 2.5 and 3 bytes respectively (though it'd be possible to limit this to 2.5 bytes max via lookup tables if you only need certain types in a given level).

--Mapping type
This property controls the pattern of how map tiles are used to build objects in your level. This can allow objects to be made much larger than their representations in Pico-8's map area while still keeping their intended traits.

To change these settings for an object, go into the object editing mode and click on the buttons in the bottom right hand corner, or press the 'V' key to cycle through variance types and the 'M' key to cycle through mapping types.

Eraser Tiles

To increase versatility and save memory, it's possible to partially erase objects and let the background show through. This is done simply by building objects partially or fully from sprite #1, which contains a picture of a large gray X, as seen at the top of the screen above. If you want an object with a portion that will let the background show through, just use sprite #1 in the parts of it you want transparent. If you want a dedicated eraser object, just make a repeating pattern object out of sprite #1.

Building maps

Now that you've created a level string and defined some objects, you can start building level maps. Before you do, though, here's something to keep in mind. While the editor will display your objects exactly how you place them, when they're exported and compressed, they'll be drawn in order of the screen in which they start.

Now using either the arrow keys or clicking and dragging with the right mouse button, you can move your view around the level to place objects. Pressing the right mouse button will also turn off the placement preview for the selected object, but you can turn that back on by clicking the left mouse button. Now, select the object you want to place, and either left click (for a fixed-size object), or left click and drag to place objects in the level. If you make a mistake, you can either click the undo icon, or you can delete any object by mousing over its top left corner until a white "X" appears, and pressing the X button. To toggle a grid that makes it easier to judge position and screen boundaries, just click the second icon at the top of the screen.

Foreground layer

Background layer

Saving maps

The system continuously outputs the level you're working on and the list of definitions for all the objects you've defined to the clipboard as strings. To save these to the cartridge, just quit PicoMap by pressing escape, paste the contents of the clipboard over the current strings in the "data strings" tab of Pico-8's code editor, and save the cart.

Exporting Maps

Once you've made some levels, you can export them to a separate viewer cartridge, suitable to be incorporated into a game. To do this, first open a new cart and paste the code from the "Game Cart Functions" tab in the editor there, and uncomment them. Then save the cart with the same name as the "export_cart_name" variable in the editor cart's _init() function, and save and close the new cart.

Next, run the editor, open the system menu, and click on one of the cartridge icons to export either the current level map, or all level maps to your new cart. Now open the viewer cart, make sure "puny font mode" is enabled by pressing ctrl+p, and paste the contents of the clipboard into it.

Layering Maps

PicoMap uses a special function called map_array() to copy tile data from each layer to a dedicated section of map memory and then display it on screen. To display multiple maps on top of each other, which can increase scene complexity and/or be used for parallax scrolling, you need to first initialize one empty array per layer when initially loading the level, then make the proper number of map_array() calls per frame to draw each layer, as follows.


array: this is the name of the array, such as "bg" for background or "fg" for foreground.

rel_scrollspeed: This sets the relative scrolling speed of each layer. Basically, it takes the camera movement commands from your code and multiplies it by the given value for each layer, which is useful for parallax scrolling effects. I recommend using a value of 1 for the foreground and a simple fraction like .25 or .5 for background layers. Layers in front of the foreground would use a value greater than 1.

width: The width of the map layer in tiles. Arrays smaller than the foreground will automatically repeat, so background layers can be much smaller than the foreground to save storage space. I recommend setting this value for the layer in question equal to the variable lvl_width at level loading, as this automatically draws the correct value from the function which builds the array.

height: The height of the map layer in tiles. Arrays smaller than the foreground will automatically repeat, so background layers can be much smaller than the foreground to save storage space. I recommend setting this value for the layer in question equal to the variable lvl_ht at level loading, as this automatically draws the correct value from the function which builds the array.

x_start_pos: This sets the horizontal position in tiles of the screen relative to each layer when it's first loaded. For instance, if you want to start a level at the fourth screen to the right, use a value of 64.

y_start_pos: This sets the vertical position in tiles of the screen relative to each layer when it's first loaded. For instance, if you want to start a level at the third screen down, use a value of 48.

Map Collision (Work-in-progress)

PicoMap works with standard tile flags for map collision, though because it uses the map() function in an unorthodox way, it requires a different approach for collision functions to work correctly. For a regular map collision, you would test collision using a simple setup like this:

position to test tile flag = ((player's position in world)+(offset to account for character size)+velocity)/8

But since PicoMap works by copying a small section of a level to a screen-size "tilebuffer" in map memory each frame, it requires something like this:

horizontal position to test tile flag = ((player's position on screen)+(distance scrolled from start pos.)%8+888+(offset to account for character width)+velocity)/8

vertical position to test tile flag = ((player's position on screen)+(distance scrolled from start pos.)%8+(offset to account for character height)+velocity)/8

Also, because the map_array() function overwrites the tile buffer for each layer, map collision code must be run directly after map_array() is run for the layer that the player interacts with.

Entity Objects (Work-in-progress)

By default, background, foreground, and entity object types all function the same way, with the categories merely helping organize map objects. If you uncomment a line of code in the build_array() function used in the viewer cart, no entity objects will be drawn to the map. This is to enable easy and storage-efficient enemy and NPC placement in levels by letting the user add an entry for each entity to a table using data from the entity object like ID number and x and y location for their spawn position in the level.

System Settings

The system has a number of settings that can be adjusted by changing values in the _init() function in the first tab.

obcounts: This table of 3 values controls the number of background, foreground, and entity object slots. There can be up to 238 object types in total, and up to 96 objects in a category can currently be displayed on screen at once. Keep in mind that each entry takes 7 hex digits, so if you want to increase the number, you'll need to add an appropriate number of zeros to the object definition string.

screen_ht: This value determines how many tiles high a single map screen is. Useful if you want to, for instance, demake an NES game with 15-tile tall screens, or an SNES game with 14-tile tall screens.

bord_obj_ptrn_size: This sets the size of the repeating pattern for bordered objects. Default is 1, but setting it to a larger number can allow larger repeating patterns.

spritesheet_w: Sets the width (in pixels) of the area of spritesheet to be encoded to a string.
spritesheet_h: Sets the height (in pixels) of the area of spritesheet to be encoded to a string.

mapdata_w: Sets the width in tiles of map area to be encoded to a string (max of 108).
mapdata_h: Sets the width in tiles of map area to be encoded to a string (max of 32).

As this is an early version of PicoMap, it's not yet complete and there are going to be bugs here and there. Please let me know about any issues you encounter, as well as any improvements you might think of. Thanks.

P#91798 2021-05-11 17:48 ( Edited 2021-12-01 00:43)


And now... The moment you've all been waiting for!

P#91828 2021-05-12 01:42

Well, one that some people have been waiting for at least. ;)

It's been up and running for a little while, but I spent some time on the little tutorial that explains the main workings, since I know asking people to switch to a new piece of software can be a tricky thing. I also tried to copy some of Pico-8's appearance and user interface elements to make things feel familiar. Hopefully people won't find it hard to understand or use.

P#91830 2021-05-12 02:47 ( Edited 2021-05-12 05:35)

The new interface and features look good! I especially like the addition of the delete level button

P#93311 2021-06-10 17:28

Thanks, figuring out how to implement the delete feature and make it work with the undo button took a while, but I think it should help avoid headaches with managing level strings manually. The redesign of the object types should add a lot more versatility and save storage space as well. If you've made some maps it could mess up parts of them, but I updated the tutorial to show how the adjustable pattern-size objects work now.

The next big revision I'm working on will let you layer metatile maps on top of each other for multiple parallax scrolling layers. =)

P#93352 2021-06-11 14:46 ( Edited 2021-07-28 03:23)

wow! I did NOT know pico8 is also able to do things like this!!

P#93357 2021-06-11 18:53

Interesting. I had looked into metatile mapping that was closer to Blaster Master, with graphic tiles themselves being 'in flux' as far as rotation and pallete in addition to how they're used in the larger map. Didn't get far as it would take a double layer metatile editor along with map editor...BUT, the fact other folks are looking into custom map editing to make bigger maps AND do it with a tool in pico8 I have no choice but to cheer you on. The 'general ram' area of memory has a lot of potential...unlock it!

P#93720 2021-06-19 03:11


I'm not currently using the general use memory, but there's probably some good uses I could put it to. I know there are lots of different approaches to using metatiles, and I've looked at some different types, like a variant that generates metatiles automatically from a map of individually-placed tiles for more versatility, but so far an object-based approach like my current one seems to offer better compression and is less likely to require additional work for users.

P#93746 2021-06-19 19:57 ( Edited 2021-06-20 01:23)

Woops I was in the process of writing one of these, I didn't see this before. I'm glad you made this, I will need some time to see how it works.

Well done.

P#93878 2021-06-23 08:04
:: touk

This looks incredibly useful! I have been experimenting with run-length encoding to pack oversized maps, but in testing with a real game it only compresses to about 25%... metatiles seem like they could compress it much further.

P#95367 2021-07-28 02:48

Definitely. For fairly simple maps, such as those in my Super Mario Bros. demo, I've been able to achieve an effective compression ration of about 25:1. This wouldn't be quite as high for denser maps, but I think in general a compression ratio of at least around 10:1 is a pretty safe bet.

In addition to that, though, the system compresses the sprite sheet at least 2:1 and stores it as a string, leaving nearly 12KB available to store level data. This means that even without factoring in level compression you have about 50% more potential level storage space than if you were just using the standard Pico-8 map system.

P#95370 2021-07-28 03:11 ( Edited 2021-07-29 02:23)

Great! I was thinking about storing tiles in Lua RAM but wasn't sure about the speed of copying it back to tilemap memory... if you can reload it every frame, surely I can do it one frame in a while! (I already have a system to detect when the character is approaching another region's border, so I can load the next region just before I need it)

If there's already an import feature I'll use that. Otherwise I won't be able to reproduce my existing level, then I'll just make a smaller one for demonstration at least.

P#99810 2021-11-08 19:12


No import feature, I'm afraid. Levels have to be built using the editor, since instancing defined patterns allows levels to be highly compressed. The copying of tile data is very fast because only about one screen's worth is needed per frame, don't know how the speed would be for a bunch of screens.

P#99820 2021-11-08 19:51 ( Edited 2021-11-08 21:34)

Hmm, just thought about your last comment. Do you mean you don't think the system will be able to handle the size of your map? It currently supports level maps of any width and height as long as width*height doesn't exceed 256 screens. Not sure if that's big enough for your whole map, but if not, you must be working on something pretty ambitious!

P#99830 2021-11-08 21:36

Import: ok, I see, patterns must be added by hand rather than deduced, but that makes sense. I got a lot of filler tiles so that would give great compression in those areas.

Limitation: no, I meant human limitation, the fact I have to redraw each region by hand. That said patterns will make filling faster at least, so it will be more about copying the unique parts of each region. I have many repeated parts like small bumps though, so again patterns may make the work faster there. (I only need 8 regions equivalent to one PICO-8 tilemap without the shared memory)

P#99940 2021-11-11 18:28 ( Edited 2021-11-11 18:28)

It's been a while since I checked this, and quite a lot of progress has been made.

P#104041 2022-01-01 20:45

Yeah, It's definitely a longer-term project. I've been working on the next version, which will add a number of new features including an autotiling system. It's taking a bit longer than I expected though, since layering the tiles in a way that's versatile and easy to use has proved complicated. Also, I'm trying to keep the size and token count of the cart functions as small as possible, and that's involved a lot of code refactoring.

Here's a little preview:

P#104088 2022-01-02 04:39 ( Edited 2022-01-02 04:45)
:: merwok

Have you noticed the «big maps» feature in latest release?
We can store extra maps in high RAM and use a poke to make the map functions take data from there.
Maybe that’s interesting for this program?

P#105000 2022-01-13 16:56

Yes, I noticed that. The next version of PicoMap will use the extended memory, but for storing compressed level data and not storing the decoded maps directly. I decided on this because storing them in arrays allows maps that can be twice the maximum size and have any desired dimensions, setting up parallax scrolling layers is made very simple, and each tile entry can store extra data that allows layering options for autotiling.

To avoid any extra difficulties due to not using the map memory directly, I've also made a small function that works as a drop-in replacement for fget(mget) and gets flag data directly from a level map array, so you'll be able to use your existing map collision routines.

P#105017 2022-01-13 18:12 ( Edited 2022-01-14 05:00)
:: merwok

That makes sense and sounds great!

P#105079 2022-01-14 16:28

@JadeLombax maps can be of any width now, allowing maps to be stored linearly

P#105080 2022-01-14 16:32


Well, I know the width is adjustable, but I thought I read it could only be set in 32-tile increments.

In any case, the current setup I have allows for levels up to 256 screens in size, and I could actually increase that if I wanted. Also, setting up parallax scrolling layers is super simple and the background maps don't eat into the space used for the foreground, and you can do some very useful things with layering autotiles using the fractional bits of each array entry.

P#105081 2022-01-14 16:47 ( Edited 2022-01-14 19:34)

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2022-01-18 13:19:21 | 0.066s | Q:44