Skip to content

Web oyunları için oyun fiziği

Fizik simülasyonu; düşen nesneler, zıplayan toplar, ragdoll karakterler, araçlar ve yok edilebilir ortamlarla oyunlara hayat verir. Bu rehber, web oyunlarında kullanılabilen başlıca fizik kütüphanelerini gerçek kod örnekleri ve dürüst artı/eksi değerlendirmeleriyle ele alıyor.

Genel bakış

MotorBoyutPerformansBoyutZorlukYumuşak CisimlerAraçlarCCDBelirlenimlilik
Rapier2D/3D⭐⭐⭐⭐⭐1.4 MBOrta
Cannon-es3D⭐⭐⭐150 KBKolay
Ammo.js3D⭐⭐⭐⭐⭐1-2 MBZor⚠️
Jolt3D⭐⭐⭐⭐⭐Zor
Oimo.js3D⭐⭐⭐100 KBKolay
Matter.js2D⭐⭐⭐80 KBKolay⚠️
Planck.js2D⭐⭐⭐⭐120 KBOrta⚠️
p2-es2D⭐⭐⭐100 KBOrta
Box2D WASM2D⭐⭐⭐⭐⭐300 KBZor

Açıklamalar:

  • CCD = Sürekli Çarpışma Algılama (hızlı nesnelerin duvarların içinden geçmesini önler)
  • ⚠️ Belirlenimlilik = Sabit zaman adımıyla mümkün olsa da platformlar arasında garanti edilmez

Hızlı öneriler

DurumunuzEn iyi seçim
Yayına hazır 3D oyunRapier — en iyi performans, modern API, aktif geliştirme
Öğrenme / prototipCannon-es (3D) veya Matter.js (2D) — basit API'ler, kolay hata ayıklama
Araç fiziğiRapier veya Ammo.js — ikisinde de ışın izlemeli araç denetleyicileri bulunur
Yumuşak cisimler, kumaşlar, halatlarAmmo.js veya Jolt (JoltPhysics.js) — ikisi de kumaşları ve yumuşak cisimleri destekler; Jolt'un WASM portu daha aktif biçimde geliştirilmektedir
Hassas 2D platform oyunuPlanck.js — Box2D algoritmaları, sabit zaman adımıyla belirlenimlilik
En yüksek 2D performansıBox2D WASM — tarayıcıda yerel kod hızı
En küçük paket boyutuOimo.js (3D) veya Matter.js (2D)

Ayrıntılı karşılaştırma

3D Fizik Motorları

MotorDilBoyutPerformansEn uygun kullanım
RapierRust/WASM~1.4 MBMükemmelYayına hazır oyunlar, karmaşık simülasyonlar
Cannon-esJavaScript~150 KBİyiPrototipler, basit oyunlar
Ammo.jsC++/WASM~1-2 MBMükemmelAAA özellikleri, yumuşak cisimler, araçlar
JoltC++/WASMWASMMükemmelAktif bakımla sunulan AAA özellikleri
Oimo.jsJavaScript~100 KBİyiBasit oyunlar, hızlı prototipler

2D Fizik Motorları

MotorDilBoyutPerformansEn uygun kullanım
Matter.jsJavaScript~80 KBİyiGörsel oyunlar, prototipler
Planck.jsJavaScript~120 KBİyiPlatform oyunları, hassas fizik
p2-esJavaScript~100 KBİyiKısıtlamalar, mekanizmalar
Box2D WASMC++/WASM~300 KBMükemmelÇok sayıda fizik cismi

3D Fizik Motorları

Rapier — Modern seçim

Rapier, JavaScript/WASM bağlayıcılarına sahip bir Rust fizik motorudur. 2024 sürümüne kıyasla 2-5 kat hız artışı sunarak 2025-2026 döneminde web oyunları için en yüksek performanslı seçenek hâline gelmiştir.

Kurulum:

bash
npm install @dimforge/rapier3d
# Or with SIMD (faster, requires modern browsers):
npm install @dimforge/rapier3d-simd

Temel kurulum:

js
import RAPIER from '@dimforge/rapier3d'

// Initialize (async required for WASM)
await RAPIER.init()

// Create world with gravity
const gravity = { x: 0, y: -9.81, z: 0 }
const world = new RAPIER.World(gravity)

// Create ground (static body)
const groundDesc = RAPIER.RigidBodyDesc.fixed()
const groundBody = world.createRigidBody(groundDesc)
const groundCollider = RAPIER.ColliderDesc.cuboid(50, 0.1, 50)
world.createCollider(groundCollider, groundBody)

