Log In  

Hi there,
I'm making a scorched earth-style game and it's all coming together pretty well, but I'm having some real problems with collision. Right now what I've got is pretty rudimentary but I can't work out how to do it any better. Basically when the tank fires a shot, the shot moves in a parabola with variables yspeed and xspeed that get added on to the y and x positions of the shot each frame. The shot cannot collide with anything for two frames (to prevent collision with the tank itself) but after that, the shot should explode on contact with any pixel that isn't the colour of the background (i.e. black). However, if moving at a decent speed it will usually move through walls for a frame or so before exploding.

I've attached the cartridge and the relevant collision code.

function shot:update()
 if self.timeout > 0 then
  self.x = flr(self.x + self.xspeed)
  self.y = flr(self.y + self.yspeed)
  self.yspeed = self.yspeed + gravity
  self.timeout -= 1
  if self.x>=127 then
   self.x = 127
   fired = false
  if self.x<=0 then
   self.x = 0
   fired = false
  if self.y>=127 then
   self.y = 127
   fired = false
   explode = true
  if pget(self.x,self.y+3)!=0 
  and pget(self.x,self.y+3)!=textclr then
   fired = false
   explode = true
  self.x = flr(self.x + self.xspeed)
  self.y = flr(self.y + self.yspeed)
  self.yspeed = self.yspeed + gravity
P#27653 2016-08-29 12:50 ( Edited 2016-09-01 13:14)

Test each point from the last position to the current. As soon as it hits anything stop and explode. Depending on your movement implementation you could either use sth like bresenham or simply divide the shot's dx and dy by some factor depending on its current speed.
Good luck!

P#27657 2016-08-29 13:38 ( Edited 2016-08-29 17:38)

I would go with the latter @johanp suggestion. Since the bullet is the only thing moving really, you might as well just waste a lot of cycles updating it's position at 10x or 100x the actual framerate. All you need to do is add a loop and divide the speed by that amount each step.

Not efficient maybe, but neither is wasting all those cycles doing nothing. ;)

P#27660 2016-08-29 14:32 ( Edited 2016-08-29 18:32)

I have to ask the obvious question. Isn't there a collision detection routine in PICO ?

Function CollideImage:Object

P#27682 2016-08-29 20:12 ( Edited 2016-08-30 00:12)

Nope, it's just that bare. The only things you get are the bank of sounds/"songs", and the bank of "sprites"/map. Just like the songs just point to a series of sounds, the map just references the sprites. (and the sprites can have flags set, but they aren't objects or anything at all. you have to manually deal with tracking, collision, iterating through for whatever updates, etc)

P#27695 2016-08-29 22:28 ( Edited 2016-08-30 02:28)

Well, if we're going old-school, sounds like we are. The easiest way to detect collision between two points I've used in the past for my games is:

if abs(x1-x2)<8 and abs(y1-y2)<8 then POW

where x1&y1 is the location of sprite#1 and x2&y2 are the location for sprite#2, both sized at 8x8 pixels.

P#27696 2016-08-29 23:19 ( Edited 2016-08-30 03:20)

what johanp & slembcke said.
use the max number of pixels travelled on x or y as steps

something like that:

dx=nx-ox -- new_pos-old_pos
for s=1,steps do
    if (c!=air) boom(x,y) break

this will test some (air) pixels multiple times, but that's a really negligible overhead.

P#27706 2016-08-30 05:49 ( Edited 2016-08-30 09:50)

@ultrabrite: this works for shots fired left, but shots fired right explode in mid-air. Any idea why this may be?

EDIT: Realised this isn't particularly helpful. Here's the implemented code:

function shot:collision()
 local dx = self.x-self.lastx
 local dy = self.y-self.lasty
 local steps = max(abs(dx),abs(dy))
 local sdx = dx/steps
 local sdy = dy/steps
 for s=1,steps do
  testx = self.lastx+sdx*s
  testy = self.lasty+sdy*s
  c = pget(testx,testy)
  if c!=0 and c!= textclr then
   fired = false
   explode = true
P#27720 2016-08-30 14:45 ( Edited 2016-09-01 09:21)


P#27826 2016-09-01 06:04 ( Edited 2016-09-01 10:04)

sorry I didn't notice!

the missile might be testing itself, try adding its color alongside air & text. hard to say why it wouldn't happen on the left though.

P#27827 2016-09-01 07:54 ( Edited 2016-09-01 11:56)

I've fixed it by changing the colour check to just check the floor colour - it means the shots won't collide directly with tanks now, but it fixes the issue, so it's better than before :) Thanks for the help!

P#27831 2016-09-01 08:44 ( Edited 2016-09-01 12:44)

but you'll get the same problem, missile moving thru tanks at high speed.
though you could test distances from missile to tanks at every testx/testy.

glad to help!

P#27834 2016-09-01 09:14 ( Edited 2016-09-01 13:14)

late to the party and might be unrelated,
but this works great for circle-circle continuous collision,

the exact timestep (0-1)
<0 if already penetrating
false if not colliding.


function poolhall(a,b)
 local relativecenter=b.center-a.center
 local relativevelocity=a.linearvelocity-b.linearvelocity

 local mrc=magnitude(relativecenter)
 local mrv=magnitude(relativevelocity)

 local radii=(a.radius+b.radius)

 if mrv<mrc-radii then
    return false

 local u=unitvector(relativevelocity)
 local d=dotproduct(relativecenter,u)

 if d<=0 then 
    return false

 local f=mrc^2-d^2
 local radiisquared=radii^2

 if f>=radiisquared then
    return false

 local t=radiisquared-f 

 if t<0 then
    return false

 local distance=d-sqrt(t)

 if mrv<distance then
    return false

 return distance/mrv

(NOTE: using 2d-vector meta tables!)

P#83985 2020-11-08 14:57 ( Edited 2020-11-08 15:20)

[Please log in to post a comment]