Skip to main content

Pico-8 Top-Down Adventure Game Tutorial

This is a writeup of the tutorial published on YouTube by Dylan Bennett. The original work is all by Dylan Bennett and not by me.

Please go here for the original pages:

You can view the whole playlist here.

Chapter 1 - The Map

Draw nine sprites:

  • grass, fancy grass and rock

  • water, fancy water and water rock

  • road, fancy road and wall

Toggle sprite flag 0 on rock, the three water tiles and wall.

Prepare two tabs of code.

Game Loop

Create the main game loop.

--game loop

function _init()
 map_setup()
end

function _update()
end

function _draw()
 cls()
 draw_map()
end

Map Code

Create code that draws the map in a new tab, map code tab.

--map code

function map_setup()
 --map tile settings
 wall=0
 key=1
 door=2
 anim1=3
 anim2=4
 lose=6
 win=7
end

function draw_map()
 map(0,0,0,0,128,64)
ends

Chapter 2 - The Player

Draw a sprite for the player.

Add a new code tab for the player code tab.

-- player code

function make_player()
 p={}
 p.x=1
 p.y=1
 p.sprite=1
 p.keys=0
end

function draw_player()
 spr(p.sprite,p.x*8,p.y*8)
end

Call these functions from the game loop tab.

--game loop

function _init()
 map_setup()
 make_player()
end

function _update()
end

function _draw()
 cls()
 draw_map()
 draw_player()
end

Chapter 3 - Movement

Create a sound effect for bumping into an obstacle.

Add two functions is_tile and can_move to the map code tab.

function is_tile(tile_type,x,y)
 tile=mget(x,y)
 has_flag=fget(tile,tile_type)
 return has_flag
end

function can_move(x,y)
 return not is_tile(wall, x, y)
end

Add a function for moving the player to the player code tab.

Note: to get the arrow characters, use shift+L, shift+R, shift+U, shift+D.

function move_player()
 newx=p.x
 newy=p.y

 if (btnp(⬅️)) newx-=1
 if (btnp(➡️)) newx+=1
 if (btnp(⬆️)) newy-=1
 if (btnp(⬇️)) newy+=1

 if can_move(newx,newy) then
  p.x=mid(0,newx,127)
  p.y=mid(0,newy,63)
 else
  sfx(0)
 end

end

Call this function from function update in the game loop tab.

function _update()
 move_player()
end

Chapter 4 - Camera

Have the camera follow the player around the map.

Add code to set the camera to the draw_map function in the map code tab.

  • divide the player's x and y coordinates by 16

  • round down

  • multiply by 16

  • take this mapx, mapy to a pixel coordinate by multiplying by 8

function draw_map()
 mapx=flr(p.x/16)*16
 mapy=flr(p.y/16)*16
 camera(mapx*8,mapy*8)

 map(0,0,0,0,128,64)
end

Chapter 5 - Keys

Draw a sprite for a key and a sprite for a chest. The key can be picked up and the chest can be opened. When the player interacts with these items they are replaced by the sprite to the right, so you will need to draw two additional sprites.

The key is replaced by grass and the chest is replaced by an open chest.

Make a sound effect for the pick-up key sound.

Add two functions, swap_tile and get_key, to the map code tab.

function swap_tile(x,y)
 tile=mget(x,y)
 mset(x,y,tile+1)
end

function get_key(x,y)
 p.keys+=1
 swap_tile(x,y)
 sfx(1)
end

Add a function, interact to the player code tab.

function interact(x,y)
 if (is_tile(key,x,y)) then
  get_key(x,y)
 end
end

Now call this function from the mode_player function.

function move_player()
 newx=p.x
 newy=p.y

 if (btnp(⬅️)) newx-=1
 if (btnp(➡️)) newx+=1
 if (btnp(⬆️)) newy-=1
 if (btnp(⬇️)) newy+=1

 interact(newx,newy)

Chapter 6 - Inventory

When the player holds down the X key we'll show how many keys the player has in their inventory.

Create a new tab called inventory code and in in put a new function called show_inventory.