// Create falling box (dynamic body)
const boxDesc = RAPIER.RigidBodyDesc.dynamic()
  .setTranslation(0, 10, 0)
const boxBody = world.createRigidBody(boxDesc)
const boxCollider = RAPIER.ColliderDesc.cuboid(0.5, 0.5, 0.5)
  .setDensity(1.0)
  .setRestitution(0.5)
world.createCollider(boxCollider, boxBody)

Oyun döngüsü entegrasyonu:

js
const FIXED_TIMESTEP = 1 / 60

function physicsStep() {
  world.step()
}

function gameLoop() {
  physicsStep()
  
  // Sync render objects with physics
  const position = boxBody.translation()
  const rotation = boxBody.rotation()
  
  // Update your Three.js/Babylon mesh
  mesh.position.set(position.x, position.y, position.z)
  mesh.quaternion.set(rotation.x, rotation.y, rotation.z, rotation.w)
  
  requestAnimationFrame(gameLoop)
}

Çarpışma algılama:

js
// Event-based collision detection
world.contactPairsWith(boxCollider, (otherCollider) => {
  console.log('Box is touching:', otherCollider)
})

// Ray casting
const ray = new RAPIER.Ray({ x: 0, y: 10, z: 0 }, { x: 0, y: -1, z: 0 })
const hit = world.castRay(ray, 100, true)
if (hit) {
  const hitPoint = ray.pointAt(hit.timeOfImpact)
  console.log('Hit at:', hitPoint)
}

Eklemler:

js
// Create a hinge joint (door)
const jointData = RAPIER.JointData.revolute(
  { x: 0, y: 0, z: 0 },  // Anchor on body1
  { x: -1, y: 0, z: 0 }, // Anchor on body2
  { x: 0, y: 1, z: 0 }   // Rotation axis
)
world.createImpulseJoint(jointData, body1, body2, true)

Araç denetleyicisi:

js
// Create chassis rigid body
const chassisDesc = RAPIER.RigidBodyDesc.dynamic()
  .setTranslation(0, 2, 0)
const chassis = world.createRigidBody(chassisDesc)
const chassisCollider = RAPIER.ColliderDesc.cuboid(1, 0.5, 2)
  .setDensity(100)
world.createCollider(chassisCollider, chassis)

// Create vehicle controller
const vehicle = world.createVehicleController(chassis)

// Add wheels (front-left, front-right, rear-left, rear-right)
const suspensionRestLength = 0.3
const wheelRadius = 0.4

// Front wheels (steering)
vehicle.addWheel(
  { x: -0.8, y: 0, z: 1.2 },   // Connection point
  { x: 0, y: -1, z: 0 },       // Suspension direction
  { x: -1, y: 0, z: 0 },       // Axle direction
  suspensionRestLength,
  wheelRadius
)
vehicle.addWheel(
  { x: 0.8, y: 0, z: 1.2 },
  { x: 0, y: -1, z: 0 },
  { x: -1, y: 0, z: 0 },
  suspensionRestLength,
  wheelRadius
)

// Rear wheels (drive)
vehicle.addWheel(
  { x: -0.8, y: 0, z: -1.2 },
  { x: 0, y: -1, z: 0 },
  { x: -1, y: 0, z: 0 },
  suspensionRestLength,
  wheelRadius
)
vehicle.addWheel(
  { x: 0.8, y: 0, z: -1.2 },
  { x: 0, y: -1, z: 0 },
  { x: -1, y: 0, z: 0 },
  suspensionRestLength,
  wheelRadius
)

// Configure suspension for all wheels
for (let i = 0; i < 4; i++) {
  vehicle.setWheelSuspensionStiffness(i, 30)
  vehicle.setWheelSuspensionCompression(i, 4.4)
  vehicle.setWheelSuspensionRelaxation(i, 2.3)
  vehicle.setWheelMaxSuspensionTravel(i, 0.5)
  vehicle.setWheelFrictionSlip(i, 2)
}

// In game loop
function updateVehicle(steering, engineForce, brakeForce) {
  // Steering (front wheels only)
  vehicle.setWheelSteering(0, steering)
  vehicle.setWheelSteering(1, steering)
  
  // Engine (rear wheels)
  vehicle.setWheelEngineForce(2, engineForce)
  vehicle.setWheelEngineForce(3, engineForce)
  
  // Brakes (all wheels)
  for (let i = 0; i < 4; i++) {
    vehicle.setWheelBrake(i, brakeForce)
  }
  
  // Update vehicle physics
  vehicle.updateVehicle(world.timestep)
}

