Realistically ricocheting bullets?


(TaylorAnderson) #1

So, I have an entity I want to deflect bullets. But if I just multiply the bullets’ x and y velocity by -1, it won’t work well. And if I do something like this:

	if (collide("deflector", x, y))
	{
		if (Math.abs(v.x) > Math.abs(v.y))
			v.x *= -1;
		else
			v.y *= -1;
	}

it works well in some cases, but then totally messes up in other edge cases. What’s the proper way to do this? I feel like this should be obvious, but I’ve googled around and most solutions seem to be about deciding the bullet’s angle (which took me a long time as well, but I got that solved)


(Helios) #2

Is your bullet ricocheting only off of square objects (as in, having only horizontal and vertical surfaces), or do you need bullets that collide with surfaces of arbitrary angles (hexagons, circles, etc)?


(TaylorAnderson) #3

OH, just square objects. I cannot think of the hell that would be angled surfaces :stuck_out_tongue:


(Helios) #4

I’m certain there are better, more concise ways of doing this, but this is how I’ve done it in the past. Until someone comes along and shows you a more concise way, this will handle things for you.

essentially, you are handling which direction to reverse by deciding whether it is moving more vertically or horizontally at the time of the crash. what you need to do instead is figure out whether you hit a vertical or horizontal wall.

if (collide("deflector", x, y))
{
	//first, store a reference to the entity we collided with
	var deflector:Entity = collide("deflector", x, y);
	
	/**
	* next reset our position back to where it was before we collided
	*  this helps to fix a lot of bugs with fast moving bullets
	* let me know if this doesn't make sense
	*/
	
	x = lastGoodX;
	y = lastGoodY;

	/**
	 * the case with squares is very simple,
	 * if the wall you strike is vertical, reverse the
	 * x movement, if it is horizontal,
	 * then reverse the y movement.
	 */
	if (collideWithVertical(x, v.x, deflector)){//if we hit a vertical wall
		v.x *= -1; //reverse x direction
	} else {//else we must have hit a horizontal wall
		v.y *= -1;//so reverse the y direction
	}
}

private function collideWithVertical(xPos:Number, xDir:Number, deflector:Entity)
{
	if (xDir > 0) {//if we are moving to the right..
		//then if we haven't yet passed the left edge,
		//we must have hit a vertical wall
		return x < deflector.left;
		
	} else if (xDir < 0) {//if we are moving to the left...
		//then if we havent yet passed the right edge,
		//we must have hit a vertical wall.
		return x > deflector.right;
		
	} 
	//if we aren't moving left or right,
	//then we must have hit a horizontal wall.
	else return false;
}

(TaylorAnderson) #6

I get the idea! I’ll probably use a more quick and dirty approach tho. Thanks so much for your help!

QUICK EDIT: The problem with this approach, as I swiftly found out, is that if you happen to nick the side of a horizontal wall the bullet goes haywire.

EDIT 2: I’M AN IDIOT! Your way works even with that edge case :stuck_out_tongue: I just didn’t want to use it cuz it seemed much longer than it actually was because the forum screws up formatting a whole lot


(Zachary Lewis) #7

If you ever do want to bounce off non-axis-aligned walls, here’s a really good explanation on how to do that from StackOverflow.


(Helios) #8

Excellent! I’m glad! And yeah, the code looked massive with the comments and the formatting, so I wouldn’t blame you!


(Jacob Albano) #9

Just pointing out that the solution above calls collide() twice, while it only needs to be called once. It’s a fairly expensive function so you should avoid that whenever possible.

var deflector:Entity = collide("deflector", x, y))
if (deflector)
{
    //  ...
}

(Helios) #10

true dat.

additionally, the only relevant details that actually need to be stored are the x coordinates of the right and left edges. even more additionally its a bit if a suboptimal approach to begin with. (For example, it starts to fail if the deflector itself is moving at high speeds)


(TaylorAnderson) #11

I hate to return to this topic, but I’m having this exact problem again, this time with levels with walls all around the sides, where this method kinda breaks down :confused:

I looked at that general method, but I’m afraid I’m entirely uncertain as to how to translate it into workable code. @zachwlewis, have you tried implementing that solution for Flashpunk? It could almost be worth it to add it to the library itself, just speaking from the amount of times I’ve had to figure out this problem.


(Bora Kasap) #12

so, if you have just square objects… use that for keep it in the level, it is simple, also good for performance…

if (this.x <= 0)  this._xspeed = Math.abs(_xspeed);
else if (this.x + this.width >= level_width) this._xspeed = -Math.abs(_xspeed);
if (this.y <= 0)  this._yspeed = Math.abs(_yspeed);
else if (this.y + this.height >= level_height) this._yspeed = -Math.abs(_yspeed);

and, so, for walls, you can use moveBy() for movement, then you can override for X, Y collisions

override public function moveCollideX(e:Entity):Boolean 
	{
		this._xspeed *= -1;
		return super.moveCollideX(e);
	}
	
	override public function moveCollideY(e:Entity):Boolean 
	{
		this._yspeed *= -1;
		return super.moveCollideY(e);
	}