Log In  


Hello!

I am horrible at Lua and horrible at PICO-8 and sorry if this question is extremely stupid.

I have a map sprite which has collision detection set up for the flag assigned to it. All good:

function platform()
    v = mget(flr(x+4)/8, flr(y)/8+4)
    return fget(v, 1)
end

However, when I generate the same sprite using a function called draw_clouds within _draw():

function draw_clouds()
	spr(32, 48, 54, 2, 1)
end

it appears onscreen, but my character goes straight through it.

Am I missing something?



It's hard to know just from this, but I see no way for the code to detect if the cloud sprite is flagged. Also I assume you flagged it in the editor? Or somewhere else in the code?

Your platform function is also referencing global variables it looks like. What are v, x and y referring to?


Oh also, with a sprite I don't think the mget part of platform() will work. Id probably want platform to accept an argument, and be able to determine if we need to check map flags or sprite flags


The way you have mget configured makes it appear that you want the center of an 8x8 sprite to collide with the cloud. I'm just going to go with that assumption for the moment. There's not a lot I can say without seeing more, but this line in particular stood out to me:

v = mget(flr(x+4)/8, flr(y)/8+4)

The purpose of flr(), of course, is to lop off the fractional portion of a number. You're dividing by 8 after this function is executed. Here's how I would do it:

flr((x+4)/8)

--Or, to save some tokens:

flr(x/8+0.5)

--Same result, with the 4 outside the parenthesis, divided by 8

Now for the Y portion, you're dividing by 8 to translate pixel coordinates to map coordinates as you should be, but you're adding 4 AFTER this operation, which means you're actually adding 4*8, or 32 pixels, instead of the 4 you were intending. The full line, as I would write it:

v = mget(flr(x/8+0.5), flr(y/8+0.5))

This will put your collision point squarely in the middle of an 8x8 sprite. If you want the contact point to be on the bottom, just take that 0.5 in the Y portion and make it a 1 for a full sprite's height, divided by 8.


I flagged the sprite in the editor. As I say, with the map sprite this all works perfectly.

I had hoped there was an obvious reason for this without my having to dump the entire code here but I guess not!

I'm aware my collision detection is horrendous. It's adapted from this tutorial https://www.reddit.com/r/pico8/comments/4w6jwk/any_good_resources_for_2d_game_physics/?st=iy1yvtam&sh=19f79f76 because I couldn't get it to work from scratch no matter what I did.

This is the whole code. The maths is weird because I basically had to gut the entire thing and use trial and error to make it work when I moved the map position down a couple of squares, which seemed to break everything irrevocably. It hadn't occurred to me this might affect the physics of a single sprite but honestly at this point I'm so confused I wouldn't be surprised. My approach is usually 'get it working then clean it up later' but I haven't managed the first part of that this time. :/

pico-8 cartridge // http://www.pico-8.com
version 8
__lua__

function _init()
    x = 20
    y = 48
    dx = 0
    dy = 0
end

function draw_background()
	rectfill(0,0,128,128,12)
	map(0,3,0,0)
end

function grounded()
    v = mget(flr(x+4)/8, flr(y)/8+4)
    return fget(v, 0)
end

function platform()
    v = mget(flr(x+4)/8, flr(y)/8+4)
    return fget(v, 1)
end

function toleft()
    v = mget(flr(x)/8-1, flr(y+4)/8+3)
    return fget(v, 1)
end

function toright()
    v = mget(flr(x)/8+1, flr(y+4)/8+3)
    return fget(v, 1)
end

function draw_clouds()
	spr(32, 48, 54, 2, 1)
end

function physics()
	-- x acceleration
    ddx = 0
    if (btn(0)) ddx=-.25
    if (btn(1)) ddx=.25

    -- apply x accel
    dx += ddx

    -- limit max speed
    if dx > 3 then
        dx = 3
    elseif dx < -3 then
        dx = -3
    end

    -- drag
    if ddx == 0 then
        dx *= 0.8
    end

  	-- y velocity
	if grounded() then
	    if (btnp(2)) then
	        dy=-8
	    else
	        dy = 0
	        -- fix position to avoid getting stuck in the floor
	        y = flr(flr(y)/8)*8
	    end
	else

    -- gravity accel
    dy += 0.98
	end

	if platform() then 
		dy=-12
		y = flr(flr(y)/8)*8
	end

	--stop for solid objects
	if toright() and btn(1) then
		dx = 0
		x +=0
		x = flr(flr(x)/8)*8
	end

	if toleft() and btn(0) then
		dx = 0
		x +=0
		x = flr(flr(x)/8)*8
	end

    -- update position based on vel
    x += dx
    y += dy
end

function _update()
	physics()
end

function _draw()
    cls()
    -- draw the map
    draw_background()

    -- draw the player
    spr(1, x, y)

    draw_clouds()
end

To be clear, flag 0 is the ground tiles, flag 1 is the platform tiles. Platforms are 2 tiles wide, solid and bouncy. One of 'em works, one of 'em doesn't.


spr() simply copies pixels to the screen, it has no concept of objects or flags. this is intended behaviour. the map handles flagged 8x8 tiles, whereas between spr and sspr you can grab any size rectangle to copy, which would make flag negotiation complicated.


Ohhh okay this makes sense, thank you.

