use the placeholder player model

This commit is contained in:
2025-01-23 23:39:10 +09:00
parent 305980b7f9
commit 52a0da10fe
8 changed files with 96 additions and 39 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 B

After

Width:  |  Height:  |  Size: 95 B

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+56 -27
View File
@@ -1,5 +1,6 @@
import * as THREE from 'three' import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { Tween } from '@tweenjs/tween.js' import { Tween } from '@tweenjs/tween.js'
import * as THREE from 'three'
import Stats from 'stats.js' import Stats from 'stats.js'
const global = (0,eval)('this') const global = (0,eval)('this')
@@ -22,6 +23,16 @@ camera.updateProjectionMatrix()
camera.layers.enable(1) camera.layers.enable(1)
camera.layers.enable(2) camera.layers.enable(2)
const gltfLoader = new GLTFLoader()
const addTo = function addTo(scene) {
return function addToScene(gltf) {
const model = gltf.scene
const scale = 2
model.scale.set(scale, scale, scale)
scene.add(model)
}
}
const projectileMaterial = new THREE.MeshToonMaterial({ color: 0xcccccc }) const projectileMaterial = new THREE.MeshToonMaterial({ color: 0xcccccc })
const terrainMaterial = new THREE.MeshToonMaterial({ color: 0x5c4033 }) const terrainMaterial = new THREE.MeshToonMaterial({ color: 0x5c4033 })
const passableTerrainMaterial = new THREE.MeshToonMaterial({ color: 0x228822, transparent: true, opacity: 0.65 }) const passableTerrainMaterial = new THREE.MeshToonMaterial({ color: 0x228822, transparent: true, opacity: 0.65 })
@@ -50,6 +61,7 @@ minimapCamera.position.set(10, 10, 10)
const entities = {} const entities = {}
const projectiles = {} const projectiles = {}
const positionTweens = {} const positionTweens = {}
const rotationTweens = {}
const terrains = {} const terrains = {}
var state = { abilities: [], entities: [], terrains: [], projectiles: [] } var state = { abilities: [], entities: [], terrains: [], projectiles: [] }
@@ -88,7 +100,8 @@ function render() {
stats.begin() stats.begin()
delta = clock.getDelta() delta = clock.getDelta()
cameraMovement() cameraMovement()
Object.values(positionTweens).forEach((tween) => tween.update()) // TODO: clean up tweens Object.values(positionTweens).forEach((tween) => tween.update())
Object.values(rotationTweens).forEach((tween) => tween.update())
renderer.render(scene, camera) renderer.render(scene, camera)
stats.end() stats.end()
} }
@@ -296,17 +309,19 @@ function connectWebSocket() {
entity = entities[e.id] entity = entities[e.id]
} }
else { else {
const entityMaterial = teamMaterials[e.team] // const entityMaterial = teamMaterials[e.team]
entity = new THREE.Mesh(new THREE.CylinderGeometry(e.visualRadius / 100, e.visualRadius / 100, e.height / 50), entityMaterial) // entity = new THREE.Mesh(new THREE.CylinderGeometry(e.visualRadius / 100, e.visualRadius / 100, e.height / 50), entityMaterial)
entity = new THREE.Group()
entity.rotation.x = Math.PI / 2 entity.rotation.x = Math.PI / 2
entity.scale.set(e.visualRadius / 100, e.height / 100, e.visualRadius / 100)
entity.userData.type = 'entity' entity.userData.type = 'entity'
entity.userData.id = e.id entity.userData.id = e.id
entity.position.set(e.position.x / 100, e.position.y / 100, e.height / 100) entity.position.set(e.position.x / 100, e.position.y / 100, 0)
scene.add(entity) scene.add(entity)
const hpMargin = 0.4 const hpMargin = 0.4
const maxHp = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0xd03333 })) const maxHp = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0xd03333 }))
maxHp.position.set(0, (e.height / 100) + hpMargin, 0) maxHp.position.set(0, (e.visualRadius / 100) + hpMargin, 0)
maxHp.scale.set(1.5, 0.2, 1) maxHp.scale.set(1.5, 0.2, 1)
maxHp.layers.set(1) maxHp.layers.set(1)
entity.add(maxHp) entity.add(maxHp)
@@ -318,12 +333,8 @@ function connectWebSocket() {
maxHp.add(hp) maxHp.add(hp)
const teamMaterial = teamMaterials[`${e.team}Transparent`] const teamMaterial = teamMaterials[`${e.team}Transparent`]
const teamMarker = new THREE.Mesh(new THREE.CylinderGeometry((e.radius) / 100, (e.radius) / 100, 1), teamMaterial) const teamMarker = new THREE.Mesh(new THREE.CylinderGeometry(1, 0.00001, 1), teamMaterial)
const teamMarkerSize = 4000 teamMarker.position.y = -0.496
teamMarker.scale.y = e.height / teamMarkerSize
teamMarker.position.y = (e.height / (teamMarkerSize * 2)) - (e.height / 100)
teamMarker.position.y += 0.01
teamMarker.layers.set(1)
entity.add(teamMarker) entity.add(teamMarker)
const buffMaterial = new THREE.MeshToonMaterial({ color: 0xffff00, transparent: true, opacity: 0.4 }) const buffMaterial = new THREE.MeshToonMaterial({ color: 0xffff00, transparent: true, opacity: 0.4 })
@@ -334,30 +345,35 @@ function connectWebSocket() {
buffMarker.visible = false buffMarker.visible = false
entity.add(buffMarker) entity.add(buffMarker)
const rotationBase = new THREE.Object3D() const castingMarkerrotationBase = new THREE.Group()
entity.add(rotationBase) entity.add(castingMarkerrotationBase)
const castingMaterial = new THREE.MeshToonMaterial({ color: 0x10dde0, transparent: true, opacity: 0.4 }) const castingMaterial = new THREE.MeshToonMaterial({ color: 0x10dde0, transparent: true, opacity: 0.4 })
const castingMarker = new THREE.Mesh(new THREE.CylinderGeometry((e.height * 0.9) / 100, (e.height * 0.9) / 100, 1), castingMaterial) const castingMarker = new THREE.Mesh(new THREE.CylinderGeometry((e.height * 0.9) / 100, (e.height * 0.9) / 100, 1), castingMaterial)
const castingMarkerSize = 800 const castingMarkerSize = 800
castingMarker.rotation.z = Math.PI / 2 castingMarker.rotation.z = Math.PI / 2
castingMarker.position.x = (e.radius) / 100 castingMarker.position.x = 1
castingMarker.position.y = 1
castingMarker.scale.y = e.height / castingMarkerSize castingMarker.scale.y = e.height / castingMarkerSize
castingMarker.layers.set(1) castingMarker.layers.set(1)
buffMarker.visible = false buffMarker.visible = false
rotationBase.add(castingMarker) castingMarkerrotationBase.add(castingMarker)
const rangeMaterial = teamMaterials['range'] const rangeMaterial = teamMaterials['range']
// const rangeSize = e.visionRange ?? 0 // const rangeSize = e.visionRange ?? 0
const rangeSize = (state.abilities.find((it) => it.id == e.abilities?.a)?.range ?? 0) + e.radius const rangeSize = (state.abilities.find((it) => it.id == e.abilities?.a)?.range ?? 0) + e.radius
const rangeMarker = new THREE.Mesh(new THREE.CylinderGeometry((rangeSize) / 100, (rangeSize) / 100, 1), rangeMaterial) const rangeMarker = new THREE.Mesh(new THREE.CylinderGeometry(rangeSize / e.visualRadius, rangeSize / e.visualRadius, 0.001), rangeMaterial)
const rangeMarkerSize = 5000 rangeMarker.position.y = 0.002
rangeMarker.scale.y = e.height / rangeMarkerSize
rangeMarker.position.y = (e.height / (rangeMarkerSize * 2)) - (e.height / 100)
rangeMarker.layers.set(1) rangeMarker.layers.set(1)
rangeMarker.visible = false rangeMarker.visible = false
entity.add(rangeMarker) entity.add(rangeMarker)
const modelRotationBase = new THREE.Object3D()
modelRotationBase.layers.set(1)
entity.add(modelRotationBase)
gltfLoader.load('models/generic-player-placeholder.gltf', addTo(modelRotationBase))
entities[e.id] = entity entities[e.id] = entity
} }
@@ -365,28 +381,36 @@ function connectWebSocket() {
entity.children.at(1).visible = !e.dead entity.children.at(1).visible = !e.dead
entity.children.at(2).visible = e.buffs.some((it) => it.id == 'exposed') // TODO: only works for Exposed now entity.children.at(2).visible = e.buffs.some((it) => it.id == 'exposed') // TODO: only works for Exposed now
let z = e.height / 100
if (e.dead) { if (e.dead) {
entity.rotation.x = 0 entity.rotation.x = 0
entity.position.z = 0 entity.children.at(5).children.at(0).rotation.y = Math.PI / 2
z = 0
} }
else { else {
entity.rotation.x = Math.PI / 2 entity.rotation.x = Math.PI / 2
entity.position.z = e.height / 100 entity.children.at(5).children.at(0).rotation.y = 0
} }
entity.userData.flaggedForRemoval = false entity.userData.flaggedForRemoval = false
entity.children.at(3).rotation.y = e.rotation entity.children.at(3).rotation.y = e.rotation
positionTweens[entity.id] = new Tween(entity.position).to({ x: e.position.x / 100, y: e.position.y / 100, z }, tweenDuration).start() // entity.children.at(5).rotation.y = e.rotation - (Math.PI / 2)
const oldRotationY = entity.children.at(5).rotation.y
const newRotationY = e.rotation - (Math.PI / 2)
if (Math.abs((oldRotationY - (2 * Math.PI)) - newRotationY) < Math.abs(oldRotationY - newRotationY)) {
entity.children.at(5).rotation.y = oldRotationY - (2 * Math.PI)
}
if (Math.abs((oldRotationY + (2 * Math.PI)) - newRotationY) < Math.abs(oldRotationY - newRotationY)) {
entity.children.at(5).rotation.y = oldRotationY + (2 * Math.PI)
}
positionTweens[entity.id] = new Tween(entity.position).to({ x: e.position.x / 100, y: e.position.y / 100, z: 0 }, tweenDuration).start()
rotationTweens[entity.id] = new Tween(entity.children.at(5).rotation).to({ x: 0, y: newRotationY, z: 0 }, tweenDuration).start()
const hp = entity.children.at(0).children.at(0) const hp = entity.children.at(0).children.at(0)
const percentageHp = e.health / e.maxHealth const percentageHp = e.health / e.maxHealth
hp.scale.x = percentageHp hp.scale.x = percentageHp
hp.position.x = -(1 - percentageHp) / 2 hp.position.x = -(1 - percentageHp) / 2
// entity.children.at(4).visible = e.id == playerId entity.children.at(4).visible = e.id == playerId
entity.children.at(3).children.at(0).visible = e.casting != null entity.children.at(3).children.at(0).visible = e.casting != null
} }
@@ -395,6 +419,7 @@ function connectWebSocket() {
scene.remove(e) scene.remove(e)
delete entities[e.userData.id] delete entities[e.userData.id]
delete positionTweens[e.userData.id] delete positionTweens[e.userData.id]
delete rotationTweens[e.userData.id]
} }
} }
@@ -561,6 +586,10 @@ window.addEventListener('load', () => {
playerId = params.id playerId = params.id
if (playerId == null) { if (playerId == null) {
playerId = prompt('Player ID:') playerId = prompt('Player ID:')
if (playerId == '') {
window.location.href = '/menu/'
return
}
} }
connectWebSocket() connectWebSocket()
+26
View File
@@ -0,0 +1,26 @@
<style>
html {
background-color: black;
color: white;
font-family: sans-serif;
}
a:link, a:hover, a:active, a:visited {
color: white;
}
</style>
<h1>Take control of a unit:</h1>
<ul id="links"></ul>
<script>
websocket = new WebSocket(`ws://${window.location.hostname}:1280/ws`)
websocket.onopen = () => { websocket.send(JSON.stringify({ action: 'entities' })) }
websocket.onmessage = (event) => {
const message = JSON.parse(event.data)
const entityIds = message?.entities
if (entityIds == null) { return }
websocket.close()
let links = ''
entityIds.forEach((entityId) => links += `<li><a href="/?id=${encodeURI(entityId)}">${entityId}</a></li>`)
document.getElementById('links').innerHTML = links
}
</script>
+4 -2
View File
@@ -18,7 +18,7 @@ export default class Entity {
dead = false dead = false
ghosting = false ghosting = false
health = null health = null
height = 40 height = null
maxHealth = 1 maxHealth = 1
position = null position = null
radius = 0 radius = 0
@@ -43,7 +43,6 @@ export default class Entity {
#pathfindingCooldown = 0 #pathfindingCooldown = 0
#pathfindingObstacleLimit = null #pathfindingObstacleLimit = null
#projectilesInVision = [] #projectilesInVision = []
#queuedAction = null
#spawnPosition = new Vector2() #spawnPosition = new Vector2()
static bbox(x, y, radius) { static bbox(x, y, radius) {
@@ -141,6 +140,9 @@ export default class Entity {
if (this.visualRadius == null) { if (this.visualRadius == null) {
this.visualRadius = this.radius this.visualRadius = this.radius
} }
if (this.height == null) {
this.height = this.visualRadius ?? this.radius
}
this.#calculateCollider() this.#calculateCollider()
} }
+7 -1
View File
@@ -23,6 +23,8 @@ app.use('/@tweenjs/', express.static('node_modules/@tweenjs'))
app.use('/stats.js/', express.static('node_modules/stats.js')) app.use('/stats.js/', express.static('node_modules/stats.js'))
app.use('/', express.static('public')) app.use('/', express.static('public'))
app.use('/models', express.static('models'))
app.use('/tools/', express.static('tools')) app.use('/tools/', express.static('tools'))
app.ws('/ws', async (req, res) => { app.ws('/ws', async (req, res) => {
@@ -31,6 +33,10 @@ app.ws('/ws', async (req, res) => {
websocket.on('message', (rawData) => { websocket.on('message', (rawData) => {
const message = JSON.parse(rawData) const message = JSON.parse(rawData)
console.info(message) console.info(message)
if (message.action == 'entities') {
websocket.send(JSON.stringify({ entities: game.entities.map((it) => it.id) }))
}
if (message.action == 'join') { if (message.action == 'join') {
const id = message.id const id = message.id
const connectionId = crypto.randomUUID() const connectionId = crypto.randomUUID()
@@ -56,7 +62,7 @@ app.ws('/ws', async (req, res) => {
}) })
app.listen(port, () => { app.listen(port, () => {
console.info({ event: 'startup', visit: `http://localhost:${port}` }) console.info({ event: 'startup', visit: `http://localhost:${port}/menu/` })
Dungeon.scenario(game) Dungeon.scenario(game)
}) })
+1 -7
View File
@@ -6,11 +6,9 @@ export default class Template {
static basilisk(overrides) { static basilisk(overrides) {
return { return {
abilities: {}, abilities: {},
height: 100,
logic: this.#basiliskLogic(), logic: this.#basiliskLogic(),
radius: 180, radius: 180,
speed: 230, speed: 230,
visualRadius: 170,
maxHealth: 3000, maxHealth: 3000,
...overrides, ...overrides,
} }
@@ -19,17 +17,15 @@ export default class Template {
static minion(team, options = {}) { static minion(team, options = {}) {
return { return {
abilities: { a: options.ranged ? Ability.rangedAttack.id : Ability.meleeAttack.id }, abilities: { a: options.ranged ? Ability.rangedAttack.id : Ability.meleeAttack.id },
height: options.ranged ? 40 : 38,
logic: this.#minionLogic(options.route, (team != Team.blue)), logic: this.#minionLogic(options.route, (team != Team.blue)),
maxHealth: options.ranged ? 300 : 450, maxHealth: options.ranged ? 300 : 450,
pathfindingCooldown: 0.2, pathfindingCooldown: 0.2,
pathfindingObstacleLimit: 0, pathfindingObstacleLimit: 0,
position: options.route?.at(0) ?? options.position ?? new Vector2(0, 0), position: options.route?.at(0) ?? options.position ?? new Vector2(0, 0),
radius: 48, radius: options.ranged ? 36 : 38,
speed: 325, speed: 325,
team, team,
visionRange: 1200, visionRange: 1200,
visualRadius: options.ranged ? 36 : 38,
} }
} }
@@ -44,14 +40,12 @@ export default class Template {
d: Ability.circleOfResurrection.id, d: Ability.circleOfResurrection.id,
f: Ability.blink.id, f: Ability.blink.id,
}, },
height: 80,
logic: this.#playerLogic, logic: this.#playerLogic,
maxHealth: 600, maxHealth: 600,
pathfindingObstacleLimit: 3, pathfindingObstacleLimit: 3,
radius: 65, radius: 65,
spawnPosition: new Vector2(500, 150), spawnPosition: new Vector2(500, 150),
visionRange: 1350, visionRange: 1350,
visualRadius: 40,
...overrides, ...overrides,
} }
} }