fix pathfinding issues

This commit is contained in:
2025-01-12 19:43:45 +09:00
parent 6ff950640c
commit 8e861929cb
6 changed files with 385 additions and 158 deletions
+17
View File
@@ -6,11 +6,14 @@ const scene = new THREE.Scene()
const raycaster = new THREE.Raycaster() const raycaster = new THREE.Raycaster()
const camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 1000) const camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer() const renderer = new THREE.WebGLRenderer()
const backgroundColor = new THREE.Color().setHex(0x112233)
scene.background = backgroundColor
renderer.setSize(window.innerWidth, window.innerHeight) renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(render) renderer.setAnimationLoop(render)
camera.position.set(5, -12, 10) camera.position.set(5, -12, 10)
camera.rotation.set((60 / 180) * Math.PI, 0, 0) camera.rotation.set((60 / 180) * Math.PI, 0, 0)
camera.layers.enable(1) camera.layers.enable(1)
camera.layers.enable(2)
const entityMaterial = new THREE.MeshToonMaterial({ color: 0xffffff }) const entityMaterial = new THREE.MeshToonMaterial({ color: 0xffffff })
const projectileMaterial = new THREE.MeshToonMaterial({ color: 0xff00ff }) const projectileMaterial = new THREE.MeshToonMaterial({ color: 0xff00ff })
@@ -159,6 +162,10 @@ function connectWebSocket() {
ground.position.set(state.width / 200, state.height / 200, 0) ground.position.set(state.width / 200, state.height / 200, 0)
} }
for (const e of Object.values(entities)) {
e.userData.flaggedForRemoval = true
}
for (const e of state.entities ?? []) { for (const e of state.entities ?? []) {
let entity let entity
if (e.id in entities) { if (e.id in entities) {
@@ -190,6 +197,7 @@ function connectWebSocket() {
entities[e.id] = entity entities[e.id] = entity
} }
entity.userData.flaggedForRemoval = false
positionTweens[entity.id] = new Tween(entity.position).to({ x: e.position.x / 100, y: e.position.y / 100, z: e.radius / 100 }, tweenDuration).start() positionTweens[entity.id] = new Tween(entity.position).to({ x: e.position.x / 100, y: e.position.y / 100, z: e.radius / 100 }, tweenDuration).start()
const hp = entity.children.at(0).children.at(0) const hp = entity.children.at(0).children.at(0)
@@ -198,6 +206,14 @@ function connectWebSocket() {
hp.position.x = -(1 - percentageHp) / 2 hp.position.x = -(1 - percentageHp) / 2
} }
for (const e of Object.values(entities)) {
if (e.userData.flaggedForRemoval) {
scene.remove(e)
delete entities[e.userData.id]
delete positionTweens[e.userData.id]
}
}
for (const p of Object.values(projectiles)) { for (const p of Object.values(projectiles)) {
p.userData.flaggedForRemoval = true p.userData.flaggedForRemoval = true
} }
@@ -212,6 +228,7 @@ function connectWebSocket() {
projectile.userData.type = 'projectile' projectile.userData.type = 'projectile'
projectile.userData.id = p.id projectile.userData.id = p.id
projectile.position.set(p.position.x / 100, p.position.y / 100, p.visualHeight / 100) projectile.position.set(p.position.x / 100, p.position.y / 100, p.visualHeight / 100)
projectile.layers.set(2)
scene.add(projectile) scene.add(projectile)
projectiles[p.id] = projectile projectiles[p.id] = projectile
+67 -12
View File
@@ -28,7 +28,7 @@ export default class Ability {
id: 'straight_shot', id: 'straight_shot',
name: 'Straight Shot', name: 'Straight Shot',
castTime: 0.1, castTime: 0.1,
cooldown: 7, cooldown: 1,
damage: 10, damage: 10,
radius: 7, radius: 7,
range: 800, range: 800,
@@ -54,20 +54,20 @@ export default class Ability {
}, },
}) })
static basicAttack = new Ability({ static rangedAttack = new Ability({
id: 'basic_attack', id: 'ranged_attack',
name: 'Basic Attack', name: 'Ranged Attack',
castTime: 0.25, castTime: 0.25,
cooldown: 1.25, cooldown: 1.25,
damage: 5, damage: 5,
radius: 5, radius: 5,
range: 600, range: 500,
speed: 600, speed: 600,
effect: function basicAttackEffect(caster, cursor) { effect: function rangedAttackEffect(caster, cursor) {
const ability = this const ability = this
let closest = null let closest = null
let distance = Infinity let distance = Infinity
caster.game?.entities.filter((e) => e.id != caster.id && e.position.clone().sub(caster.position).length() < ability.range).forEach((e) => { caster.game?.entities.filter((e) => e.team != caster.team && e.position.clone().sub(caster.position).length() < ability.range).forEach((e) => {
const newDistance = e.position.clone().sub(cursor).length() < distance const newDistance = e.position.clone().sub(cursor).length() < distance
if (newDistance < distance) { if (newDistance < distance) {
closest = e closest = e
@@ -77,12 +77,12 @@ export default class Ability {
if (closest == null) { return } if (closest == null) { return }
const basicAttackAfter = function basicAttackAfter() { const rangedAttackAfter = function rangedAttackAfter() {
closest.damage(ability.damage) closest.damage(ability.damage)
} }
const projectile = new Projectile({ const projectile = new Projectile({
after: basicAttackAfter, after: rangedAttackAfter,
homingTarget: closest, homingTarget: closest,
owner: caster, owner: caster,
position: caster.position.clone(), position: caster.position.clone(),
@@ -95,12 +95,67 @@ export default class Ability {
}, },
}) })
static meleeAttack = new Ability({
id: 'melee_attack',
name: 'Melee Attack',
castTime: 0.1,
cooldown: 1.75,
damage: 10,
radius: 5,
range: 100,
effect: function meleeAttackEffect(caster, cursor) {
const ability = this
let closest = null
let distance = Infinity
caster.game?.entities.filter((e) => e.team != caster.team && e.position.clone().sub(caster.position).length() < ability.range).forEach((e) => {
const newDistance = e.position.clone().sub(cursor).length() < distance
if (newDistance < distance) {
closest = e
distance = newDistance
}
})
if (closest == null) { return }
closest.damage(ability.damage)
caster.cooldown(ability.id)
},
})
static shieldThrow = new Ability({ static shieldThrow = new Ability({
id: 'shield_throw', id: 'shield_throw',
name: 'Shield Throw', name: 'Shield Throw',
castTime: 0.1, castTime: 0.15,
cooldown: 7, cooldown: 5,
effect: function shieldThrowEffect(caster, cursor) { }, radius: 20,
range: 1000,
speed: 2000,
effect: function shieldThrowEffect(caster, cursor) {
const ability = this
const shieldThrowReturn = function shieldThrowReturn(projectile, homingTarget) {
const returnProjectile = new Projectile({
owner: caster,
position: projectile.position.clone(),
radius: ability.radius,
speed: ability.speed,
homingTarget: caster,
})
caster.game?.spawnProjectile(returnProjectile)
}
const projectile = new Projectile({
after: shieldThrowReturn,
owner: caster,
position: caster.position.clone(),
radius: ability.radius,
speed: ability.speed,
})
projectile.destination = caster.position.clone().add(cursor.clone().sub(caster.position).normalize().multiplyScalar(ability.range))
caster.game?.spawnProjectile(projectile)
caster.cooldown(ability.id)
},
}) })
static blink = new Ability({ static blink = new Ability({
+74 -67
View File
@@ -3,6 +3,7 @@ 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 Ability from './ability.js'
import Team from './team.js'
export default class Entity { export default class Entity {
id = crypto.randomUUID() id = crypto.randomUUID()
@@ -11,21 +12,23 @@ export default class Entity {
health = 1 health = 1
maxHealth = 1 maxHealth = 1
abilities = [ abilities = [
Ability.basicAttack, Ability.rangedAttack,
Ability.straightShot, Ability.straightShot,
Ability.shieldThrow, Ability.shieldThrow,
Ability.blink, Ability.blink,
] ]
casting = null casting = null
// TODO: teams team = Team.neutral
cooldowns = {} cooldowns = {}
#attack = false #attack = false
#position = new Vector2()
#dest = null #dest = null
#game = null #game = null
#logic = null
#path = [] #path = []
#position = new Vector2()
#scheduledPathfinding = null
static collider(x, y, radius) { static collider(x, y, radius) {
return new SAT.Circle(new SAT.Vector(x, y), radius) return new SAT.Circle(new SAT.Vector(x, y), radius)
@@ -35,11 +38,19 @@ export default class Entity {
Object.entries(options).forEach(([key, value]) => this[key] = value) Object.entries(options).forEach(([key, value]) => this[key] = value)
} }
get destination() { return this.#dest }
get logic() { return this.#logic }
get game() { return this.#game } get game() { return this.#game }
get position() { return this.#position } get position() { return this.#position }
get scheduledPathfinding() { return this.#scheduledPathfinding }
get x() { return this.position.x } get x() { return this.position.x }
get y() { return this.position.y } get y() { return this.position.y }
set destination(value) { this.#dest = value }
set logic(value) { this.#logic = value }
set game(value) { this.#game = value } set game(value) { this.#game = value }
set position(value) { this.#position = value }
set scheduledPathfinding(value) { this.#scheduledPathfinding = value }
set x(value) { this.position.x = value } set x(value) { this.position.x = value }
set y(value) { this.position.y = value } set y(value) { this.position.y = value }
@@ -123,20 +134,6 @@ export default class Entity {
this.#attack = false this.#attack = false
} }
autoAttack() {
if (!this.#attack) { return false }
if (this.game?.entities.some((e) => e.id != this.id && e.position.clone().sub(this.position).length() < this.abilities[0].range)) {
const cooldown = this.game?.secToTick(this.abilities[0].cooldown) ?? 0
const lastCast = this.cooldowns[this.abilities[0].id]
const timestamp = this.game?.currentTick ?? 0
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
const target = this.#dest ?? this.position
this.castAction(0, target.x, target.y, false)
}
}
cast() { cast() {
if (this.casting == null) { if (this.casting == null) {
return false return false
@@ -209,50 +206,71 @@ export default class Entity {
this.fixPosition() this.fixPosition()
} }
// TODO: unset destination on teleports, etc.
// TODO: recalculate path on obstructions (currently next waypoint is used)
takeStep(distanceTraveled = 0) { takeStep(distanceTraveled = 0) {
if (this.casting != null) { return false } if (this.casting != null) { return false }
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled if (this.#attack && this.game?.entities.some((e) => e.team != this.team && e.position.clone().sub(this.position).length() < this.abilities[0].range)) {
const collidables = this.collidables() const cooldown = this.game?.secToTick(this.abilities[0].cooldown) ?? 0
if (this.#dest != null) { const lastCast = this.cooldowns[this.abilities[0].id]
const fixedDest = SATX.clamp(SATX.fixCollisions(this.#dest, collidables, this.radius), this.game?.width, this.game?.height, this.radius) const timestamp = this.game?.currentTick ?? 0
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
if (this.#path.length < 1 || !this.#path.at(-1).equals(fixedDest)) { const target = this.#dest ?? this.position
// console.time('pathfinding') this.castAction(0, target.x, target.y, false)
const start = SATX.vectorToFloat32Array(this.position) return true
const goal = SATX.vectorToFloat32Array(fixedDest) }
const nonUniqueWaypoints = this.waypoints().map((w) => SATX.vectorToFloat32Array(w)).concat([start, goal])
const waypoints = Pathfind.uniqueWaypoints(nonUniqueWaypoints) if (this.#dest == null) { return false }
const graph = Pathfind.buildGraph(waypoints, collidables, this.radius)
this.#path = Pathfind.shortestPath(graph, start, goal).map((waypoint) => new Vector2(waypoint[0], waypoint[1])) const collidables = this.collidables()
// console.timeEnd('pathfinding') const fixedDest = SATX.clamp(SATX.fixCollisions(this.#dest, collidables, this.radius), this.game?.width, this.game?.height, this.radius)
const tunnel = SATX.entityTunnel(this.#position.x, this.#position.y, fixedDest.x, fixedDest.y, this.radius)
const destinationInLineOfSight = !SATX.collideObjects(tunnel, collidables)
if (this.#path.length > 0) {
if (!destinationInLineOfSight) {
this.#path = []
}
}
if (this.#path.length < 1 || !this.#path.at(-1).equals(fixedDest)) {
if (destinationInLineOfSight) {
this.#path = [fixedDest]
}
}
if ((this.#path.length < 1 || !this.#path.at(-1).equals(fixedDest)) && (!this.#scheduledPathfinding || this.game?.currentTick % this.game?.tickRate == this.#scheduledPathfinding)) {
const start = SATX.vectorToFloat32Array(this.position)
const goal = SATX.vectorToFloat32Array(fixedDest)
const nonUniqueWaypoints = this.waypoints().map((w) => SATX.vectorToFloat32Array(w)).concat([start, goal])
const waypoints = Pathfind.uniqueWaypoints(nonUniqueWaypoints)
const graph = Pathfind.buildGraph(waypoints, collidables, this.radius)
this.#path = Pathfind.shortestPath(graph, start, goal).map((waypoint) => new Vector2(waypoint[0], waypoint[1]))
}
if (this.#path.length > 0) {
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled
const destination = this.#path.at(0)
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
const collider = Entity.collider(position.x, position.y, this.radius)
const isColliding = SATX.collideObjects(collider, this.collidables())
if (!isColliding) {
this.position.copy(position)
} }
if (this.#path.length > 0) { if (this.position.equals(destination)) {
const destination = this.#path.at(0) this.#path = this.#path.slice(1)
const difference = destination.clone().sub(this.position) if (this.#path.length > 0) {
const distance = difference.length() this.takeStep(distance)
const direction = difference.clone().normalize()
const stepTaken = this.position.clone().add(direction.multiplyScalar(speed))
const position = distance <= speed ? destination : stepTaken
const collider = Entity.collider(position.x, position.y, this.radius)
const isColliding = SATX.collideObjects(collider, this.collidables())
if (!isColliding) {
this.position.copy(position)
} }
else {
if (this.position.equals(destination)) { this.#dest = null
this.#path = this.#path.slice(1)
if (this.#path.length > 0) {
this.takeStep(distance)
}
else {
this.#dest = null
}
} }
} }
} }
@@ -262,19 +280,8 @@ export default class Entity {
this.cast() this.cast()
this.takeStep() this.takeStep()
this.fixPosition() this.fixPosition()
this.autoAttack() if (this.#logic != null) {
this.#logic()
// TODO: proper death and respawn
if (this.health <= 0) {
if (this.id == '1' || this.id == '2') {
this.health = this.maxHealth
if (this.id == '1') {
this.teleport(new Vector2(200, 200))
}
if (this.id == '2') {
this.teleport(new Vector2(1800, 1800))
}
}
} }
} }
+7
View File
@@ -11,17 +11,20 @@ export default class Game {
running = false running = false
nextTick = 0 nextTick = 0
#logic = null
#entities = [] #entities = []
#eventEmitter = new EventEmitter() #eventEmitter = new EventEmitter()
#projectiles = [] #projectiles = []
#terrains = [] #terrains = []
#tickBudget = 1000 / this.tickRate #tickBudget = 1000 / this.tickRate
get logic() { return this.#logic }
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 projectiles() { return this.#projectiles }
get terrains() { return this.#terrains } get terrains() { return this.#terrains }
get tickBudget() { return this.#tickBudget } get tickBudget() { return this.#tickBudget }
set logic(value) { this.#logic = value }
get unadjustedWaypoints() { get unadjustedWaypoints() {
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()
@@ -104,6 +107,10 @@ export default class Game {
update() { update() {
this.#entities.forEach((e) => e.update()) this.#entities.forEach((e) => e.update())
this.#projectiles.forEach((p) => p.update()) this.#projectiles.forEach((p) => p.update())
if (this.#logic != null) {
this.#logic()
}
this.currentTick++ this.currentTick++
this.eventEmitter.emit('tick') this.eventEmitter.emit('tick')
} }
+215 -79
View File
@@ -4,6 +4,8 @@ import Game from './game.js'
import Entity from './entity.js' import Entity from './entity.js'
import Terrain from './terrain.js' import Terrain from './terrain.js'
import { Vector2 } from 'three' import { Vector2 } from 'three'
import Team from './team.js'
import Ability from './ability.js'
const app = new WebSocketExpress() const app = new WebSocketExpress()
const port = 1280 const port = 1280
@@ -60,94 +62,100 @@ app.ws('/ws', async (req, res) => {
}) })
}) })
function testScenario() { // function testScenario() {
const entity1 = new Entity() // const entity1 = new Entity()
entity1.id = '1' // entity1.id = '1'
entity1.teleport(new Vector2(200, 500)) // entity1.teleport(new Vector2(200, 500))
entity1.radius = 50 // entity1.radius = 50
entity1.maxHealth = 100 // entity1.maxHealth = 100
entity1.health = 80 // entity1.health = 80
game.spawnEntity(entity1) // game.spawnEntity(entity1)
const entity2 = new Entity() // const entity2 = new Entity()
entity2.id = '2' // entity2.id = '2'
entity2.teleport(new Vector2(110, 110)) // entity2.teleport(new Vector2(110, 110))
entity2.radius = 50 // entity2.radius = 50
entity2.maxHealth = 50 // entity2.maxHealth = 50
entity2.health = 50 // entity2.health = 50
game.spawnEntity(entity2) // game.spawnEntity(entity2)
const horseshoe = new Terrain([ // const horseshoe = new Terrain([
{ x: 400, y: 200 }, // { x: 400, y: 200 },
{ x: 600, y: 200 }, // { x: 600, y: 200 },
{ x: 700, y: 300 }, // { x: 700, y: 300 },
{ x: 650, y: 600 }, // { x: 650, y: 600 },
{ x: 400, y: 600 }, // { x: 400, y: 600 },
{ x: 400, y: 450 }, // { x: 400, y: 450 },
{ x: 600, y: 500 }, // { x: 600, y: 500 },
{ x: 600, y: 300 }, // { x: 600, y: 300 },
{ x: 400, y: 300 }, // { x: 400, y: 300 },
]) // ])
horseshoe.id = 'horseshoe' // horseshoe.id = 'horseshoe'
game.addTerrain(horseshoe) // game.addTerrain(horseshoe)
const stopsign = new Terrain([ // const stopsign = new Terrain([
{ x: 800, y: 800 }, // { x: 800, y: 800 },
{ x: 900, y: 900 }, // { x: 900, y: 900 },
{ x: 900, y: 1000 }, // { x: 900, y: 1000 },
{ x: 800, y: 1100 }, // { x: 800, y: 1100 },
{ x: 800, y: 1100 }, // { x: 800, y: 1100 },
{ x: 700, y: 1100 }, // { x: 700, y: 1100 },
{ x: 600, y: 1000 }, // { x: 600, y: 1000 },
{ x: 600, y: 900 }, // { x: 600, y: 900 },
{ x: 700, y: 800 }, // { x: 700, y: 800 },
]) // ])
stopsign.id = 'stopsign' // stopsign.id = 'stopsign'
game.addTerrain(stopsign) // game.addTerrain(stopsign)
const box = new Terrain([ // const box = new Terrain([
{ x: 1200, y: 700 }, // { x: 1200, y: 700 },
{ x: 1200, y: 800 }, // { x: 1200, y: 800 },
{ x: 1300, y: 800 }, // { x: 1300, y: 800 },
{ x: 1300, y: 700 }, // { x: 1300, y: 700 },
]) // ])
box.id = 'box' // box.id = 'box'
game.addTerrain(box) // game.addTerrain(box)
const diamond = new Terrain([ // const diamond = new Terrain([
{ x: 1000, y: 300 }, // { x: 1000, y: 300 },
{ x: 1100, y: 400 }, // { x: 1100, y: 400 },
{ x: 1000, y: 500 }, // { x: 1000, y: 500 },
{ x: 900, y: 400 }, // { x: 900, y: 400 },
]) // ])
diamond.id = 'diamond' // diamond.id = 'diamond'
game.addTerrain(diamond) // game.addTerrain(diamond)
const pole = new Terrain([ // const pole = new Terrain([
{ x: 400, y: 1000 }, // { x: 400, y: 1000 },
{ x: 410, y: 1000 }, // { x: 410, y: 1000 },
{ x: 410, y: 1010 }, // { x: 410, y: 1010 },
{ x: 400, y: 1010 }, // { x: 400, y: 1010 },
]) // ])
pole.id = 'pole' // pole.id = 'pole'
game.addTerrain(pole) // game.addTerrain(pole)
} // }
function laneScenario() { function laneScenario() {
const entity1 = new Entity() const entity1 = new Entity({
entity1.id = '1' id: '1',
entity1.teleport(new Vector2(200, 200)) health: 100,
entity1.radius = 50 maxHealth: 100,
entity1.maxHealth = 100 position: new Vector2(500, 150),
entity1.health = 100 radius: 50,
team: Team.blue,
})
game.spawnEntity(entity1) game.spawnEntity(entity1)
const entity2 = new Entity() const entity2 = new Entity({
entity2.id = '2' id: '2',
entity2.teleport(new Vector2(1800, 1800)) health: 100,
entity2.radius = 50 maxHealth: 100,
entity2.maxHealth = 100 position: new Vector2(1600, 1800),
entity2.health = 100 radius: 50,
team: Team.red,
})
game.spawnEntity(entity2) game.spawnEntity(entity2)
const midWallStart = new Vector2(400, 400) const midWallStart = new Vector2(400, 400)
@@ -174,13 +182,141 @@ function laneScenario() {
const midSouthWall = new Terrain(midSouthWallPoints) const midSouthWall = new Terrain(midSouthWallPoints)
midSouthWall.id = 'midSouthWall' midSouthWall.id = 'midSouthWall'
game.addTerrain(midSouthWall) game.addTerrain(midSouthWall)
// TODO: proper death and respawn
const playerLogic = function playerLogic() {
const entity = this
if (entity.health <= 0) {
if (entity.id == '1' || entity.id == '2') {
entity.health = entity.maxHealth
if (entity.id == '1') {
entity.teleport(new Vector2(500, 150))
}
if (entity.id == '2') {
entity.teleport(new Vector2(1600, 1800))
}
}
}
}
entity1.logic = playerLogic
entity2.logic = playerLogic
const blueMinionLogic = function minionLogic() {
const entity = this
let goal = new Vector2(1900, 1900)
if (entity.position.x < 800 || entity.position.y < 1100) {
goal = new Vector2(850, 1150)
}
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(75)
const subGoal = entity.position.clone().add(direction)
// console.log(subGoal)
entity.attackAction(subGoal.x, subGoal.y)
if (entity.health <= 0) {
entity.despawn()
}
}
const redMinionLogic = function minionLogic() {
const entity = this
let goal = new Vector2(100, 100)
if (entity.position.x > 900 || entity.position.y > 1200) {
goal = new Vector2(850, 1150)
}
const direction = goal.clone().sub(entity.position).normalize().multiplyScalar(75)
const subGoal = entity.position.clone().add(direction)
entity.attackAction(subGoal.x, subGoal.y)
if (entity.health <= 0) {
entity.despawn()
}
}
const minionTemplate = {
health: 20,
maxHealth: 20,
radius: 30,
speed: 300,
}
const gameLogic = function gameLogic() {
const game = this
const blueMinion = new Entity({
...minionTemplate,
logic: blueMinionLogic,
team: Team.blue,
position: new Vector2(200, 200),
})
// blueMinion.scheduledPathfinding = game.entities.length % game.tickRate
const blueMeleeMinion = new Entity({
...minionTemplate,
logic: blueMinionLogic,
team: Team.blue,
position: new Vector2(200, 200),
})
blueMeleeMinion.abilities[0] = Ability.meleeAttack
const redMinion = new Entity({
...minionTemplate,
logic: redMinionLogic,
team: Team.red,
position: new Vector2(1800, 1800),
})
// redMinion.scheduledPathfinding = game.entities.length % game.tickRate
const redMeleeMinion = new Entity({
...minionTemplate,
logic: redMinionLogic,
team: Team.red,
position: new Vector2(1800, 1800),
})
redMeleeMinion.abilities[0] = Ability.meleeAttack
if (game.currentTick % (30 * game.tickRate) == (0 * game.tickRate)) {
game.spawnEntity(blueMeleeMinion)
game.spawnEntity(redMeleeMinion)
}
if (game.currentTick % (30 * game.tickRate) == (1 * game.tickRate)) {
game.spawnEntity(blueMeleeMinion)
game.spawnEntity(redMeleeMinion)
}
if (game.currentTick % (30 * game.tickRate) == (2 * game.tickRate)) {
game.spawnEntity(blueMeleeMinion)
game.spawnEntity(redMeleeMinion)
}
if (game.currentTick % (30 * game.tickRate) == (3 * game.tickRate)) {
game.spawnEntity(blueMinion)
game.spawnEntity(redMinion)
}
if (game.currentTick % (30 * game.tickRate) == (4 * game.tickRate)) {
game.spawnEntity(blueMinion)
game.spawnEntity(redMinion)
}
if (game.currentTick % (30 * game.tickRate) == (5 * game.tickRate)) {
game.spawnEntity(blueMinion)
game.spawnEntity(redMinion)
}
}
entity2.attackAction(1600, 1800)
entity1.attackAction(500, 150)
game.logic = gameLogic
} }
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(3, 2000, 2000)
game.start() game.start()
}) })
+5
View File
@@ -0,0 +1,5 @@
export default class Team {
static neutral = 'neutral'
static blue = 'blue'
static red = 'red'
}