MAKE A ONE(MAYBE TWO)-BUTTON(S) GAME USING PICO8 #9 – ADD CHECKPOINT

In this post, we will add a checkpoint that will use for reset positions when the player’s character dies from fall out of the screen or hit obstacles.

The tasks for adding checkpoints and reset player position to the checkpoint.
1. modifying block data
2. modifying draw_block() function
3. create new functions for a collision between the player’s character and the checkpoint.
4. modify player_reset() function to support the checkpoint.
5. call player_reset() when the player’s character goes outsidethe of screen.

A BLOCK AND A CHECKPOINT
A maximum checkpoint in a block is one. From this logic. we will add a variable to keep the position of the checkpoint in the same way as the obstacle's position. (0 - 1)

function create_block(x,y,w,h,left_wall,right_wall, floor, check_point)
   return {x=x,y=y,w=w,h=h,obstacles={},left_wall=left_wall,right_wall=right_wall,floor=floor,check_point=check_point}
end

And create a new variable for saving the current checkpoint.
current_check_point = nil

Next is modifying draw_block() function to support showing checkpoint inside the block and the checkpoint’s color will different base on “current_check_point” value.

function draw_block(b,c)
   if (b == nil) return

   if b.floor == true then
      line(b.x,b.y,b.x+b.w,b.y,c)
   end

   if b.left_wall == true then
      line(b.x,b.y,b.x,b.y-b.h,c)
   end
   if b.right_wall == true then
      line(b.x+b.w,b.y,b.x+b.w,b.y-b.h,c)
   end
   
   for obs in all(b.obstacles) do
      print("✽", b.x + (b.w*obs.value), b.y - 6, 8)
   end

   if b.check_point != nil then
      if current_check_point != b then
         print("♥", b.x + (b.w*b.check_point), b.y-6, 11)
      else
         print("♥", b.x + (b.w*b.check_point), b.y-6, 7)
      end
   end
end

Don’t forget to add a checkpoint when creating block data.

function _init()
   add(block_list, create_block(16,64,96,8,true ,true , true, nil))
   add(block_list, create_block(16,56,96,8,true ,false, true, nil))
   add(block_list, create_block(16,48,96,8,false,false , true, 0.5))
   add(block_list, create_block(16,40,96,8,true,true , false, nil))
   player_reset()

end
THE PLAYER'S CHARACTER AND A CHECKPOINT

This part will make the player’s character can touch the checkpoint. A new function (“player_hit_check_point()”) will return block data when the player’s character touches the checkpoint and the checkpoint needs to be different from the last touching checkpoint.

function player_hit_check_point(b)
   if current_check_point != b and b.check_point != nil then

      if is_inside_block(player.x, player.y, b) == false  then 
         return nil 
      else
         local d = distance(player.x, player.y, (b.x + b.w * b.check_point), b.y)

         if d < 3 then
            return b
         end
      end

   end
   return nil
end

And modify “_update60()” function to use the new function.

function _update60()
   player_running()
   player_jumping()
   player_drop_down()
   player_apply_gravity()
   player_find_new_current_block()
   local hit_result = player_hit_obstacle(current_block)
   if hit_result == true then
      player_reset()
   end
   local chk_point = player_hit_check_point(current_block)
   if chk_point != nil then
      current_check_point = chk_point
   end

   update_camera()
end
RESET THE PLAYER'S CHARACTER POSITION

For the last part, we will make the player’s character reset position to the last touching checkpoint when it go outside of the screen. First, we create a new function for checking that the given position stays inside the screen or not.

function is_inside_screen(x,y)
   if x >= camera_data.x and x <= 127 + camera_data.x then
      if y >= camera_data.y and y <= 127 + camera_data.y then
         return true
      end
   end

   return false
end

Second, We modifying “player_reset()” function to use value from “current_check_point” variable.

function player_reset()
   player = {x=64,y=64,di=1,jump_counter=0,ground=true}
   change_current_block(block_list[1])

   if current_check_point != nil then
      player.x = current_check_point.x + current_check_point.w * current_check_point.check_point
      player.y = current_check_point.y
      change_current_block(current_check_point)
   end
end

Last, we modify “_update60()” function to support the new checking function.

function _update60()
   player_running()
   player_jumping()
   player_drop_down()
   player_apply_gravity()
   player_find_new_current_block()
   local hit_result = player_hit_obstacle(current_block)
   if hit_result == true then
      player_reset()
   end
   local chk_point = player_hit_check_point(current_block)
   if chk_point != nil then
      current_check_point = chk_point
   end

   if is_inside_screen(player.x, player.y) == false then
      player_reset()
   end

   update_camera()
end
RESULT
SOURCE CODE
player = {x=64,y=64,di=1,jump_counter=0,ground=true}

ignore_block = nil
current_block = nil
block_list = {}

current_check_point = nil

function create_block(x,y,w,h,left_wall,right_wall, floor, check_point)
   return {x=x,y=y,w=w,h=h,obstacles={},left_wall=left_wall,right_wall=right_wall,floor=floor,check_point=check_point}
end

function draw_block(b,c)
   if (b == nil) return

   if b.floor == true then
      line(b.x,b.y,b.x+b.w,b.y,c)
   end

   
   if b.left_wall == true then
      line(b.x,b.y,b.x,b.y-b.h,c)
   end
   if b.right_wall == true then
      line(b.x+b.w,b.y,b.x+b.w,b.y-b.h,c)
   end
   
   for obs in all(b.obstacles) do
      print("✽", b.x + (b.w*obs.value), b.y - 6, 8)
   end

   if b.check_point != nil then
      if current_check_point != b then
         print("♥", b.x + (b.w*b.check_point), b.y-6, 11)
      else
         print("♥", b.x + (b.w*b.check_point), b.y-6, 7)
      end
   end
