3

Velocity-Based Following (Premium)

This advanced method uses velocity vectors to create more realistic following behavior with acceleration, deceleration, and damping.

Code Example

// Velocity-Based Following
const maxSpeed = 0.5;
const acceleration = 0.02;
let velocity = new THREE.Vector3();

function updateFollower() {
    const direction = new THREE.Vector3();
    direction.subVectors(target.position, follower.position);
    
    if (direction.length() > 0.1) {
        direction.normalize();
        velocity.addScaledVector(direction, acceleration);
        
        if (velocity.length() > maxSpeed) {
            velocity.normalize().multiplyScalar(maxSpeed);
        }
    } else {
        velocity.multiplyScalar(0.9); // Damping
    }
    
    follower.position.add(velocity);
}
4

Look-At Following (Premium)

Combines position following with rotation tracking so the follower always faces the target.

Code Example

// Following with Look-At Behavior
const followSpeed = 0.1;

function updateFollower() {
    follower.position.lerp(target.position, followSpeed);
    follower.lookAt(target.position);
}
5

Complete ObjectFollower Class (Premium)

A production-ready, reusable class that combines all methods into one flexible solution you can drop into any project.

Full Reusable Class

class ObjectFollower {
    constructor(follower, target, options = {}) {
        this.follower = follower;
        this.target = target;
        this.followSpeed = options.followSpeed || 0.1;
        this.distance = options.distance || null;
        this.lookAt = options.lookAt || false;
        this.velocity = new THREE.Vector3();
        this.useVelocity = options.useVelocity || false;
        this.maxSpeed = options.maxSpeed || 0.5;
    }

    update() {
        if (this.useVelocity) {
            this.updateWithVelocity();
        } else if (this.distance) {
            this.updateWithDistance();
        } else {
            this.updateSimple();
        }
        if (this.lookAt) {
            this.follower.lookAt(this.target.position);
        }
    }

    updateSimple() {
        this.follower.position.lerp(
            this.target.position, this.followSpeed
        );
    }

    updateWithDistance() {
        const dir = new THREE.Vector3()
            .subVectors(this.follower.position, this.target.position)
            .normalize();
        const desired = this.target.position.clone()
            .addScaledVector(dir, this.distance);
        this.follower.position.lerp(desired, this.followSpeed);
    }

    updateWithVelocity() {
        const dir = new THREE.Vector3()
            .subVectors(this.target.position, this.follower.position);
        if (dir.length() > 0.1) {
            dir.normalize();
            this.velocity.addScaledVector(dir, 0.02);
            if (this.velocity.length() > this.maxSpeed) {
                this.velocity.normalize().multiplyScalar(this.maxSpeed);
            }
        } else {
            this.velocity.multiplyScalar(0.9);
        }
        this.follower.position.add(this.velocity);
    }
}

// Usage:
const follower = new ObjectFollower(camera, player, {
    followSpeed: 0.1,
    distance: 5,
    lookAt: true,
    useVelocity: false
});

// In your animation loop:
function animate() {
    follower.update();
    renderer.render(scene, camera);
    requestAnimationFrame(animate);
}