Artıları:

  • En iyi performans (WASM + SIMD)
  • Mükemmel çarpışma algılama doğruluğu
  • Platformlar arası belirlenimlilik (aynı girdiler = aynı çıktılar)
  • Aktif geliştirme, modern API
  • Sürekli çarpışma algılama (nesneler yüzeylerin içinden geçmez)
  • Yerleşik karakter ve araç denetleyicileri

Eksileri:

  • Daha büyük paket boyutu (~1.4 MB)
  • Eşzamansız başlatma gerektirir
  • Saf JS alternatiflerinden daha karmaşık API
  • WASM'de hata ayıklamak zor olabilir

Cannon-es — Basit ve etkili

Cannon-es, Cannon.js'in bakımı sürdürülen çatallanmış sürümüdür. Saf JavaScript ile yazılmıştır; anlaşılması kolaydır ve öğrenme ile prototip geliştirme için idealdir.

Kurulum:

bash
npm install cannon-es

Temel kurulum:

js
import * as CANNON from 'cannon-es'

// Create world
const world = new CANNON.World({
  gravity: new CANNON.Vec3(0, -9.81, 0)
})

// Ground
const groundBody = new CANNON.Body({
  type: CANNON.Body.STATIC,
  shape: new CANNON.Plane()
})
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
world.addBody(groundBody)

// Falling sphere
const sphereBody = new CANNON.Body({
  mass: 1,
  shape: new CANNON.Sphere(0.5),
  position: new CANNON.Vec3(0, 10, 0)
})
sphereBody.linearDamping = 0.1
world.addBody(sphereBody)

Oyun döngüsü:

js
const TIMESTEP = 1 / 60

function animate() {
  world.step(TIMESTEP)
  
  // Sync with Three.js
  mesh.position.copy(sphereBody.position)
  mesh.quaternion.copy(sphereBody.quaternion)
  
  requestAnimationFrame(animate)
}

Çarpışma olayları:

js
sphereBody.addEventListener('collide', (event) => {
  const contact = event.contact
  const impactVelocity = contact.getImpactVelocityAlongNormal()
  
  if (Math.abs(impactVelocity) > 5) {
    console.log('Hard impact!')
  }
})

Kısıtlamalar:

js
// Distance constraint (rope-like)
const constraint = new CANNON.DistanceConstraint(
  bodyA, bodyB, 
  2 // distance
)
world.addConstraint(constraint)

// Hinge constraint
const hinge = new CANNON.HingeConstraint(bodyA, bodyB, {
  pivotA: new CANNON.Vec3(1, 0, 0),
  axisA: new CANNON.Vec3(0, 1, 0),
  pivotB: new CANNON.Vec3(-1, 0, 0),
  axisB: new CANNON.Vec3(0, 1, 0)
})
world.addConstraint(hinge)

Artıları:

  • Saf JavaScript, WASM karmaşıklığı yok
  • Küçük paket boyutu (~150 KB)
  • Öğrenmesi ve hata ayıklaması kolay
  • Her yerde çalışır
  • Güçlü Three.js entegrasyonu
  • İyi dokümantasyon

Eksileri:

  • WASM alternatiflerinden daha yavaş
  • Çok sayıda cisimle (>100) zorlanır
  • Sınırlı üçgen örgü desteği
  • Yerleşik CCD yoktur (nesneler yüzeylerin içinden geçebilir)
  • Geliştirme etkinliği daha düşük

Ammo.js — Bullet Physics'in tüm gücü

Ammo.js, WebAssembly'ye derlenmiş Bullet Physics motorudur. Yumuşak cisimler, araçlar ve gelişmiş kısıtlamalar dâhil en kapsamlı özellik setini sunar.

Kurulum:

bash
npm install ammo.js
# Or use from CDN

Temel kurulum:

js
import Ammo from 'ammo.js'

let physicsWorld

async function initPhysics() {
  await Ammo()
  
  const collisionConfig = new Ammo.btDefaultCollisionConfiguration()
  const dispatcher = new Ammo.btCollisionDispatcher(collisionConfig)
  const broadphase = new Ammo.btDbvtBroadphase()
  const solver = new Ammo.btSequentialImpulseConstraintSolver()
  
  physicsWorld = new Ammo.btDiscreteDynamicsWorld(
    dispatcher, broadphase, solver, collisionConfig
  )
  physicsWorld.setGravity(new Ammo.btVector3(0, -9.81, 0))
}

