| name | physics-engines |
| description | Moteurs physiques pour jeux vidéo — collision detection (AABB, SAT, GJK, CCD), rigidbodies, joints, constraints, character controllers, soft bodies, raycasts, moteurs (PhysX, Havok, Jolt, Box2D, Bullet, Godot Physics, Rapier). |
| tags | ["physics","collision-detection","rigidbody","joints","constraints","physx","havok","jolt","box2d","bullet","godot-physics","rapi-er"] |
Physics Engines — Guide Complet
Ce skill couvre la conception et l'utilisation de moteurs physiques pour jeux vidéo, de la détection de collision aux contraintes avancées. À charger pour toute tâche impliquant la physique de jeu (collisions, ragdolls, véhicules, destruction).
1. Moteurs Physiques — Comparaison
| Moteur | Langage | Usage | Particularité |
|---|
| PhysX 4/5 | C++ | Unity, UE4/5 (jusqu'à 5.3) | Standard industrie, GPU Flex |
| Jolt | C++ | Horizon FW, Godot 4 | Bon multithreading, déterminisme |
| Havok | C++ | Halo, Battlefield, Skyrim | Legacy, racheté par Microsoft |
| Bullet | C++ | GTA V, Blender | Open source, pybullet |
| Box2D | C++ | Angry Birds, Limbo | 2D uniquement, très stable |
| Box2D (Godot) | C++ | Godot 4 | Fork Godot du Box2D |
| Rapier | Rust | Bevy ECS | Moderne, WASM, déterministe |
| Chipmunk | C | 2D jeux indie | Léger, simple |
2. Collision Detection
Broadphase — Quels objets sont proches?
Broadphase: élimine rapidement les paires impossibles
Coût: O(n log n) ou O(n)
Sweep and Prune (SAP):
1. Trier les AABB sur l'axe X
2. Balayer: si deux AABB se chevauchent → Narrowphase
3. Incrémental: réinsérer les AABB qui bougent
Spatial Hash:
1. Grille de cellules (taille = diamètre max des objets)
2. Chaque objet va dans sa cellule + 8 voisines
3. Tester collision seulement dans la même cellule
BVH (Bounding Volume Hierarchy):
- Arbre binaire d'AABB
- O(log n) pour trouver les collisions
- Utilisé par PhysX, Bullet, Jolt
Multi-SAP (PhysX 5):
- SAP par axe
- Parallélisé par thread
Narrowphase — Forme précise contre forme
AABB vs AABB: 6 comparaisons (min/max x,y,z)
Sphere vs Sphere: 1 distance check
Capsule vs Capsule: distance entre segments + rayons
OBB vs OBB: SAT (Separating Axis Theorem) → 15 axes max
Convex vs Convex: GJK (Gilbert-Johnson-Keerthi) + EPA
Mesh vs anything: BVH traversal + triangle intersection
AABB Collision — La Plus Rapide
public struct AABB
{
public Vector3 Center;
public Vector3 HalfExtents;
public bool Overlaps(AABB other)
{
Vector3 d = other.Center - Center;
return Mathf.Abs(d.x) <= HalfExtents.x + other.HalfExtents.x &&
Mathf.Abs(d.y) <= HalfExtents.y + other.HalfExtents.y &&
Mathf.Abs(d.z) <= HalfExtents.z + other.HalfExtents.z;
}
}
SAT (Separating Axis Theorem) — OBB vs OBB
public static bool OBBCollision(Vector3[] axesA, Vector3[] axesB,
float[] projA, float[] projB)
{
foreach (var axis in GetAllAxes(axesA, axesB))
{
float minA, maxA, minB, maxB;
ProjectOntoAxis(verticesA, axis, out minA, out maxA);
ProjectOntoAxis(verticesB, axis, out minB, out maxB);
if (maxA < minB || maxB < minA)
return false;
}
return true;
}
GJK (Gilbert-Johnson-Keerthi) — Convex vs Convex
GJK: Algorithme itératif qui détermine si deux convexes se touchent
1. Prendre un point de départ (somme des centres)
2. Construire un simplex (tétraèdre en 3D, triangle en 2D)
3. Chercher dans la direction vers l'origine
4. Si l'origine est DANS le simplex → collision
5. Si le simplex ne peut pas s'approcher → pas de collision
Avantages:
- Supporte TOUTES les formes convexes
- Complexité presque O(1)
- Combine avec EPA pour la profondeur de pénétration
CCD (Continuous Collision Detection)
using UnityEngine;
public class CCDSetup : MonoBehaviour
{
public Rigidbody rb;
void Start()
{
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
public class ManualCCD : MonoBehaviour
{
public float speed = 50f;
void FixedUpdate()
{
Vector3 movement = transform.forward * speed * Time.fixedDeltaTime;
float distance = movement.magnitude;
if (Physics.BoxCast(transform.position, _halfExtents,
movement.normalized, out RaycastHit hit, transform.rotation, distance))
{
transform.position += movement.normalized * hit.distance;
OnHit(hit);
}
else
{
transform.position += movement;
}
}
}
3. Rigidbodies et Forces
Types de Rigidbody
public enum RigidbodyType
{
Dynamic,
Kinematic,
Static
}
Kinematic:
- Pas affecté par la gravité ou les forces
- Se déplace via transform.position ou rigidbody.MovePosition()
- Peut interagir avec les Dynamic (les pousse)
- Utile pour: plateformes mobiles, portes, ascenseurs, personnages
Dynamic:
- Masse, inertie, gravité, forces
- Collision et réaction
- Utile pour: objets physiques, ragdolls, débris
Forces — Types et Application
public class ForceExamples : MonoBehaviour
{
public Rigidbody rb;
void ApplyForces()
{
rb.AddForce(Vector3.forward * 10f, ForceMode.Force);
rb.AddForce(Vector3.up * 5f, ForceMode.Impulse);
rb.AddForce(Vector3.forward * 10f, ForceMode.VelocityChange);
rb.AddForce(Vector3.forward * 10f, ForceMode.Acceleration);
rb.AddTorque(Vector3.up * 5f, ForceMode.Impulse);
rb.AddForceAtPosition(Vector3.up * 10f, _gripPoint.position, ForceMode.Impulse);
}
}
Inertia Tensor — Rotation Réaliste
void Start()
{
rb.inertiaTensor = new Vector3(
(mass / 12f) * (height² + depth²),
(mass / 12f) * (width² + depth²),
(mass / 12f) * (width² + height²)
);
rb.inertiaTensorRotation = Quaternion.identity;
}
Center of Mass (Centre de Masse)
public class UnstableBox : MonoBehaviour
{
void Start()
{
rb.centerOfMass = new Vector3(0, 1.5f, 0);
Debug.DrawRay(transform.TransformPoint(rb.centerOfMass), Vector3.up, Color.red);
}
}
4. Joints et Constraints
Joints Unity
[RequireComponent(typeof(FixedJoint))]
public class FixedJointExample : MonoBehaviour
{
void Start()
{
var joint = GetComponent<FixedJoint>();
joint.connectedBody = _parentRigidbody;
joint.breakForce = 500f;
joint.breakTorque = 500f;
}
}
[RequireComponent(typeof(HingeJoint))]
public class HingeJointExample : MonoBehaviour
{
void Start()
{
var joint = GetComponent<HingeJoint>();
var limits = new JointLimits
{
min = -90f,
max = 90f,
bounciness = 0.2f,
contactDistance = 0.01f
};
joint.limits = limits;
joint.useLimits = true;
var motor = new JointMotor
{
force = 100f,
targetVelocity = 30f,
freeSpin = false
};
joint.motor = motor;
joint.useMotor = true;
var spring = new JointSpring
{
spring = ,
damper = ,
targetPosition =
};
joint.spring = spring;
joint.useSpring = ;
}
}
[]
:
{
{
joint = GetComponent<SpringJoint>();
joint.spring = ;
joint.damper = ;
joint.minDistance = ;
joint.maxDistance = ;
joint.tolerance = ;
}
}
[]
:
{
{
joint = GetComponent<ConfigurableJoint>();
joint.xMotion = ConfigurableJointMotion.Locked;
joint.yMotion = ConfigurableJointMotion.Locked;
joint.zMotion = ConfigurableJointMotion.Free;
joint.angularXMotion = ConfigurableJointMotion.Free;
joint.angularYMotion = ConfigurableJointMotion.Free;
joint.angularZMotion = ConfigurableJointMotion.Free;
drive = JointDrive
{
positionSpring = ,
positionDamper = ,
maximumForce = .MaxValue
};
joint.xDrive = drive;
}
}
Breakable Joints — Destruction
public class BreakableJoint : MonoBehaviour
{
public float breakForce = 500f;
public GameObject brokenPiecePrefab;
void OnJointBreak(float breakForce)
{
Debug.Log($"Joint cassé avec force {breakForce}");
if (brokenPiecePrefab)
{
Instantiate(brokenPiecePrefab, transform.position, transform.rotation);
}
Destroy(gameObject);
}
}
5. Character Controllers
Unity Character Controller
[RequireComponent(typeof(CharacterController))]
public class CustomCharacterController : MonoBehaviour
{
private CharacterController _controller;
public float speed = 6f;
public float jumpHeight = 2f;
public float gravity = -9.81f;
private Vector3 _velocity;
private bool _isGrounded;
void Start() => _controller = GetComponent<CharacterController>();
void Update()
{
_isGrounded = _controller.isGrounded;
if (_isGrounded && _velocity.y < 0)
_velocity.y = -2f;
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
_controller.Move(move * speed * Time.deltaTime);
if (Input.GetButtonDown("Jump") && _isGrounded)
_velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
_velocity.y += gravity * Time.deltaTime;
_controller.Move(_velocity * Time.deltaTime);
}
}
PhysX-based Character Controller (Capsule)
Godot CharacterBody
extends CharacterBody3D
@export var speed := 5.0
@export var jump_velocity := 4.5
@export var max_slope_angle := 45.0
func _physics_process(delta: float) -> void:
# Mouvement horizontal
var input_dir := Input.get_vector("gauche", "droite", "avant", "arriere")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
else:
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
# Saut
if Input.is_action_just_pressed("sauter") and is_on_floor():
velocity.y = jump_velocity
# Gravité
if not is_on_floor():
velocity.y += gravity * delta
move_and_slide()
6. Soft Bodies et Cloth
Soft Bodies → maillage déformable avec ressorts
Types:
- Tetrahedral: volume complet (découpe réelle)
- Spring Mesh: surface (vêtements, drapeaux)
- Cloth: contraintes spécialisées (plis, cisaillement)
Unreal: Chaos Cloth (héritier d'APEX)
Unity: Cloth component (PhysX legacy)
Godot: SoftBody node (Bullet)
Custom: Verlet Integration (simple, performant)
Verlet Integration — Soft Body Custom
public struct VerletPoint
{
public Vector3 Position;
public Vector3 OldPosition;
public bool Pinned;
}
public class VerletCloth : MonoBehaviour
{
public struct Constraint
{
public int A, B;
public float RestLength;
}
private VerletPoint[] _points;
private Constraint[] _constraints;
public float gravity = -9.81f;
public int iterations = 8;
void Update()
{
float dt = Time.deltaTime;
foreach (ref var p in _points.AsSpan())
{
if (p.Pinned) continue;
Vector3 velocity = p.Position - p.OldPosition;
p.OldPosition = p.Position;
p.Position += velocity;
p.Position.y += gravity * dt * dt;
}
for (int i = 0; i < iterations; i++)
{
foreach (var c _constraints)
{
Vector3 delta = _points[c.B].Position - _points[c.A].Position;
dist = delta.magnitude;
error = (dist - c.RestLength) / dist;
(!_points[c.A].Pinned)
_points[c.A].Position += delta * error * ;
(!_points[c.B].Pinned)
_points[c.B].Position -= delta * error * ;
}
}
}
}
7. Raycasts et Queries
public class RaycastExamples : MonoBehaviour
{
void Update()
{
if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, 100f))
{
Debug.Log($"Touché: {hit.collider.name} à {hit.distance}m");
}
int layerMask = LayerMask.GetMask("Enemies", "Walls");
Physics.Raycast(origin, direction, out hit, 100f, layerMask);
if (Physics.SphereCast(origin, radius, direction, out hit, 100f))
{
}
Physics.BoxCast(center, halfExtents, direction, out hit, rotation, distance);
Collider[] hits = Physics.OverlapSphere(transform.position, 5f, enemyMask);
foreach (var c in hits)
c.GetComponent<Enemy>()?.TakeDamage(10);
Collider[] boxHits = Physics.OverlapBox(center, halfExtents, rotation, layerMask);
Collider[] capsuleHits = Physics.OverlapCapsule(
bottom, top, radius, layerMask);
RaycastHit[] allHits = Physics.RaycastAll(origin, direction, );
System.Array.Sort(allHits, (a, b) => a.distance.CompareTo(b.distance));
count = Physics.RaycastNonAlloc(origin, direction, _hitBuffer, );
( i = ; i < count; i++)
ProcessHit(_hitBuffer[i]);
}
}
Trigger vs Collision
void OnCollisionEnter(Collision collision)
{
}
void OnTriggerEnter(Collider other)
{
}
void OnTriggerStay(Collider other) { }
void OnTriggerExit(Collider other) { }
8. Physical Materials (Friction et Rebond)
public class PhysicsMaterialSetup : MonoBehaviour
{
void Start()
{
var mat = new PhysicMaterial();
mat.staticFriction = 0.6f;
mat.dynamicFriction = 0.4f;
mat.frictionCombine = PhysicMaterialCombine.Average;
mat.bounciness = 0.5f;
mat.bounceCombine = PhysicMaterialCombine.Maximum;
GetComponent<Collider>().material = mat;
}
}
9. Déterminisme et Fixed Timestep
void Awake()
{
Time.fixedDeltaTime = 0.02f;
Time.maximumDeltaTime = 0.1f;
}
10. Pièges Courants
- CCD désactivé → balles qui traversent les murs à haute vitesse. Toujours activer sur les objets rapides.
- FixedUpdate à 60Hz inutile → 30Hz suffit pour 80% des jeux.
- Layer collision matrix pas configurée → des balles qui se cognent entre elles → collisions inutiles.
- Trop d'objets dynamiques → 100+ rigidbodies = solver surchargé. 30 max pour la stabilité.
- Kinematic mis à jour via transform → pas d'interaction physique. Utiliser MovePosition/MoveRotation.
- Joints sans connectedBody → attaché au monde (mauvais). Toujours assigner un Rigidbody.
- Mass trop faible/trop élevée → masse < 0.01 = instable. > 10000 = perce tout.
- Sleep désactivé → tous les rigidbodies simulés tout le temps → CPU saturé. Laisser le sleep.
- OnCollisionEnter coûteux → instancier effets à chaque collision → pool d'effets.
- Mesh colliders convexes → Mesh Collider non-convexe = pas de collision avec un autre mesh. Marquer comme convexe ou utiliser des colliders simples.