Is it possible to generate platforms on the fly, then? I assume so but am not sure where to start. Eventually I want to generate platforms in random positions but obviously I want to sort out the actual process of generating them first hence the dilemma.

I already tried running fset 1 within the draw_clouds() function but couldn't get it working so am assuming this also only works on map tiles.


You need a way to check a sprites flag. So you'd want to make a table to hold the sprites you want to draw, and in update you'd check collisions with those sprite positions and draw them in draw.

So...

clouds = {sprite = 32, x = 48, y = 54, width = 2, height = 1}

this wont really work for your platform code, you want something that can check a sprite object flag like:

function solid_sprite(sprite, flag)
return fget(sprite, flag)
end

now if you do solid_sprite(clouds, 1) it will return true if flag 1 is marked. so then youd have to add that to your physics logic somewhere to tell the player to collide with the cloud. however, i dont know that marking the flag means that every pixel of the sprite gets marked as collidable. theres probably more work to define the bounding boxes for collision

id be happy to try and lay out a way to do it, but ill need some time ;)


ok so i have a suggested solution, but you'll probably have to redo or add the collision behavior yourself. i didn't get that far.

to draw sprites with game properties on the fly, we have to create a table to store the data. like in your _init() i assume the variables there represent the player? so i'd do something like this

function _init()

player = {
  x = 20,
  y = 48,
  dx = 0,
  yx = 0
}
end

then when you need those values in your code, use player.x, player.y, etc...you can get or set those values.

you could do the same thing for clouds, enemies, or other stuff you draw from the sprite sheet that will be affected by your physics. this implies a generic object that holds data about drawable objects that have properties like movement, solidness, being affected by gravity, etc. checkout Jelpi, its a little complicated but it handles all of that stuff. its generic form is the ACTOR, and the player, monster, and pickups are all kinds of ACTORS.

here lets not go that into it, right now you just want your player and clouds. so put your player in a table, and your clouds can be tables too. but i assume you want more than one cloud, so we need a function to make cloud tables and a table to hold all the clouds we want to draw on the screen.

i made a demonstration cart to show how we can make and draw a bunch of clouds. i ran out of time to figure out how to make them platforms. the data stored in cloud.width, cloud.height, cloud.cx, and cloud.cy can be used to do the AABB collision detection method. this is just a box which in your clouds case will be 16x8 pixels. you have to calculate a box for your player too, and then have a function to take the player and a cloud to detect if they are colliding

Cart #36031 | 2017-01-18 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

probably tonight i'll work on the collision.


Amazing, thank you. I have had a look at Jelpi but am finding it hard to follow exactly what's happening in the code because there's a huge amount going on. This is enormously helpful and all makes sense EXCEPT I can't work out what the 'c' argument is in your code.

Edit: ok I'm incredibly confused. In Jelpi the stuff that spawns during the level (enemies etc) is still placed within the map so is still identified by flags, so I don't really have a reference point for this.


Yeah sorry. I am totally willing to help you figure it out. I think it might be hard to mix the new code with your old stuff without a lot of reconfiguring .

Your code you posted has some problems (imho) because it is all written to change the global variables you define at the beginning for your player. Colliding two sprites requires a function that can process two sprites. Right now, you check to see if the player is on top of a place where a flagged map tiles is drawn. Map tiles are handled differently by pico8 because they don't move after you draw them. The collision box is the same as the tile. That could be different with a sprite where you may only want a single pixel to detect collision.

So if you store your player data in a table you can get it back with the dot syntax.

player = {
  id = 1,
  x = 64,
  y = 64
}

function draw_player()
  spr(player.id, player.x, player.y)
end

stick draw player in _draw() and youre good.

in terms of flags, you can still use them to mark for sprites you want to be solid and collide with everything, or as a platform which i assume is only solid if you are on top of it. but to do that we have to define collision boxes for those sprites. coming up soon!


2

Alright! I incorporated your code into mine and it seems like it works! i didn't really have to change that much, so you had all your collision effects right. i'll try to work on writing up comments soon, but for now here's a working cart to mess with.


oh and if you go off the screen... you simply fall for ever. easy fix though, check out how i made the clouds wrap and try to add it to the player too. the code is still pretty hacky, but it works! rather than use your platform function, in _update() i use a for loop to check if each cloud to see if the player is colliding with it. if so, we use the effect from your platform function on the player sprite. i think the collision with the platform could be different too, you get bounced up if you collide with any part of the cloud, not just coming from above. i also dont have solid object collision in there either

and the 'c' argument; c is a cloud table, like any of the ones we return from make_cloud. ideally you'd make a generic type of table like SPRITE or ACTOR. anything with an x,y and sprite id can be drawn with a properly written draw_sprite(s) function. you could do something like this to understand it:

cls()

cloud_a = make_cloud()

draw_cloud(cloud_a)

print(cloud_a)

print(cloud_a.x)

foreach(clouds, draw_cloud)

try that at the lua prompt after running the game and hitting esc


I am a newbie and I tried the code above, can any body be a little bit more precise about how do you fix the position of so the player does not sink in the floor?


Most platformers keep track of whether the player is in the air, and only move the player vertically after a jump/fall and until there is floor underneath again. (so every frame check for floor or the jump button, and if the player is in the air then adjust y speed with gravity etc)

In the code above, the "if grounded()" part is where the loop checks and where it starts jumps.



[Please log in to post a comment]