From 37a77e902cc749ce16f4cf36708024634ece0695 Mon Sep 17 00:00:00 2001 From: Thayol Date: Mon, 23 Dec 2024 18:30:59 +0900 Subject: [PATCH] add terrain collision --- public/client.js | 28 ++++++++++++++++++++++++++-- src/entity.js | 15 +++++++++++---- src/index.js | 18 +++++++----------- src/satx.js | 6 +++++- src/terrain.js | 8 ++------ 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/public/client.js b/public/client.js index 13b04c6..7099c79 100644 --- a/public/client.js +++ b/public/client.js @@ -10,6 +10,9 @@ renderer.setAnimationLoop(render) camera.position.set(5, -12, 10) camera.rotation.set((60 / 180) * Math.PI, 0, 0) +const entityMaterial = new THREE.MeshToonMaterial({ color: 0xffffff }) +const terrainMaterial = new THREE.MeshToonMaterial({ color: 0xffd700 }) + const minimapCamera = new THREE.OrthographicCamera(-5, 5, 5, -5) const minimapRenderer = new THREE.WebGLRenderer() @@ -18,6 +21,7 @@ minimapRenderer.setAnimationLoop(minimapRender) minimapCamera.position.set(5, 5, 10) const entities = {} +const terrains = {} const geometry = new THREE.PlaneGeometry(0, 0) const material = new THREE.MeshToonMaterial({ color: 0x115011 }) @@ -75,13 +79,13 @@ function connectWebSocket() { ground.position.set(state.width / 200, state.height / 200, 0) } - for (const e of state.entities) { + for (const e of state.entities ?? []) { let entity if (e.id in entities) { entity = entities[e.id] } else { - entity = new THREE.Mesh(new THREE.SphereGeometry(e.radius / 100), new THREE.MeshToonMaterial({ color: 0xffffff })) + entity = new THREE.Mesh(new THREE.SphereGeometry(e.radius / 100), entityMaterial) entity.userData.type = 'entity' entity.userData.id = e.id scene.add(entity) @@ -91,6 +95,26 @@ function connectWebSocket() { entity.position.set(e.position.x / 100, e.position.y / 100, e.radius / 100) } + for (const t of state.terrains ?? []) { + let terrain + if (t.id in terrains) { + terrain = terrains[t.id] + } + else { + const vertices = t.relativeVertices + const shape = new THREE.Shape() + shape.moveTo(vertices.at(0).x / 100, vertices.at(0).y / 100) + vertices.slice(1).forEach((v) => shape.lineTo(v.x / 100, v.y / 100)) + terrain = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { bevelEnabled: false, depth: 0.5 }), terrainMaterial) + terrain.userData.type = 'terrain' + terrain.userData.id = t.id + scene.add(terrain) + terrains[t.id] = terrain + } + + terrain.position.set(t.position.x / 100, t.position.y / 100, 0) + } + document.getElementById('state').innerHTML = JSON.stringify(state, null, 2) } } diff --git a/src/entity.js b/src/entity.js index 720eee4..786df39 100644 --- a/src/entity.js +++ b/src/entity.js @@ -28,15 +28,22 @@ export default class Entity { set y(value) { this.position.y = value } get collidables() { - return this.game?.entities.filter((e) => e.id != this.id).map((e) => e.collider) + const entityColliders = (this.game?.entities ?? []).filter((e) => e.id != this.id).map((e) => e.collider) + const terrainColliders = (this.game?.terrains ?? []).map((t) => t.colliders).flat() + + return entityColliders.concat(terrainColliders) } get collider() { return new SAT.Circle(new SAT.Vector(this.x, this.y), this.radius) } - isColliding(collider) { - return SATX.colliding(this.collider, collider) + get colliders() { + return [this.collider] + } + + isColliding(...colliders) { + return SATX.collideObjects(this.collider, colliders) } moveAction(x, y) { @@ -71,7 +78,7 @@ export default class Entity { const position = distance <= speed ? fixedDest : stepTaken const collider = Entity.collider(position.x, position.y, this.radius) - const isColliding = this.collidables.some((c) => SATX.colliding(collider, c)) + const isColliding = SATX.collideObjects(collider, this.collidables) if (!isColliding) { this.position.copy(position) diff --git a/src/index.js b/src/index.js index 4d3053c..742e0c8 100644 --- a/src/index.js +++ b/src/index.js @@ -42,25 +42,21 @@ app.listen(port, () => { const entity1 = new Entity() entity1.id = '1' - entity1.teleport(100, 100) - entity1.radius = 35 + entity1.teleport(350, 500) + entity1.radius = 50 game.spawn_entity(entity1) const entity2 = new Entity() entity2.id = '2' - entity2.teleport(200, 100) + entity2.teleport(800, 100) entity2.radius = 35 game.spawn_entity(entity2) const vertices = [ - { x: 0, y: 0 }, - { x: 20, y: 0 }, - { x: 20, y: 20 }, - { x: 10, y: 20 }, - { x: 10, y: 5 }, - { x: 5, y: 5 }, - { x: 5, y: 20 }, - { x: 0, y: 20 }, + { x: 400, y: 400 }, + { x: 600, y: 400 }, + { x: 600, y: 600 }, + { x: 400, y: 600 }, ] const terrain1 = new Terrain(vertices) diff --git a/src/satx.js b/src/satx.js index 76e4b63..26b07f9 100644 --- a/src/satx.js +++ b/src/satx.js @@ -1,7 +1,7 @@ import SAT from 'sat' export default class SATX { - static colliding(collider1, collider2) { + static collideObject(collider1, collider2) { if (collider1 instanceof SAT.Circle && collider2 instanceof SAT.Circle) { return SAT.testCircleCircle(collider1, collider2) } @@ -20,4 +20,8 @@ export default class SATX { return false } + + static collideObjects(collider1, colliders) { + return colliders.some((c) => this.collideObject(collider1, c)) + } } diff --git a/src/terrain.js b/src/terrain.js index 6978dd3..25e4efb 100644 --- a/src/terrain.js +++ b/src/terrain.js @@ -23,10 +23,6 @@ export default class Terrain { state() { return { ...this, - position: { - x: this.x, - y: this.y, - }, } } @@ -41,8 +37,8 @@ export default class Terrain { const indicesToPolygon = (indices) => { const satPoints = [ new SAT.Vector(...points.shape[indices[0]].toArray()), - new SAT.Vector(...points.shape[indices[1]].toArray()), - new SAT.Vector(...points.shape[indices[2]].toArray()), + new SAT.Vector(...points.shape[indices[1]].clone().sub(points.shape[indices[0]]).toArray()), + new SAT.Vector(...points.shape[indices[2]].clone().sub(points.shape[indices[0]]).toArray()), ] return new SAT.Polygon(satPoints[0], [new SAT.Vector(), satPoints[1], satPoints[2]])