function createBox(mass, width, height, depth, x, y, z) {
  const transform = new Ammo.btTransform()
  transform.setIdentity()
  transform.setOrigin(new Ammo.btVector3(x, y, z))
  
  const motionState = new Ammo.btDefaultMotionState(transform)
  const shape = new Ammo.btBoxShape(
    new Ammo.btVector3(width / 2, height / 2, depth / 2)
  )
  
  const localInertia = new Ammo.btVector3(0, 0, 0)
  if (mass > 0) {
    shape.calculateLocalInertia(mass, localInertia)
  }
  
  const rbInfo = new Ammo.btRigidBodyConstructionInfo(
    mass, motionState, shape, localInertia
  )
  const body = new Ammo.btRigidBody(rbInfo)
  
  physicsWorld.addRigidBody(body)
  return body
}

Yumuşak cisimler (kumaş):

js
function createCloth(width, height, segments) {
  const softBodyHelpers = new Ammo.btSoftBodyHelpers()
  
  const corner00 = new Ammo.btVector3(-width/2, height, 0)
  const corner10 = new Ammo.btVector3(width/2, height, 0)
  const corner01 = new Ammo.btVector3(-width/2, 0, 0)
  const corner11 = new Ammo.btVector3(width/2, 0, 0)
  
  const softBody = softBodyHelpers.CreatePatch(
    physicsWorld.getWorldInfo(),
    corner00, corner10, corner01, corner11,
    segments, segments,
    0, true
  )
  
  const sbConfig = softBody.get_m_cfg()
  sbConfig.set_viterations(10)
  sbConfig.set_piterations(10)
  
  softBody.setTotalMass(0.9, false)
  Ammo.castObject(softBody, Ammo.btCollisionObject)
    .getCollisionShape().setMargin(0.05)
  
  physicsWorld.addSoftBody(softBody, 1, -1)
  return softBody
}

Araç fiziği:

js
function createVehicle(chassisBody) {
  const tuning = new Ammo.btVehicleTuning()
  const rayCaster = new Ammo.btDefaultVehicleRaycaster(physicsWorld)
  const vehicle = new Ammo.btRaycastVehicle(tuning, chassisBody, rayCaster)
  
  vehicle.setCoordinateSystem(0, 1, 2)
  physicsWorld.addAction(vehicle)
  
  // Add wheels
  const wheelRadius = 0.4
  const wheelWidth = 0.3
  const suspensionRestLength = 0.3
  
  const wheelDirectionCS = new Ammo.btVector3(0, -1, 0)
  const wheelAxleCS = new Ammo.btVector3(-1, 0, 0)
  
  function addWheel(isFront, pos) {
    const wheelInfo = vehicle.addWheel(
      pos, wheelDirectionCS, wheelAxleCS,
      suspensionRestLength, wheelRadius, tuning, isFront
    )
    wheelInfo.set_m_suspensionStiffness(20)
    wheelInfo.set_m_wheelsDampingRelaxation(2.3)
    wheelInfo.set_m_wheelsDampingCompression(4.4)
    wheelInfo.set_m_frictionSlip(1000)
    wheelInfo.set_m_rollInfluence(0.1)
  }
  
  addWheel(true, new Ammo.btVector3(1, 0, 1.5))   // Front left
  addWheel(true, new Ammo.btVector3(-1, 0, 1.5))  // Front right
  addWheel(false, new Ammo.btVector3(1, 0, -1.5)) // Rear left
  addWheel(false, new Ammo.btVector3(-1, 0, -1.5))// Rear right
  
  return vehicle
}

Artıları:

  • En kapsamlı özellik seti
  • Yumuşak cisimler, kumaşlar, halatlar
  • Gelişmiş araç fiziği
  • Savaş koşullarında kendini kanıtlamış teknoloji (birçok AAA oyunda kullanılır)
  • Son derece özelleştirilebilir Eksileri:
  • Karmaşık ve ayrıntılı API
  • Büyük paket boyutu
  • Bellek yönetimi gerektirir (nesneleri yok etme)
  • Zorlu öğrenme süreci
  • Dağınık dokümantasyon
  • Platformlar arasında deterministik değildir (dikkatli yapılandırmayla yalnızca aynı cihazda)

