API

Customizing Camera Inputs

Note: This page covers the legacy plugin-style input manager (camera.inputs). It currently applies to FreeCamera, UniversalCamera, FollowCamera, FlyCamera, and the VR / touch-joystick inputs — but those cameras are being ported to the newer declarative inputMap + movement system used by ArcRotateCamera and GeospatialCamera today, documented in Camera Movement and Input System. For the cameras already on the new system, existing legacy flags (e.g. useCtrlForPanning, panningMouseButton, useAltToZoom, inertia, panningInertia) continue to work and are bridged onto the new inputMap automatically — but new customizations should prefer the new system.

How to Customize Camera Inputs

Every Babylon.js camera will automatically handle inputs for you, once you call the camera's attachControl method. You can revoke the control by using the detachControl method. Most Babylon.js experts use a two-step process to activate and attach a camera:

//First, set the scene's activeCamera... to be YOUR camera.
scene.activeCamera = myCamera;
// Then attach the activeCamera to the canvas.
//Parameters: canvas, noPreventDefault
scene.activeCamera.attachControl(canvas, true);

A simpler version might look like this:

myCamera.attachControl(canvas);

By default noPreventDefault is set to false, meaning that preventDefault() is automatically called on all canvas mouse clicks and touch events.

Babylon.js v2.4 introduced a different way to manage camera inputs to provide an approach oriented toward input composability. You can now use an input manager, and each input can be seen as a plugin that is specific to a camera family and a given input type (mouse, keyboard, gamepad, device orientation, etc.).

Using input managers, you can add, remove, enable, or disable any input available for the camera. You can also easily implement custom input mechanisms or override the existing ones.

The input manager is available through the camera's inputs property. For example:

import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
const camera = new FreeCamera("sceneCamera", new Vector3(0, 1, -15), scene);
const inputManager = camera.inputs;

Configure your inputs

Most inputs provide settings to customize the sensibility and adapt it to your own scene.

Each input provides a short name available on the manager. The goal is to provide a friendly syntax when playing with your inputs.

import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
import { FreeCameraGamepadInput } from "@babylonjs/core/Cameras/Inputs/freeCameraGamepadInput";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
const camera = new FreeCamera("sceneCamera", new Vector3(0, 1, -15), scene);
camera.inputs.add(new FreeCameraGamepadInput());
camera.inputs.attached.gamepad.gamepadAngularSensibility = 250;

Adding an existing input

The input managers of ArcRotateCamera and FreeCamera expose short-hand functions for adding built-in inputs.

import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
const camera = new FreeCamera("sceneCamera", new Vector3(0, 1, -15), scene);
camera.inputs.addGamepad();

If you wish, you can also add an instance of your own input (we will cover how to implement your own input at the end of this article).

import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
import { FreeCameraGamepadInput } from "@babylonjs/core/Cameras/Inputs/freeCameraGamepadInput";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
const camera = new FreeCamera("sceneCamera", new Vector3(0, 1, -15), scene);
camera.inputs.add(new FreeCameraGamepadInput());

Enable or disable inputs

When you call attachControl on the camera, you are activating all inputs attached to the input manager. In the same way, you could turn off all inputs by calling detachControl on the camera.

If you want to disable an input temporarily, you can call detachControl directly on the input... like this:

import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
const camera = new FreeCamera("sceneCamera", new Vector3(0, 1, -15), scene);
camera.inputs.attached.mouse.detachControl();
camera.inputs.addGamepad();

You can then call attachInput when you want to turn it on again.

camera.inputs.attachInput(camera.inputs.attached.mouse);

Removing inputs

Sometimes you want a very specific input mechanism. The best approach in such cases is probably to clear all inputs and add only those you may want in your scene.

import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
const camera = new FreeCamera("sceneCamera", new Vector3(0, 1, -15), scene);
camera.inputs.clear();
camera.inputs.addMouse();

You can also remove a single input from your input manager. You can remove it by instance or by type name.

import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
const camera = new FreeCamera("sceneCamera", new Vector3(0, 1, -15), scene);
// remove by instance
camera.inputs.remove(camera.inputs.attached.mouse);
// remove by type
camera.inputs.removeByType("FreeCameraKeyboardMoveInput");

Implementing Your Own Input

Your input method is created as a function object. You must then write code for several methods, with required names, that are called by the input function object. The method names and purpose are:

// This function must return the class name of the camera, it could be used for serializing your scene
getClassName();
// This function must return the simple name that will be injected in the input manager as short hand
// for example "mouse" will turn into camera.inputs.attached.mouse
getSimpleName();
//T his function must activate your input event. Even if your input does not need a DOM element
// element and noPreventDefault must be present and used as parameter names.
// Return void.
attachControl(noPreventDefault);
// Detach control must deactivate your input and release all pointers, closures or event listeners
// element must be present as a parameter name.
// Return void.
detachControl();
// This optional function will get called for each rendered frame, if you want to synchronize your
// input to rendering, no need to use requestAnimationFrame. It's a good place for applying
// calculations if you have to.
// Return void.
checkInputs();

HammerJS Input

If the built-in touch controls for the ArcRotateCamera are not enough for you, you can implement your own camera input using the well-known touch gesture library HammerJS: https://hammerjs.github.io/

We have an example of how to use HammerJS to simulate something similar to Google Earth controls. We use BabylonJS's ArcRotateCamera for this purpose. The camera is locked on the Y axis, horizontal pan moves the camera along the X axis, and vertical pan moves it along the Z axis. The camera's alpha angle is taken into account when panning. By pinching, you can change the camera's radius to zoom in and out. You can change the camera's alpha angle with a two-finger rotate gesture, so you can rotate around the camera's target.

Example app: https://demos.babylonjs.xyz/hammerjs-example/#/

GitHub repo: https://github.com/RolandCsibrei/babylonjs-hammerjs-arc-rotate-camera

The example contains utils\ArcRotateCameraHammerJsInput.ts, which implements ICameraInput<ArcRotateCamera>. There are multiple parameters for the input, and they are pretty self-explanatory. Please refer to the input source code and set the sensitivity and threshold values that fit your needs. First, you need to install HammerJS. Please refer to https://hammerjs.github.io/getting-started/ and import it.

import "hammerjs";

To use the new input, add it to your camera.inputs after you have created the camera. To avoid one input fighting the other, remove ArcRotateCameraPointersInput from camera.inputs. After you create your input, you can set its parameters. The default values (please refer to https://github.com/RolandCsibrei/babylonjs-hammerjs-arc-rotate-camera/blob/680cf12155924a818faac5ff9d7f0a0271bb632b/src/utils/ArcRotateCameraHammerJsInput.ts#L21) are good for a general touch-screen monitor, so you may need to adjust them for your needs.

// remove mouse input
camera.inputs.removeByType("ArcRotateCameraPointersInput");
// add hammer js input
const hammerJsInput = new ArcRotateCameraHammerJsInput();
// now you can set the parameters you like
// let's double the zoomSensitivity (default is 1)
hammerJsInput.zoomSensitivity = 2;
// add the input to the camera
camera.inputs.add(hammerJsInput);

Feel free to use this input class as a starter for your own HammerJS-based input.

With Javascript

This changes the normal key mappings for moving the camera left, right, forward, and back, and rotating at its current position.

First, remove the default keyboard input.

camera.inputs.removeByType("FreeCameraKeyboardMoveInput");

Now create the new input method FreeCameraKeyboardRotateInput:

const FreeCameraKeyboardRotateInput = function () {
this._keys = [];
this.keysLeft = [37];
this.keysRight = [39];
this.sensibility = 0.01;
};

Add get name methods:

FreeCameraKeyboardRotateInput.prototype.getClassName = function () {
return "FreeCameraKeyboardRotateInput";
};
FreeCameraKeyboardRotateInput.prototype.getSimpleName = function () {
return "keyboardRotate";
};

and attach and detach methods:

import { Tools } from "@babylonjs/core/Misc/tools";
FreeCameraKeyboardRotateInput.prototype.attachControl = function (noPreventDefault) {
const _this = this;
const engine = this.camera.getEngine();
const element = engine.getInputElement();
if (!this._onKeyDown) {
element.tabIndex = 1;
this._onKeyDown = function (evt) {
if (_this.keysLeft.indexOf(evt.keyCode) !== -1 || _this.keysRight.indexOf(evt.keyCode) !== -1) {
const index = _this._keys.indexOf(evt.keyCode);
if (index === -1) {
_this._keys.push(evt.keyCode);
}
if (!noPreventDefault) {
evt.preventDefault();
}
}
};
this._onKeyUp = function (evt) {
if (_this.keysLeft.indexOf(evt.keyCode) !== -1 || _this.keysRight.indexOf(evt.keyCode) !== -1) {
const index = _this._keys.indexOf(evt.keyCode);
if (index >= 0) {
_this._keys.splice(index, 1);
}
if (!noPreventDefault) {
evt.preventDefault();
}
}
};
element.addEventListener("keydown", this._onKeyDown, false);
element.addEventListener("keyup", this._onKeyUp, false);
Tools.RegisterTopRootEvents(canvas, [{ name: "blur", handler: this._onLostFocus }]);
}
};
FreeCameraKeyboardRotateInput.prototype.detachControl = function () {
const engine = this.camera.getEngine();
const element = engine.getInputElement();
if (this._onKeyDown) {
element.removeEventListener("keydown", this._onKeyDown);
element.removeEventListener("keyup", this._onKeyUp);
Tools.UnregisterTopRootEvents(canvas, [{ name: "blur", handler: this._onLostFocus }]);
this._keys = [];
this._onKeyDown = null;
this._onKeyUp = null;
}
};

Optionally, add checking inputs:

FreeCameraKeyboardRotateInput.prototype.checkInputs = function () {
if (this._onKeyDown) {
const camera = this.camera;
// Keyboard
for (let index = 0; index < this._keys.length; index++) {
const keyCode = this._keys[index];
if (this.keysLeft.indexOf(keyCode) !== -1) {
camera.cameraRotation.y += this.sensibility;
} else if (this.keysRight.indexOf(keyCode) !== -1) {
camera.cameraRotation.y -= this.sensibility;
}
}
}
};

Finally, add this new input method to the camera inputs:

camera.inputs.add(new FreeCameraKeyboardRotateInput());
Rotate Free Camera Example

With Typescript

Using TypeScript, you could implement the interface ICameraInput:

import type { Camera } from "@babylonjs/core/Cameras/camera";
interface ICameraInput<TCamera extends Camera> {
// the input manager will fill the parent camera
camera: TCamera;
//this function must return the class name of the camera, it could be used for serializing your scene
getClassName(): string;
//this function must return the simple name that will be injected in the input manager as short hand
//for example "mouse" will turn into camera.inputs.attached.mouse
getSimpleName(): string;
//this function must activate your input, event if your input does not need a DOM element
attachControl: (noPreventDefault?: boolean) => void;
//detach control must deactivate your input and release all pointers, closures or event listeners
detachControl: () => void;
//this optional function will get called for each rendered frame, if you want to synchronize your input to rendering,
//no need to use requestAnimationFrame. It's a good place for applying calculations if you have to
checkInputs?: () => void;
}

How to Make a Walk and Look Around Camera

The following example customizes the keyboard and mouse inputs to a universal camera. With this change, using the arrow keys you can walk forwards and backward in the scene and rotate to look left and right. Using the mouse you can look around and above and below.

In the example there are two viewports, the upper one gives a first-person view as you move and look around. The lower one gives a representation of the camera and the collision volume surrounding it.

Remember to click on the scene before using the arrow keys.

Walk and Look Camera Example

Using BaseCameraPointersInput to Create Custom Inputs

In addition to making custom camera inputs as illustrated in the Implementing Your Own Input section, you can also extend the functionality of some implemented base classes to make creating custom classes easier. One such class is BaseCameraPointersInput:

For either Javascript (ES6+) or Typescript, you should be able to extend the functionality of the BaseCameraPointersInput class. From there, you just need to override a few functions.

import { BaseCameraPointersInput } from "@babylonjs/core/Cameras/Inputs/BaseCameraPointersInput";
// You need to extend the BaseCameraPointersInput to get the required functionality
class YourCustomInputClass extends BaseCameraPointersInput {
// This is the constructor. Unless you have something specific that you need
// to do when you create your object, you don't need to implement this. You
// must call the super() function though, if you do.
// constructor() { super(); }
// This is exactly the same the function in the previous section and will still need to be
// implemented.
getClassName() { }
// This function is the exact same thing as the previous section. However, it has already
// been implemented with a value of "pointers" and is technically optional.
// getSimpleName() {};
// This function is already implemented. If you are planning to use this class, it is
// recommened to not override it.
// attachControl(noPreventDefault) {};
// Same thing with detachControl
// detachControl() {};
// This optional function will get called for each rendered frame, if you want to synchronize your
// input to rendering, no need to use requestAnimationFrame. It's a good place for applying
// calculations if you have to.
// Return void.
checkInputs() { }
// This function will fire during a POINTERMOVE event where there is either an active mouse
// button down or only one active touch. "point" will contain the coordinates, pointerId,
// and pointer type. The offsets are just the changes in position from the previous point.
// This will NOT fire if multiple touches are active. This method is required.
onTouch(point, offsetX, offsetY) { }
// This function will only fire during a POINTERMOVE event where more than one touch is active.
// This function will only support the first two active touches and all others will be ignored.
// Points A and B are said touches. Both previous and current pinch distances and positions are
// available to support basic gesture logic, as needed. As a warning, the previous movement may
// be null at the beginning of a multi-touch movement.
onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition) { }
// This function will only fire during a POINTERDOUBLETAP event. The "type" parameter
// is just the pointer type (mouse, touch, etc.). This is optional.
onDoubleTap(type) { }
// This function will fire when a contextmenu event occurs (right-click menu).
// "evt" is the triggering event. This is optional.
onContextMenu(evt) { }
// This function will fire when a POINTERDOWN event occurs.
// "evt" is the triggering event. This is optional.
onButtonDown(evt) { }
// This function will fire when a POINTERUP event occurs (right-click menu).
// "evt" is the triggering event. This is optional.
onButtonUp(evt) { }
// This function will fire when the window loses focus (eg. blur event)
// This is optional.
onLostFocus() { }
}

This may seem like a lot, but the big takeaways are that onTouch is where you handle single-pointer source events, and onMultiTouch handles events with at least two touch sources.

If you find yourself asking, "What benefit is there to using this versus creating my own from scratch?", here are a few benefits. The BaseCameraPointersInput class will automatically handle various input- and event-based details such as preventDefault, pointer capture, and pointer lock. On top of that, event handling is taken care of for you. While there is less flexibility in going this route, it might be easier to work with.

FreeCameraPointersInput (JS) Example FreeCameraPointersInput (TS) Example

Further reading

Camera Movement and Input System
How the declarative camera movement and input mapping system on ArcRotateCamera and GeospatialCamera works, and how to customize it.
Camera Movement and Input System