Log In  

edit: latest version all objects check all objects and transfer momentum

Cart #nonmapbasedcollisions-2 | 2024-04-05 | Code ▽ | Embed ▽ | No License
2

edit: forgot cart

Cart #nonmapbasedcollisions-0 | 2024-02-20 | Code ▽ | Embed ▽ | No License
6


edit: solved!
Cart #nonmapbasedcollisions-1 | 2024-02-20 | Code ▽ | Embed ▽ | No License
1

Can anyone advise me on where I am going wrong with my collission detection. I want to be able to use table objects instead of map tiles and flags as I want to be able to continuously scroll for wider than the map allows.

I have pieced the following together from various sources (omitted the init and draw parts of the code, they just set up the various objects, but are in the carts code). It is using the method of checking whether the distance between the midpoints of two objects are smaller than the sum of the halves of the objects for both dimensions.

When something affects the players movement, either buttons or gravity, the new Y and X positions are run through a collision detection which loops through every object, with y then x positions being checked within each loop, if a movement results in a collision the new X or Y position is adjusted to the max position that doesn't overlap and momentum is stopped. If there is no collision the new X and Y positions become the actual X and Y positions, if there is a collision the new X and Y positions are adjusted before becoming the actual X or Y positions.

It almost works except for on corners where the player can get stuck and it judders. I've set up the cart so that it is easy to recreate the problem by falling/jumping along the vertical side of the squares while pressing left or right.

I guess this is where within a loop both X and Y are adjusted putting them back to a position where it will keep happening while those directions are maintained. I assume I have to give priority to one axis but I can't figure out how to do this. I was wondering what the best approach is for this.

Also is this approach on collisions generally ok or is there an easier or more efficient way, will it use up too much processing to do an entire map this way? I have tried to figure this out but most of the collision guides seem to be either map tiles and flags, or simple one pixel by pixel movement, or comparing only two objects at a time.

thanks

here

--move objects
function _update()
move(pl)

end

--move and collission functions

function move(obj) 

 if (btn(0)) then 
 obj.dx-=obj.acc --move left amount
 end

 if (btn(1)) then 
 obj.dx+=obj.acc --move right amount
 end

 if (btnp(5)) then 
 obj.dy-=obj.boost --move up amount when jump
 end

 --reduce vertical movement by gravity each update
 obj.dy+=gravity
 --reduce horizontal movement by friction
 obj.dx*=friction

 --limit max speed in all directions
 obj.dy=mid(
 -obj.max_dy,
 obj.dy,
 obj.max_dy)

 obj.dx=mid(
 -obj.max_dx,
 obj.dx,
 obj.max_dx)

    --new locations if movement applied
 obj.newx=obj.x+obj.dx
 obj.newy=obj.y+obj.dy

 --check whether new locations collide with other objects
 --will adjust newx/newy positions to not overlap collided object

        for v in all(things) do
        if v!=pl then
        --check collision of new vertical
    box_hit(obj,v,"y")
    --check collision of new horizontal
    box_hit(obj,v,"x")
            --apply new positions (adjusted to stop before collide)
    end
    end

 obj.y=obj.newy 
 obj.x=obj.newx     

 obj.y=mid(0,obj.y,120)
 obj.x=mid(0,obj.x,120)

end

function box_hit(obj,obj2,axis)

--compares one object x/y/w/h to another  
--axis is which dimension of movement being checked
--hitbox different depending on direction

  if axis=="y" then
  y1=obj.newy
  x1=obj.x

  elseif axis=="x" then
  y1=obj.y  
  x1=obj.newx
  end

  w1=obj.w
  h1=obj.h
  x2=obj2.x
  y2=obj2.y
  w2=obj2.w
  h2=obj2.h

  --collision check method 
  --if distance between midpoints greater than 
  --the sum of the two box halves

  --distance mid points
  local xd=abs((x1+(w1/2))
  -(x2+(w2/2)))
  --sum of box widths/2
  local xs=w1*0.5+w2*0.5
  --distance mid points
  local yd=abs((y1+(h1/2))
  -(y2+(h2/2)))
  --distance mid points
  local ys=h1/2+h2/2