Jolt — modern, AAA kalitesinde 3D fizik

Jolt Physics, Horizon Forbidden West gibi oyunların arkasındaki motordur; JoltPhysics.js ise onu bir WASM portu olarak tarayıcıya getirir. Rapier ile Ammo.js arasında güçlü bir orta yoldur: Rapier'den daha fazla özellik (yumuşak cisimler, kumaş, tekerlekli araç kontrolcüsü) sunarken Ammo.js'de bulunmayan aktif bakıma sahiptir. npm paketi jolt-physics'tir; React Three Fiber (@react-three/jolt) ve Babylon.js için hazır entegrasyonlar da bulunur.

bash
npm install jolt-physics

Rapier gibi, C++ arayüzünü yansıtan ayrıntılı ama eksiksiz bir API'ye sahip, eşzamansız başlatılan bir WASM çözümüdür. Bullet sınıfı özellikler (yumuşak cisimler, araçlar) istediğiniz ancak Ammo.js yerine bakımı sürdürülen modern bir kod tabanı tercih ettiğiniz durumlarda Jolt'u kullanın.


Oimo.js — Hafif ve hızlı

Oimo.js hafif bir 3D fizik motorudur. Gelişmiş özelliklere ihtiyaç duymadığınız basit oyunlar için idealdir.

Kurulum:

bash
npm install oimo

Temel kurulum:

js
import * as OIMO from 'oimo'

const world = new OIMO.World({
  timestep: 1/60,
  iterations: 8,
  broadphase: 2, // 1: kaba kuvvet, 2: süpür ve buda, 3: hacim ağacı
  worldscale: 1,
  random: true,
  gravity: [0, -9.8, 0]
})

// Zemini oluştur
world.add({
  type: 'box',
  size: [100, 1, 100],
  pos: [0, -0.5, 0],
  move: false
})

// Düşen küreyi oluştur
const sphere = world.add({
  type: 'sphere',
  size: [1],
  pos: [0, 10, 0],
  move: true,
  density: 1,
  friction: 0.4,
  restitution: 0.2
})

Oyun döngüsü:

js
function animate() {
  world.step()
  
  // Konumu/dönüşü al
  const pos = sphere.getPosition()
  const rot = sphere.getQuaternion()
  
  mesh.position.set(pos.x, pos.y, pos.z)
  mesh.quaternion.set(rot.x, rot.y, rot.z, rot.w)
  
  requestAnimationFrame(animate)
}

Artıları:

  • Çok küçük (~100 KB)
  • Basit API
  • Temel sahnelerde iyi performans
  • Yerleşik Babylon.js desteği

Eksileri:

  • Sınırlı şekiller (yalnızca temel geometriler)
  • Yumuşak cisim desteği yok
  • Yetersiz dokümantasyon
  • Daha az aktif geliştirme
  • Sınırlı eklem seçenekleri

2D Fizik Motorları

Matter.js — Güzel ve sezgisel

Matter.js, mükemmel render ve hata ayıklama araçlarına sahip, özellik bakımından zengin bir 2D fizik motorudur.

Kurulum:

bash
npm install matter-js

Temel kurulum:

js
import Matter from 'matter-js'

const { Engine, Render, World, Bodies, Runner } = Matter

// Motoru oluştur
const engine = Engine.create()

// Render aracını oluştur (isteğe bağlı, hata ayıklama için harika)
const render = Render.create({
  element: document.body,
  engine: engine,
  options: {
    width: 800,
    height: 600,
    wireframes: false
  }
})

// Cisimleri oluştur
const ground = Bodies.rectangle(400, 580, 810, 60, { 
  isStatic: true 
})

const box = Bodies.rectangle(400, 200, 80, 80, {
  restitution: 0.8,
  friction: 0.5
})

const circle = Bodies.circle(300, 100, 40, {
  restitution: 0.9
})

// Dünyaya ekle
World.add(engine.world, [ground, box, circle])

// Çalıştır
Render.run(render)
Runner.run(Runner.create(), engine)

Çarpışma olayları:

js
Matter.Events.on(engine, 'collisionStart', (event) => {
  event.pairs.forEach(pair => {
    console.log('Şunlar arasında çarpışma:', pair.bodyA.label, pair.bodyB.label)
  })
})

Kısıtlamalar:

js
// Sabitleme kısıtlaması
const pin = Matter.Constraint.create({
  pointA: { x: 400, y: 100 },
  bodyB: box,
  stiffness: 0.9
})

