Hello everyone, it's been a while, life gets in the way and my little pico-8 project gets pushed away, but here I am again with more questions.
I got to the point where I basically have all the game elements I need running (I think), and I need to start building levels, but I have no idea how to go at this really.
My game is a flip screen type, 8 screens wide per level.
Each level will of course have an increasing number of features with different attributes.
Some of them are collectables (two types), some of them are enemies (3 types)
I know there's two ways I could approach this:
- Have a fixed amount of levels that are all the same and tweak them difficulty-wise as I see fit, by hand
- Have the levels be created procedurally
I don't have the foggiest idea about procedural generation of anything, so I don't think I would understand ANY explanation about that route. Although it would be nice to learn eventually and maybe change the game to do that in the future, at this point I think option 1 is my only course of action for the time being.
With that in mind, how would you approach this task?
My main idea was to have an object level{}, which would have as elements each level (up to 15 or whatever I decide I can have), and each level element would have the data I need, in some format.
i thought maybe an array of numbers representing each level would tell it what each screens has, but haven't really figured that one out yet.
Looking forward to hear your ideas!
Do you know that pico as a built-in map editor? Given you are apparently new to gamedev, I’d suggest to stick with that and release a working game. You should be able to fit a couple of levels in the cart.
Next step could be multicart (not BBS compatible but not necessary a big deal).
Procgen is cool but will burn gazillions of tokens to interesting gameplay (works well for dungeon crawler though).
What do you mean by "flip screen type"? like zelda1?
Check out the cart of my game "the lost beans" as an example of how you can use pico-8's map feature. In this game I only use the first two spritespaces for tiles, which means I have a lot of space for the map.
However you don't have to use the map feature. In my latest game I store the level as an array of tiles, e.g. {2,1,2,2} could be a wall tile followed by a door tile followed by more wall tiles.
I then have a 2nd array for collectibles or things you can interact with in some way.
made another tiny little game today for the onehourgamejam
In this game the map consists of single pixels each representing a sprite. Since its a tiny game I am reading directly from the spritesheet but you could also have written them to (programmatically or designed in the pico8 map function) the map which would then have given you a massive in-game world map to play with. Check it out:
Arrows to move, Z for minimap
haha! the original idea was to collect various bits in order to build a raft. however, with 1 hour its hard to get anything more than the absolute basics.
I had an idea after this: you could add another layer, e.g. the map could be a few tiles big, with each pixel in the tile expanding into a "map tile" which again expands into an actual tile, which would give you a mahoosive world. might use this technique if I ever want to make a PICO-8 RPG!
I've never used the map editor in any of my games :D
The map editor is obviously very nice, and you should definitely use it if you can. It's by far the easiest option. Bubble Pond, one of my favorites, does that.
That said, you might run out of space if you plan on storing many levels. It can only store 128x32 tiles, or 256x32 if you use the shared sprite memory as well, so that's a maximum of 32 full screens of tiles if you don't do anything fancy like Pico-Sokoban (scroll down to see a GIF of how the map is crammed together in that game).
I have made some tile-based puzzle games with fixed-size levels, and like I said I've never used the map. For my first game (Picolarium) I could've crammed them in like Pico-Sokoban (100 levels of a size of up to 8x8 each), but instead I stored them as numbers in tables like @dollarone did in Wildfire. That REALLY eats up tokens though, so I changed my approach in an update where I encoded the levels as fewer numbers in a table instead (with byte-packing; Lua numbers are 32-bit but I originally just used 1 bit per number, lol).
For my next game, LaserTank, with levels up to 16x16 and with much more variety in tiles per level, I instead stored each level as a string. Each level is represented by lots of bytes, which I converted to a string of hexadecimal "numbers", which I compressed with RLE (run-length encoding). Even so, this quickly runs into the compressed size limit, as strings don't compress well.
So those are some options with different limits you might hit: Map memory size, token count, character count and/or compressed size. Depends what you need most of in your game.
I've also made a couple of randomly generated games (Patrick's Cyberpunk Challenge and Meteor Night). I'm not going to call them procedurally generated, because they just use random numbers and the results aren't very interesting, but I still didn't have to use the map editor, hehe.
Then there are multicarts, of course, but I don't really like those as they don't work on the BBS. At that point I'd rather just use something other than Pico-8. But the option is very nice, and there's the PX8 data compression routines @zep wrote back in the day.
So, I put together a small project that might help with the level storage/loading thing -
- First, make a row of sprites as your "Key" - each sprite in the row corresponding to one of the 16 colors
- Next, draw your level as another row of sprites (you may want to do a better job than I did at matching the sprites to colors that make sense)
- Now you can load that level into the normal map data with 'LoadLevel', by giving it the first 'Map' sprite for the level and the first 'Key' sprite. You can use this to have multiple levels and multiple 'tilesets'
Currently it's set to use a single row of 8 sprites (levels will be 8 tiles tall, 64 tiles wide) but those numbers can be tweaked. The idea is that you can save space if there's some kind of HUD at the top of the screen, so you don't need the full 16 tiles as level info.
You can also add things to the "CleanMap" function for any post-loading stuff you need to do, like save map tiles and collectables into a table or swapping tiles out for variation.
It's a bit rough, but I hope it helps!
The main thing I would keep in mind with any level system is that you end up doing a kind of compilation from "source data" to "result data". The source data expresses enough of the level - perhaps omitting some details - that you can get the result you want by applying an algorithm to instance the rest of the level. For example, things like the exact animation state enemies start in are not things you have to express in the source data, and can just be initialized to defaults.
There are game engines that take the route of making the live data editable in 1:1 form, but this is often really unwieldy as an authoring tool and it means you have to store a lot more, present a lot more editing UI, and so on. A small source format is a kind of specialized compression algorithm - and procedural generation is like an extreme of that compression, one that allows the input data to just be a random seed value. So to do procedural content you don't have to know "generic procedural generation algorithms", but rather, "how would I design a compressed source format that would always emit coherent results if I randomized the input". If your level layouts are trivial like, for example, most infinite runner games, the procedural generation aspect is trivial too. When you have an engine in an early working form, there's often a kind of bootstrapping where your levels are only trivially procedural at first(e.g. big empty box, infinite plane, etc), but then you switch to adding more authoring later.
For a typical platforming type of game, storing some kind of tilemap as the source data makes a lot of sense and gives you precise control over the layout, but it often comes with the caveat that you either have to think about how to express elements that aren't really part of the tilemap(player, NPCs) as tilemap elements and rewrite them to load the final scene, or store two entirely different forms of data to express different elements differently.
Many older games will take the approach of having multiple "sizes" or "chunks" of tilemap(e.g. reusable prefabs of 2x2, 4x4, 8x8 tiles, or sometimes "shapes", sort of like vector graphics) and reusing them to describe the final layout so that the total data size is smaller. This can be taken to quite an extreme.
As a starting place, I would suggest just using the PICO-8 tilemap space, drawing in a layout, and trying to load that. And then after you get that working, you can think about what tradeoffs you want to make to compress it, if any.
Thanks for all your responses and sorry I didn't reply before, been busy.
There's a reason I don't use the map tool in pico-8: i deemed it easier and better to do it by code. I'm not as much of a n00b as freds72 thinks. Yeah I am not a "developer" but I'm not a total neophyte to code. So I made a decision that in my head made it all more feasible. I still think this is much much better than using the map editor if only because of flexibility.
Each level has 8 screens, and using the map editor would have made me end up with very few levels in this solution. Most levels share most of the qualities, like, the "location" is the same so it's always drawn the same way. There's a lot of empty space, so why use tiles at all? I'm drawing a bunch of rectangles for most of the level layout.
However, I am trying to map out moving, "live" sprites, not map tiles, on the level. My "level" is already there and working. I need a system to put the collectibles and enemies. You don't use the map editor for that at all. Maybe my description was confusing so sorry about that, I am not looking to draw the level, I already got that working. I need to place my enemies and goodies in there.
So to sum up:
- I have a level with 8 screens I "flip" through (yeah like Zelda!), always looks the same, and I render it using my simple system to do it. That's working and will be the same for every level.
- I need a way to place the enemies and collectibles on these screens, and this will change from level to level. How much I can fit will depend on how much space I have and how this system will work.
As triplefox says I am basically trying to come up of a way of "encoding" the minimum amount of data for each level so the game can render the objects in place. It is some kinda compression yes! But the theory for this escapes me so I was looking for practical suggestions, this is why I mentioned that I have:
- Collectables (2 different types)
- Enemies (3 different types)
Now what does change with time?
- More enemy types appear (you start with one type, as you progress, second and third type appear)
- More enemies and collectibles as you progress (up to a limit)
- Enemy qualities change (speed increases, movement changes)
Let me try to illustrate a little bit more, I know this is hard without posting a cart or code but bear with me for a sec.
Let's say I have 2 collectible types, circle and square. We also have 3 enemy types, red, green and blue.
Level 1: only red enemies, not on every screen, with certain speed/movement parameters. Zero collectibles
Level 2: A couple circle collectibles. Few more red enemies
Level 3: Same amount of collectibles, red enemies about the same, we add a green enemy.
.. and so on.
As I said I can hardcode these to have a fixed amount of levels, but I still need a way to encode this so it's optimal and doesn't take much space. This is why I thought of an object/array element that would contain the info for each level. Yeah this is not procedural, because as I said, I have no idea how to do that.
Anyway, thanks so much for all your input, all very much appreciated, and looking forward to further thoughts on this from you all!
If you're not using the map (0x2000-0x2fff) for actual map data, why not store your information about each level's enemy/item layout there? I'm doing something similar in the game I'm working on.
Just decide how many bytes you need per entity (in my case the first byte is the "type" (which just corresponds to the entity's sprite id for readability's sake) and the second byte is the entity's x/y position (grid-based, so 0-15) packed into one byte, e.g. {7, 12} = 0x7c). You could dedicate 2 bytes to position (one for x, one for y) if you wanted pixel-resolution placement. Then just decide how many enemies/items you want to be able to store per level and how many levels you can have. You've got 4,096 bytes of free real estate, so there's no reason to waste all those tokens laying out your data with code.
When you load a level, you just need to do a little math to figure out what part of the map you need to start reading through with mget(). As you read through, you construct the objects and add them to a table so you can update/draw them in your game loop. If you want, you can even write the object data back to the map before you load the next map, so that dead enemies won't respawn when you come back, etc.
Hope that helps!
Thought I would get my 2-bits in here.
The way I handle generating maps is always with the computer's help. That is, I have the computer generate whatever it is that needs to be.
Dungeon, Island, City, whole Planets, even just an area to bip-bop around on a simple platformer.
By having your code generate this for you, it frees you from the drudgery of building a fixed and static map. And that's always a good thing as you are guaranteed to get a new and unique map each time you play - whatever game you decide to write.
Here is an engine I wrote for BlitzMAX, a simple dungeon level creator. The player can only see the 3x3 area around themselves in actual play though the map records everywhere they have been.
They start on the WHITE stairs and ultimately must find the BLACK stairs. Yellow is treasure, X is trap, Red is monster matching level of this dungeon, and the white square between rooms is a door.
Here's a little more on practical compression strategies that could help you wrap your head around this problem:
When you store the data in an array or in the tilemap, what you have is a "linear" mapping. Why is it linear? It's because, if we imagine the set of all our symbols(tiles etc.) existing on a line, we can describe any sequence of symbols as a sequence of equivalent points along that line. Using the built-in tilemap versus an array in code changes the source representation - and so it's meaningful in PICO-8 since the system categorizes these representations differently - but it doesn't change the theoretical underlying nature of this mapping.
To make this data compressed, what we need to do is to either change the mapping method to be non-linear(more data encoded by context in the sequence) or to change the symbols we are working with to encode more data at a time.
What are examples of those strategies that we can apply?
To make a non-linear sequence, we can apply "run-length encoding". What this does is, instead of 1:1 sequences of symbols, we encode "runs" of the same symbol by noting how many times it occurs. So repetitions become compressed.
So if we have a sequence like:
a a a a a a b b a
We can rewrite this as "6 a 2 b 1 a". The more the data repeats, the better this strategy performs.
Rewriting our symbols produces a different strategy: Dictionary compression.
Here, imagine your basic symbols as the letters of the alphabet. What you are doing is finding specific combinations of letters and saying, that is the word "cat" - I'll assign it to symbol 1. And so for each occurrence of "cat" you only have to write 1 instead. Applied with a whole dictionary of words you can achieve a high compression ratio.
What most general-purpose compression algorithms do is a combination of these strategies: Find reoccurring blocks that make good dictionary words. Then encode all the runs of those words. To improve ratios further, contrive ways of rearranging the blocks so that they can form larger run lengths. This pretty quickly turns into a specialized kind of thing since tuning for maximum ratios works best if you have a really good understanding of the patterns of the data - and compression for large media resorts to "lossy" encoding strategies that reconstruct something perceptually similar without having exactly the same content.
Hopefully that gives you some starting points. It's easy to overengineer your algorithms and have something that's complicated and hard to test: that's why it's worth just having the linear strategy as a baseline so that you know the basic process works before you optimize.
Hey Triplefox:
That's exactly what I'm doing in my compressor. But instead of "6a2b1a" it's more like 3-characters to represent a repeated mark up to 4096 iterations and patterns, saves space as you don't have to use 4-characters for it instead and you are also allowed to use digits in addition to letters as data.
The BEST way to compress map data, Kikendo ?
You can take entire worlds and break them down to single long integer numbers auto-created from the computer and used as the foundation of all creation. For instance, that dungeon scene above ? I could use a single long integer number to represent absolutely everything that was created in it. Same rooms, doors, treasures, traps, critters, and stairs - all in the exact same position created from that single number.
Again I want to clarify I don't need to make world/level map data, I am trying to figure out where to put enemies and collectibles in each level.
Hmm I kinda like bab_b's idea of using the unused map data as enemy data, I just don't know how much info I can realistically put it this way. As I have an increasing number of objects to lay out, this might turn short. Yeah 4KB seems a lot, but maybe, it isn't. The parameters each object should have are:
- which general "section" of the screen they appear first in (always the same, out of 4 possibilities)
- the speed the object moves at
- the movement type the object does
Might have to get into compression and really learn something here. Thanks Triplefox for your explanation!
If that's the case, Kikendo. You could always create a new SPRITE, that is the image of a critter or some other moving object that appears on the map but actually has a true identity when the entire map is scanned for the first time.
Use a function to recall the specific map data, then place object instances (enemies/collectibles) in that instance of map space.
Alternatively, you can make keypoints on the map to replace with an array of objects, and then wrap the map recall function in another function that then replaces those keypoints with an array of objects (or populating them with a probability function).
If I understand correctly, it sounds like you're trying to handle with one system what is normally handled by at least a couple different systems. Instead of a system that handles both object spawning and object behavior, you should probably consider separating them. Also, the implementation of each system will depend on whether you want levels that are exactly the same each time the game is played (i.e. hard-coded), or levels that are more procedurally generated.
In either case, you'll probably want to store objects active on the current level in their own table; one table for collectables, and another for enemies. Within each of these tables, each element will be another table representing each object, where its elements are the object's properties (e.g. enemy type, speed, movement type, etc.). Then, you'd have a function that iterates through these objects each update cycle to control their behavior.
Then, separate from that, you'll want a system to determine where objects are placed in each level. If you go with hard-coded levels, you might try a table of "spawn points", where each element of the table is its own table with X/Y coordinates and the type of object that should be spawned. This will take up token space proportional to the number of levels. If you go with more procedural levels, you can simply iterate through the coordinates of your level, determine if it's appropriate for an object to spawn there (i.e. solid space vs. non-solid space), then "roll a die" to control how often that object appears (e.g. if rnd(100) < 11 will spawn the object ~10% of the time).
[Please log in to post a comment]