Firing non-instant bullets

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.

Getting ready

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).

How to do it...

Perform the following steps to fire non-instant bullets:

  1. Let's begin by defining our 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;
  2. To avoid unnecessary object creation, we instance Ray in the constructor as follows, which we'll reuse throughout the lifespan of the bullet:
    ray = new Ray(origin, direction);
    ray.setOrigin(worldPosition);
  3. It's in the 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));
  4. If the total distance is longer than the range, the bullet can be considered beyond its effective range. We set alive to false as follows so that it can be removed:
    if(distance >= RANGE){
      alive = false;
    }
  5. The 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;
    }
  6. Next, we'll add some code to the application class. It needs to keep track of List<Collidable>, called targets and List<Bullet>, called bullets.
  7. The 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--;
      }
    }
  8. Create a 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()));
  9. The method is called from the InputAppState's 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();
    }

How it works...

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.231.194