// Cisimler arasında yay
const spring = Matter.Constraint.create({
  bodyA: box,
  bodyB: circle,
  stiffness: 0.01,
  length: 100
})

World.add(engine.world, [pin, spring])

Fare etkileşimi:

js
const mouse = Matter.Mouse.create(render.canvas)
const mouseConstraint = Matter.MouseConstraint.create(engine, {
  mouse: mouse,
  constraint: {
    stiffness: 0.2,
    render: { visible: false }
  }
})
World.add(engine.world, mouseConstraint)

Artıları:

  • Güzel varsayılan render
  • Öğrenme/prototipleme için harika
  • Sezgisel API
  • İyi dokümantasyon
  • Aktif topluluk
  • Varsayılan olarak deterministik — Matter.Runner artık sabit ve deterministik bir zaman adımı kullanıyor (sabit olmayan zaman adımı v0.20.0'da kaldırıldı)

Eksileri:

  • CCD yok (hızlı nesneler diğer cisimlerin içinden geçebilir; bunu azaltmak için alt adımlama kullanın)
  • Çok sayıda cisimde performans sorunları
  • WASM seçeneği yok
  • Karmaşık simülasyonlarda sınırlı hassasiyet

Planck.js — JavaScript için Box2D

Planck.js, Box2D'nin JavaScript ile baştan yazılmış eksiksiz bir sürümüdür. Savaşta sınanmış fizik sunar ve sabit zaman adımı kullanıldığında deterministiktir.

Kurulum:

bash
npm install planck

Temel kurulum:

js
import { World, Vec2, Box, Circle, Edge } from 'planck'

// Dünyayı oluştur
const world = new World({
  gravity: Vec2(0, -10)
})

// Zemini oluştur
const ground = world.createBody()
ground.createFixture({
  shape: Edge(Vec2(-40, 0), Vec2(40, 0))
})

// Dinamik kutuyu oluştur
const box = world.createBody({
  type: 'dynamic',
  position: Vec2(0, 10)
})
box.createFixture({
  shape: Box(1, 1),
  density: 1,
  friction: 0.3,
  restitution: 0.5
})

Sabit zaman adımlı oyun döngüsü:

js
const TIMESTEP = 1 / 60
const VELOCITY_ITERATIONS = 8
const POSITION_ITERATIONS = 3

function gameLoop() {
  world.step(TIMESTEP, VELOCITY_ITERATIONS, POSITION_ITERATIONS)
  
  // Tüm cisimleri dolaş
  for (let body = world.getBodyList(); body; body = body.getNext()) {
    const pos = body.getPosition()
    const angle = body.getAngle()
    // Sprite'larınızı güncelleyin...
  }
  
  requestAnimationFrame(gameLoop)
}

Çarpışma geri çağrıları:

js
world.on('begin-contact', (contact) => {
  const fixtureA = contact.getFixtureA()
  const fixtureB = contact.getFixtureB()
  console.log('Temas başladı')
})

world.on('end-contact', (contact) => {
  console.log('Temas sona erdi')
})

world.on('pre-solve', (contact, oldManifold) => {
  // Temas burada devre dışı bırakılabilir
  // contact.setEnabled(false)
})

Eklemler:

js
import { RevoluteJoint, DistanceJoint, PrismaticJoint } from 'planck'

// Döner eklem (menteşe)
const joint = world.createJoint(RevoluteJoint({
  bodyA: ground,
  bodyB: box,
  localAnchorA: Vec2(0, 5),
  localAnchorB: Vec2(-1, 0),
  enableMotor: true,
  maxMotorTorque: 1000,
  motorSpeed: 2
}))

// Mesafe eklemi (yay)
world.createJoint(DistanceJoint({
  bodyA: boxA,
  bodyB: boxB,
  localAnchorA: Vec2(0, 0),
  localAnchorB: Vec2(0, 0),
  length: 5,
  stiffness: 10,
  damping: 0.5
}))

Artıları:

  • Sabit zaman adımıyla deterministik
  • İyi belgelenmiş (Box2D dokümanları geçerlidir)
  • İyi performans
  • TypeScript desteği
  • Küçük paket boyutu

Eksileri:

  • CCD eklemleri işlemez; bu nedenle eklemlerle bağlanan hızlı nesneler esneyebilir
  • Matter.js'e göre daha zorlu öğrenme süreci
  • Yerleşik render aracı yok
  • Box2D'ye özgü ayrıntılar (birim ölçeği önemlidir)

p2-es — Esnek 2D fizik

p2-es, p2.js'in bakımı sürdürülen çatallanmış sürümüdür. Karmaşık kısıtlamalara ve mekanizmalara ihtiyaç duyan oyunlar için harikadır.

Kurulum:

bash
npm install p2-es

Temel kurulum:

js
import * as p2 from 'p2-es'

const world = new p2.World({
  gravity: [0, -9.81]
})

// Zemin
const groundBody = new p2.Body({
  mass: 0, // statik
  position: [0, -1]
})
groundBody.addShape(new p2.Plane())
world.addBody(groundBody)

// Dinamik daire
const circleBody = new p2.Body({
  mass: 1,
  position: [0, 5]
})
circleBody.addShape(new p2.Circle({ radius: 0.5 }))
world.addBody(circleBody)

Gelişmiş kısıtlamalar:

js
// Dişli kısıtlaması
const gear = new p2.GearConstraint(bodyA, bodyB, {
  ratio: 2 // bodyB iki kat hızlı döner
})
world.addConstraint(gear)

// Prizmatik kısıtlama (kaydırıcı)
const prismatic = new p2.PrismaticConstraint(bodyA, bodyB, {
  localAnchorA: [0, 0],
  localAnchorB: [0, 0],
  localAxisA: [1, 0],
  disableRotationalLock: false
})
world.addConstraint(prismatic)

// Kilit kısıtlaması (kaynak)
const lock = new p2.LockConstraint(bodyA, bodyB)
world.addConstraint(lock)

Temas malzemeleri:

js
const ice = new p2.Material()
const rubber = new p2.Material()

const iceRubber = new p2.ContactMaterial(ice, rubber, {
  friction: 0.1,
  restitution: 0.9
})
world.addContactMaterial(iceRubber)

// Şekillere uygula
iceBody.shapes[0].material = ice
rubberBody.shapes[0].material = rubber

Artıları:

  • Zengin kısıtlama sistemi
  • Temas malzemeleri
  • Mekanizmalar/makineler için uygun
  • Uyuyan cisimler (performans)
  • ES modülleri, tree-shaking desteği

Eksileri:

  • Yerleşik render aracı yok
  • Bazı şekil çiftleri desteklenmiyor
  • Alternatiflerden daha az aktif
  • Dokümantasyon eksiklikleri

Performans ipuçları

1. Sabit zaman adımı kullanın

js
const TIMESTEP = 1 / 60
let accumulator = 0

function gameLoop(deltaTime) {
  accumulator += deltaTime
  
  while (accumulator >= TIMESTEP) {
    world.step(TIMESTEP)
    accumulator -= TIMESTEP
  }
  
  // Akıcı render için enterpolasyon yap
  const alpha = accumulator / TIMESTEP
  // lerp(previousState, currentState, alpha)
}

2. Etkin olmayan cisimleri uyutun

Çoğu motor uyku özelliğini destekler. Etkinleştirin:

js
// Cannon-es
world.allowSleep = true
body.allowSleep = true
body.sleepSpeedLimit = 0.1
body.sleepTimeLimit = 1

// Rapier
bodyDesc.setCanSleep(true)

3. Basit çarpışma şekilleri kullanın

js
// İYİ: Basit temel geometriler
const sphere = new CANNON.Sphere(1)
const box = new CANNON.Box(new CANNON.Vec3(1, 1, 1))

// KAÇININ: Dinamik cisimler için karmaşık üçgen ağ
const trimesh = new CANNON.Trimesh(vertices, indices) // Yavaş!

4. Çözücü yinelemelerini azaltın (dikkatlice)

js
// Cannon-es
world.solver.iterations = 5 // Varsayılan 10

// Rapier - dünya oluşturulurken yapılandırılır

5. Fiziği bir Web Worker içinde çalıştırın

js
// main.js
const worker = new Worker('physics-worker.js')

worker.postMessage({ type: 'step', deltaTime: 1/60 })
worker.onmessage = (e) => {
  const { positions, rotations } = e.data
  // Render nesnelerini güncelle
}

// physics-worker.js
importScripts('cannon-es.js')
const world = new CANNON.World()

onmessage = (e) => {
  if (e.data.type === 'step') {
    world.step(e.data.deltaTime)
    postMessage({
      positions: bodies.map(b => b.position.toArray()),
      rotations: bodies.map(b => b.quaternion.toArray())
    })
  }
}

Hangi durumda hangisini kullanmalı?

SenaryoÖnerilenNeden
Üretime hazır 3D oyunRapierEn iyi performans, modern API, araçlar
3D oyun prototipiCannon-esBasit, hata ayıklaması kolay
Yumuşak cisimler, kumaş, halatlarAmmo.js veya JoltHer ikisi de kumaş/yumuşak cisimleri destekler; Jolt'un WASM portunun (JoltPhysics.js) bakımı daha aktif biçimde sürdürülür
Basit 3D tarayıcı oyunuOimo.jsKüçük, yeterli
2D game jamMatter.jsHızlı kurulum, yerleşik render
Hassas platform oyunuPlanck.jsSabit zaman adımı determinizmi, iyi dokümantasyon
Karmaşık 2D mekanizmalarp2-esZengin kısıtlama sistemi
Performans açısından kritik 2DBox2D WASMEn hızlı 2D seçeneği, gerçek CCD

Render araçlarıyla entegrasyon

Three.js + Rapier

js
import * as THREE from 'three'
import RAPIER from '@dimforge/rapier3d'

await RAPIER.init()

const scene = new THREE.Scene()
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 })

