add client side terrain without collision

This commit is contained in:
2024-12-23 11:57:36 +09:00
parent 604368b52c
commit ba0d8f606a
6 changed files with 107 additions and 9 deletions
+2
View File
@@ -128,3 +128,5 @@ dist
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
public/temp
+4 -4
View File
@@ -1,4 +1,4 @@
import * as THREE from 'three' import { Vector2 } from 'three'
import SAT from 'sat' import SAT from 'sat'
import SATX from './satx.js' import SATX from './satx.js'
@@ -7,7 +7,7 @@ export default class Entity {
speed = 400 speed = 400
radius = 0 radius = 0
#position = new THREE.Vector2(0, 0) #position = new Vector2()
#dest = null #dest = null
#game = null #game = null
@@ -40,7 +40,7 @@ export default class Entity {
} }
moveAction(x, y) { moveAction(x, y) {
this.#dest = new THREE.Vector3(x, y, 0) this.#dest = new Vector2(x, y)
} }
state() { state() {
@@ -60,7 +60,7 @@ export default class Entity {
takeStep() { takeStep() {
const speed = this.speed / (this.game?.tickBudget ?? 1000) const speed = this.speed / (this.game?.tickBudget ?? 1000)
if (this.#dest != null) { if (this.#dest != null) {
const fixedDest = new THREE.Vector2( const fixedDest = new Vector2(
Math.min(Math.max(this.radius, this.#dest.x), (this.game?.width ?? Infinity) - this.radius), Math.min(Math.max(this.radius, this.#dest.x), (this.game?.width ?? Infinity) - this.radius),
Math.min(Math.max(this.radius, this.#dest.y), (this.game?.height ?? Infinity) - this.radius), Math.min(Math.max(this.radius, this.#dest.y), (this.game?.height ?? Infinity) - this.radius),
) )
+7
View File
@@ -8,10 +8,12 @@ export default class Game {
#entities = [] #entities = []
#eventEmitter = new EventEmitter() #eventEmitter = new EventEmitter()
#terrains = []
#tickBudget = Math.floor(1000 / this.tickRate) #tickBudget = Math.floor(1000 / this.tickRate)
get entities() { return this.#entities } get entities() { return this.#entities }
get eventEmitter() { return this.#eventEmitter } get eventEmitter() { return this.#eventEmitter }
get terrains() { return this.#terrains }
get tickBudget() { return this.#tickBudget } get tickBudget() { return this.#tickBudget }
spawn_entity(entity) { spawn_entity(entity) {
@@ -19,10 +21,15 @@ export default class Game {
entity.game = this entity.game = this
} }
add_terrain(terrain) {
this.#terrains.push(terrain)
}
state() { state() {
return { return {
...this, ...this,
entities: this.#entities.map((e) => e.state()), entities: this.#entities.map((e) => e.state()),
terrains: this.#terrains.map((t) => t.state()),
} }
} }
+21 -5
View File
@@ -2,6 +2,7 @@ import express from 'express'
import { WebSocketExpress } from 'websocket-express' import { WebSocketExpress } from 'websocket-express'
import Game from './game.js' import Game from './game.js'
import Entity from './entity.js' import Entity from './entity.js'
import Terrain from './terrain.js'
const app = new WebSocketExpress() const app = new WebSocketExpress()
const port = 1280 const port = 1280
@@ -39,11 +40,11 @@ app.ws('/ws', async (req, res) => {
app.listen(port, () => { app.listen(port, () => {
console.log(`Server started! Visit http://localhost:${port}`) console.log(`Server started! Visit http://localhost:${port}`)
const entity = new Entity() const entity1 = new Entity()
entity.id = '1' entity1.id = '1'
entity.teleport(100, 100) entity1.teleport(100, 100)
entity.radius = 35 entity1.radius = 35
game.spawn_entity(entity) game.spawn_entity(entity1)
const entity2 = new Entity() const entity2 = new Entity()
entity2.id = '2' entity2.id = '2'
@@ -51,5 +52,20 @@ app.listen(port, () => {
entity2.radius = 35 entity2.radius = 35
game.spawn_entity(entity2) 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 },
]
const terrain1 = new Terrain(vertices)
terrain1.id = 'a'
game.add_terrain(terrain1)
game.start() game.start()
}) })
+12
View File
@@ -6,6 +6,18 @@ export default class SATX {
return SAT.testCircleCircle(collider1, collider2) return SAT.testCircleCircle(collider1, collider2)
} }
if (collider1 instanceof SAT.Circle && collider2 instanceof SAT.Polygon) {
return SAT.testCirclePolygon(collider1, collider2)
}
if (collider1 instanceof SAT.Polygon && collider2 instanceof SAT.Circle) {
return SAT.testPolygonCircle(collider1, collider2)
}
if (collider1 instanceof SAT.Polygon && collider2 instanceof SAT.Polygon) {
return SAT.testPolygonPolygon(collider1, collider2)
}
return false return false
} }
} }
+61
View File
@@ -0,0 +1,61 @@
import SAT from 'sat'
import { Shape, ShapeUtils, Vector2 } from 'three'
export default class Terrain {
id = crypto.randomUUID()
position = new Vector2()
relativeVertices = []
#colliders = []
#vertices = []
constructor(vertices) {
this.#vertices = vertices.map((v) => new Vector2(v.x, v.y))
this.#calculateColliders()
this.#calculatePosition()
this.#calculateRelativeVertices()
}
get colliders() { return this.#colliders }
get vertices() { return this.#vertices }
state() {
return {
...this,
position: {
x: this.x,
y: this.y,
},
}
}
#calculateColliders() {
const complexShape = new Shape()
complexShape.moveTo(this.#vertices.at(0).x, this.#vertices.at(0).y)
this.#vertices.slice(1).forEach((v) => complexShape.lineTo(v.x, v.y))
const points = complexShape.extractPoints(16)
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()),
]
return new SAT.Polygon(satPoints[0], [new SAT.Vector(), satPoints[1], satPoints[2]])
}
this.#colliders = ShapeUtils.triangulateShape(points.shape, points.holes).map(indicesToPolygon)
}
#calculatePosition() {
this.position = this.#vertices.reduce(((sum, v) => sum.add(v)), new Vector2()).divideScalar(this.#vertices.length)
}
#calculateRelativeVertices() {
this.relativeVertices = this.#vertices.map((v) => v.clone().sub(this.position))
}
}