Log In  

I'm not sure if I should post this here or in the "Tutorials"-Section, but I think it better fits here:

Cart #pimegutezu-0 | 2022-01-15 | Code ▽ | Embed ▽ | No License
11

I wanted to show a way on how to do Map-Collision, using the Flag-System in PICO8. This Function also takes care if your Sprite is wider or taller than 8 Pixels.

Basically you give a Function a few Parameters (like Position, Width & Height from the Player) and this Function checks the Flags from the Tile-Map on certain Pixels, depending on the Player-Position and it's Width/Height. You can use the Return-Value from this Function to allow or deny Player-Movement.


Before I show the "How-To use the essential Function", there's some preparation that you need to do.

For example, you have a Table called "Player" (like in a few NerdyTeacher-Tutorials) with some Variables, like:

player = {}
player.x = 3
player.y = 4
-- etc.

This needs some Addition to properly use the Collision-Function:

  • "w" for the Width of the Sprites in Pixels Minus 1.
  • "h" for the Height of the Sprites in Pixels Minus 1.
  • "ox" for Offset-X of the Sprites (black/transparent color) until Character starts - from left.
  • "oy" for Offset-Y of the Sprites (black/transparent color) until Character starts - from top.

Example for this Cart:

player = {
  w=3,
  h=13,
  ox=2,
  oy=1,
}

Why these Values? Because:


For your own Sprites, you can change these Values as you like.


After that, I can show the Function I mentioned:

is_solid(opt,f,ox,oy,flags,debug)

This Function can be called if you want to check if a Map-Collision happened and returns TRUE if detected, otherwise FALSE.

A quick description of the Parameters:

  • opt == a String that describes on which places should the Collision be checked. Possible Values are:
    • full, left, right, up, down, lup, rup, ldown, rdown
  • f == Usually the Player in a Table. Generally a Table that contains at least:
    • w, h, ox, oy
  • ox and oy == Offset-X and Offset-Y, that will automatically be added to the "Calculation" of the Map-Collision.
    • This is really useful to check a possible Player-Movement, but BEFORE the Player has moved. You can check if Player-Movement to the "ox"-/"oy"-Position are possible without Collisions. If so, move the Player to the new Location...
  • flags == A Number or a sequential Table of Numbers, which represents the Flags that will be checked for the Map-Collision.
    • It works like this: If this Parameter has a Table like, for example {1,2,0}, it will check if the Tile has the Flag 1, OR the Flag 2, OR the FLAG 0 activated. If the Collision-System finds at least one Tile where at least one Flag is activated, the Function will return TRUE.
  • debug == Boolean. If true, it will draw red Pixels to the "Collision-Positions". Useful for Debugging and Testing.
    • If you do that, then you should do that inside the _DRAW-Function. Keep in mind that this Function doesn't block anything, it just checks the Pixels around a Sprite and reads the Tile-Flags from these.

How to use it

First things first: You set up the _INIT and initialise the Player:

function _init()
 player = {
  x=5*8,
  y=2*8,
  w=3,
  h=13,
  ox=2,
  oy=1,
 }
end

Of course, modify these Values as you like to match your own Sprites.


Inside the _DRAW-Function, simply clear the Screen, draw the Map and the Player-Sprite:

function _draw()
 cls()
 map()
 spr(1, player.x, player.y)
end

If you want a Debug-Output for the Collision Detection, you can add this Line in the 2nd last Line:

 is_solid("full",player,0,0,{},true)

Inside the _UPDATE-Function, first thing to do is check Player-Input and saving them in Variables:

function _update()
 local dx,dy=0,0
 if(btn(⬅️))dx=-1
 if(btn(➡️))dx+=1
 if(btn(⬆️))dy=-1
 if(btn(⬇️))dy+=1