if xd<xs and 
   yd<ys then 

--is a collission    

  if axis=="y" then
    --kill vertical momentum
                obj.dy=0
    if y1<y2 then
    --if collide from above limit movement to last non collide point 
    obj.newy=flr(y1-(ys-yd)) 
    else 
    --if collide from below limit to lowest point of 2nd object
    obj.newy=ceil(y2+h2) end

  else
    --kill horizontal momentum
                obj.dx=0

    if x1<x2 then
    --if colliding from left
    obj.newx=flr(x2-obj.w) 
    else 
    --if colliding from right
    obj.newx=ceil(x2+w2) end

 end
 end 
P#141692 2024-02-20 19:36 ( Edited 2024-04-05 11:04)

1

I'm not able to dig into the code thoroughly but keep in mind that you will want to perform each axis check and move independently. It looks like you have the axis checks independent, but you don't update the hitbox (move) in between, so you're essentially still doing both checks together.

To talk a bit further about theory, especially with regard to platformers (with gravity), there's actually a benefit to breaking it out into three parts: up, left&right, down in that order, updating the bounding box in-between each. Consider the scenario where your player sprite is one-pixel-diagonally away from a ledge. If the player is moving upward, you will want to move them up first and then over to give them the best opportunity of landing on the ledge. If they are moving downward, you will want to move them over first and then down second.

Good luck with your platformer, I like your idea of being able to continuously generate level patterns.. with some good procedural generation this could be very interesting!

