In the previous tutorial, we added an option to get list of bodies around the player using QueryAABB method present in box2d in libgdx. This method gives list of all bodies in a bounding box. So it is helpful in giving details of bodies in blast radius. Though the issue is that the bounding box is a rectangle. For our blast we would need a circular area to be queried to get the affected bodies. So in this post we will learn about raycast method.
To help with that we will go through another method today called world.raycast. This will be helpful in creating a more accurate explosion. It will only affects bodies which are in blast radius rather than the bounding box. Below is its signature.
world.raycast(raycastCallback callback //Callback which is called when ray intersects a fixture ,Vector2 point1, //Start point of the ray Vector2 point2 //End point of the ray );
What is a Raycast?
A raycast is a straight line for which we define a start point and an end point. In box2d, the raycast method helps in giving a list of bodies which this raycast intersects along the way. Along with that, it gives the normal of the reflected ray at the point of intersection with fixture. Finally it also gives the distance traveled from the start point. This is particularly helpful in querying the bodies in blast radius. Below is an image for reference
In the above image, a line is drawn to represent the raycast in red color. It stops when it encounters a body (it is possible to stop or go further after it hits a body). So we can draw rays like this all round the player to get list of bodies which will collide in the explosion radius around the player. Below is a snippet to show how we can use raycast method in box2d in libgdx.
RayCastCallback callback; Vector2 collisionPoint; World world; public void Init(){ ... collisionPoint =new Vector2(); callback=new RayCastCallback() { @Override public float reportRayFixture(Fixture fixture, Vector2 point, Vector2 normal, float fraction) { //The point of collision with the fixture in box coordinates collisionPoint.set(point); //-1 to ignore this fixture and continue //0 to terminate the ray cast //fraction to clip the ray at this point //1 to not the clip the ray and continues return fraction; } }; } // public void CalculateRaycast(){ Vector2 start=new Vector2(); Vector2 end=new Vector2(); //callback listens to the query and calls reportRayFixture method on colliding with a fixture //start and end are start and end points of the ray world.raycast(callback,start,end); }
There are few things to note in the above snippet. reportRayFixture is called when a ray intersects a fixture. It contains the point and normal vector2 of reflected ray after collision. Also it has a value “fraction” which defines the fraction of distance traveled by ray before it collided with the fixture. This is helpful in calculating the distance of fixture from source. Distance = end.dst(start)*fraction. In return values we can define if we want to the ray to continue or stop at the fixture. Another point to note is in method world.raycast, the passed start and end variables values will change as they are reused in calculations in raycast. So its better to pass a copy of the coordinates.
How many Rays?
The rays are a good way to check if body is in range of another body in a particular direction. For an explosion though, we would need rays to cover 360 degrees around the center of explosion. We can limit the number of rays to a fixed count. We can then divide 360 degrees by that count and rotate rays every x degrees. The more the number, more accurate would be count list of bodies in explosion radius. Though, as you can see, raycastCallback does not save any information regarding collision. It is therefore a better option to implement a custom callback as show below.
public class MyRaycastCallback implements RayCastCallback { float BOX_TO_WORLD =100; //As our box to world ratio is 1:100 Vector2 collisionPoint; boolean isColliding; Array<Body> collisionBodies; public MyRaycastCallback(Array<Body> collisionBodies,Vector2 ep){ this.collisionBodies=collisionBodies; collisionPoint=new Vector2(ep.x,ep.y).scl(BoxConstants.BOX_TO_WORLD); isColliding=false; } public Vector2 GetCollisionPoint(){ return collisionPoint; } public boolean DidRayCollide(){ return isColliding; } @Override public float reportRayFixture(Fixture fixture, Vector2 point, Vector2 normal, float fraction) { //return -1 - to ignore the current fixture //return 0 - to terminate the raycast //return fraction - to clip the raycast at current point //return 1 - don't clip the ray and continue isColliding=true; collisionPoint.set(point).scl(BOX_TO_WORLD); if(!collisionBodies.contains(fixture.getBody(),true)) collisionBodies.add(fixture.getBody()); return fraction; } } public class Main { ... final int RAY_COUNT=30; Array<MyRaycastCallback> raycasts; Array<Body> raycastBodies; Vector2 direction; World world; private float ConvertToBoxCoordinate (float x){ return x*0.01f; } private void CalculateRaycast(Vector2 touchDownPoint ){ raycasts.clear(); raycastBodies.clear(); //START WITH 0 Degree direction.set(1,0); float rotateAngle=360/RAY_COUNT; blastRadius =200; for(int i=0;i<RAY_COUNT;i++) { float x1 = ConvertToBoxCoordinate(touchDownPoint.x); float y1 = ConvertToBoxCoordinate(touchDownPoint.y); float x2 = x1 + ConvertToBoxCoordinate(direction.x); float y2 = y1 + ConvertToBoxCoordinate(direction.y); float boxBlastRadius = ConvertToBoxCoordinate(blastRadius) ; Vector2 endPoint=new Vector2(x2,y2).sub(x1,y1).nor().scl(boxBlastRadius).add(x1,y1); MyRaycastCallback ray=new MyRaycastCallback(raycastBodies,endPoint); world.rayCast(ray,new Vector2(x1,y1),new Vector2(endPoint)); raycasts.add(ray); direction.rotate(rotateAngle); //world.rayCast(callback,new Vector2(x,y),new Vector2(x2,y2)); } } }
In the above code we create a custom class to store point of collision as well as list of bodies which are queried though the callback. Then in main class we create an array of rays around the center point, rotating each by small angle. Below is a screenshot for reference
Another way to go about this would be to first use QueryAABB. The bounding box can be of width and height equal to twice of radius of explosion to give list of bodies in that range. Then we can set explosion source as start and direction would be normal to the fixture edge closest to the source of explosion.
Source Code
Below is link to github code repository which has an example of Raycast along with other examples.
1 thought on “Add Explosion in Box2D using Raycast in libgdx”
Comments are closed.