end

function distance(x1,y1,x2,y2)
   local dx = x1-x2
   local dy = y1-y2
   local distance = sqrt(dx*dx+dy*dy)

   return distance
end

function change_current_block(b)
   
   current_block = b

   if current_block != ignore_block then
      ignore_block = nil
   end

   local d = distance(camera_data.target_x + 64, camera_data.target_y + 64, current_block.x + current_block.w/2, current_block.y - current_block.h/2)
   _debug = d
   if d > 12 then
      set_camera_target(current_block)
   end
end

function add_obstacle(b,v)
   if (b == nil) return
   add(b.obstacles, {value=v})
end

function is_inside_block(x,y,b)

   if (b == nil) return

   if x >= b.x and x < b.x + b.w then

      if y <= b.y and y > b.y - b.h then
          return true
      end
   end
   return false 
end

function is_inside_screen(x,y)
   if x >= camera_data.x and x <= 127 + camera_data.x then
      if y >= camera_data.y and y <= 127 + camera_data.y then
         return true
      end
   end

   return false
end

function player_running()
   local next_px = player.x + player.di
   if is_inside_block(player.x, player.y, current_block) == true and 
      is_inside_block(next_px, player.y, current_block) == false then

      if player.di == 1 and current_block.right_wall == false then

      elseif player.di == -1 and current_block.left_wall == false then

      else
         player.di = player.di * -1
         next_px = player.x + player.di
      end
   end

   player.x = next_px
end

function player_jumping()
   if btnp(4) then
      if player.jump_counter == 0 and player.ground == true then
         player.jump_counter = 20
         player.ground = false
      end
   end

   local next_py = player.y
   if player.jump_counter > 0 then
      player.jump_counter -= 1

      if player.jump_counter > 10 then
         next_py = player.y - 1  
      else
         next_py = player.y
      end
   end
   player.y = next_py
end

function player_drop_down()
   if btnp(5) and player.ground == true and 
      current_block != nil and 
      ignore_block == nil then
         
      ignore_block = current_block
   end
end

function player_apply_gravity()
   local next_py = player.y
   if player.ground == false then
      
      if player.jump_counter <= 0 then
         next_py += 1
         if  is_inside_block(player.x, player.y, current_block) == true and 
            is_inside_block(player.x, next_py, current_block) == false and 
            current_block.floor == true
            then

               if current_block != ignore_block then
                  next_py = player.y
                  player.ground = true
               end
         end
      end
   else
      if is_inside_block(player.x, player.y, current_block) == false or current_block == ignore_block then
         player.ground = false
      end
   end

   player.y = next_py
end


function player_find_new_current_block()
   if player.ground == false and 
      is_inside_block(player.x, player.y, current_block) == false then

      for b in all(block_list) do
         if is_inside_block(player.x, player.y, b) == true then
            change_current_block(b)
            break
         end
      end
   end
end

function player_hit_obstacle(b)
   if b == nil then return false end
   if is_inside_block(player.x, player.y, current_block) == false  then return false end

   for o in all(b.obstacles) do
      local d = distance(player.x, player.y, (b.x + b.w * o.value), b.y)

      if d < 3 then
         return true
      end
   end
end

function player_hit_check_point(b)
   if current_check_point != b and b.check_point != nil then

      if is_inside_block(player.x, player.y, b) == false  then 
         return nil 
      else
         local d = distance(player.x, player.y, (b.x + b.w * b.check_point), b.y)

         if d < 3 then
            return b
         end
      end

   end
   return nil
end

function player_reset()
   player = {x=64,y=64,di=1,jump_counter=0,ground=true}
   change_current_block(block_list[1])

   if current_check_point != nil then
      player.x = current_check_point.x + current_check_point.w * current_check_point.check_point
      player.y = current_check_point.y
      change_current_block(current_check_point)
   end
end

camera_data = {x=0,y=0,target_x=0,target_y=0}

function set_camera_target(b)
   if b == nil then return end

   local block_center_x = b.x + b.w/2
   local block_center_y = b.y - b.h/2

   camera_data.target_x = block_center_x - 64
   camera_data.target_y = block_center_y - 64
end

function update_camera()
   if camera_data.x > camera_data.target_x then
      camera_data.x -= 1
   elseif camera_data.x < camera_data.target_x then
      camera_data.x += 1
   end

   if camera_data.y > camera_data.target_y then
      camera_data.y -= 1
   elseif camera_data.y < camera_data.target_y then
      camera_data.y += 1
   end

   camera(camera_data.x, camera_data.y)
end

function _init()
   add(block_list, create_block(16,64,96,8,true ,true , true, nil))
   add(block_list, create_block(16,56,96,8,true ,false, true, nil))
   add(block_list, create_block(16,48,96,8,false,false , true, 0.5))
   add(block_list, create_block(16,40,96,8,true,true , false, nil))
   player_reset()

end

function _update60()
   player_running()
   player_jumping()
   player_drop_down()
   player_apply_gravity()
   player_find_new_current_block()
   local hit_result = player_hit_obstacle(current_block)
   if hit_result == true then
      player_reset()
   end
   local chk_point = player_hit_check_point(current_block)
   if chk_point != nil then
      current_check_point = chk_point
   end

   if is_inside_screen(player.x, player.y) == false then
      player_reset()
   end

   update_camera()
end

function _draw()
   cls(0)
   print("웃", player.x-3, player.y - 5, 7)
   
   for b in all(block_list) do
     draw_block(b, 7)
   end

end

Related Posts