PS: You can use ``` at the start and end of a block of code in a BBS post to format it as a code snippet.

P#141696 2024-02-20 20:27

thanks for this, I will try and figure out how to get the axis checks properly independent. I hadn't thought about separating out the checks with different types of movement, is it just a case of calling the collision check at different stages, and having different logic over which axis is done first? I will edit my post to show the code block

thanks again

P#141697 2024-02-20 20:41
1

Cart #nonmapbasedcollisions-1 | 2024-02-20 | Code ▽ | Embed ▽ | No License
1

I think that has worked, thanks!

P#141702 2024-02-20 21:25 ( Edited 2024-02-20 21:26)
1

Edit: Oop, I see you fixed it while I was typing! Still I'll leave the original response:

Essentially, instead of being something like

collision(x)
collision(y)
move(x)
move(y)

It should be something like

collsion(x)
move(x)
collision(y)
move(y)

being sure to update your bounding box with any move, so that your updated position is used for the second collision check, rather than your original position.

The three-stage check I described is a little more advanced but it would be something like:

--⬆️
if p.dy<0 then
 collision(y)
 move(y)
end
--⬅️ or ➡️
if p.dx<0 or p.dx>0 then
 collision(x)
 move(x)
end
--⬇️
if p.dy>0 then
 collision(y)
 move(y)
end

Change this to suit your use case.. in the platformer I'm working on I have different checks for each direction so it looks quite a bit different. I'm still not really a PICO-8 expert so this is all just from what I've personally surmised and may not be the ideal approach.

P#141700 2024-02-20 21:32 ( Edited 2024-02-20 21:33)

thanks for your help, will definitely think about a multi stage approach for different types of movement

P#141705 2024-02-20 22:01
2

I actually use array-based collision with my PicoMap system for storing large levels, and my Mario and Mega Man projects are based on it.

https://www.lexaloffle.com/bbs/?tid=42848
https://www.lexaloffle.com/bbs/?tid=51064

It works the same way as with map tiles, just that the mget and mset functions are replaced with small aget and aset functions. This allows you to use flags like normal, and the fact that map tile data is in an array in Lua RAM lets you have levels up to 256 screens in size. It doesn't take much extra CPU, either, because a screen's worth of tile data is copied to map memory each frame, so you can still draw it with a map() call. The system was designed around metatiles, but I recently made a demo of it using map data compressed with PX9, which would be easier if you just need 100-200 screens' worth of map space.

P#141723 2024-02-21 07:35 ( Edited 2024-02-21 18:57)
1

thanks I will look into that, good to know there is a way of using map like functionality on larger maps

P#141733 2024-02-21 13:58
2

Cart #nonmapbasedcollisions-2 | 2024-04-05 | Code ▽ | Embed ▽ | No License
2

got it working for checking all objects against all objects and passing on momentum. need to have a think about how it should work exactly, at the moment when an object collides the second object takes on the momentum of the first and the first is reduced to 90% but guess that only makes sense if one object is hitting a static one. at the moment whether the object is object1 or object2 will just depend what point the loop is at, so maybe it should be something like it evaluates both objects momentum and the stronger one should pass onto the weaker one regardless of whether it is object 1 or object 2? and maybe when objects collide from different directions they should cancel each other out or reverse each others direction rather than just change the direction of object 2?

Any tips here. Not really aiming for any game goal in mind but just trying to figure out some vaguely natural feeling movement and collisions

thanks

P#145784 2024-04-05 11:03
1

@rbndr : the 1st thing is to decide what happens when two objects share the same space : they could change shape for example. In your demo, it's reasonable to assume your cats have rectangular hit boxes and magically stay horizontally aligned at all times and keep their shapes and size, so no spinning cat if hit from the side. If we also assume that every cat has the same mass, things become much simpler : when you try to move a cat, if it would hit another one, swap their velocity vectors and apply a heat impact velocity loss (10% ?), and if the resulting speed goes below a certain value, set it back to 0 in order to avoid infinite loops of bumping. Move the same cat again with it's new vector until it finally successfully moves to an empty space or looses all it's speed by bumping into other cats. Remember to add a small friction penalty to every cat, not just the player controlled one.
Rather than directly moving the player cat with the arrows, you can add a fixed amount to the speed of the cat each frame depending on arrows pressed, and then eventually scale the speed vector if you've exceeded the max intended speed. This way, after that, it's not to be treated differently than the rest, and would also make multiplayer cost very little extra effort.
The result should look like pucks on ice.
Careful with wraparound : a well placed cat should be able to occupy the 4 corners of the screen at once and collisions should apply properly. You can have 2 or 4 objects in the collision queue for cats on the border, to simplify collision handling.
Since we're dealing with cats rather than inanimate pucks, a hit cat could change to a temporary irate state and input his own energy towards the cat that hit them for example.

P#145787 2024-04-05 12:38

thanks, I might already be doing the speed thing, I borrowed the code from nerdy teachers I think that uses an acceleration stage. I will try the rest, swapping velocity, 10% loss, I like the irate cate idea might stop them getting clumped together, thanks again

P#145790 2024-04-05 13:16
1

You could use round collision which is fairly efficient and allows objects to push naturally away from each other at the appropriate angles.. something like this:
https://www.lexaloffle.com/bbs/?pid=140496#p

P#145810 2024-04-05 16:40

thanks that is really helpful, can't believe how concise the code is.

the approach I have taken was initially about trying to use sprites instead of a map, and then thought I would extend it to be able to interact with objects and other characters, I think your circular method might be better for that, and I can use my original method for the sprites as a map (as can just check player against everything else rather than everything against everything)

thanks

P#145815 2024-04-05 17:27
3

Cart #zejipuhoyu-0 | 2024-04-11 | Code ▽ | Embed ▽ | No License
3

I tweaked the cart a bit with previously suggested changes :

Tab 0
npc friction at 0.99 instead of 1 (frictionless)

tab 1
smooth warparound instead of snap teleport

tab 3
display 4sprites per cat for warparound, naive approach that could be performance improved by checking if sprites overlap borders

tab 4
modify distance calculation to take care of warparound
in case of collision, exchange momentum instead of transfer

P#146397 2024-04-11 21:57

thanks that is really useful, hadn't thought of duplicating sprites for the corners. I made this to try an understand collisions and add some interaction with things in the platform game i'm making, but might try to turn this into a mini game. Player as one cat, CPU as another cat, plus lots of other cats that take on another cat's colour when they collide, the aim being to turn all the cats the same colour before the CPU does, maybe with different objects appearing each time that can be used to ricochet off of. Need to figure out how the AI would work for the CPU player

thanks again

P#146402 2024-04-11 22:41

[Please log in to post a comment]