After that, you can check if the new Location (that's the Location where the Player wants to move) has no Collision. If that's the case, you can simply move the Player to the new Location:

First, the X-Coordinate

 if not is_solid("full",player,dx,0,7) then
  player.x+=dx
 end

Parameters are "full" for a "full-sprite-check" to the new Position, player for the Player-Table, dx for the additional Movement-Position (because we won't wanna check the "old" Player-Position for Map-Collision, we wanna check the new Location if there's a Map-Collison...). 0 is the Additional "Offset-Y". We wanna check this Value a little bit later (the Reason will be explained soon*). The last Parameter 7 (could also be {7}) checks the Flags of the Tiles. If one Tile has the Flag "7" activated, the Function will return "TRUE", the Line after the IF-Command wouldn't be executed and the Player won't move to the new Position.

The last four Lines inside "_UPDATE" are:

 if not is_solid("full",player,0,dy,7) then
  player.y+=dy
 end
end

It's basically the same as before, but now for the Y-Position.

*Maybe you have the Question "Why do we check X and Y seperately?". Because you won't completely stop the Movement if you bump into a "forbidden" Tile. For example, if you move diagonally and check the OX- and OY-Collision at the same time, the Player will instantly stop moving around. I think for the sake of "Game-feel", it's better that the Player could "Slide around" on the Tile...


Hope this Example could be useful for you. I included some additional "flavors" on the Cartridge (like Flipping the Sprite on X and Y, and drawing the Sprite in the right size depending on the previous discussed Values), but it's basically the same as the Example I described^^. Cartridge is also commented as well.

Would like to hear Feedback and Stuff. Also if you found an error (I hope there are no errors^^) please let me know. Use the Source as you like^^.

Last but not least, the whole Stuff without any comment for Copy'n Pasting:

function is_solid(opt,f,ox,oy,flags,debug)
 local collist={}
 ox = ox or 0
 oy = oy or 0
 flags=flags or {0}
 if(type(flags)!="table")flags={flags}
 local ix=f.x+f.ox+ox
 local iy=f.y+f.oy+oy
 for x=ix,ix+f.w+7,8 do
  for y=iy,iy+f.h+7,8 do
   if opt==nil
   or opt=="full"
   or opt=="left" and x==ix
   or opt=="right" and x>=ix+f.w
   or opt=="up" and y==iy
   or opt=="down" and y>=iy+f.h
   or opt=="rdown" and y>=iy+f.h and x>=ix+f.w
   or opt=="ldown" and x==ix and y>=iy+f.h
   or opt=="rup" and y==iy and x>=ix+f.w
   or opt=="lup" and y==iy and x==ix
   then
    add(collist, {x=min(x,ix+f.w), y=min(y,iy+f.h)})
   end
  end
 end
 if(debug)print(#collist)
 for c in all(collist) do
  if(debug)pset(c.x,c.y,8)
  for v in all(flags) do
   if(fget(mget(c.x/8,c.y/8),v))return true
  end
 end
 return false
end

Wish you a good day! :)

P#105154 2022-01-15 23:51

Hey! So I'm pretty new to Pico-8 and Lua and I really appreciate this tutorial. I've implemented it well so far. However, I've got an issue where my sprite's collision will stop it once it goes one sprite on the map's worth down. I would assume the issue stems from its width, your example uses the pixel count width and height, however I have mine as 1 to draw the singular sprite from the sprite chart. Any reason this would be happening?

P#111992 2022-05-18 13:39 ( Edited 2022-05-18 13:53)

Thank you, your Map collision Example helps me a lot, amazing job ^^

P#123870 2023-01-07 08:21

@Astorek86 Thanks for the code, really helpful. Just wanted to ask, I cant see the reason to want to have ox/y in both a function parameter and a parameter within your object (playing in your example). Using both incorrectly offsets the sprint collision.
Wanted to understand why you have done this?

P#139079 2023-12-23 13:35

@TheGrovesy I believe the separate parameters for the function are really the delta that you want the player/object to move on that axis, rather than an "offset"

P#139090 2023-12-23 19:15 ( Edited 2023-12-23 19:17)

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-29 10:11:41 | 0.060s | Q:21