Hi! I'm attempting to build my first game using PICO-8 and am having some trouble with finding an online tutorial for this specific issue.
I'm looking for a simple function that will help make a defined object spawn periodically according to a timer. I've created a moving sprite and a frame counter that counts to 600 and repeats, and I'd like to have a specific enemy spawn on frame 600, instead of just spawning at the RUN command. Any suggestions?
I've included my code below, but it's admittedly very messy, as this is my first experience with programming outside of HTML. Also, if it's out of line to just hop on the forum and start pleading for help, let me know too! :P
--nessie player={} player.x=64 player.y=64 player.sprite=1 player.speed=1 --redbads --bluebads --timer --spawncount function _move() function _direction() function _screenedge() function _dive() function _surface() function _redchase() function _timer() function _spawncount() function _bluedice() function _bluemove() function _update() function _draw() --draw nessie --draw redbad --draw bluebad --draw framecount (del later) --draw spawncount (del later) end |
I've been using a technique called a closure.
Function make_counter(limit) Local n = 0 Return function() If n >= limit then n = 1 Else n += 1 end Return n End End Counter_a = make_counter(100) Counter_b = make_counter(10) |
Then call counter_a() like a function, it will increment every time it is called and return it's current count. When it hits limit it will reset to 1. I'm doing this on my phone so I might have messed something up but I think that should work. You can make more elaborate counters with the addition of more parameters
Wow, your 'dumb cart' looks pretty smart to me! That sort of timed spawn is exactly what I've been trying to do for days now. I'll analyze the code for your cart first thing in the morning, and I'll check back in once I make some progress! Thanks gcentauri! :)
I am actually interested in trying to make good tutorials for basic stuff like this, and yours is a good question. of course there are several ways you could tackle this problem, and i said my cart was "dumb" because in the end i did the simplest way to spawn sprites i could think of. Just have a frame count on a loop and have a function in _update() to add new bad guys to some global list of bad guys if FRAME == SPAWN_FRAME or whatever.
But there's a smarter way to do all of this so that any bad guy you want to create can have its own spawn rate and spawn function.
I'm working on making a commented cart to try and introduce some ideas, but I'm realizing there's several different topics to cover. First, I think it might help to point out how Lua can use tables. The table is the only data structure available, and it changes depending on how we use it. in one instance we want to use tables to represent sprites, you already did that by making a table for the player and redbad and bluebad. If we step back theres sort of a general pattern to represent a sprite:
{sprite, x, y} |
thats where my function MAKE_BADDIE comes from. it could be MAKE_SPRITE or MAKE_ACTOR or MAKE_ENTITY... you may see those kind of things in other carts too. And we can add more data to the table as well. For instance, we probably want to add the width and height options in case we have sprites larger than 8x8. But for now we'll keep it simple. The main thing to notice is that you're creating a chunk of data in a table using the KEY:VALUE version of tables. You could think of these tables as 'objects', but I think of them as 'structs' or structures.
When we make a struct, we can now write functions to do things to that struct. The simple example is DRAW_BADDIE. I know that a BADDIE struct has values for SPRITE, X, and Y. Therefore, i can make a function that takes in a baddie table and uses the SPR() function to draw it. Here's a function that can take in a baddie table and move it by a change in x or y or both.
function move_baddie(bad, dx, dy) bad.x += dx bad.y += dy end |
this could work for any table structure that has values for the key x and y.
the other way we can use a table is as a list, or array. thats what i do with the BADDIES table in the _INIT() function. We start out with an empty table:
baddies = {} |
then as the game progresses we add baddies to the table (we could also have game logic for removing them as well)
add(baddies, make_baddie(0,64,64) |
so we have a table called baddies that holds other tables which are structs representing our baddies. but the table baddies doesn't have a key:value structure. instead it is an array with each item indexed with an integer starting at one. after we added the baddie, we can now access it by BADDIES[1].
the benefit of the array structure is that we can do something to each item on the list with FOR loops. so for every baddie on the list BADDIES we can DRAW_BADDIE:
foreach(baddies, draw_baddie) |
FOREACH provides each item in an array to the function as an argument. this won't work for MOVE_BADDIE. instead we would need to use a for loop with ALL:
for bad in all(baddies) do move_baddie(bad, rnd(6)-3, rnd(6)-3) end |
try adding in the move_baddie function and then put that for loop in _update()
thats it for now, gotta go! what i'd like to do is figure out how to make monster spawners that could have their own counter that could add new baddies to the BADDIES table every time their counter looped. then you don't necessarily need a global frame counter, which can be an issue in Pico-8 (if the number gets too big there's an overflow)
okay. this closure concept is slightly complicated, but if you really check it out you'll see the spawny thing is quite simple in the end.
update i fixed some typos and added a few more comments
dont be afraid to ask questions, and i don't intend to "do your work for you", this was just a good opportunity to demonstrate one way to solve your spawning problem involving a technique i've been curious to see in action.
Hi gcentauri! Thanks for all the suggestions here! I've been working hard at this all afternoon, trying to make this code work with my own code, but it's starting to be apparent that I might just not have enough of a grasp on concepts like tables, and terms such as 'for', 'do', and many of the other functions that are being used here. Perhaps I need to sit down and do a little more homework on Lua before I proceed again; I really do appreciate all the help, and hopefully I'll be able to tackle this once I expand my understanding of these concepts a little more!
Oh wow, I didn't see that new tutorial before I posted! I'll check the new cart out in the morning; it looks like it has a more detailed tutorial explaining things, so I might have a better chance at understanding the process. Thanks so much gcentauri! I honestly didn't expect so much assistance when I posted my question here!
Glad to help. It's good practice for me to apply what I have learned. The Lua manual is good, and not difficult. The main thing I needed to start understanding programming was the distinction of data and code. Functions operate upon data. Data has different types, and depending on the shape or structure of our data we tend to do different things with it. But there are common patterns. FOR is one pattern. Its intended to loop over a collection of data, like... Say a list
list = {"a", "b", "c"} |
This type of table is a Lua array and we can access each value in a FOR loop with the ALL function:
For each_item in all(list) do Print(each_item) End |
This does exactly what it says. For every item in the entire list do the function print on each item.
We constantly have big collections of data objects to do the same thing to, e.g. draw sprites, move sprites, check if enemies are dead etc... So the for loop is really important to try and understand.
i fixed some things in the comments of the spawner cart. it'll run out of memory eventually if left running
It's finally working! I nearly gave up a hundred times, but it's working! :D
The current code is almost completely cut & paste from what you sent me, but each time that I tried to combine it with my own work, I ended up with syntax or logic errors all over the place! So, I decided to import some of my own work into the stuff that you sent me, and here we are! I read all your notes and did my best to internalize the lessons; understanding 'local' and creating functions that are defined by other functions will help me improve how I write things for my next project. But for now, it's working, so I'm going to proceed and try to make the sprites behave how I want them to.
I posted a little gif of it over at my Twitter, if you'd like to see!
Alternately, in the future I could just post a cart here in the forum post, but that's another lesson for another day. Thanks so much for your help, gcentauri! You're a total gem!
One more question! You mentioned that it's very important to understand the distinction between data and code, and that functions operate upon data. What exactly is the difference between data and code?
Data is what we use to represent stuff. Numbers, strings, pixels, sprites, binary, hex, etc. The code is how we process that stuff, to have it change over time or interact with other pieces of data. In math terms, you could think of it like code is functions while data are the actual numbers input and output by the functions. f(x)=x^2. you can imagine applying that function to a list of numbers to be x. in lua we can represent it like this:
number_list = {1,2,3,4,5,6,7} function square(x) return x * x end for number in all(number_list) do print(square(number)) end |
so there's three things here. first we describe our data. we make a variable called number_list that points to a table organized as an array. in Lua code we create this table by using curly brackets. it automatically indexes each item between commas with an integer starting at 1. number_list[5] would return the number 5.
next we have a function that expects a number as input. if we try to square("foo") it won't work, because we don't know how to multiply the string data type to itself. it should be easy to see what the square function does.
next we have a FOR loop that takes every item on the list using ALL(number_list). ALL() only works for tables that are organized like an array. if we had a different kind of table we might use PAIRS(). This is how we can take a function like square(x) and use it to get values from a whole list of numbers at once.
now one way to think of making your game is how to describe the data of your graphics. we can use several ways to do that. so far we are using a table as a list or array to hold all of the bad guys. baddies = {}, we also represent bad guys as a table too. baddie.sprite = sprite_id, baddie.x = x baddie.y = y... etc. that is a key:value table, or hash table.
the thing is, data and code are different, but the same. we use code to represent data, and code can be data too. the spawner function creates a piece of code that is another function which also holds data inside it as local variables. there's another, perhaps, more straight forward way to do this, and i'm going to work on another cart.
meanwhile, maybe you'd like to check out this demo cart i made for some friends to introduce tables, you ll have to actually run it in Pico 8 and uncomment stuff to use it:
ok. there might be more stuff in there i didnt explain too well, but i think its pretty good. this cart uses a more straightforward way of using tables to act as spawners, instead of a closure. i also included lots of comments describing the idea behind how we do this.
Hey gcentauri! As the game I was working on was for My First Game Jam, I dropped my initial idea and scaled back the whole thing, as I had definitely bitten off more than I could chew, and I wanted to finish a game within the time frame. I'd definitely like to make a proper action game with enemy spawning sometime this year though, so I'll certainly revisit all of your tutorials when that time comes! Thanks again for all the help in this thread, you've been amazingly helpful!
[Please log in to post a comment]