🏗️ 3D Mängumootor Ehitamine
Füüsikamootor Cannon.js + Rapier, 3D kokkupõrked, ECS 3D-s, scene graph — ehita oma 3D mängumootori alus!
⚡ Samm 1 — Füüsikamootor (Cannon-es)
npm install cannon-es # Cannon.js TypeScript fork
import * as CANNON from 'cannon-es';
import * as THREE from 'three';
// Füüsikamaailm
const world = new CANNON.World({
gravity: new CANNON.Vec3(0, -9.82, 0),
});
world.broadphase = new CANNON.SAPBroadphase(world);
// Põrand (staatiline keha)
const floorBody = new CANNON.Body({
type: CANNON.Body.STATIC,
shape: new CANNON.Plane(),
});
floorBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
world.addBody(floorBody);
// Dünaamiline kuubik
const boxBody = new CANNON.Body({
mass: 1,
shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)),
position: new CANNON.Vec3(0, 5, 0),
});
world.addBody(boxBody);
// Three.js mesh
const boxMesh = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x22c55e })
);
scene.add(boxMesh);
// Sünkroniseeri füüsika ja graafika
function updatePhysics(delta: number): void {
world.step(1 / 60, delta, 3);
// Kopeeri füüsika positsioon mesh'ile
boxMesh.position.copy(boxBody.position as any);
boxMesh.quaternion.copy(boxBody.quaternion as any);
}
// Jõu rakendamine
function applyForce(direction: CANNON.Vec3): void {
boxBody.applyForce(direction, boxBody.position);
}
// Impulss (ühekordne jõud, nt hüpe)
function jump(): void {
if (isGrounded()) {
boxBody.applyImpulse(new CANNON.Vec3(0, 8, 0), boxBody.position);
}
}
function isGrounded(): boolean {
// Ray cast alla
const ray = new CANNON.Ray(boxBody.position, new CANNON.Vec3(0, -1, 0));
const result = new CANNON.RaycastResult();
ray.intersectWorld(world, { result, skipBackfaces: true });
return result.hasHit && result.distance < 0.6;
}
🧱 Samm 2 — Physics Body Manager
interface PhysicsObject {
body: CANNON.Body;
mesh: THREE.Mesh;
}
class PhysicsManager {
private world: CANNON.World;
private objects: PhysicsObject[] = [];
constructor() {
this.world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0) });
this.world.broadphase = new CANNON.SAPBroadphase(this.world);
this.world.allowSleep = true; // Magavad kehad ei arvuteta
// Kontaktmaterjal (hõõrdumine ja põrkumine)
const defaultMaterial = new CANNON.Material('default');
const contactMaterial = new CANNON.ContactMaterial(
defaultMaterial, defaultMaterial,
{ friction: 0.3, restitution: 0.4 }
);
this.world.addContactMaterial(contactMaterial);
this.world.defaultContactMaterial = contactMaterial;
}
addBox(mesh: THREE.Mesh, mass: number, size: THREE.Vector3): CANNON.Body {
const halfExtents = new CANNON.Vec3(size.x / 2, size.y / 2, size.z / 2);
const body = new CANNON.Body({
mass,
shape: new CANNON.Box(halfExtents),
position: new CANNON.Vec3(mesh.position.x, mesh.position.y, mesh.position.z),
});
this.world.addBody(body);
this.objects.push({ body, mesh });
return body;
}
addSphere(mesh: THREE.Mesh, mass: number, radius: number): CANNON.Body {
const body = new CANNON.Body({
mass,
shape: new CANNON.Sphere(radius),
position: new CANNON.Vec3(mesh.position.x, mesh.position.y, mesh.position.z),
});
this.world.addBody(body);
this.objects.push({ body, mesh });
return body;
}
update(delta: number): void {
this.world.step(1 / 60, delta, 3);
for (const obj of this.objects) {
obj.mesh.position.copy(obj.body.position as any);
obj.mesh.quaternion.copy(obj.body.quaternion as any);
}
}
removeObject(body: CANNON.Body): void {
const index = this.objects.findIndex(o => o.body === body);
if (index >= 0) {
this.world.removeBody(body);
this.objects.splice(index, 1);
}
}
}
🎮 Samm 3 — 3D Mängumootori Struktuur
class GameEngine {
private scene: THREE.Scene;
private camera: THREE.PerspectiveCamera;
private renderer: THREE.WebGLRenderer;
private physics: PhysicsManager;
private clock: THREE.Clock;
private entities: Map<string, GameEntity> = new Map();
private systems: GameSystem[] = [];
private isRunning = false;
constructor(container: HTMLElement) {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
container.appendChild(this.renderer.domElement);
this.physics = new PhysicsManager();
this.clock = new THREE.Clock();
this.setupDefaultLighting();
}
private setupDefaultLighting(): void {
this.scene.add(new THREE.AmbientLight(0x404080, 0.5));
const sun = new THREE.DirectionalLight(0xffffff, 1);
sun.position.set(10, 20, 10);
sun.castShadow = true;
this.scene.add(sun);
}
addEntity(entity: GameEntity): void {
this.entities.set(entity.id, entity);
if (entity.mesh) this.scene.add(entity.mesh);
}
removeEntity(id: string): void {
const entity = this.entities.get(id);
if (entity) {
if (entity.mesh) this.scene.remove(entity.mesh);
entity.destroy();
this.entities.delete(id);
}
}
addSystem(system: GameSystem): void {
this.systems.push(system);
this.systems.sort((a, b) => a.priority - b.priority);
}
start(): void {
this.isRunning = true;
this.gameLoop();
}
private gameLoop(): void {
if (!this.isRunning) return;
requestAnimationFrame(() => this.gameLoop());
const delta = this.clock.getDelta();
// Update süsteemid
for (const system of this.systems) {
system.update(this.entities, delta);
}
// Füüsika
this.physics.update(delta);
// Render
this.renderer.render(this.scene, this.camera);
}
}
// Kasutamine:
// const engine = new GameEngine(document.body);
// engine.addSystem(new InputSystem());
// engine.addSystem(new MovementSystem());
// engine.addSystem(new CameraSystem());
// engine.start();
📝 Harjutused
-
Füüsikamootor integratsioon
Seadista Three.js + Cannon-es. Loo põrand ja 20 kuubikut mis kukuvad ja põrkavad. Lisa materjalide hõõrdumine ja põrkumine. Debug wireframe.
-
PhysicsManager klass
Loo PhysicsManager: addBox, addSphere, removeObject, raycast. Sünkroniseeri automaatselt Cannon kehad ja Three meshid. Lisa kontaktmaterjalid.
-
3D mängija kontroller füüsikaga
Loo mängija kontroller: WASD liikumine jõududega, hüppamine impulssiga, maapinna tuvastamine raycast'iga. Kaamera jälgib sujuvalt (lerp).
-
GameEngine klass
Loo mängumootori tuumik: Entity haldus, System pipeline, füüsika integratsioon, renderdamine. Lisai start/stop/pause. FPS counter.
-
Kokkupõrkereaktsioonid
Kokkupõrke sündmused: collision callback. Kui kuubik tabab teist → heliefekt + värvimuutus + partiklid + score. Lisa trigger zone'id.
-
3D platformer prototüüp
Ehita 3D platformer: liikuvad platvormid, hüppamine, kogutavad mündid, ohtlikud alad. 3 taset, iga tase keerulisem. Checkpoint süsteem.
-
Vehicle physics
Loo sõiduk Cannon-es RaycastVehicle'iga: 4 ratast, gaas/pidur, roolimine. Lisa rambi ja hüpped. Lihtne rada koonustega.