Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
E

ecctrl

> 编程语言
Open source

️ A physics-driven controller toolkit for React Three Fiber + Rapier

770 stars0 likes0 views
WebsiteGitHub

About

️ A physics-driven controller toolkit for React Three Fiber + Rapier

Ecctrl

Physics-driven character, vehicle, drone, and custom-gravity controllers for React Three Fiber and Rapier.

Ecctrl is a modular controller toolkit for custom-gravity worlds, ShapeCast characters, torque-driven cars, thrust-driven drones, touch controls, runtime animation states, and editable physics curves.

Live Demo

Explore Ecctrl at ecctrl.app.

This is the best way to test the character controller, custom gravity, cars, drones, touch controls, time control, and tuning tools before adding Ecctrl to your project.

  • Full docs: API and configuration guide
  • Changelog: Release history and upcoming changes
  • Discord: Ecctrl discussion channel
  • Related project: BVHEcctrl, BVHEcctrl demo
  • Author: GitHub, X / Twitter, Website

Contents

  • Live Demo
  • Install
  • Quick Start
  • Detailed Docs
  • Why Ecctrl
  • Highlights
  • Module Imports
  • Custom Gravity
  • ShapeCast Character Controller
  • Animation State
  • Vehicle System
  • ShapeCast Wheels
  • Propeller Drones
  • Curve LUTs
  • Leva Curve Editor
  • Time Control
  • Touch Input
  • Performance Notes
  • Support
  • Contributing
  • Roadmap
  • Local Development
  • License

Install

bash
npm install ecctrl

Peer dependencies:

bash
npm install three react react-dom @react-three/fiber @react-three/rapier

Optional packages:

bash
npm install @react-three/drei leva

@react-three/drei is only needed for helpers such as KeyboardControls and camera tooling. If you do not use Drei, you can still drive controller input through refs or stores and build your own camera follow logic. leva is only needed if you want the visual curve editor or debug panels.

Quick Start

This is the smallest useful ShapeCast character setup:

typescript
import { KeyboardControls } from "@react-three/drei";
import { Physics } from "@react-three/rapier";
import { Ecctrl } from "ecctrl";

const keyboardMap = [
  { name: "forward", keys: ["ArrowUp", "KeyW"] },
  { name: "backward", keys: ["ArrowDown", "KeyS"] },
  { name: "leftward", keys: ["ArrowLeft", "KeyA"] },
  { name: "rightward", keys: ["ArrowRight", "KeyD"] },
  { name: "jump", keys: ["Space"] },
  { name: "run", keys: ["Shift"] },
];

export function CharacterScene() {
  return (
    
      
        
          
        
      
    
  );
}

For custom gravity scenes, set Rapier gravity to [0, 0, 0] and let Ecctrl apply gravity per body:

typescript

  
    
  

Detailed Docs

The README gives the main concepts and common setup paths. Full prop tables, config defaults, handle output, input shapes, and tuning notes are available in the detailed docs:

  • API and configuration guide

Why Ecctrl

Ecctrl is built for games and simulations where controlled objects need to behave like part of the physics world.

The character floats with spring and damping, pushes back on the object below, follows moving platforms, aligns with custom gravity, and exposes runtime state for animation and gameplay logic. Ecctrl keeps that physics-driven direction and extends it into cars, drones, touch input, time control, and editable curve-driven behavior.

Highlights

Feature What it gives you
Custom gravity Position-based gravity fields shared by characters, vehicles, drones, and custom rigid bodies
ShapeCast character Stable floating support, accurate standing points, gap handling, slope checks, and optional RayCast mode
ShapeCast wheels Torque-driven wheel simulation with editable longitudinal and lateral slip curves
Propeller drones Thrust and reaction torque are mixed per propeller for stabilized velocity or position flight
Curve LUTs Runtime-cheap curve sampling for grip, engine torque, steering, and mass falloff
Leva curve editor Optional draggable curve editor for tuning points, tangents, and weights
Time control Pause and slow down the physics world for bullet-time style control
Touch controls DOM-based joystick and virtual buttons that can be replaced by your own UI

Module Imports

Ecctrl is split into subpath exports so you can import only the parts you need.

typescript
import { Ecctrl } from "ecctrl";
import { EcctrlVehicle, ShapeCastWheel, ThrustPropeller } from "ecctrl/vehicle";
import { Joystick, VirtualButton } from "ecctrl/input";
import { useCustomGravity } from "ecctrl/gravity";
import { EcctrlCameraControls } from "ecctrl/camera";
import { TimeControl } from "ecctrl/time";
import { EcctrlAnimationStateController } from "ecctrl/animation";
import { bakeCurveLUT, evaluateCurveLUT } from "ecctrl/curves";
import { CurveEditorPlugin } from "ecctrl/leva";

For prototypes or examples, you can also import everything from ecctrl/all.

Custom Gravity

Custom gravity is one of the main systems in Ecctrl.

Instead of relying only on Rapier's global gravity vector, Ecctrl can read a gravity field function and apply gravity per body. This makes it possible to build spherical gravity, cylindrical gravity, wall-walking zones, gravity tunnels, rotating gravity fields, or gameplay-specific attraction areas.

When Ecctrl applies custom gravity, set Rapier's global gravity to [0, 0, 0] so the global gravity force and custom gravity field do not stack.

…

For custom rigid bodies outside Ecctrl, call applyGravityField each frame:

