Spawning and Managing Objects - nesbox/TIC-80 GitHub Wiki
Most games require objects to be dynamically created. It might be enemies that appear periodically, or coins that sprout out of boxes...either way you need a method for creating things, without having to manually place every one of them in a game. This tutorial deals with spawning and managing multiple objects.
We will write a game where sheep will spawn when the player presses A...each sheep will walk in their own direction. If they hit a wall they will change directions. If they fall in a hole, they will die and be removed from the game.
https://tic80.com/play?cart=249
You can play the completed game here:We want the sheep to be able to move on their own, so we need to make some direction constants...
U={x= 0,y=-1} --up
D={x= 0,y= 1} --down
L={x=-1,y= 0} --left
R={x= 1,y= 0} --right
DIRS={U,D,L,R}
We need to make our objects and store them in a table...so let's make an empty table
--create an empty table for storing our
--objects as we create them
sheeps={}
Now we can write some functions to handle building and updating the objects... random numbers are used constantly throughout game logic. so having them easy to get is a must...
--before we do anything, I always make
--a shortcut for getting random numbers
-- make it a local function so it's
-- faster to execute
-- could also just write
-- `local r=math.random`
-- but for the sake of showing our work
-- make it more explicit
local function r(a,b)
return math.random(a,b)
end
now we need a function to build our objects. We want to test the object for certain flaws (like being spawned inside a wall or too close to the player) and log it into our table we wrote earlier.
function build_sheep()
--we want the sheep to pop up in a random
--spot when this function is called. so
--let's pick a random (x,y) to start
local tx=r(MINX,MAXX) --temporary x
local ty=r(MINY,MAXY) --temporary y
--now we need to check if the chosen
--(x,y) is already occupied...
--to do this we'll just read the map
--cell at the given coor and check if
--it's a "solid"
if mget(tx,ty)>=WALL then
--the cell is solid so recall the
--build_sheep() function again to
--start over
return build_sheep()
end
--now that we tested the coors, let's
--build a sheep.
--store the sheep in the temporary "z"
--variable...
local z={
x=tx,
y=ty,
d=DIRS[r(1,4)]}
--and now we can store it in the "sheeps"
--table
table.insert(sheeps,#sheeps+1,z)
end
if you're confused about how I'm testing map cells for being "solid" then check out my other tutorial on collision detection. It will fill you in. https://github.com/nesbox/TIC-80/wiki/Simple-Collision-Detection
now notice that each "sheep object" has three attributes, x coordinate, y coordinate, and direction. but an object can have as many attributes as you need. You can specify the sprite to use for it, the gender of the object, friend or enemy, health, speed, strength...etc. This is just a method of reading an objects unique attributes, and doing something different for each one. So next let's write a function to move and kill them!
to do this, we need to use a for loop to read the info from each object.
--this move function will iterate
--over all our sheep and update them
--based on their personal information
function move_sheep()
for id,sheep in pairs(sheeps) do
--first up, if a sheep somehow spawned
--inside a wall...let's remove him
if mget(sheep.x,sheep.y)>=WALL then
table.remove(sheeps,id)
end
--log the sheeps next coors...
--where he is plus where he's going
local tx=sheep.x+sheep.d.x
local ty=sheep.y+sheep.d.y
--test for obstacles...
if mget(tx,ty)>=WALL then
--pick a new direction if the sheep
--ran into a wall.
sheep.d=DIRS[r(1,4)]
else
--the area is clear so move him there
sheep.x=tx
sheep.y=ty
end
--now we can check for a hole and
--delete the sheep if he has fallen in
if mget(sheep.x,sheep.y)==HOLE then
--here we can use the id to remove a
--specific sheep from the list.
table.remove(sheeps,id)
end
end
end
to understand what's going on here, we need to look at the pairs() function. it takes a table as an argument, and returns the key, and the value. for example...
list={"spam","toast","blah"}
so if we called:
for key,item in pairs(list) do
the first time the for loop is executed...
key=1 --the index of the value
item="spam" --the value of the index
the second time the for loop is executed...
key=2
item="toast"
in this way we can use the "key" variable to remove the current item.
this may be redundant for anyone fluent in lua, but I decided to include it because I hadn't used pairs() before working with TIC-80.
finally we can draw all of the updates to the screen for the player to see!
--last we need a draw() function for
--updating the screen
function draw()
cls()
map(0,0,MAXX,MAXY)
--we need to iterate over the sheep and
--draw each at their personal (x,y)
if #sheeps>0 then
for id,sheep in pairs(sheeps) do
--multiply the coors by 8 to place
--sheep correctly on the map
spr(SHEEP,sheep.x*8,sheep.y*8,0)
end
end
end
and now we can write the game loop (FINALLY!)
t=0 --time
function TIC()
t=t+1
--every time we press a button it will
--spawn a sheep!
if btnp(4) then build_sheep() end
--frameskipping to slow game down
if t%8==0 then
if #sheeps>0 then
move_sheep()
end
draw()
end
end
and here's the completed code (with all the parts included that don't have to do with spawning)
-- title: Spawning
-- author: Bear Thorne
-- desc: Spawning and managing objects
-- script: lua
--lonliness is my protege and binds me
--to myself -
--VARIABLES
--first let's declare some sprite vars
SPACE=0
GRASS=1
HOLE=2
WALL=17
SHEEP=33
--and to move the sheep around aimlessly,
--we'll want some direction constants
U={x= 0,y=-1} --up
D={x= 0,y= 1} --down
L={x=-1,y= 0} --left
R={x= 1,y= 0} --right
DIRS={U,D,L,R}
--these help keep track of map size, and
--makes scaling the code up or down a cinch.
MINX=0 --minimum x coordinate
MAXX=29 --maximum x coordinate
MINY=0 --minimum y coordinate
MAXY=16 --maximum y coordinate
--create an empty table for storing our
--objects as we create them
sheeps={}
--FUNCTIONS
--before we do anything, I always make
--a shortcut for getting random numbers
function r(a,b)
return math.random(a,b)
end
--this function will build our objects,
--test them, and if they pass, build 'em
function build_sheep()
--we want the sheep to pop up in a random
--spot when this function is called. so
--let's pick a random (x,y) to start
local tx=r(MINX,MAXX) --temporary x
local ty=r(MINY,MAXY) --temporary y
--now we need to check if the chosen
--(x,y) is already occupied...
--to do this we'll just read the map
--cell at the given coor and check if
--it's a "solid"
if mget(tx,ty)>=WALL then
--the cell is solid so recall the
--build_sheep() function again to
--start over
build_sheep()
end
--now that we tested the coors, let's
--build a sheep.
--store the sheep in the temporary "z"
--variable...
local z={
x=tx,
y=ty,
d=DIRS[r(1,4)]}
--and now we can store it in the "sheeps"
--table
table.insert(sheeps,#sheeps+1,z)
end
--next we can write a function to iterate
--over all our sheep and update them
--based on their personal information
function move_sheep()
for id,sheep in pairs(sheeps) do
--first up, if a sheep somehow spawned
--inside a wall...let's remove him
if mget(sheep.x,sheep.y)>=WALL then
table.remove(sheeps,id)
end
--log the sheeps next coors...
--where he is plus where he's going
local tx=sheep.x+sheep.d.x
local ty=sheep.y+sheep.d.y
--test for obstacles...
if mget(tx,ty)>=WALL then
--pick a new direction if the sheep
--ran into a wall.
sheep.d=DIRS[r(1,4)]
else
--the area is clear so move him there
sheep.x=tx
sheep.y=ty
end
--now we can check for a hole and
--delete the sheep if he has fallen in
if mget(sheep.x,sheep.y)==HOLE then
--here we can use the id to remove a
--specific sheep from the list.
table.remove(sheeps,id)
end
end
end
--last we need a draw() function for
--updating the screen
function draw()
cls()
map(0,0,MAXX,MAXY)
--we need to iterate over the sheep and
--draw each at their personal (x,y)
if #sheeps>0 then
for id,sheep in pairs(sheeps) do
--multiply the coors by 8 to place
--sheep correctly on the map
spr(SHEEP,sheep.x*8,sheep.y*8,0)
end
end
end
t=0 --time
function TIC()
t=t+1
--every time we press a button it will
--spawn a sheep!
if btnp(4) then build_sheep() end
--frameskipping to slow game down
if t%8==0 then
if #sheeps>0 then
move_sheep()
end
draw()
end
end
Feel free to use any portion of this code in your own projects, if you're feeling generous, put a credit to me at the top of your file!
if you have questions or requests for future tutorials...you can email me at [email protected]