function show_inventory()
 invx=mapx*8+40
 invy=mapy*8+8

 rectfill(invx,invy,invx+48,invy+24,0)
 print("inventory",invx+7,invy+4,7)
 print("keys "..p.keys,invx+12,invy+14,9)
end
Then call this function from the _draw function.

You can use shift-x to get the ❎ button.

function _draw()
 cls()
 draw_map()
 draw_player()
 if (btn()) show_inventory()
end

Chapter 7 - Doors

We'll add doors that they player can open with their keys. First draw a pair if sprites, one for a closed door and, next to it, one for an open door.

Add a sound effect for opening a door.

Add a function open_door to open the door to the map code tab.

function open_door(x,y)
 p.keys-=1
 swap_tile(x,y)
 sfx(2)
end

This function gets called in the interact function.

function interact(x,y)
 if (is_tile(key,x,y)) then
  get_key(x,y)
 elseif (is_tile(door,x,y) and p.keys>0) then
  open_door(x,y)
 end
end

Chater 8 - Animated Tiles

In this step we add animated tiles. Animated tiles bring the map to life. Here we'll use them to make a spike trap that the player has to navigate through.

Create a pair of sprites, one for the spikes up and one with the spikes down. The animation will have cause the two sprites to flip between each other.

The tile with spikes up should have two flags set, flag 3 (anim1, or step one of the animation) and flag 6 (lose), which will cause the player to lose if they step on them. The tile with spikes down should have one flag set, flag 4, (anim2, step 2 of the animation).

Add two variables to the map_setup function in the map code tab.

function map_setup()
 --timers
 timer=0
 anim_time=30 -- 30 = 1 second

Add a new function unswap_tile to the map code tab. Note that this function is nearly the same as swap_tile and so you can use copy paste to write it.

function unswap_tile(x,y)
 tile=mget(x,y)
 mset(x,y,tile-1)
end

Add a new tab, animation code. Add a new function toggle_tiles to this tab.

function toggle_tiles()
 for x=mapx,mapx+15 do
  for y=mapy,mapy+15 do
   if (is_tile(anim1,x,y)) then
    swap_tile(x,y)
    sfx(3)
   elseif (is_tile(anim2,x,y))then
    unswap_tile(x,y)
    sfx(3)
   end
  end
 end
end

Add a new function called update_map to the map code tab.

function update_map()
 if (timer<0) then
  toggle_tiles()
  timer=anim_time
 end
 timer-=1
end

Finally, call the update_map function from the _update function in the game loop tab.

function _update()
 update_map()
 move_player()
end

Chapter 9 - Winning and Losing

  • add a new sprite that the player has to move over to win the game

  • add game_win and game_over to _init

function _init()
 map_setup()
 make_player()

 game_win=false
 game_over=false
end
  • add new tab win/lose code

  • add two new functions, check_win_lose, draw_win_lose

-- win/lose code

function check_win_lose()
 if (is_tile(win,p.x,p.y)) then
  game_win=true
  game_over=true
 elseif (is_tile(lose,p.x,p.y)) then
  game_win=false
  game_over=true
 end
end

function draw_win_lose()
 camera()
 if (game_win) then
  print("★ you win! ★",37,64,7)
 else
  print("game over!",38,64,7)
 end
end
  • change _update to only run if not game_over, and call check_win_lose

function _update()
 update_map()
 move_player()
 if (not game_over) then
  update_map()
  move_player()
  check_win_lose()
 end
end
  • change _draw to only draw if not game_over else draw_win_lose

function _draw()
 cls()
 draw_map()
 draw_player()
 if (btn()) show_inventory()
 if (not game_over) then
  draw_map()
  draw_player()
  if (btn()) show_inventory()
 else
  draw_win_lose()
 end
end

Chapter 10 - Trying Again

Add a way for the player to restart the game when they win or lose.

Add the following else condition to the _update function.

else
 if (btnp()) extcmd("reset")

Add the following code to the draw_win_lose function

print("press ❎ to play again",20,72,5)

Finally add a comment with the name of the game to the start of the script and save an image for the cart by pressing F7.

Congratulations!