const bodies = new Map() // fizik cismi -> mesh

function createPhysicsBox(x, y, z) {
  // Fizik
  const bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(x, y, z)
  const body = world.createRigidBody(bodyDesc)
  const collider = RAPIER.ColliderDesc.cuboid(0.5, 0.5, 0.5)
  world.createCollider(collider, body)
  
  // Render
  const mesh = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshStandardMaterial({ color: 0x00ff00 })
  )
  scene.add(mesh)
  
  bodies.set(body, mesh)
  return body
}

function syncPhysics() {
  bodies.forEach((mesh, body) => {
    const pos = body.translation()
    const rot = body.rotation()
    mesh.position.set(pos.x, pos.y, pos.z)
    mesh.quaternion.set(rot.x, rot.y, rot.z, rot.w)
  })
}

PixiJS + Matter.js

js
import * as PIXI from 'pixi.js'
import Matter from 'matter-js'

const app = new PIXI.Application({ width: 800, height: 600 })
document.body.appendChild(app.view)

const engine = Matter.Engine.create()
const bodies = new Map()

function createPhysicsSprite(texture, x, y, width, height) {
  // Fizik
  const body = Matter.Bodies.rectangle(x, y, width, height)
  Matter.World.add(engine.world, body)
  
  // Render
  const sprite = new PIXI.Sprite(texture)
  sprite.anchor.set(0.5)
  app.stage.addChild(sprite)
  
  bodies.set(body, sprite)
  return body
}

