add skillshots
This commit is contained in:
+84
-9
@@ -13,6 +13,7 @@ camera.rotation.set((60 / 180) * Math.PI, 0, 0)
|
|||||||
camera.layers.enable(1)
|
camera.layers.enable(1)
|
||||||
|
|
||||||
const entityMaterial = new THREE.MeshToonMaterial({ color: 0xffffff })
|
const entityMaterial = new THREE.MeshToonMaterial({ color: 0xffffff })
|
||||||
|
const projectileMaterial = new THREE.MeshToonMaterial({ color: 0xff00ff })
|
||||||
const terrainMaterial = new THREE.MeshToonMaterial({ color: 0xffd700 })
|
const terrainMaterial = new THREE.MeshToonMaterial({ color: 0xffd700 })
|
||||||
|
|
||||||
const minimapCamera = new THREE.OrthographicCamera(-10, 10, 10, -10)
|
const minimapCamera = new THREE.OrthographicCamera(-10, 10, 10, -10)
|
||||||
@@ -23,6 +24,7 @@ minimapRenderer.setAnimationLoop(minimapRender)
|
|||||||
minimapCamera.position.set(10, 10, 10)
|
minimapCamera.position.set(10, 10, 10)
|
||||||
|
|
||||||
const entities = {}
|
const entities = {}
|
||||||
|
const projectiles = {}
|
||||||
const positionTweens = {}
|
const positionTweens = {}
|
||||||
const terrains = {}
|
const terrains = {}
|
||||||
|
|
||||||
@@ -44,12 +46,13 @@ global.renderer = renderer
|
|||||||
global.camera = camera
|
global.camera = camera
|
||||||
global.scene = scene
|
global.scene = scene
|
||||||
|
|
||||||
const tweenDuration = 60
|
const tweenDuration = 33
|
||||||
const keysDown = {}
|
const keysDown = {}
|
||||||
|
const mouse = {}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
cameraMovement()
|
cameraMovement()
|
||||||
Object.values(positionTweens).forEach((tween) => tween.update())
|
Object.values(positionTweens).forEach((tween) => tween.update()) // TODO: clean up tweens
|
||||||
renderer.render(scene, camera)
|
renderer.render(scene, camera)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +114,20 @@ function cameraMovement() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function raycastToGround() {
|
||||||
|
const canvas = renderer.domElement
|
||||||
|
raycaster.setFromCamera(new THREE.Vector2((mouse.x / canvas.clientWidth) * 2 - 1, (mouse.y / canvas.clientHeight) * -2 + 1), camera)
|
||||||
|
const intersect = raycaster.intersectObject(ground).at(0)?.point
|
||||||
|
if (intersect != null) {
|
||||||
|
return {
|
||||||
|
x: Math.round(intersect.x * 100),
|
||||||
|
y: Math.round(intersect.y * 100),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
var websocket = null
|
var websocket = null
|
||||||
global.websocket = null
|
global.websocket = null
|
||||||
var timerId = null
|
var timerId = null
|
||||||
@@ -152,6 +169,7 @@ function connectWebSocket() {
|
|||||||
entity.rotation.x = Math.PI / 2
|
entity.rotation.x = Math.PI / 2
|
||||||
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.radius / 100)
|
||||||
scene.add(entity)
|
scene.add(entity)
|
||||||
|
|
||||||
const hpMargin = 0.4
|
const hpMargin = 0.4
|
||||||
@@ -179,6 +197,38 @@ function connectWebSocket() {
|
|||||||
hp.position.x = -(1 - percentageHp) / 2
|
hp.position.x = -(1 - percentageHp) / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const p of Object.values(projectiles)) {
|
||||||
|
p.userData.flaggedForRemoval = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const p of state.projectiles ?? []) {
|
||||||
|
let projectile
|
||||||
|
if (p.id in projectiles) {
|
||||||
|
projectile = projectiles[p.id]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
projectile = new THREE.Mesh(new THREE.SphereGeometry(p.radius / 100), projectileMaterial)
|
||||||
|
projectile.userData.type = 'projectile'
|
||||||
|
projectile.userData.id = p.id
|
||||||
|
projectile.position.set(p.position.x / 100, p.position.y / 100, p.visualHeight / 100)
|
||||||
|
scene.add(projectile)
|
||||||
|
|
||||||
|
projectiles[p.id] = projectile
|
||||||
|
}
|
||||||
|
|
||||||
|
projectile.userData.flaggedForRemoval = false
|
||||||
|
// projectile.position.set(p.position.x / 100, p.position.y / 100, p.visualHeight / 100)
|
||||||
|
positionTweens[projectile.id] = new Tween(projectile.position).to({ x: p.position.x / 100, y: p.position.y / 100, z: p.visualHeight / 100 }, tweenDuration).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const p of Object.values(projectiles)) {
|
||||||
|
if (p.userData.flaggedForRemoval) {
|
||||||
|
scene.remove(p)
|
||||||
|
delete projectiles[p.userData.id]
|
||||||
|
delete positionTweens[p.userData.id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const t of state.terrains ?? []) {
|
for (const t of state.terrains ?? []) {
|
||||||
let terrain
|
let terrain
|
||||||
if (t.id in terrains) {
|
if (t.id in terrains) {
|
||||||
@@ -216,22 +266,43 @@ window.addEventListener('load', () => {
|
|||||||
canvas.classList.add('canvas')
|
canvas.classList.add('canvas')
|
||||||
|
|
||||||
canvas.addEventListener('mousedown', (event) => {
|
canvas.addEventListener('mousedown', (event) => {
|
||||||
raycaster.setFromCamera(new THREE.Vector2((event.clientX / canvas.clientWidth) * 2 - 1, (event.clientY / canvas.clientHeight) * -2 + 1), camera)
|
const intersect = raycastToGround()
|
||||||
const intersect = raycaster.intersectObject(ground).at(0)?.point
|
|
||||||
if (intersect != null) {
|
if (intersect != null) {
|
||||||
|
const { x, y } = intersect
|
||||||
if (event.button == 0) {
|
if (event.button == 0) {
|
||||||
const x = Math.round(intersect.x * 100)
|
websocket.send(JSON.stringify({ action: 'cast', slot: 0, id: playerId, x, y }))
|
||||||
const y = Math.round(intersect.y * 100)
|
|
||||||
websocket.send(JSON.stringify({ action: 'teleport', id: playerId, x, y }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.button == 2) {
|
if (event.button == 2) {
|
||||||
const x = Math.round(intersect.x * 100)
|
|
||||||
const y = Math.round(intersect.y * 100)
|
|
||||||
websocket.send(JSON.stringify({ action: 'move', id: playerId, x, y }))
|
websocket.send(JSON.stringify({ action: 'move', id: playerId, x, y }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
window.addEventListener('keydown', (event) => {
|
||||||
|
const intersect = raycastToGround()
|
||||||
|
if (intersect != null) {
|
||||||
|
const { x, y } = intersect
|
||||||
|
if (event.code == 'KeyQ') {
|
||||||
|
websocket.send(JSON.stringify({ action: 'cast', slot: 1, id: playerId, x, y }))
|
||||||
|
}
|
||||||
|
if (event.code == 'KeyW') {
|
||||||
|
websocket.send(JSON.stringify({ action: 'cast', slot: 2, id: playerId, x, y }))
|
||||||
|
}
|
||||||
|
if (event.code == 'KeyE') {
|
||||||
|
websocket.send(JSON.stringify({ action: 'cast', slot: 3, id: playerId, x, y }))
|
||||||
|
}
|
||||||
|
if (event.code == 'KeyR') {
|
||||||
|
websocket.send(JSON.stringify({ action: 'cast', slot: 4, id: playerId, x, y }))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code == 'KeyD') {
|
||||||
|
websocket.send(JSON.stringify({ action: 'teleport', id: playerId, x, y }))
|
||||||
|
}
|
||||||
|
if (event.code == 'KeyF') {
|
||||||
|
websocket.send(JSON.stringify({ action: 'teleport', id: playerId, x, y }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
document.addEventListener('wheel', (event) => {
|
document.addEventListener('wheel', (event) => {
|
||||||
if (event.deltaY < 0) {
|
if (event.deltaY < 0) {
|
||||||
@@ -264,6 +335,10 @@ window.addEventListener('load', () => {
|
|||||||
cameraLocked = !cameraLocked
|
cameraLocked = !cameraLocked
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
window.addEventListener('mousemove', (event) => {
|
||||||
|
mouse.x = event.clientX
|
||||||
|
mouse.y = event.clientY
|
||||||
|
})
|
||||||
|
|
||||||
document.body.appendChild(canvas)
|
document.body.appendChild(canvas)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Vector2 } from 'three'
|
||||||
|
import Projectile from './projectile.js'
|
||||||
|
|
||||||
|
export default class Ability {
|
||||||
|
static skillshot({ range, radius, speed, onCollide, after }) {
|
||||||
|
return function(x, y) {
|
||||||
|
console.log(this)
|
||||||
|
const projectile = new Projectile()
|
||||||
|
const destination = this.position.clone().add(new Vector2(x, y).sub(this.position).normalize().multiplyScalar(range))
|
||||||
|
projectile.owner = this.id
|
||||||
|
projectile.position.copy(this.position)
|
||||||
|
projectile.destination = destination
|
||||||
|
projectile.radius = radius
|
||||||
|
projectile.speed = speed
|
||||||
|
projectile.after = after
|
||||||
|
projectile.onCollide = onCollide
|
||||||
|
this.game?.spawnProjectile(projectile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export default class Effect {
|
||||||
|
static damage({ despawn }) {
|
||||||
|
return function(projectile, entity) {
|
||||||
|
entity.health -= 10
|
||||||
|
if (despawn) {
|
||||||
|
projectile.despawn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-2
@@ -2,6 +2,8 @@ import { Vector2 } from 'three'
|
|||||||
import SAT from 'sat'
|
import SAT from 'sat'
|
||||||
import SATX from './satx.js'
|
import SATX from './satx.js'
|
||||||
import Pathfind from './pathfind.js'
|
import Pathfind from './pathfind.js'
|
||||||
|
import Ability from './ability.js'
|
||||||
|
import Effect from './effect.js'
|
||||||
|
|
||||||
export default class Entity {
|
export default class Entity {
|
||||||
id = crypto.randomUUID()
|
id = crypto.randomUUID()
|
||||||
@@ -9,6 +11,13 @@ export default class Entity {
|
|||||||
radius = 0
|
radius = 0
|
||||||
health = 1
|
health = 1
|
||||||
maxHealth = 1
|
maxHealth = 1
|
||||||
|
abilities = [
|
||||||
|
() => {},
|
||||||
|
Ability.skillshot({ range: 800, radius: 5, speed: 3000, onCollide: Effect.damage({ despawn: true }) }),
|
||||||
|
() => {},
|
||||||
|
() => {},
|
||||||
|
() => {},
|
||||||
|
]
|
||||||
|
|
||||||
#position = new Vector2()
|
#position = new Vector2()
|
||||||
#dest = null
|
#dest = null
|
||||||
@@ -59,6 +68,10 @@ export default class Entity {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
castAction(slot, x, y) {
|
||||||
|
this.abilities[slot].bind(this)(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
collidables() {
|
collidables() {
|
||||||
const entityColliders = (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()
|
const terrainColliders = (this.game?.terrains ?? []).map((t) => t.colliders).flat()
|
||||||
@@ -74,6 +87,10 @@ export default class Entity {
|
|||||||
return entityColliders.concat(terrainColliders)
|
return entityColliders.concat(terrainColliders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
despawn() {
|
||||||
|
this.game?.despawn(this)
|
||||||
|
}
|
||||||
|
|
||||||
fixPosition() {
|
fixPosition() {
|
||||||
this.#position = SATX.fixCollisions(this.#position, this.collidables(), this.radius, this.game?.width, this.game?.height)
|
this.#position = SATX.fixCollisions(this.#position, this.collidables(), this.radius, this.game?.width, this.game?.height)
|
||||||
}
|
}
|
||||||
@@ -122,8 +139,9 @@ export default class Entity {
|
|||||||
|
|
||||||
if (this.#path.length > 0) {
|
if (this.#path.length > 0) {
|
||||||
const destination = this.#path.at(0)
|
const destination = this.#path.at(0)
|
||||||
const distance = this.position.clone().sub(destination).length()
|
const difference = destination.clone().sub(this.position)
|
||||||
const direction = destination.clone().sub(this.position).normalize()
|
const distance = difference.length()
|
||||||
|
const direction = difference.clone().normalize()
|
||||||
const stepTaken = this.position.clone().add(direction.multiplyScalar(speed))
|
const stepTaken = this.position.clone().add(direction.multiplyScalar(speed))
|
||||||
const position = distance <= speed ? destination : stepTaken
|
const position = distance <= speed ? destination : stepTaken
|
||||||
|
|
||||||
|
|||||||
+39
-8
@@ -1,4 +1,7 @@
|
|||||||
import { EventEmitter } from 'node:events'
|
import { EventEmitter } from 'node:events'
|
||||||
|
import Entity from './entity.js'
|
||||||
|
import Terrain from './terrain.js'
|
||||||
|
import Projectile from './projectile.js'
|
||||||
|
|
||||||
export default class Game {
|
export default class Game {
|
||||||
tickRate = 30
|
tickRate = 30
|
||||||
@@ -8,11 +11,13 @@ export default class Game {
|
|||||||
|
|
||||||
#entities = []
|
#entities = []
|
||||||
#eventEmitter = new EventEmitter()
|
#eventEmitter = new EventEmitter()
|
||||||
|
#projectiles = []
|
||||||
#terrains = []
|
#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 projectiles() { return this.#projectiles }
|
||||||
get terrains() { return this.#terrains }
|
get terrains() { return this.#terrains }
|
||||||
get tickBudget() { return this.#tickBudget }
|
get tickBudget() { return this.#tickBudget }
|
||||||
|
|
||||||
@@ -20,29 +25,54 @@ export default class Game {
|
|||||||
return this.terrains.map((t) => t.unadjustedWaypoints).concat(this.entities.map((e) => e.unadjustedWaypoints)).flat()
|
return this.terrains.map((t) => t.unadjustedWaypoints).concat(this.entities.map((e) => e.unadjustedWaypoints)).flat()
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn_entity(entity) {
|
addTerrain(terrain) {
|
||||||
this.#entities.push(entity)
|
this.#terrains.push(terrain)
|
||||||
entity.game = this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
despawn(entity) {
|
despawn(object) {
|
||||||
|
if (object instanceof Entity) { this.despawnEntity(object) }
|
||||||
|
else if (object instanceof Terrain) { this.removeTerrain(object) }
|
||||||
|
else if (object instanceof Projectile) { this.despawnProjectile(object) }
|
||||||
|
else { console.error({ error: { reason: 'Can\'t despawn object', object } }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
despawnEntity(entity) {
|
||||||
this.#entities = this.#entities.filter((e) => e.id != entity.id)
|
this.#entities = this.#entities.filter((e) => e.id != entity.id)
|
||||||
entity.game = null
|
entity.game = null
|
||||||
}
|
}
|
||||||
|
|
||||||
add_terrain(terrain) {
|
despawnProjectile(projectile) {
|
||||||
this.#terrains.push(terrain)
|
this.#projectiles = this.#projectiles.filter((p) => p.id != projectile.id)
|
||||||
|
projectile.game = null
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_terrain(terrain) {
|
removeTerrain(terrain) {
|
||||||
this.#terrains = this.#terrains.filter((t) => t.id != terrain.id)
|
this.#terrains = this.#terrains.filter((t) => t.id != terrain.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spawn(object) {
|
||||||
|
if (object instanceof Entity) { this.spawnEntity(object) }
|
||||||
|
else if (object instanceof Terrain) { this.addTerrain(object) }
|
||||||
|
else if (object instanceof Projectile) { this.spawnProjectile(object) }
|
||||||
|
else { console.error({ error: { reason: 'Can\'t spawn object', object } }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnEntity(entity) {
|
||||||
|
this.#entities.push(entity)
|
||||||
|
entity.game = this
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnProjectile(projectile) {
|
||||||
|
this.#projectiles.push(projectile)
|
||||||
|
projectile.game = this
|
||||||
|
}
|
||||||
|
|
||||||
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()),
|
terrains: this.#terrains.map((t) => t.state()),
|
||||||
|
projectiles: this.#projectiles.map((p) => p.state()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +83,8 @@ export default class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this.#entities.map((e) => e.update())
|
this.#entities.forEach((e) => e.update())
|
||||||
|
this.#projectiles.forEach((p) => p.update())
|
||||||
this.currentTick++
|
this.currentTick++
|
||||||
this.eventEmitter.emit('tick')
|
this.eventEmitter.emit('tick')
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-11
@@ -41,6 +41,10 @@ app.ws('/ws', async (req, res) => {
|
|||||||
if (message.action == 'move') {
|
if (message.action == 'move') {
|
||||||
entity.moveAction(message.x, message.y)
|
entity.moveAction(message.x, message.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.action == 'cast') {
|
||||||
|
entity.castAction(message.slot, message.x, message.y)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -51,7 +55,7 @@ function testScenario() {
|
|||||||
entity1.radius = 50
|
entity1.radius = 50
|
||||||
entity1.maxHealth = 100
|
entity1.maxHealth = 100
|
||||||
entity1.health = 80
|
entity1.health = 80
|
||||||
game.spawn_entity(entity1)
|
game.spawnEntity(entity1)
|
||||||
|
|
||||||
const entity2 = new Entity()
|
const entity2 = new Entity()
|
||||||
entity2.id = '2'
|
entity2.id = '2'
|
||||||
@@ -59,7 +63,7 @@ function testScenario() {
|
|||||||
entity2.radius = 50
|
entity2.radius = 50
|
||||||
entity2.maxHealth = 50
|
entity2.maxHealth = 50
|
||||||
entity2.health = 50
|
entity2.health = 50
|
||||||
game.spawn_entity(entity2)
|
game.spawnEntity(entity2)
|
||||||
|
|
||||||
const horseshoe = new Terrain([
|
const horseshoe = new Terrain([
|
||||||
{ x: 400, y: 200 },
|
{ x: 400, y: 200 },
|
||||||
@@ -73,7 +77,7 @@ function testScenario() {
|
|||||||
{ x: 400, y: 300 },
|
{ x: 400, y: 300 },
|
||||||
])
|
])
|
||||||
horseshoe.id = 'horseshoe'
|
horseshoe.id = 'horseshoe'
|
||||||
game.add_terrain(horseshoe)
|
game.addTerrain(horseshoe)
|
||||||
|
|
||||||
const stopsign = new Terrain([
|
const stopsign = new Terrain([
|
||||||
{ x: 800, y: 800 },
|
{ x: 800, y: 800 },
|
||||||
@@ -87,7 +91,7 @@ function testScenario() {
|
|||||||
{ x: 700, y: 800 },
|
{ x: 700, y: 800 },
|
||||||
])
|
])
|
||||||
stopsign.id = 'stopsign'
|
stopsign.id = 'stopsign'
|
||||||
game.add_terrain(stopsign)
|
game.addTerrain(stopsign)
|
||||||
|
|
||||||
const box = new Terrain([
|
const box = new Terrain([
|
||||||
{ x: 1200, y: 700 },
|
{ x: 1200, y: 700 },
|
||||||
@@ -96,7 +100,7 @@ function testScenario() {
|
|||||||
{ x: 1300, y: 700 },
|
{ x: 1300, y: 700 },
|
||||||
])
|
])
|
||||||
box.id = 'box'
|
box.id = 'box'
|
||||||
game.add_terrain(box)
|
game.addTerrain(box)
|
||||||
|
|
||||||
const diamond = new Terrain([
|
const diamond = new Terrain([
|
||||||
{ x: 1000, y: 300 },
|
{ x: 1000, y: 300 },
|
||||||
@@ -105,7 +109,7 @@ function testScenario() {
|
|||||||
{ x: 900, y: 400 },
|
{ x: 900, y: 400 },
|
||||||
])
|
])
|
||||||
diamond.id = 'diamond'
|
diamond.id = 'diamond'
|
||||||
game.add_terrain(diamond)
|
game.addTerrain(diamond)
|
||||||
|
|
||||||
const pole = new Terrain([
|
const pole = new Terrain([
|
||||||
{ x: 400, y: 1000 },
|
{ x: 400, y: 1000 },
|
||||||
@@ -114,7 +118,7 @@ function testScenario() {
|
|||||||
{ x: 400, y: 1010 },
|
{ x: 400, y: 1010 },
|
||||||
])
|
])
|
||||||
pole.id = 'pole'
|
pole.id = 'pole'
|
||||||
game.add_terrain(pole)
|
game.addTerrain(pole)
|
||||||
}
|
}
|
||||||
|
|
||||||
function laneScenario() {
|
function laneScenario() {
|
||||||
@@ -124,7 +128,7 @@ function laneScenario() {
|
|||||||
entity1.radius = 50
|
entity1.radius = 50
|
||||||
entity1.maxHealth = 100
|
entity1.maxHealth = 100
|
||||||
entity1.health = 100
|
entity1.health = 100
|
||||||
game.spawn_entity(entity1)
|
game.spawnEntity(entity1)
|
||||||
|
|
||||||
const entity2 = new Entity()
|
const entity2 = new Entity()
|
||||||
entity2.id = '2'
|
entity2.id = '2'
|
||||||
@@ -132,7 +136,7 @@ function laneScenario() {
|
|||||||
entity2.radius = 50
|
entity2.radius = 50
|
||||||
entity2.maxHealth = 100
|
entity2.maxHealth = 100
|
||||||
entity2.health = 100
|
entity2.health = 100
|
||||||
game.spawn_entity(entity2)
|
game.spawnEntity(entity2)
|
||||||
|
|
||||||
const midWallStart = new Vector2(400, 400)
|
const midWallStart = new Vector2(400, 400)
|
||||||
const midWallEnd = new Vector2(1600, 1600)
|
const midWallEnd = new Vector2(1600, 1600)
|
||||||
@@ -151,19 +155,20 @@ function laneScenario() {
|
|||||||
const midNorthWallPoints = midWallPoints.map((p) => p.clone().add(midNorthWallOffset))
|
const midNorthWallPoints = midWallPoints.map((p) => p.clone().add(midNorthWallOffset))
|
||||||
const midNorthWall = new Terrain(midNorthWallPoints)
|
const midNorthWall = new Terrain(midNorthWallPoints)
|
||||||
midNorthWall.id = 'midNorthWall'
|
midNorthWall.id = 'midNorthWall'
|
||||||
game.add_terrain(midNorthWall)
|
game.addTerrain(midNorthWall)
|
||||||
|
|
||||||
const midSouthWallOffset = new Vector2(200, -200)
|
const midSouthWallOffset = new Vector2(200, -200)
|
||||||
const midSouthWallPoints = midWallPoints.map((p) => p.clone().add(midSouthWallOffset))
|
const midSouthWallPoints = midWallPoints.map((p) => p.clone().add(midSouthWallOffset))
|
||||||
const midSouthWall = new Terrain(midSouthWallPoints)
|
const midSouthWall = new Terrain(midSouthWallPoints)
|
||||||
midSouthWall.id = 'midSouthWall'
|
midSouthWall.id = 'midSouthWall'
|
||||||
game.add_terrain(midSouthWall)
|
game.addTerrain(midSouthWall)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server started! Visit http://localhost:${port}`)
|
console.log(`Server started! Visit http://localhost:${port}`)
|
||||||
|
|
||||||
laneScenario()
|
laneScenario()
|
||||||
|
game.entities[0].castAction(1, 2000, 2000)
|
||||||
|
|
||||||
game.start()
|
game.start()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import SAT from 'sat'
|
||||||
|
import SATX from './satx.js'
|
||||||
|
import { Vector2 } from 'three'
|
||||||
|
|
||||||
|
export default class Projectile {
|
||||||
|
id = crypto.randomUUID()
|
||||||
|
after = null
|
||||||
|
speed = 1000
|
||||||
|
radius = 5
|
||||||
|
owner = null
|
||||||
|
onCollide = null
|
||||||
|
visualHeight = 50
|
||||||
|
|
||||||
|
#position = new Vector2()
|
||||||
|
#dest = null
|
||||||
|
#game = null
|
||||||
|
|
||||||
|
get collider() {
|
||||||
|
return new SAT.Circle(new SAT.Vector(this.x, this.y), this.radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(...options) {
|
||||||
|
Object.entries(options).forEach((value, key) => this[key] = value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get game() { return this.#game }
|
||||||
|
get position() { return this.#position }
|
||||||
|
get x() { return this.position.x }
|
||||||
|
get y() { return this.position.y }
|
||||||
|
set game(value) { this.#game = value }
|
||||||
|
set x(value) { this.position.x = value }
|
||||||
|
set y(value) { this.position.y = value }
|
||||||
|
set destination(value) { this.#dest = value }
|
||||||
|
|
||||||
|
checkCollisions() {
|
||||||
|
(this.game?.entities ?? []).filter((e) => e.id != this.id).forEach((e) => {
|
||||||
|
if (e.id == this.owner) { return }
|
||||||
|
|
||||||
|
if (SATX.collideObject(this.collider, e.collider)) {
|
||||||
|
this.onCollide(this, e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIfArrived() {
|
||||||
|
if (!this.#position.equals(this.#dest)) { return }
|
||||||
|
|
||||||
|
if (this.after != null) {
|
||||||
|
this.after(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.despawn()
|
||||||
|
}
|
||||||
|
|
||||||
|
despawn() {
|
||||||
|
this.game?.despawn(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
...this,
|
||||||
|
position: {
|
||||||
|
x: this.x,
|
||||||
|
y: this.y,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
takeStep() {
|
||||||
|
const speed = (this.speed / (this.game?.tickBudget ?? 1000))
|
||||||
|
const destination = this.#dest
|
||||||
|
const difference = destination.clone().sub(this.position)
|
||||||
|
const distance = difference.length()
|
||||||
|
const direction = difference.clone().normalize()
|
||||||
|
const stepTaken = this.position.clone().add(direction.multiplyScalar(speed))
|
||||||
|
const position = distance <= speed ? destination : stepTaken
|
||||||
|
|
||||||
|
this.position.copy(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.takeStep()
|
||||||
|
if (this.onCollide != null) { this.checkCollisions() }
|
||||||
|
this.checkIfArrived()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user