typescript
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { RigidBody, useRapier, type RapierRigidBody } from "@react-three/rapier";
import { useCustomGravity } from "ecctrl/gravity";

function CustomGravityRigidBody() {
  const body = useRef(null);
  const { world } = useRapier();
  const applyGravityField = useCustomGravity((state) => state.applyGravityField);

  useFrame(() => {
    if (body.current) applyGravityField(body.current, world.timestep);
  });

  return ;
}

For dynamic gravity, keep the field function stable and update refs that the function reads. This avoids React renders and Zustand updates on every frame.

typescript
const gravityStrength = useRef(9.81);
const gravityTarget = useRef(new THREE.Vector3(0, 20, 0));

useEffect(() => {
  setGravityField((bodyPos) => gravity.subVectors(gravityTarget.current, bodyPos).normalize().multiplyScalar(gravityStrength.current));
}, [setGravityField]);

useFrame(({ clock }) => {
  // Move the attractor without replacing the gravity field function.
  const t = clock.elapsedTime;
  gravityTarget.current.set(Math.sin(t) * 10, 20, Math.cos(t) * 10);
});

Camera Controls For Custom Gravity

EcctrlCameraControls extends Drei CameraControls with setUp(newUp). It does not automatically follow a character by itself; you still drive the follow target from your controller ref.

…

ShapeCast Character Controller

The character controller now uses ShapeCast ground detection by default. RayCast mode is still available when you want the lowest possible detection cost.

For the minimum setup, see Quick Start.

Ground Detection

ShapeCast mode gives the character a stronger ground signal than a single ray:

  • More natural movement over gaps and small seams
  • More accurate standing point and ground normal data
  • One cast can drive floating, slope, and support detection
  • Fallback center ray improves steep-slope and sharp-surface handling
  • Optional groundDetection="rayCast" mode for lower-cost scenes
typescript

Physics Interaction

The character can push back on the world instead of only moving itself:

  • Standing applies counter mass to the supporting body
  • Moving applies opposite impulse to the standing point
  • Jumping applies downward impulse to the object below
  • Moving and rotating platforms are followed smoothly

These behaviors are enabled by default. You can disable them when a scene needs simpler interaction:

typescript

Runtime Character State

EcctrlHandle exposes readonly runtime state for animation, gameplay logic, debug UI, or user-defined systems.

typescript
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { Ecctrl, type EcctrlHandle } from "ecctrl";

const ecctrl = useRef(null);

useFrame(() => {
  if (!ecctrl.current) return;

  const grounded = ecctrl.current.isOnGround;
  const speed = ecctrl.current.moveSpeed;
  const falling = ecctrl.current.isFalling;
});

  

EcctrlHandle exposes more runtime values than the three shown here, including position, velocity, gravity direction, input state, and body axes. See the detailed docs for the full handle shape.

Animation State

Ecctrl includes a lightweight animation state resolver. The controller writes the current state into useEcctrlAnimationStore; your model decides how to play, blend, or hold each animation clip.

Default states:

typescript
"IDLE" | "WALK" | "RUN" | "JUMP_START" | "JUMP_IDLE" | "JUMP_FALL" | "JUMP_LAND"

Basic setup:

…

For custom state mapping, pass a resolver:

typescript
import {
  EcctrlAnimationStateController,
  resolveEcctrlAnimationState,
  type EcctrlAnimationStateResolver,
} from "ecctrl/animation";

const resolver: EcctrlAnimationStateResolver = (ctx) => {
  if (ctx.handle.moveSpeed > 8) return "RUN";
  return resolveEcctrlAnimationState(ctx);
};

;

For fully custom animation graphs, skip the store and read EcctrlHandle directly in your animation system.

Vehicle System

Ecctrl includes a vehicle controller that can be built from wheels, propellers, or both.

typescript
import { EcctrlVehicle, ShapeCastWheel } from "ecctrl/vehicle";
import { CuboidCollider } from "@react-three/rapier";

  
  
  
  
  

EcctrlVehicle owns the body state, input state, custom gravity, and high-level control logic. Wheels and propellers register as modules, compute local wheel or propeller data, and feed the vehicle with the values it needs to apply impulses.

Driving input can come from keyboard controls, touch controls, AI, or your own code:

typescript
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { EcctrlVehicle, type EcctrlVehicleHandle, type VehicleInput } from "ecctrl/vehicle";

function VehicleInputExample({ input }: { input: VehicleInput }) {
  const vehicle = useRef(null);

  useFrame(() => {
    vehicle.current?.setMovement(input);
  });

  return (
    
      {/* body collider and vehicle modules */}
    
  );
}

ShapeCast Wheels

ShapeCastWheel is designed for physics-driven vehicle control.

The wheel receives drive torque, updates wheel angular velocity, evaluates slip through baked curves, then applies longitudinal and lateral grip impulses to the vehicle body.

torque -> wheel speed -> slip -> grip -> impulse

Key features:

  • ShapeCast wheel detection for wheel-like support
  • Optional groundDetection="rayCast" mode for maximum performance
  • Stable support point handling for suspension force
  • Accurate contact, normal, slip, torque, RPM, and impulse data
  • Drive, steer, brake, rolling resistance, and tire relaxation logic
  • AWD, RWD, FWD, and weighted torque distribution
  • Longitudinal and lateral grip curves through LUTs
  • Low-speed stability and tire relaxation behavior
  • Counter imp

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

TypeScript

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言