app.ticker.add(() => {
  Matter.Engine.update(engine, 1000 / 60)
  
  bodies.forEach((sprite, body) => {
    sprite.position.set(body.position.x, body.position.y)
    sprite.rotation = body.angle
  })
})

Yaygın tuzaklar

Ölçek önemlidir

Fizik motorları gerçek dünya ölçeğiyle (1 birim = 1 metre) en iyi şekilde çalışır. Piksel koordinatlarını doğrudan kullanmayın.

js
// KÖTÜ: Piksel konumlarını kullanmak
const body = Bodies.circle(400, 300, 50) // 50 piksel yarıçap mı?

// İYİ: Bir ölçek faktörü kullanın
const SCALE = 50 // metre başına 50 piksel
const body = Bodies.circle(8, 6, 1) // 1 metre yarıçap
// Ardından render sırasında SCALE ile çarpın

WASM motorlarında bellek sızıntıları

Cisimleri kaldırırken her zaman temizleyin:

js
// Rapier
world.removeRigidBody(body)

// Ammo.js
physicsWorld.removeRigidBody(body)
Ammo.destroy(body)
Ammo.destroy(shape)
Ammo.destroy(motionState)

Tünelleme (nesnelerin birbirinin içinden geçmesi)

Hızlı hareket eden nesneler ince duvarların içinden geçebilir. Çözümler:

js
// Rapier: CCD'yi etkinleştir
const bodyDesc = RAPIER.RigidBodyDesc.dynamic().setCcdEnabled(true)

// Cannon-es: Daha küçük zaman adımları veya daha kalın duvarlar kullan
world.step(1/120) // 60 yerine 120 Hz

İlgili

Harici Kaynaklar