Log In  

So this will by my introduction. hello community. I need your help.
so the game I created is a platforming game. for which I designed a external level editor in c++.
my theory is. I can really only fit one level into the pico 8 map editor.
but if I parse my map data as a string, despite adding a few extra thousand lines of code to my file, I will at least have a streamlined way to create and import levels. so far the level editor works great. I tell it how big I want the map and then once I have placed all my tiles I can save it to a text which just writes the x, y position and a type variable. 3 variables in total. so my output file ends up looking something like this

x y t
0 8 0
1 8 0
3 8 0
2 8 0
4 8 0
6 9 0
7 10 0
9 12 0
11 9 3
13 8 0
15 8 0
17 7 0
18 6 0

there has GOT to be a way to parse this data. in regular lua it would be something like

platforms={}
function _makeplatform(x,y,t)
pf.t=t
pf.x=x 
pf.y=y
end
function load_platforms(s)
    local str = s
                for line in string.gmatch(str, "%.+\n" do -- for every line in the string
         for a,b,c in string.gmatch(line, "(%d+)(%d+)(%d+)") do --for every number in the line
          pf =_makeplatform(a,b,c)
                  add(pf, platforms)
         end
                end
     k = 0
end
level1 =[[ 
--copy and paste level output here
]]
function _init()
load_platforms(level1)
end

the big kick in the pants being string.gmatch() not being in pico8.
the good news is, I can basically output this information any way I want since I authored the editor. but whats the nicest way to have pico8 read it in?
my best guess with sub() is to make each number 4 chars long so '2' would be '0002'. that way I can iterate through every 4 numbers. not sure if I like the sound of that solution. so here I am.

this is the level editor.

and here is a very early release of my game. please excuse me for not uploading it to BBS. I am still figuring all this out but I swear once the game is playable again I will put it there.

EDIT:
I just added a Work in progress version of the game to BBS. so maybe it will become more clear what I am trying to accomplish. pease feel free to check it out.

Cart #runrun-0 | 2020-09-23 | Code ▽ | Embed ▽ | No License

P#81948 2020-09-18 04:12 ( Edited 2020-09-23 21:23)

If you prefer the way the code looks with gmatch, you could implement an equivalent yourself. It looks like all it's doing in your code example is checking for 2 delimiters (space and newline), which is easy enough to do. You'd end up having to use sub in a loop regardless, since I don't recall seeing any other options included in Pico-8. It would be less efficient to parse, but it might be better for saving space. That said, the number of 0's being repeated in the 4 char version would likely make your map data compress well.

In either case, I doubt there'd be any noticeable loading time unless you have a ridiculous amount of instantiation code for your objects. As such, whatever way you like that works is probably best.

P#81953 2020-09-18 08:40

Let's see. If we limit the map size to 256x256 and to a maximum of 256 tiles, you can use bytes for everything.
Right now you need 3 bytes per tile, which should work great if your map is mainly transparent. 5 bytes would allow yo to store lines of tiles, which might be a good way in your typical platformer, since even a two tile line would take 5 bytes instead of 6. Longer lines would save more. Esp if you only store the base tile type and choose fitting variation programmatically.
If you want to decrease you data size, switching to base64 encoding and packing the bits might be an option.But that would mainly depend on what you want to do.

What is the problem you want to solve using your editor and format?

P#81968 2020-09-18 21:54

@kimiyoribaka I might have made a mistake with that first gsub but its just supposed to be everything before a /n newline. the important gsub is the second one which takes in 3 digits at a time. a, b , c = 1 , 2 ,3 so, so convenient. some of the things I am unsure of is what limits pico 8 will have with the string size. if I have to read one number at a time then would the file data need to be one number every line? Yikes! how about copying one big long string into pico8? double yikes. I want to shoot for 3 strings of level data. maybe 1000 platforms each.
I will give the 4 char digits a go. because I could use gsub to predict the length of any number of characters therfore giving me the freedom to make the data however many rows or columns I want. the more I think about it the more it makes sense. thanks so much for your reply.

P#81971 2020-09-18 23:07

@spellcaster that is a very fascinating solution. choosing the variation programatically is exactly what I do.
the level editor in pico 8 is big enough for perhaps 1, playable level. which is what I have found in most situations for most pico8 games.
however, my level design is simple enough that the t value will generate everything from the width, height, friction, etc.. so I theorized that I could potentially parse several strings of "leveldata" into my game therefore circumventing the map limitation, and see how many level string I could paste into my script before hitting some sort of limit.
there are other aspects of your comment, however, that lead me to believe there may be some other limitations I was un aware of.
namely, I am going to have to research how bytes are used and resourced in pico 8.
something about the low level nature scared me a little, but as if figuring out these charming programming secrets otherwise obscured in advanced architecture isn't half the fun of pico8 in the first place!
thanks for your reply.

P#81972 2020-09-18 23:23

I don't think there's a limit on string size. There is a 2 MB limit on general ram, but it takes a pretty reckless usage of memory to hit it with just map data. The main limits you'll need to consider are the maximum code size and the maximum compressed code size, both of which can be hit pretty quickly by using large strings of data.

"how about copying one big long string into pico8?" I actually did that for the game I made a couple months ago. The final build used 5 maps that were each 4k-5k character long strings. And that was after I switched away from the method that made them 8k long. I also experimented with porting a game from a different engine that used a custom mini-script, so I can verify that a 30k length string works.

P#81977 2020-09-19 01:28

Yeah, for efficiency you'll definitely want to reference groups of tiles instead of individual ones, preferably 2-dimensional ones that can define objects or level segments. That's the basic concept behind metatiles, which they used in old console games, and I used it in my Super Mario Bros. demo to fit all the levels in under 4KB.

https://www.lexaloffle.com/bbs/?tid=39469

In simplest terms, I just access specified rectangles of tiles from a section in map data that's used as a work area, but I managed to use just 4 hex characters per 'object' (2 bytes of binary in the final cart) using a few tricks, such as a lookup string that contains workspace rectangle locations and sizes referenced by a single 1-byte ID number, and making level map position values relative to the last one, so they only take 4 bits each. I also found that if you pack the level data tightly like this, it's more efficient to store level data in the map area and the sprite sheet in a string, because the latter compresses better. That's for final cart storage, though. It's definitely easier to work with and edit levels in string form.

P#81980 2020-09-19 04:41 ( Edited 2020-09-19 19:07)

@kimiyoribaka that is good to know.
I am suprised to hear that about the string length, I always just assumed there would have to be a break in the line somewhere. really? a 4k long one line string? but am excited at the prospect that I am going to get away with my level design strategy.

@JadeLombax well this is neat. I actually referenced your Super Mario Bros demo when I was first figuring out collisions.
oh boy, looks like I have some further digging as I barely scratched the surface.
I am having a hard time understanding how you assign meaning to all of those glyphs, in an un ambiguous way. and
whats the criteria for how long a string gets before you break it too a new line?
Im curious how much of that gobbledy gook string represents say one 'workspace rectangle' and how many tiles would one screen space rectangle take up?
my game was going to have the platforms scroll by, so making the platforms position relative is actually what I plan on doing. but I was going to do it like
newPlatform.x - oldplatform.x. which now clearly seems like a waste.
adjusting the output to relative x and y will keep all the digits under 10.

I will have to take a closer look at your code because I am a little floored by how much you are potentially able to represent with a 1 byte lookup number.

P#82029 2020-09-20 21:44 ( Edited 2020-09-20 21:45)

@Cieroceramics

I hope you don't take the collision system there as too much of an example. It has a few bugs to iron out, partially because I'm not that experienced with map collision, partially because I'm using an unusual camera offset to allow feeding a single screen's worth of a level map into a corner of the map area each frame so I'm not limited to 8 screens in width. I'm thinking about starting a thread and asking for some help working out the kinks.

To answer your other questions, the demo doesn't actually store any level data in strings, the string containing all the glyphs actually contains the graphics from the sprite sheet, which are uncompressed using a version of run-length encoding on startup. The glyphs themselves are numeric values expressed in terms of Pico-8's extended character set, each one corresponding to a sequence number which is evaluated using the ord() function (the -96 offset cuts the usable range down to 160 characters, but removes all problematic ones). As far as I know there's no cutoff for string length, as long as you don't go over the 65,535 character limit.

The level information is all stored as binary data in the upper half of the map area. Each object is called using a 2-byte description: a relative x value (0-15), a screenspace y value (0-11), and a one-byte ID number. The ID number is then plugged into a hex object lookup string, called "os". This gives the map coordinates, default width and height (each can be 1-16 tiles in size), and (for the objects whose dimensions can vary, since there are only 120 object types, but 256 possibilities), the amount of variance from the default width and height. There's a few more tricks in there, but that's the main gist of things.

The string called "scratchpad" contains the rle-encoded data of the "palette of objects" in map data that is pulled from to build the levels, here's what that looks like when it's decoded:

:

The string called "tbl_loc" (short for table location) lists the byte addresses and byte lengths of the binary data for each level so they can be directly referenced. This is converted into a table using the split() function so that each can be called up directly instead of iterating sequentially through the whole string.

Hopefully this isn't too confusing. I designed the demo to be very space-efficient, so it's not easy to read. I also may be doing things in an unorthodox way, as I hadn't done much programming before starting with Pico-8. Aside from a few tutorials, I've largely been figuring things out on my own, and the biggest help to me in developing my current system has been this page, which gives basic descriptions of some level compression schemes in NES games:

http://wiki.nesdev.com/w/index.php/Level_compression

P#82035 2020-09-21 00:27 ( Edited 2020-09-21 05:43)

@JadeLombax haha not anymore. you were one of the only people who didn't try to encapsulate collisions into luas OOP, which was helpful for me to learn. I looked through many examples trying to figure it out. I have rewritten that part probably a hundred times since then. so your influence is likely minimal by now

I'm going to have to boot up your cart to see your sprite sheet. I was just looking through the code for now.
I am so sooo sorry if I am not understanding correctly but it seems like from what you are saying. the os string where all the object locations are stored?
I am familiar with index lookup tables from doing .OBJ loading.
my brain always gets lost when it comes to index lookups though.

by @kimiyoribaka s suggestion I was able to load in objects from the string and have them appear in my game "yyayyyy!"
well I kind of cheated though. I didn't adjust my level editor yet to save to proper format. I just fiddled with the numbers until it worked.
also that was only with 20 platforms. and they are all being processed at the same time. so I am going to have to figure a way to "recycle" the recently off screen platform and draw it at the next location in the string.

P#82054 2020-09-21 17:16 ( Edited 2020-09-21 17:19)

Yes, the object string "os" contains the definition of each object, namely its location and default dimensions in tiles.

As for the spritesheet, you won't be able to easily view that using the demo cart, as it's only in viewable form when the cart is actually running. To optimize storage, I stored everything besides the level data in strings because it was all compressible. When the program is run, the whole memory map basically transforms via a series of operations in the init() function, with the spritesheet and object 'scratchpad' being transferred into pico-8 memory, and the level data transferred to a table in Lua memory. This enables straighforward use of sprites, the 'scratchpad', and an area in the map region which is used as a framebuffer of sorts, while allowing all 12KB of graphics and map storage to potentially store level data.

Anyway, here's what the spritesheet looks like when it's decoded. It takes up 6KB worth of spritesheet space when uncompressed, but compresses down to 2.36KB in the code section via a custom RLE function which handles both sprite and map decoding. This is about 2-3% larger in compressed size than if I were using Zep's PX9 compression, but the decompressor code's a lot smaller and takes a third of the tokens (and yes, I used some of the Jelpi spritesheet as filler to approximate the data requirements of character sprites I hadn't made =p).

P#82076 2020-09-22 02:32 ( Edited 2020-09-22 03:00)
:: merwok
P#82116 2020-09-22 16:49

I have a somewhat working solution at the momemnt, but some changes will need to be done.
I ended up using a 4 char format for my positions (x, y) and platform type (t). concatenated into a single line of 12 chars. the string itself looks something like

[[
05000500
001500450000
001600450000
001700450000
001800450000
004300500001
004800460003
005000510000
... -- many many lines. 
]]

the 8 digits at the top are for the external map editor to know the width and height, in this example its w=500, h=500.
the following lines of 12 digit numbers are all x, w, t values followed by a carraige return '\n' making it 13 characters in total. the string is fed into the following function.

function load_platforms(s)
    local str = s --store the value into a string
    for i = 10, #str, 13 do -- start after the w/h values. increment by 13 to the end of the string.  
        line = sub(str,i, i+13 ) -- get the string between i and i+13
            a= tonum(sub(line, 0,4))*8-- store each value into seperate integers. 
            b = tonum(sub(line, 5,8))*8-- multiply by 8 to convert to tile coordinates. 
            c= tonum(sub(line, 9,12))
            _makeplatform(a,b,c); -- make a platform with the given x, y coordinates and t type. 

    end
end

while this works well on smaller scale levels, when I boost the numbers up past 400 I have some major problems.

the viewport itself is static. the levels are auto scrolled, so instead of having a camera pan across the screen, the platforms and character themselves are having their x values -1 every frame. (kind of a panic level vibe)

function _updateplat(p)
        p.y-=player.dy -- update the y coordinate based on the characters position
        p.dx=t -- t is 1
        p.x-=p.dx - move the platform x to the left.
        if(p.x<=1) then -- if platform is off screen
         recycleplat(p) -- recycle the current platform into the next one on the list. 
        end
end

this does not work well when the y values start in radically different places.
I want the player and platforms to converge at the middle somehow.
@JadeLombax made the suggestion to just have the relative x y values instead of absolute, which would not only further compress my string size, but could also potentially solve this problem. as the first platform would always be relative to the players starting pos. but alas.
lazy programmer syndrome persists and I was hoping to find a way to do the fancy math stuff inside pico8 with the absolute values instead. something stupidly simple like plat.y= plat.y-(firstplat.y +player.y).

P#82133 2020-09-23 03:26

Even if you don't move the pico-8 viewport, I would recommend keeping a pair of global offsets around and drawing everything in a different spot instead, as if still being relative to a camera position. That way there's less chance that anything could go out of sync.

In case you haven't done manual relative drawing before, all you would need to do is subtract the relevant component of the camera position from the position of the thing you're drawing. (ie. drawing the plat at coordinate (plat.x - cam.x, plat.y - cam.y), assuming you prefer your camera to be handled as an object)

P#82142 2020-09-23 10:05

@kimiyoribaka I am having a hard time figuring out how to go about doing this.
the character itself already acts as a sort of offset for everything on the screen so wouldn't that be redundant?
I'm a bit of a messy coder but I uploaded a working progress of my game to bbs so it might be more clear as to what I am trying to accomplish.

Cart #runrun-0 | 2020-09-23 | Code ▽ | Embed ▽ | No License

P#82190 2020-09-23 21:32

I'm not that experienced with forums, but this part of the game seems like it could use a separate thread, either as a work-in-progress thread or a discussion of camera methods thread.

Seeing your game in action did clarify things a bit. I think if you don't feel the need to make the camera more complicated than just following the the player character, it's fine to leave it as is. Your game seems to be working pretty well, though I think some tweaking could help. The cost would for the platforms following the player is just that the camera will not be able to as easily consider player y-position on the screen like a lot of platformers do (which I'm not sure is needed given how frantic your game is). If you do want to use the camera object instead, it would mean needing to replace the scrolling adjustments to player position and platform with adjustments to the camera object and adjustments in where you draw things.

I think this may be a case of my preferred games biasing my assumptions. I play more puzzle platformers which tend towards consistent levels, so having the auto-scroll change speeds hadn't occurred to me.

The 2 concerns I have looking at your game are this: 1. I suspect the use of pl.dy as the basis for scrolling the platforms vertically has the effect of doubling the functional vertical speed of the player character. (because player moves down at the same speed as the world itself moves up.) this isn't a problem, but it is something to consider if you need to adjust the mechanics or reuse them in a later game. It looks like the reason it doesn't affect the horizontal speed is because the variable t is the actual basis for moving everything rather than the player directly. 2. The platforms are getting out of sync with regard to the sub-pixel rounding, probably as a result of having a different decimal portion to their positions each time. By the time I get to 50 platforms, nearly all the platforms jitter. Again, this isn't a problem here since it's 1 pixel of jittering, but it means that the place the player is exploring is increasingly unstable, as if it's collapsing, which would need to be considered for theming and/or narrative (making it not a good idea to re-use whatever is causing it in a different game or in a less panicky part of the same game).

By the way, your game is already pretty fun, especially by Pico-8 standards.

P#82204 2020-09-24 03:22

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2020-10-24 01:00 | 0.202s | 4194k | Q:74