In the previous recipe, we implemented a basic form of firing that will work for many cases. The exit velocity for a bullet is usually around 300 m/s (or close to 1000 feet/s) and might seem near-instant at close range. For ranges over 30 m (approximately 90 feet), the delay starts to get noticeable though, and more realistic games might need another model. In this recipe, we'll look into a type of bullet that travels in the game world. It's still an invisible bullet, but it can easily be visualized if required.
This recipe can be seen as a more advanced version of the previous recipe and won't require many changes to what we did there but will mainly contain additions. Almost all of the functionalities will be implemented in a new class, called Bullet
(not to be confused with the physics engine with the same name that we use in a Chapter 8, Physics with Bullet).
Perform the following steps to fire non-instant bullets:
Bullet
class. The worldPosition
and direction
variables are used by the Ray
class as a starting position each step it takes. The RANGE
field is a static field that defines the maximum range, inside which the bullet will be effective. The distance
variable is the distance the bullet has traveled since it was instanced. It also needs to keep track of whether it's alive or not, for cleanup reasons. It should be said that this particular bullet is rather slow and short lived.private Vector3f worldPosition; private Vector3f direction; private float speed = 10; private Ray ray; private final static int RANGE = 10; private float distance; private boolean alive = true;
Ray
in the constructor as follows, which we'll reuse throughout the lifespan of the bullet:ray = new Ray(origin, direction); ray.setOrigin(worldPosition);
update
method that most of the work is done. At the beginning, we set the ray's origin to be the current position of the bullet. The direction will stay the same, so no need to change that. We do, however, need to set limit factorized by the time passed for the update (tpf
). The limit is also the distance the bullet has traveled since the last update, so we use this to update the current position of the bullet:ray.setLimit (speed * tpf); distance += ray.limit; worldPosition.addLocal(direction.mult(ray.limit));
alive
to false
as follows so that it can be removed:if(distance >= RANGE){ alive = false; }
Bullet
class also has a checkCollision
method. It takes a list of targets as input and tries a collision between each of them and the ray. If any collision is detected, alive
will be set to false
and the closest CollisionResult
will be returned to the calling method as follows:public CollisionResult checkCollision(List<Collidable> targets){ CollisionResults collRes = new CollisionResults(); for(Collidable g: targets){ g.collideWith(ray, collRes); } if(collRes.size() > 0){ alive = false; return collRes.getClosestCollision(); } return null; }
List<Collidable>
, called targets
and List<Bullet>
, called bullets
.simpleUpdate
method updates the movement of all bullets by calling their update method before seeing whether any collisions have occurred or not. Any depleted bullets are removed in a way that avoids ArrayIndexOutOfBounds
exceptions:Bullet b = bullets.get(i); b.update(tpf); CollisionResult result = b.checkCollision(targets); if(result != null){ System.out.println("hit " + result); } if(!b.isAlive()){ bullets.remove(b); bulletAmount--; if(i > 0){ i--; } }
fire()
method that creates a new bullet by using the camera's location and direction as follows:bullets.add(new Bullet(cam.getLocation().clone(), cam.getDirection().clone()));
onAction
method, which is similar to how it looked in the previous recipe:if (isPressed && character.getCooldown() == 0f){
((CharacterInputTest_Firing_NonInstant) app ).fire();
character.onFire();
}
The Bullet
class can almost be seen as a slow ray. The Ray
instance we have in Bullet
is mostly out of convenience, since it's already prepared to collide with targets. By incrementing the position of the ray and having a short limit for it, we have a Ray
instance that takes little steps forward in the game world, checking for collisions in each update.
If a collision has occurred, the returned CollisionResult
contains information about where the collision has occurred, with what, and whether it can be used to build further functionalities.
3.15.231.194