Log In  

So I have an issue where the player object can clip through about half a collision tile, but only when moving to the left. As I type that I realize how weird and niche this problem is. Anyways, here's my move function:

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

if (btn(⬅️)) then
newx-=.1 p.sprite=d.left 
elseif (btn(➡️)) then 
newx+=.1 p.sprite=d.right 
elseif (btn(⬆️)) then
newy-=.1 p.sprite=d.up 
elseif (btn(⬇️)) then
newy+=.1 p.sprite=d.down 
end

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

if (door_tile(newx,newy)) then
scene="room1"
p.x=6
p.y=10
end
end

and here's my collision function:

function is_tile(tile_type,x,y)
local tile_x=newx+pixeloffset_x
local tile_y=newy+pixeloffset_y
tile=mget(tile_x,tile_y)
has_flag=fget(tile,tile_type)

return has_flag
end 

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

function door_tile(x,y)
return is_tile(door,x,y)
end

pixel_offset is initially 0, and changes based on scene to account for the map change.
Any idea what is causing this?

side note: Everyone on this forum has been so helpful and I just want to say thanks for all the help! This is my first coding project outside of html and I really appreciate it :]

P#99015 2021-10-22 02:53

So, the main thing that stands out to me is that you have non-integer coordinates for player position being fed to mget() which converts them to integers by cutting off the decimal portion. That wouldn't fully explain why the character would specifically need to be moving left for this issue, nor being able to move halfway through. However, if the issue is the integer conversion, then the specifics would also rely on where the player character is being drawn relative to the player position.

That said, if that is the problem, then the solution would be to change your can_move() function to check all four corners of the player character's sprite. This would also require using x and y instead of newx and newy in your is_tile() function, though I'm not sure why that function doesn't already use x and y, since those are the parameters it takes.

Would you be able to post the code that draws the player character? Also, have you tested to make sure that the issue doesn't occur in any other direction?

P#99018 2021-10-22 03:40

@kimiyoribaka Here's the player table and the draw player function:

function make_player(x,y)
p={}
p.x=5
p.y=10
p.sprite=1

d={}
d.left=9
d.right=4
d.up=2
d.down=7

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

I went and tested it and the clipping isn't related to moving left, its that the left side of a tile doesn't clip like the right side, so walking up and down also causes the issue. Some of the functions I took from tutorials and changed it to work in my code, so it's entirely possible I messed up some key components that I didn't realize were important at the time.

P#99022 2021-10-22 04:08

Okay, so the spr() function is drawing from the top-left of the sprite, and the multiplication by 8 is translating the player position roughly into pixels. This means that the top-left of the sprite is the spot being tested when your code checks for a collision tile. This would be sufficient if you were having your character move one full map tile each time they move (like the tutorials you used probably intended), but since your character can occupy more than one tile at a time, you need to check more precisely.

There's two changes I recommend:

  1. In is_tile(), use the parameters instead of the globals for calculating tile_x and tile_y like so:

    local tile_x=x+pixeloffset_x
    local tile_y=y+pixeloffset_y

    That's important, as it will allow you to use any point instead of just (newx, newy).

  2. Have can_move() check the other three corners as well by calling is_tile() 3 more times. Now, the coordinates to use for each corner is based on a number slightly less than the width of the sprite and a number slightly less than the height. Since your code appears to use 8x8 sprites (or rather 1x1 tile sprites), that means .9 and .9 should work. The resulting code would be this:
    function can_move(x,y)
    return not is_tile(wall,x,y) and not is_tile(wall,x+.9,y) and not is_tile(wall,x,y+.9) and not is_tile(wall,x+.9,y+.9)
    end

    You should use a number slightly closer to 1 if your character ever has to take smaller movements, though. I believe .9 should work at the moment because it's 1 frame of movement short of hitting the next tile.

P#99023 2021-10-22 04:39

@kimiyoribaka Thank you so much! It worked perfect :]

P#99030 2021-10-22 07:50

@kimiyoribaka Actually, after a little bit I noticed that the hit boxes for the tiles seem to occupy its own tile, and then the tile above it. At least the player reacts like it is. Any idea why that only happens in one direction?

P#99033 2021-10-22 08:39

Yes, I think I do know why that would happen. It's probably a fixed-point precision bug. See, before I posted my first answer, I tried checking the behavior of mget() since the manual doesn't actually say that it needs an integer. It also doesn't mention what the behavior would be when it receives a non-integer. This isn't on the wiki either. If you give it numbers between 0 and 2 at intervals of .1, it will change which tile it gives precisely at 1 and 2. That behavior matches with what I'd expect if the numbers given to mget() are always being rounded down. From there I figured it'd be accurate enough for numbers that always have exactly 1 decimal place, like your movement code would. The problem is, I forgot that adding binary fixed-point numbers has less accuracy than just declaring them. When I instead used a for loop that adds .1 and repeats the call to mget(), I found that the change in which tile it returns occurs 1 iteration after it should.

To explain why that would happen, it's because 1/10 is a repeating decimal in binary, so pico-8 (as well as any system that isn't really bizarrely designed) is incapable of storing it perfectly. The solution is to either use a power of 2 instead (such as 1/8) or to add an epsilon (a really small amount relative to the scale being used) to your checks. I would recommend the latter, since it doesn't restrict what numbers you can use.

Instead of .9, try using .91 in the calls to is_tile(). If that doesn't work try .901.

P#99035 2021-10-22 12:12

@kimiyoribaka Adding an epsilon doesn't seem to change anything, and swapping .9 with 1/8 stops it, but brings the original clipping issue back.

P#99052 2021-10-22 21:22

Oh, when I mentioned 1/8, I meant for the amount the character moves each frame.

Okay, I tried quick and dirty equivalent to the code you've shown, with the changes, but it seems to work. I used full 8x8 rectangles though. The only other thing that comes to mind is the player sprite.

Do the collision tiles occupy the entire tile above them or only a portion? Also, if it's only a portion, does the actual image for your character cover a full 8x8 square, or is there empty space at the bottom? If there's empty space on any edge, then that would mean the corners to check for would be in different spots relative to the player position.

P#99053 2021-10-22 21:57

@kimiyoribaka Okay, I changed it to match what you said and the issue is still there, no idea why.
I'll attach some pictures but I'm pretty sure the sprite size isn't an issue

This is where it stops me, and I've checked that none of my floor sprites are flagged and when I remove the black tiles the issue stops, so it's definitely the collision tiles. Really weird

P#99054 2021-10-22 22:17

I'm just guessing with this one, but is the map being drawn at the position (0, 0)? (ie. if you used map(), then the 3rd and 4th values will probably be 0 and 0)

The only thing I can think of that would cause that effect with that code is if the map is being drawn 8 pixels lower than its raw position without the player being drawn lower too.

P#99055 2021-10-22 22:36

@kimiyoribaka My first map is drawn at 0,7 and the one in the screenshot is 0,8.

P#99057 2021-10-22 23:20

In that case, the quickest solution would be to convert those into map tile offsets (divide by 8) and then include them in the pixeloffset via subtraction. You said before that pixeloffset starts at 0. If the map is being drawn at (0,7) then pixeloffset_y should be -7/8.

A more thorough solution would involve drawing the player at the same offset as the map, but that'd only be worth it if the above solution causes other bugs.

P#99059 2021-10-22 23:43

@kimiyoribaka Yep!! that worked perfect, thank you so so much !!!!!!!

P#99061 2021-10-23 00:34

[Please log in to post a comment]