@gwenjs/physics3d
pnpm add @gwenjs/physics3d
Module de moteur physique 3D propulsé par Rapier3D. Fournit la dynamique des corps rigides, la détection de collisions, les événements de capteur, le filtrage par calques et les colliders de maillage accélérés par BVH.
Configuration du module
Enregistrez le module dans gwen.config.ts :
// gwen.config.ts
export default defineConfig({
modules: [
['@gwenjs/physics3d', {
gravity: { x: 0, y: -9.81, z: 0 },
maxEntities: 10_000,
qualityPreset: 'medium',
debug: false,
coalesceEvents: true,
layers: ['default', 'player', 'enemy'],
vite: {
bvhPrebake: false,
debug: false,
},
}],
],
})Physics3DConfig
| Champ | Type | Défaut | Description |
|---|---|---|---|
gravity | Partial<Physics3DVec3> | { x: 0, y: -9.81, z: 0 } | Vecteur de gravité du monde |
maxEntities | number | 10_000 | Nombre maximum d'entités physiques |
qualityPreset | Physics3DQualityPreset | 'medium' | Qualité du solveur : 'low', 'medium', 'high' ou 'esport' |
debug | boolean | false | Active les journaux de débogage physique à l'exécution |
coalesceEvents | boolean | true | Fusionne les événements de contact dupliqués dans une frame |
layers | string[] | ['default'] | Liste de calques de collision nommés (max 32) |
vite | object | — | Options du plugin Vite au moment du build (voir ci-dessous) |
vite.bvhPrebake | boolean | false | Pré-compile le BVH pour useMeshCollider('./x.glb') au moment du build |
vite.debug | boolean | false | Active la journalisation du plugin Vite |
Composables
Tous les composables sont importés depuis @gwenjs/physics3d et doivent être appelés à l'intérieur de defineActor.
Corps
useDynamicBody(options?)
function useDynamicBody(options?: Physics3DBodyOptions): DynamicBodyHandleAjoute un corps rigide dynamique affecté par la gravité et les forces.
Retourne : DynamicBodyHandle
import { useDynamicBody, useSphereCollider } from '@gwenjs/physics3d'
export const BallActor = defineActor(BallPrefab, () => {
const body = useDynamicBody({ mass: 5, restitution: 0.6 })
useSphereCollider({ radius: 1 })
})useStaticBody(options?)
function useStaticBody(options?: StaticBodyOptions3D): StaticBodyHandle3DAjoute un corps statique (immobile). À utiliser pour le terrain et les obstacles fixes. Retourne un handle avec bodyId, active, enable() et disable() pour activer/désactiver le corps.
import { useStaticBody, useMeshCollider } from '@gwenjs/physics3d'
export const TerrainActor = defineActor(TerrainPrefab, () => {
useStaticBody()
useMeshCollider('./terrain.glb')
})useKinematicBody(options?)
function useKinematicBody(options?: Physics3DBodyOptions): KinematicBodyHandleAjoute un corps cinématique contrôlé par la vélocité, non affecté par la gravité.
Retourne : KinematicBodyHandle
import { useKinematicBody, useCapsuleCollider } from '@gwenjs/physics3d'
export const PlayerActor = defineActor(PlayerPrefab, () => {
const body = useKinematicBody()
useCapsuleCollider({ radius: 0.4, halfHeight: 0.9 })
})Colliders
useBoxCollider(options)
function useBoxCollider(options: {
extents: Physics3DVec3
sensor?: boolean
density?: number
}): voidAjoute un collider de boîte avec les demi-extensions données.
| Paramètre | Type | Description |
|---|---|---|
options.extents | Physics3DVec3 | Demi-taille sur chaque axe |
options.sensor | boolean | Déclencheur uniquement (sans réponse physique) |
options.density | number | Densité du collider |
useBoxCollider({ extents: { x: 1, y: 2, z: 1 } })useSphereCollider(options)
function useSphereCollider(options: {
radius: number
sensor?: boolean
density?: number
}): voidAjoute un collider de sphère.
useSphereCollider({ radius: 0.5 })useCapsuleCollider(options)
function useCapsuleCollider(options: {
radius: number
halfHeight: number
sensor?: boolean
density?: number
}): voidAjoute un collider de capsule (cylindre coiffé d'hémisphères). Couramment utilisé pour les corps de personnages.
useCapsuleCollider({ radius: 0.4, halfHeight: 0.9 })useMeshCollider(path | options)
function useMeshCollider(source: string | {
vertices: Float32Array
indices: Uint32Array
sensor?: boolean
}): voidAjoute un collider de maillage triangulé (maille polygonale arbitraire). Destiné à la géométrie statique uniquement. Accepte soit un chemin .glb (résolu au moment du build lorsque le pré-calcul BVH est activé), soit des données de sommets/indices explicites.
// Basé sur un chemin (déclenche le pré-calcul BVH quand vite.bvhPrebake est true)
useMeshCollider('./terrain.glb')
// Données de sommets manuelles
useMeshCollider({ vertices: myFloat32Array, indices: myUint32Array })useConvexCollider(path | options)
function useConvexCollider(source: string | {
vertices: Float32Array
sensor?: boolean
density?: number
}): voidAjoute un collider d'enveloppe convexe. Plus rapide que le trimesh ; adapté aux corps dynamiques.
useConvexCollider('./rock.glb')Événements
Tous les composables d'événements sont automatiquement nettoyés à la destruction de l'acteur.
onContact(handler)
function onContact(handler: (event: ContactEvent3D) => void): voidEnregistre un gestionnaire appelé lorsque le collider de cet acteur entre ou sort de contact avec un autre.
import { onContact } from '@gwenjs/physics3d'
export const EnemyActor = defineActor(EnemyPrefab, () => {
onContact((event) => {
if (event.started) {
console.log('Hit by entity', event.otherId)
}
})
})onSensorEnter(handler)
function onSensorEnter(handler: (otherId: number) => void): voidAppelé lorsqu'un autre collider entre dans le collider capteur de cet acteur.
import { useBoxCollider, onSensorEnter } from '@gwenjs/physics3d'
export const TriggerZone = defineActor(TriggerPrefab, () => {
useBoxCollider({ extents: { x: 3, y: 1, z: 3 }, sensor: true })
onSensorEnter((otherId) => {
console.log('Entity entered zone:', otherId)
})
})onSensorExit(handler)
function onSensorExit(handler: (otherId: number) => void): voidAppelé lorsqu'un autre collider quitte le collider capteur de cet acteur.
Calques
defineLayers(layerList)
function defineLayers(layerList: string[]): Record<string, number>Convertit une liste de calques nommés (correspondant à celle dans gwen.config.ts) en un objet de masques de bits. Le plugin Vite intègre ces valeurs littérales au moment du build.
import { defineLayers } from '@gwenjs/physics3d'
const Layers = defineLayers(['default', 'player', 'enemy'])
// Layers.default === 1, Layers.player === 2, Layers.enemy === 4
useBoxCollider({
extents: { x: 1, y: 1, z: 1 },
// les appartenances et filtres utilisent des valeurs de masque de bits
})Service physique
usePhysics3D()
function usePhysics3D(): Physics3DAPIRetourne l'API physique d'exécution pour les opérations impératives. À appeler dans defineSystem ou defineActor.
import { usePhysics3D } from '@gwenjs/physics3d'
const physics = usePhysics3D()
physics.applyImpulse(entityId, { x: 0, y: 500, z: 0 })Méthodes de Physics3DAPI
| Méthode | Signature | Description |
|---|---|---|
applyImpulse | (entityId: Physics3DEntityId, impulse: Partial<Physics3DVec3>) => boolean | Applique une impulsion linéaire instantanée (N·s) |
applyAngularImpulse | (entityId: Physics3DEntityId, impulse: Partial<Physics3DVec3>) => boolean | Applique une impulsion angulaire instantanée |
addForce | (entityId: Physics3DEntityId, force: Partial<Physics3DVec3>) => void | Accumule une force continue pour ce step (N) |
addTorque | (entityId: Physics3DEntityId, torque: Partial<Physics3DVec3>) => void | Accumule un couple continu pour ce step (N·m) |
setLinearVelocity | (entityId: Physics3DEntityId, velocity: Partial<Physics3DVec3>) => boolean | Remplace la vélocité linéaire (m/s) |
getLinearVelocity | (entityId: Physics3DEntityId) => Physics3DVec3 | undefined | Lit la vélocité linéaire actuelle |
setAngularVelocity | (entityId: Physics3DEntityId, velocity: Partial<Physics3DVec3>) => boolean | Remplace la vélocité angulaire (rad/s) |
getAngularVelocity | (entityId: Physics3DEntityId) => Physics3DVec3 | undefined | Lit la vélocité angulaire actuelle |
setGravityScale | (entityId: Physics3DEntityId, scale: number) => void | Échelle de gravité par corps (0 désactive, 1 = normal) |
getGravityScale | (entityId: Physics3DEntityId) => number | Lit l'échelle de gravité actuelle d'un corps |
castRay | (origin: Physics3DVec3, direction: Physics3DVec3, maxDist: number, opts?) => RayHit | null | Lance un rayon, retourne l'impact le plus proche ou null |
const physics = usePhysics3D()
// Saut
physics.applyImpulse(entityId, { x: 0, y: 300, z: 0 })
// Vérification du sol
const hit = physics.castRay(
{ x: 0, y: 1, z: 0 },
{ x: 0, y: -1, z: 0 },
1.1,
)
const isGrounded = hit !== nullIntégration Vite
physics3dVitePlugin(options?)
function physics3dVitePlugin(options?: GwenPhysics3DPluginOptions): VitePluginPlugin Vite au moment du build. Il est enregistré automatiquement par le module @gwenjs/physics3d — aucune inscription manuelle n'est nécessaire dans la plupart des projets. Configurez-le via la clé vite dans les options du module.
interface GwenPhysics3DPluginOptions {
debug?: boolean // défaut : false — active la journalisation du plugin Vite
bvhPrebake?: boolean // défaut : false — pré-compile le BVH pour les chemins de collider de maillage
}Le plugin effectue deux transformations au moment du build :
- Intégration des calques — remplace
Layers.playerpar sa valeur de masque de bits littérale. Élimine la résolution à l'exécution et permet l'élimination du code mort. - Pré-calcul BVH (opt-in) — détecte les occurrences de
useMeshCollider('./terrain.glb'), compile le BVH au moment du build et remplace le chemin par{ __bvhUrl: 'bvh-<hash>.bin' }.
Avertissement pour les calques non utilisés
Lorsque l'intégration des calques est active, le plugin Vite émet un avertissement de build pour tout calque défini dans la configuration mais jamais référencé dans le code source. Utilisez ceci pour maintenir votre liste de calques propre.
Flux de travail BVH pre-bake
Activez bvhPrebake: true (via vite.bvhPrebake dans la configuration du module) pour les grands maillages de terrain. Le BVH est compilé une seule fois au moment du build et servi sous forme d'asset binaire, de sorte que l'initialisation du raycast à l'exécution est quasi instantanée — sans coût de reconstruction BVH par frame.
Déprécié
createGwenPhysics3DPlugin() est déprécié. Remplacez par physics3dVitePlugin({ bvhPrebake: true }) si vous avez besoin d'enregistrer le plugin Vite manuellement.
Définitions de type
Physics3DConfig
interface Physics3DConfig {
gravity: Partial<Physics3DVec3>
maxEntities: number
qualityPreset: 'low' | 'medium' | 'high' | 'esport'
debug: boolean
coalesceEvents: boolean
layers: string[]
vite: {
bvhPrebake: boolean
debug: boolean
}
}Physics3DVec3
interface Physics3DVec3 {
x: number
y: number
z: number
}Physics3DQuat
interface Physics3DQuat {
x: number
y: number
z: number
w: number
}Physics3DQualityPreset
type Physics3DQualityPreset = 'low' | 'medium' | 'high' | 'esport'