vibe-starter-3d-test
v0.1.4
Published
3D library for React applications
Readme
Vibe Starter 3D TEST
This project is a foundational library for 3D projects used in Verse8. It provides essential components to start 3D development.
Introduction
Vibe Starter 3D is a library designed to easily integrate 3D elements into React applications. Built on Three.js, React Three Fiber, and related technologies, it specifically offers functionality for easily rendering and animating 3D character models in VRM and GLTF/GLB formats.
Installation
npm install vibe-starter-3dArchitecture Overview
Controller-Player Separation
Vibe Starter 3D follows a clear separation between control systems and player objects:
- Controllers (
FreeViewController,FirstPersonViewController, etc.) are independent input and camera management systems - RigidBodyPlayer is the actual player character in the physics world
- Controllers automatically detect and remotely control the
RigidBodyPlayercomponent in the scene - This architecture allows for flexible controller switching without affecting the player object itself
Key Features
Components
- CharacterRenderer: Unified component for rendering VRM and GLTF/GLB models
- View Controllers: Components that automatically detect and control existing
RigidBodyPlayerin the sceneFreeViewController: Free camera perspective controllerFirstPersonViewController: First-person perspective controllerQuarterViewController: Quarter view perspective controllerSideViewController: Side view perspective controllerSimulationViewController: Simulation view perspective controller with edge scrolling and camera controlsFlightViewController: Flight perspective controller with configurable handlePointToMoveController: Point-to-move perspective controller with configurable camera mode and zoom
- Physics Components: Components for physics-based interactions
RigidBodyObject: Base component for physics objects with trigger event handlingRigidBodyPlayer: Player-specific physics component with automatic collider creation
- NetworkObject: Component for handling network-connected objects
NetworkObjectProps: Defines network object propertiesNetworkObjectHandle: Handle for manipulating network objects
- FollowLight: Component for creating a configurable light source that can follow a target.
Utilities
- controllerUtils: Controller-related utilities
position,camInitDis,camMinDis,camMaxDis,floatHeight
- collisionUtils: Collision detection and handling utilities
isInCollisionGroup,createLocalPlayerCollisionGroups,collisionGroups, etc.
- typeUtils: Type conversion utilities between Three.js and React Three Fiber
toVector3,toVector3Array,toQuaternion,toQuaternionArrayetc.
- characterUtils: Character calculation utilities
CharacterUtils.targetHeight(): Returns target height with fallback to default (1.6m)CharacterUtils.capsuleRadius(): Calculates capsule collider radius based on character heightCharacterUtils.capsuleHalfHeight(): Calculates capsule collider half-height based on character height
Hooks
- useMouseControls: Hook for handling mouse input controls
- useControllerState: Hook for accessing and managing controller state
setEnableInput,setPosition,setRotation,setVelocity,setMoveToPoint,isPointMoving
Types
Animation-related Types:
AnimationType: A string type used to identify specific animations. Developers can define unique string keys for various actions a character can perform, such as 'IDLE', 'WALK', 'RUN', 'JUMP', 'ATTACK', etc. ThisAnimationTypeserves as the key inAnimationConfigMapto map to specific animation configurations (AnimationConfig). It is also used withcurrentAnimationRefto specify the animation currently playing and is passed as an argument to theonAnimationCompletecallback to indicate which animation has finished.AnimationConfig: An interface defining the configuration for a single animation. It includes the following properties:url: string: The path or URL to the animation file (e.g., an .fbx or .glb file containing the animation data). This is a required field.loop: boolean: Specifies whether the animation should repeat from the beginning once it reaches the end. Defaults totrue.duration?: number: An optional duration for the animation in seconds. If not provided, the animation's own inherent duration will be used. This can be useful for overriding or standardizing animation lengths.clampWhenFinished?: boolean: If set totrue, and the animation is not looping (loop: false), the animation will hold on its last frame when it finishes playing. Iffalse, it will revert to the model's base pose (or the first frame of the animation, depending on the renderer's behavior). Defaults tofalse.
AnimationConfigMap: This type is defined asRecord<AnimationType, AnimationConfig>. It represents an object where keys areAnimationTypestrings (e.g., 'IDLE', 'WALK', 'RUN') and values are their respectiveAnimationConfigobjects. It serves as a dictionary or a central registry for all animations available to a character model, allowing easy access to the configuration of each animation by its type. Example:
This map is then passed to theconst characterAnimations: AnimationConfigMap = { idle: { url: '/animations/character-idle.glb', loop: true }, jump: { url: '/animations/character-jump.glb', loop: false, clampWhenFinished: true }, attack: { url: '/animations/character-attack.glb', loop: false, duration: 0.75 }, };CharacterRendererto provide all necessary animation data.
Controller-related Types:
ControllerProps: Defines controller component propertiesFollowLightProps: Settings for following light (position, intensity, color, etc.)ControllerMode: Controller modes such as 'CameraBasedMovement', 'FixedCamera', 'PointToMove', etc.
Physics-related Types:
RigidBodyObjectProps: Properties for physics objects with trigger event handlingRigidBodyPlayerProps: Properties for player physics objects with auto-collider creationRigidBodyObjectRef: Reference interface for player physics objects that extendsRapierRigidBodyRigidBodyPlayerRef: Reference interface for player physics objects that extendsRapierRigidBodywith additionalbottomYproperty
Constants
Animation Constants:
ANIMATION_KEYWORDS: Keyword mapping for identifying animation typesrigmap: Rig mapping constants for character animation
Collision Group Constants:
CollisionGroup: Defines collision detection groups (Default, Player, Enemy, NPC, Projectile, etc.)
Character Utilities (CharacterUtils)
The CharacterUtils object provides utility functions for calculating player character dimensions and collider properties. These utilities are particularly useful when working with RigidBodyPlayer components and custom player setups.
CharacterUtils Methods
| Method | Parameters | Return Type | Description |
| ---------------------------- | ----------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| targetHeight(value?) | value?: number | number | Returns the target height for a player character. If no value is provided, defaults to 1.6 meters. |
| capsuleRadius(height?) | height?: number | number | Calculates the appropriate capsule collider radius based on character height. Formula: Math.max(height / 5, 0.3) |
| capsuleHalfHeight(height?) | height?: number | number | Calculates the capsule collider half-height based on character height. Formula: Math.max((height - radius * 2) / 2, 0.25) |
Usage Examples
import { CharacterUtils } from 'vibe-starter-3d';
// Get default player height
const defaultHeight = CharacterUtils.targetHeight(); // Returns 1.6
// Get custom player height with fallback
const customHeight = CharacterUtils.targetHeight(1.8); // Returns 1.8
const fallbackHeight = CharacterUtils.targetHeight(undefined); // Returns 1.6
// Calculate collider dimensions for a 1.8m tall character
const height = 1.8;
const radius = CharacterUtils.capsuleRadius(height); // Returns 0.36
const halfHeight = CharacterUtils.capsuleHalfHeight(height); // Returns 0.54
// Use with RigidBodyPlayer
function CustomPlayer() {
const playerHeight = 1.75;
return (
<RigidBodyPlayer targetHeight={CharacterUtils.targetHeight(playerHeight)}>
<CharacterRenderer
url="/models/player.vrm"
targetHeight={playerHeight}
// ... other props
/>
</RigidBodyPlayer>
);
}
// Manual collider setup (if autoCreateCollider is false)
function ManualColliderPlayer() {
const height = 1.9;
const radius = CharacterUtils.capsuleRadius(height);
const halfHeight = CharacterUtils.capsuleHalfHeight(height);
return (
<RigidBodyPlayer targetHeight={height} autoCreateCollider={false}>
<CapsuleCollider args={[halfHeight, radius]} />
<CharacterRenderer url="/models/tall-player.vrm" targetHeight={height} />
</RigidBodyPlayer>
);
}Design Rationale
The CharacterUtils calculations are designed to create proportional and realistic colliders:
- Radius Calculation: Uses 1/5 of the character height with a minimum of 0.3m to ensure reasonable collision detection
- Half-Height Calculation: Accounts for the capsule's rounded ends by subtracting the radius diameter from the total height
- Minimum Values: Ensures colliders never become too small to function properly in physics simulations
Collision Groups (CollisionGroup)
Below is the complete list of collision groups used in the Rapier physics engine. Each value represents a group index between 0 and 15.
| Group | Value | Description |
| -------------- | ----- | ----------------------------------------------- |
| Default | 0 | General object (default) |
| Player | 1 | Player character |
| Enemy | 2 | Enemy character |
| NPC | 3 | Neutral character |
| Projectile | 4 | Projectile |
| Environment | 5 | Walls, floors, roads, structures, etc. |
| Item | 6 | Interactive or acquirable item |
| Trigger | 7 | Event trigger |
| UI | 8 | For UI raycast |
| Sensor | 9 | Invisible sensor (vision, proximity, etc.) |
| DeadBody | 10 | Deceased unit |
| LocalPlayer | 11 | Self in multiplayer |
| RemotePlayer | 12 | Other user characters in multiplayer |
| Vehicle | 13 | Vehicle/mount |
| Terrain | 14 | Terrain for special judgment |
| Particle | 15 | Particle effect (for hit judgment or to ignore) |
Detailed Interface Descriptions
CharacterRendererProps
The CharacterRenderer component is a unified component for rendering and animating 3D character models in VRM and GLTF/GLB formats. It allows controlling various aspects like visibility, resource information, animation configuration, and current action state through its props.
The CharacterRenderer component accepts the following CharacterRendererProps:
| Property | Type | Description | Default Value |
| :-------------------------------------------- | :-------------------------------------- | :----------------------------------------------------------------------------------------- | :------------ |
| visible | boolean | Determines whether the character model is displayed on the screen. | true |
| url | string | Model path (file path or URL). | Required |
| animationConfigMap | AnimationConfigMap<AnimationType> | A map defining animation settings for each action type. | Required |
| currentAnimationRef | RefObject<AnimationType \| undefined> | A Ref object pointing to the current character animation type. | Required |
| targetHeight | number | Target height for the character model. | Optional |
| disableAnimationAdjustmentToModelProportion | boolean | If true, animation won't be adjusted to fit the model's body proportion (GLTF/GLB only). | Optional |
| onAnimationComplete | (type: AnimationType) => void | Optional callback function invoked when a specific animation completes. | Optional |
| onSizeChange | (boundingBox: THREE.Box3) => void | Optional callback function invoked when the model's bounding box size information changes. | Optional |
| fallback | React.ReactNode | Optional fallback component for Suspense when loading unsupported file formats. | Optional |
| children | React.ReactNode | Optional child components to render inside the CharacterRenderer. | Optional |
Usage Example:
import { CharacterRenderer } from 'vibe-starter-3d';
import { useRef, useState } from 'react';
import * as THREE from 'three';
function MyCharacter() {
const currentAnimationRef = useRef<'idle' | 'walk' | 'run'>('idle');
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
const animationConfigMap = {
idle: { url: '/animations/idle.glb', loop: true },
walk: { url: '/animations/walk.glb', loop: true },
run: { url: '/animations/run.glb', loop: true },
};
const handleSizeChange = (box: THREE.Box3) => {
setBoundingBox(box);
console.log('Character bounding box updated:', box);
};
const handleAnimationComplete = (type: string) => {
console.log('Animation completed:', type);
};
return (
<CharacterRenderer
url="/models/character.vrm"
animationConfigMap={animationConfigMap}
currentAnimationRef={currentAnimationRef}
targetHeight={1.8}
onSizeChange={handleSizeChange}
onAnimationComplete={handleAnimationComplete}
/>
);
}FollowLightProps
Defines settings for the light that follows the target object.
| Property | Type | Description | Default Value |
| -------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| offset | Vector3 | Light position offset relative to the followObjectRef or RigidBodyPlayer. Defaults to [30, 100, 30] (shining down from above and behind). This only takes effect if followObjectRef is set, or if it defaults to the RigidBodyPlayer group. Has no effect if the light is not following any object. | [30, 100, 30] |
| intensity | number | Light intensity. | 1 |
| color | Color | Light color. | 0xffffff (white) |
| shadowCameraNear | number | Near plane for the shadow camera's perspective frustum. | 0.5 |
| shadowCameraFar | number | Far plane for the shadow camera's perspective frustum. | 500 |
| shadowCameraLeft | number | Left plane for the shadow camera's perspective frustum. | -75 |
| shadowCameraRight | number | Right plane for the shadow camera's perspective frustum. | 75 |
| shadowCameraTop | number | Top plane for the shadow camera's perspective frustum. | 75 |
| shadowCameraBottom | number | Bottom plane for the shadow camera's perspective frustum. | -75 |
| shadowMapSize | number[]|Vector2 | Shadow map size (width, height). Must be powers of 2. Higher values improve shadow quality but increase computation time. | [8192, 8192] |
| shadowBias | number | Shadow map bias. Very tiny adjustments (e.g., 0.0001) can help reduce shadow artifacts. | 0 |
| shadowNormalBias | number | Offset along the object normal for shadow map queries. Helps reduce shadow acne, especially at shallow angles. | 0 |
| followObjectRef | React.RefObject<THREE.Object3D> | null | Optional reference to a THREE.Object3D that the light should follow. If not set, the light will follow the RigidBodyPlayer group. | null |
FollowLight Component
The FollowLight component creates a directional light that can be configured to follow an object within the scene. It is typically used to ensure a character or a specific area remains well-lit as it moves. The behavior and appearance of the light, including its offset from the target, intensity, color, and shadow casting properties, are defined by FollowLightProps.
Usage Example:
This component is designed to be used within your 3D scene setup. By default, if not provided with a followObjectRef (see FollowLightProps), it will attempt to follow the RigidBodyPlayer group.
import { FollowLight } from 'vibe-starter-3d';
import { Canvas } from '@react-three/fiber'; // Or your existing R3F setup
function MySceneWithFollowLight() {
return (
<>
{' '}
{/* Or <Canvas> if this is the root of your R3F scene */}
{/* ... other lights, environment, and 3D objects ... */}
{/* Add FollowLight to the scene */}
{/* It can be customized using FollowLightProps */}
<FollowLight offset={[30, 100, 30]} intensity={1.2} color="white" />
{/* ... your player character and other elements ... */}
{/* For example, if used with a FreeViewController: */}
{/*
<FreeViewController />
<RigidBodyPlayer>
<PlayerModel />
</RigidBodyPlayer>
*/}
{/* The FollowLight will automatically try to follow the RigidBodyPlayer in this setup */}
</>
);
}Note: For detailed information on the available properties, refer to the FollowLightProps section.
ControllerProps
Defines the basic properties of all controller components. Controllers are independent components that automatically detect and control any RigidBodyPlayer present in the scene. FreeViewController, FirstPersonViewController, QuarterViewController, SideViewController, and SimulationViewController all inherit (extend) this interface.
| Property | Type | Description | Default Value |
| --------------------- | --------- | ---------------------------------- | ------------- |
| camInitDis | number | Initial camera distance | -4 |
| camMinDis | number | Minimum camera zoom-in distance | -4 |
| camMaxDis | number | Maximum camera zoom-out distance | -4 |
| floatHeight | number | Height above ground (m) | 0.01 |
| followCameraForward | boolean | Whether to follow camera direction | false |
QuarterViewControllerProps
Defines properties for the quarter view controller. (extends ControllerProps)
| Property | Type | Description | Default Value |
| ----------------- | ----------------------------------- | ------------------------------- | --------------- |
| followCharacter | boolean | Whether to follow the character | false |
| inputMode | 'keyboard' | 'pointToMove' | Input mode | 'pointToMove' |
| cameraMode | 'perspective' | 'orthographic' | Camera mode | 'perspective' |
Note: This interface inherits all properties from ControllerProps.
SideViewControllerProps
Defines properties for the side view controller. (extends ControllerProps)
| Property | Type | Description | Default Value |
| ------------ | ----------------------------------- | ----------- | ---------------- |
| cameraMode | 'perspective' | 'orthographic' | Camera mode | 'orthographic' |
Note: This interface inherits all properties from ControllerProps.
SimulationViewControllerProps
Defines properties for the simulation view controller. (extends ControllerProps)
| Property | Type | Description | Default Value |
| ------------ | ----------------------------------- | ----------- | --------------- |
| cameraMode | 'perspective' | 'orthographic' | Camera mode | 'perspective' |
Note: This interface inherits all properties from ControllerProps. The SimulationViewController provides edge scrolling functionality and smooth camera controls optimized for simulation environments.
FlightViewControllerProps
Defines properties for the flight view controller. (Does not inherit ControllerProps)
| Property | Type | Description | Default Value |
| ------------------------------- | ------------------------- | ---------------------------------------------------------------- | ------------- |
| minSpeed | number | Minimum flight speed (m/s) | 0 |
| maxSpeed | number | Maximum flight speed (m/s) | 120 |
| speedIncreasePerSecond | number | Speed increase per second (m/s) | 20 |
| speedDecreasePerSecond | number | Speed decrease per second (m/s) | 80 |
| pitchRotationSpeed | number | Pitch rotation speed | 0.5 |
| rollRotationSpeed | number | Roll rotation speed | Math.PI |
| directionRotationAcceleration | number | Direction rotation acceleration | 0.3 |
| maxRollAngle | number | Maximum roll angle | Math.PI / 2 |
| maxPitchAngle | number | Maximum pitch angle | Math.PI / 2 |
| cameraOffset | Vector3 | Camera offset position | [0, 3, 15] |
| onSpeedChange | (speed: number) => void | Optional callback function invoked when the flight speed changes | Optional |
PointToMoveControllerProps
Defines properties for the point-to-move view controller. (extends ControllerProps)
| Property | Type | Description | Default Value |
| ----------------- | ----------------------------------- | ---------------------------------------- | --------------- |
| followCharacter | boolean | Whether the camera follows the character | false |
| cameraMode | 'perspective' | 'orthographic' | Camera mode | 'perspective' |
| maxVelLimit | number | Maximum velocity limit for the character | 3 |
Note: This interface inherits all properties from ControllerProps.
RigidBodyObject Component
The RigidBodyObject component is an enhanced wrapper around the standard RigidBody component from @react-three/rapier. It provides simplified trigger event handling, making it extremely easy to implement interactions between different objects in your physics simulation.
Why use RigidBodyObject instead of RigidBody?
We strongly recommend using RigidBodyObject instead of the standard RigidBody component because:
- Unified Collision Events: Automatically handles both physical collisions and sensor intersections, providing unified
onTriggerEnterandonTriggerExitcallbacks for all collision types - Event Deduplication: Prevents duplicate trigger events that can occur with rapid collision detection
- Comprehensive Detection: Triggers on both solid collisions and sensor intersections, making it easier to handle all types of object interactions
- Seamless Integration: Drop-in replacement for
RigidBodywith all the same props plus enhanced functionality
Usage Example:
import { RigidBodyObject } from 'vibe-starter-3d';
import { CuboidCollider } from '@react-three/rapier';
function InteractiveObject() {
const handleTriggerEnter = (payload) => {
console.log('Collision detected with:', payload.other.rigidBodyObject);
// Handle interaction logic here (works for both physical collisions and sensor triggers)
};
const handleTriggerExit = (payload) => {
console.log('Collision ended with:', payload.other.rigidBodyObject);
// Handle cleanup logic here (works for both physical collisions and sensor triggers)
};
return (
<RigidBodyObject type="fixed" onTriggerEnter={handleTriggerEnter} onTriggerExit={handleTriggerExit}>
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="blue" />
</mesh>
<CuboidCollider args={[0.5, 0.5, 0.5]} sensor />
</RigidBodyObject>
);
}RigidBodyObjectProps
Defines properties for physics objects with trigger event handling. (extends RigidBodyProps)
| Property | Type | Description | Default Value |
| ---------------- | ------------------------------------- | ------------------------------------------------------------------------------------------- | ------------- |
| onTriggerEnter | (payload: CollisionPayload) => void | Callback when collision starts (includes both physical collisions and sensor intersections) | Optional |
| onTriggerExit | (payload: CollisionPayload) => void | Callback when collision ends (includes both physical collisions and sensor intersections) | Optional |
Note: This interface extends RigidBodyProps from @react-three/rapier and adds trigger event handling capabilities. All standard RigidBody properties are available.
RigidBodyPlayer Component
The RigidBodyPlayer component is a specialized physics component designed specifically for player characters. It extends RigidBodyObject with player-specific functionality and automatic controller integration.
⚠️ CRITICAL ARCHITECTURE UNDERSTANDING:
Controllers are NOT Player Objects - They are Control Systems
- Controllers are Independent: Controller components (
FreeViewController,FirstPersonViewController, etc.) are standalone control systems that do NOT create or own any player objects - RigidBodyPlayer is the Actual Player: The
RigidBodyPlayercomponent represents the actual player character in the physics world - Automatic Detection and Control: Controllers automatically scan the scene to find the
RigidBodyPlayercomponent and control it remotely - Single Player Requirement: There must be EXACTLY ONE
RigidBodyPlayerin the scene for controllers to function - multiple instances will cause conflicts - Separation of Concerns: Controllers handle input and camera logic, while
RigidBodyPlayerhandles physics and character representation
Key Features:
- Automatic Collider Creation: Automatically generates a CapsuleCollider based on character height
- Controller Detection Target: Serves as the target that controller components automatically detect and control
- Physics Initialization: Handles ground detection and proper positioning automatically
- Player-Specific Properties: Includes
bottomYproperty for ground-relative positioning - Player Collision Events: Supports
onTriggerEnterandonTriggerExitfor handling all types of player collisions (physical collisions and sensor intersections)
Usage Example:
import { RigidBodyPlayer, FreeViewController, CharacterRenderer } from 'vibe-starter-3d';
function PlayerScene() {
// Handle player collisions with other objects
const handlePlayerTriggerEnter = (payload) => {
console.log('Player collision started with:', payload.other.rigidBodyObject);
// Handle player collision logic (e.g., item pickup, door opening, obstacle hit, etc.)
};
const handlePlayerTriggerExit = (payload) => {
console.log('Player collision ended with:', payload.other.rigidBodyObject);
// Handle cleanup logic (e.g., hide interaction UI, stop collision effects, etc.)
};
return (
<>
{/* Controller automatically finds and controls the RigidBodyPlayer below */}
<FreeViewController />
{/* CRITICAL: Only ONE RigidBodyPlayer per scene - this is what the controller will control */}
<RigidBodyPlayer targetHeight={1.6} position={[0, 0, 0]} onTriggerEnter={handlePlayerTriggerEnter} onTriggerExit={handlePlayerTriggerExit}>
<CharacterRenderer url="/models/player-character.vrm" animationConfigMap={animationConfig} currentAnimationRef={currentAnimationRef} />
</RigidBodyPlayer>
</>
);
}
// ❌ WRONG: Multiple RigidBodyPlayer instances
function WrongUsage() {
return (
<>
{/* Controller cannot determine which player to control! */}
<FreeViewController />
<RigidBodyPlayer>
<CharacterRenderer url="/player1.vrm" />
</RigidBodyPlayer>
<RigidBodyPlayer>
{/* This will cause conflicts - controller needs exactly ONE target! */}
<CharacterRenderer url="/player2.vrm" />
</RigidBodyPlayer>
</>
);
}
// ✅ CORRECT: Use RigidBodyObject for non-player characters
function CorrectUsage() {
return (
<>
{/* Controller automatically detects and controls the single RigidBodyPlayer */}
<FreeViewController />
{/* The ONE player that the controller will control */}
<RigidBodyPlayer>
<CharacterRenderer url="/player.vrm" />
</RigidBodyPlayer>
{/* Use RigidBodyObject for NPCs and other characters - these are NOT controlled by the controller */}
<RigidBodyObject type="dynamic">
<CharacterRenderer url="/npc1.vrm" />
</RigidBodyObject>
<RigidBodyObject type="dynamic">
<CharacterRenderer url="/npc2.vrm" />
</RigidBodyObject>
</>
);
}RigidBodyPlayerProps
Defines properties for player physics objects with automatic collider creation. (extends RigidBodyObjectProps)
| Property | Type | Description | Default Value |
| -------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------- |
| targetHeight | number | Height of the character to be controlled (unit: meters) | 1.6 |
| autoCreateCollider | boolean | Whether to automatically create a default CapsuleCollider | true |
| offsetY | number | Vertical offset for the children group (unit: meters) | Optional |
| onTriggerEnter | (payload: CollisionPayload) => void | Callback when player collision starts (includes both physical collisions and sensor intersections) | Optional |
| onTriggerExit | (payload: CollisionPayload) => void | Callback when player collision ends (includes both physical collisions and sensor intersections) | Optional |
Note: This interface extends RigidBodyObjectProps and provides player-specific functionality including automatic capsule collider creation based on character height. This component serves as the target that controller systems automatically detect and control. Use onTriggerEnter and onTriggerExit to handle all types of player collisions with other objects in the scene (e.g., item pickups, door triggers, interaction zones, physical obstacles).
RigidBodyPlayerRef Interface
⚠️ CRITICAL: RigidBodyPlayerRef is completely identical to RapierRigidBody interface!
RigidBodyPlayerRef is an interface that extends RapierRigidBody, allowing you to use all RapierRigidBody methods and properties exactly as they are, with an additional bottomY property.
export interface RigidBodyPlayerRef extends RapierRigidBody {
readonly bottomY: number;
}Key Features:
- Complete RapierRigidBody Compatibility: All methods and properties of
RapierRigidBodycan be used identically - Additional bottomY Property: A read-only property for easy access to the player's bottom Y coordinate
- Type Safety: Full TypeScript type support
Usage Examples:
import { RigidBodyPlayer, RigidBodyPlayerRef } from 'vibe-starter-3d';
import { useRef, useEffect } from 'react';
function PlayerComponent() {
const playerRef = useRef<RigidBodyPlayerRef>(null);
useEffect(() => {
if (!playerRef.current) return;
// ✅ All RapierRigidBody methods are available
const position = playerRef.current.translation(); // { x, y, z }
const velocity = playerRef.current.linvel(); // { x, y, z }
const rotation = playerRef.current.rotation(); // Quaternion
// ✅ Use additional bottomY property
const groundLevel = playerRef.current.bottomY; // number
// ✅ Control player using RapierRigidBody methods
playerRef.current.setTranslation({ x: 0, y: 10, z: 0 }, true);
playerRef.current.setLinvel({ x: 0, y: 0, z: 5 }, true);
playerRef.current.setRotation({ x: 0, y: 0, z: 0, w: 1 }, true);
// ✅ Set physics properties
playerRef.current.setGravityScale(1.0, true);
playerRef.current.setEnabled(true);
console.log('Player position:', position);
console.log('Player velocity:', velocity);
console.log('Player bottom Y:', groundLevel);
}, []);
return (
<RigidBodyPlayer ref={playerRef} targetHeight={1.8}>
{/* Character content */}
</RigidBodyPlayer>
);
}
// Basic RigidBodyPlayerRef usage example
function PlayerWithRef() {
const playerRef = useRef<RigidBodyPlayerRef>(null);
useEffect(() => {
if (!playerRef.current) return;
// Monitor player state
const logPlayerState = () => {
const position = playerRef.current!.translation();
const velocity = playerRef.current!.linvel();
const bottomY = playerRef.current!.bottomY;
console.log('Player position:', position);
console.log('Player velocity:', velocity);
console.log('Player bottom Y:', bottomY);
};
// Periodically check player state
const interval = setInterval(logPlayerState, 1000);
return () => clearInterval(interval);
}, []);
return (
<RigidBodyPlayer ref={playerRef} targetHeight={1.8}>
<CharacterRenderer url="/models/player.vrm" animationConfigMap={animationConfig} currentAnimationRef={currentAnimationRef} />
</RigidBodyPlayer>
);
}Available Key RapierRigidBody Methods:
| Method | Description | Example |
| -------------------------------- | -------------------------------------------- | --------------------------------------------------------------- |
| translation() | Get current position | const pos = playerRef.current.translation() |
| setTranslation(pos, wakeUp) | Set position | playerRef.current.setTranslation({x: 0, y: 5, z: 0}, true) |
| linvel() | Get linear velocity | const vel = playerRef.current.linvel() |
| setLinvel(vel, wakeUp) | Set linear velocity | playerRef.current.setLinvel({x: 0, y: 0, z: 0}, true) |
| rotation() | Get rotation | const rot = playerRef.current.rotation() |
| setRotation(rot, wakeUp) | Set rotation | playerRef.current.setRotation({x: 0, y: 0, z: 0, w: 1}, true) |
| angvel() | Get angular velocity | const angVel = playerRef.current.angvel() |
| setAngvel(vel, wakeUp) | Set angular velocity | playerRef.current.setAngvel({x: 0, y: 0, z: 0}, true) |
| setGravityScale(scale, wakeUp) | Set gravity scale | playerRef.current.setGravityScale(1.0, true) |
| setEnabled(enabled) | Enable/disable physics | playerRef.current.setEnabled(true) |
| bottomY | Additional Property: Bottom Y coordinate | const groundLevel = playerRef.current.bottomY |
CollisionPayload Interface
The CollisionPayload interface is provided by @react-three/rapier and contains detailed information about collision events. It is used in both onTriggerEnter and onTriggerExit callbacks to provide context about the collision.
CollisionPayload Structure
interface CollisionPayload {
target: {
rigidBody: RapierRigidBody; // The RigidBody that triggered the event
collider: Collider; // The Collider that triggered the event
};
other: {
rigidBody: RapierRigidBody; // The other RigidBody involved in the collision
collider: Collider; // The other Collider involved in the collision
};
}Key Properties
| Property | Type | Description |
| ------------------ | ----------------- | ----------------------------------------------------------------------------------- |
| target.rigidBody | RapierRigidBody | The RigidBody component that owns the trigger event (usually your player or object) |
| target.collider | Collider | The Collider component that detected the collision |
| other.rigidBody | RapierRigidBody | The RigidBody of the object that entered/exited the trigger zone |
| other.collider | Collider | The Collider of the object that entered/exited the trigger zone |
Useful Methods and Properties
RigidBody Methods:
rigidBody.translation()- Get the current position as{x, y, z}rigidBody.rotation()- Get the current rotation as a quaternionrigidBody.userData- Access custom data attached to the RigidBody
Collider Methods:
collider.translation()- Get the collider's positioncollider.isSensor()- Check if the collider is a sensor (trigger zone)collider.handle- Unique identifier for the collider
Practical Usage Examples
// Example 1: Item pickup system
const handlePlayerTriggerEnter = (payload: CollisionPayload) => {
const otherType = payload.other.rigidBody?.userData?.type;
if (otherType === 'ITEM') {
const itemId = payload.other.rigidBody?.userData?.itemId;
const itemPosition = payload.other.rigidBody.translation();
console.log(`Player picked up item ${itemId} at position:`, itemPosition);
// Handle item pickup logic
}
};
// Example 2: Area detection with position checking
const handlePlayerTriggerEnter = (payload: CollisionPayload) => {
const otherType = payload.other.rigidBody?.userData?.type;
if (otherType === 'SAFE_ZONE') {
const playerPosition = payload.target.rigidBody.translation();
const zonePosition = payload.other.rigidBody.translation();
console.log(`Player entered safe zone at ${zonePosition.x}, ${zonePosition.y}, ${zonePosition.z}`);
// Enable safe zone effects
}
};
// Example 3: Enemy detection with distance calculation
const handlePlayerTriggerEnter = (payload: CollisionPayload) => {
const otherType = payload.other.rigidBody?.userData?.type;
if (otherType === 'ENEMY') {
const playerPos = payload.target.rigidBody.translation();
const enemyPos = payload.other.rigidBody.translation();
const distance = Math.sqrt(Math.pow(playerPos.x - enemyPos.x, 2) + Math.pow(playerPos.y - enemyPos.y, 2) + Math.pow(playerPos.z - enemyPos.z, 2));
console.log(`Enemy detected at distance: ${distance}`);
// Handle combat logic
}
};
// Example 4: Checking collision handles to avoid duplicate events
const handlePlayerTriggerEnter = (payload: CollisionPayload) => {
const targetHandle = payload.target.collider.handle;
const otherHandle = payload.other.collider.handle;
// Use handles for unique identification
console.log(`Collision between ${targetHandle} and ${otherHandle}`);
};Best Practices
- Always check userData.type: Use
payload.other.rigidBody?.userData?.typeto identify object types - Handle null cases: RigidBody or userData might be null, always use optional chaining (
?.) - Use handles for deduplication: Collider handles are unique and useful for preventing duplicate events
- Access position data: Use
translation()method to get current positions for distance calculations - Store custom data in userData: Attach relevant information like IDs, types, or states to
userDatafor easy access
