In this chapter, we’ll focus on animating the bullets that we spawned in the previous chapter. Specifically, we’ll make the bullets move forward in the direction the controller is pointing and disappear after a short duration. This involves managing the bullets’ movement, ensuring they don’t remain in the scene indefinitely, and updating their positions each frame.
We begin by defining constants that will control the behavior of the bullets:
const forwardVector = new THREE.Vector3(0, 0, -1);
const bulletSpeed = 5;
const bulletTimeToLive = 1;
forwardVector
: This vector points forward along the negative Z-axis. It represents the default direction in which bullets will move.bulletSpeed
: Sets the speed of the bullets, meaning they will travel 5 units per second.bulletTimeToLive
: Determines how long each bullet will remain active in the scene (1 second). This ensures bullets that are no longer visible are removed to prevent performance degradation over time.
We use an object to keep track of all active bullets:
const bullets = {};
bullets
: This object stores all active bullets using their unique IDs (uuid
). This allows us to manage and update each bullet individually during every frame.
In our onFrame
function, when a bullet is fired, we calculate its movement direction and assign it a time-to-live (TTL):
if (controllers.right) {
const { gamepad, raySpace } = controllers.right;
if (gamepad.getButtonClick(XR_BUTTONS.TRIGGER)) {
const bullet = bulletPrototype.clone();
scene.add(bullet);
raySpace.getWorldPosition(bullet.position);
raySpace.getWorldQuaternion(bullet.quaternion);
const directionVector = forwardVector
.clone()
.applyQuaternion(bullet.quaternion);
bullet.userData = {
velocity: directionVector.multiplyScalar(bulletSpeed),
timeToLive: bulletTimeToLive,
};
bullets[bullet.uuid] = bullet;
}
}
-
globalQuaternion
: This represents the controller’s orientation in world space. By getting the world quaternion of the controller’s ray space, we capture how the controller is rotated in 3D space. -
directionVector
: To determine the direction in which the bullet will move, we take theforwardVector
and apply theglobalQuaternion
to it. This rotates the forward vector to match the orientation of the controller. This step is crucial because it ensures that bullets are fired in the direction the controller is pointing, regardless of its rotation. -
userData
: Three.js provides a built-inuserData
property on allObject3D
instances, which we can use to store custom information about our objects. Here, we store the bullet’s velocity (its movement direction and speed) and its TTL. This data will be used to update the bullet’s position and ensure it’s removed after its lifespan ends.
Each frame, we update the bullets' positions and reduce their TTL:
Object.values(bullets).forEach((bullet) => {
if (bullet.userData.timeToLive < 0) {
delete bullets[bullet.uuid];
scene.remove(bullet);
return;
}
const deltaVec = bullet.userData.velocity.clone().multiplyScalar(delta);
bullet.position.add(deltaVec);
bullet.userData.timeToLive -= delta;
});
-
Lifespan Check: Each bullet’s TTL decreases over time. Once the TTL drops below zero, the bullet is removed from both the scene and the
bullets
object to free up resources and maintain performance. -
Position Update: The bullet’s position is updated by moving it along its velocity vector, scaled by
delta
(the time since the last frame). This makes the bullet move forward at the correct speed each frame.
In this chapter, you’ve enhanced your WebXR scene by animating the bullets. Now, when fired, the bullets travel in the direction the controller is pointing and disappear after a set time. This addition introduces dynamic motion into your scene and helps maintain performance by cleaning up bullets after they’re no longer needed.
Here’s what our scene looks like with the bullet animation feature: