Character Controller

The Character Controller Component is a modular, customizable movement system designed to support various gameplay formats, including third-person, first-person, side-scroller, and top-down style experiences. It provides a non-physics-based movement model with optional physics interactions, allowing for smooth, responsive controls without physics body dependencies. The Character Controller is designed for compatibility with Bitmoji 3D and custom characters.
Key Features
- Multiple control schemes: Joystick or manual API control.
- Physics integration: Collision detection, ground checking, gravity.
- Animation support: Optional Idle and movement animations with speed thresholds.
- Axis constraints: Lock movement along specific axes.
- Sprint: Configurable sprint speeds and controls.
Usage
To use the Character Controller:

- Go to the Asset Library in Lens Studio and search for Character Controller in the
Custom Components
section. - Click
Install
. The component will be added to the Asset Browser. - Drag the Character Controller component into the scene or add it to an empty scene object.
Character's scale and position will not be changed in runtime by updating Transform. To change position, use setPosition()
API instead.
- Add the Bitmoji 3D component to the same scene object.
Animation Setup: Make sure Adapt to Mixamo
is enabled on the Bitmoji 3D component for animation support.

- In the Input Control section of the component inspector, set the
Tracking Camera
to aCamera
object for the Joystick location.

- Adjust the movement settings to customize how your character responds to input.
Ground Setup: By default, Ground Is Zero
is enabled in the Physics settings creating an infinite floor plane as a ground surface. Toggle this off to create an environment with custom ground surfaces.
- Add
Physics Body
andPhysics Collider
objects to the scene to create an environment for your character to interact with.
Physics Objects: Use objects with a Physics Body
Component to create movable objects. Use objects with a Physics Collider
Component for immovable objects (walls, grounds, platforms).
Character Controller supports only 1 Physics World
Component in the project for proper collision handling.
Component Inputs
Name | Type | Description |
---|---|---|
Tracking Camera | Camera | Camera which renders character controller. |
Move Speed | number | Controls how fast the character moves. |
Sprint Speed | number | Controls how fast the character runs. |
Acceleration | number | Determines how quickly the character reaches full speed. |
Deceleration | number | Defines how quickly the character slows down when input stops. |
Min Move Distance | number | Defines the smallest movement distance before applying updates. |
Auto Face Movement Direction | number | Determines if the character automatically rotates to match the movement direction. |
Rotation Smoothing | number | Defines how smoothly the character rotates towards movement direction, from 0 to 1. |
Input Control Type | None | Joystick | If None is selected, user can move character controller manually with move(direction: vec3): void API. If joystick is enabled character will be moved using joystick. |
Joystick Position | Free | Left | Right | Custom | Sets position of joystick. If Free is selected, joystick appears each time when user touches screen in that position. |
Interactive Area | SceneObject | Parent of joystick in case Custom is selected for joystickPosition. Should have ScreenTransform. |
Joystick Parent | SceneObject | Parent of joystick in case Custom is selected for joystickPosition. Should have ScreenTransform. |
Sensitivity | number | Adjusts how responsive joystick input is. |
Dead Zone | number | Size of the central region around the stick's neutral position where small movements are ignored to prevent unintended input. |
Render Order | number | Render order of joystick. |
Lock X Axis | boolean | Disables movement along the X axis. |
Lock Y Axis | boolean | Disables movement along the Y axis. |
Lock Z Axis | boolean | Disables movement along the Z axis. |
Ground Check Distance | number | Defines how far the system looks below and above the character to detect ground. |
Slope Limit | number | Limits movement on steep inclines to prevent unnatural climbing. |
Step Height | number | Maximum step height when climbing a step. |
Ground Is Zero | boolean | Enables a virtual ground plane at Y = 0 for simplified grounding behavior without requiring floor colliders. |
Show Collider | boolean | Makes character controller collider visible. |
Collider Height | number | Length of capsule. |
Collider Radius | number | Radius of capsule. |
Collider Center | vec3 | Offset of capsule. |
Animation Asset | AnimationAsset | Animation asset for specified animation state. |
Playback Speed | number | Playback speed of specified animation state. |
Min Character Speed | number | Minimum speed of character to trigger specified animation state. |
Component API
Name | Description |
---|---|
move(direction: vec3): void | Moves the character in the specified direction. Y value will be ignored. To use this API to set character direction manually, please set Input Control Type to None. |
stopMovement(): void | Immediately stops character movement. |
setPosition(position: vec3): void | Teleports the character to a specific world position. |
getPosition(): vec3 | Returns the current world position of the character. |
setRotation(rotation: quat): void | Sets the character's facing rotation. Will rotate character only around y axis. |
getRotation(): quat | Gets the character's current rotation. |
getDirection(): vec3 | Returns the current movement direction. |
getVelocity(): vec3 | Returns the character's current velocity vector. |
setSprintEnabled(enabled: boolean): void | If true, enables sprinting speed, disables otherwise. |
isSprinting(): boolean | Returns true if sprint is currently active. |
setMoveSpeed(speed: number): void | Sets the character's base movement speed. |
getMoveSpeed(): number | Returns the current base movement speed. |
setSprintSpeed(speed: number): void | Sets the character's sprint speed. |
getSprintSpeed(): number | Returns the current sprint speed. |
isGrounded(): boolean | Returns true if the character is currently grounded. |
isMoving(): boolean | Returns true if the character is currently moving. |
setAutoFaceMovement(enabled: boolean): void | Enables or disables auto-facing toward movement direction. |
getAutoFaceMovement(): boolean | Returns whether auto-facing movement is enabled. |
setAcceleration(value: number): void | Sets the acceleration value. |
getAcceleration(): number | Returns the acceleration value. |
setDeceleration(value: number): void | Sets the deceleration value. |
getDeceleration(): number | Returns the deceleration value. |
setLockXAxis(enabled: boolean): void | Enables or disables movement along the X axis. |
getLockXAxis(): boolean | Returns whether movement along the X axis is currently locked. |
setLockYAxis(enabled: boolean): void | Enables or disables movement along the Y axis. |
getLockYAxis(): boolean | Returns whether movement along the Y axis is currently locked. |
setLockZAxis(enabled: boolean): void | Enables or disables movement along the Z axis. |
getLockZAxis(): boolean | Returns whether movement along the Z axis is currently locked. |
setShowCollider(value: boolean): void | If true is set character's collider is visible. |
getShowCollider(): boolean | Returns whether character's collider is visible. |
API Events
Event Name | Type | Description |
---|---|---|
onCollisionEnter | event1<CollisionEnterEventArgs, void> | Triggered when character starts colliding with another collider. |
onCollisionStay | event1<CollisionEnterEventArgs, void> | Triggered while character remains in collision. |
onCollisionExit | event1<CollisionEnterEventArgs, void> | Triggered when character exits a collision. |
onOverlapEnter | event1<OverlapEnterEventArgs, void> | Triggered when character enters an overlap volume. |
onOverlapStay | event1<OverlapEnterEventArgs, void> | Triggered while character remains in overlap volumes. |
onOverlapExit | event1<OverlapEnterEventArgs, void> | Triggered when character exits an overlap volume. |
Moving the Character Toward Touch Position

Use the following example to move the character toward the most recent screen touch position.
Make sure that Input Control
Type on the Character Controller is set to None
, otherwise the call to move()
will be ignored.
- JavaScript
- TypeScript
// Move the character towards the most recent touch position on the screen.
//@input Component.Camera camera;
//@input Component.ScriptComponent characterController
//@input float speed = 25
//@input float minDist = 25
let targetPos = null;
script.createEvent('UpdateEvent').bind(() => handleUpdate());
script.createEvent('TouchStartEvent').bind((e) => handleTouch(e));
script.createEvent('TouchMoveEvent').bind((e) => handleTouch(e));
function handleUpdate() {
if (targetPos) {
const charPos = script.characterController
.getTransform()
.getWorldPosition();
let offset = targetPos.sub(charPos);
offset.y = 0;
if (offset.length <= script.minDist) {
script.characterController.stopMovement();
targetPos = null;
} else {
const movement = offset
.normalize()
.uniformScale(script.speed * getDeltaTime());
script.characterController.move(movement);
}
}
}
function handleTouch(touchEvent) {
let screenPos = touchEvent.getTouchPosition();
let startPos = script.camera.screenSpaceToWorldSpace(
screenPos,
script.camera.near
);
let endPos = script.camera.screenSpaceToWorldSpace(
screenPos,
script.camera.far
);
let probe = Physics.createGlobalProbe();
probe.rayCast(startPos, endPos, (hit) => {
if (hit) {
targetPos = hit.position;
}
});
}
// Move the character towards the most recent touch position on the screen.
@component
export class MoveCharacterToTouch extends BaseScriptComponent {
@input camera: Camera;
@input('Component.ScriptComponent')
characterController: ScriptComponent & {
move: (movement: vec3) => void;
stopMovement: () => void;
};
@input speed: number = 25;
@input minDist: number = 25;
private targetPos: vec3 = null;
onAwake() {
this.createEvent('UpdateEvent').bind(() => this.handleUpdate());
this.createEvent('TouchStartEvent').bind((e) => this.handleTouch(e));
this.createEvent('TouchMoveEvent').bind((e) => this.handleTouch(e));
}
private handleUpdate() {
if (this.targetPos) {
const charPos = this.characterController
.getTransform()
.getWorldPosition();
let offset = this.targetPos.sub(charPos);
offset.y = 0;
if (offset.length <= this.minDist) {
this.characterController.stopMovement();
this.targetPos = null;
} else {
const movement = offset
.normalize()
.uniformScale(this.speed * getDeltaTime());
this.characterController.move(movement);
}
}
}
private handleTouch(touchEvent: TouchStartEvent | TouchMoveEvent) {
let screenPos = touchEvent.getTouchPosition();
let startPos = this.camera.screenSpaceToWorldSpace(
screenPos,
this.camera.near
);
let endPos = this.camera.screenSpaceToWorldSpace(
screenPos,
this.camera.far
);
let probe = Physics.createGlobalProbe();
probe.rayCast(startPos, endPos, (hit) => {
if (hit) {
this.targetPos = hit.position;
}
});
}
}
Testing on Device
To preview your Lens in Snapchat, follow the Pairing to Snapchat guide.