add catching up mechanic for ticks
This commit is contained in:
@@ -423,6 +423,7 @@ function connectWebSocket() {
|
||||
|
||||
const bbox = new THREE.Mesh(new THREE.BoxGeometry(width, height, 0.2), bboxMaterial)
|
||||
bbox.position.set((bboxValues[3] / 100) + (width / 2), (bboxValues[2] / 100) + (height / 2), 0)
|
||||
bbox.layers.set(1)
|
||||
scene.add(bbox)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -47,7 +47,7 @@ export default class Ability {
|
||||
|
||||
const projectile = new Projectile({
|
||||
onCollide: straightShotCollision,
|
||||
owner: caster,
|
||||
owner: caster.id,
|
||||
position: caster.position.clone(),
|
||||
radius: ability.radius,
|
||||
speed: ability.speed,
|
||||
@@ -82,7 +82,7 @@ export default class Ability {
|
||||
const projectile = new Projectile({
|
||||
after: rangedAttackAfter,
|
||||
homingTarget: target,
|
||||
owner: caster,
|
||||
owner: caster.id,
|
||||
position: caster.position.clone(),
|
||||
radius: ability.radius,
|
||||
speed: ability.speed,
|
||||
@@ -124,7 +124,7 @@ export default class Ability {
|
||||
const ability = this
|
||||
const shieldThrowReturn = function shieldThrowReturn(projectile, homingTarget) {
|
||||
const returnProjectile = new Projectile({
|
||||
owner: caster,
|
||||
owner: caster.id,
|
||||
position: projectile.position.clone(),
|
||||
radius: ability.radius,
|
||||
speed: ability.speed,
|
||||
@@ -136,7 +136,7 @@ export default class Ability {
|
||||
|
||||
const projectile = new Projectile({
|
||||
after: shieldThrowReturn,
|
||||
owner: caster,
|
||||
owner: caster.id,
|
||||
position: caster.position.clone(),
|
||||
radius: ability.radius,
|
||||
speed: ability.speed,
|
||||
@@ -191,7 +191,7 @@ export default class Ability {
|
||||
|
||||
const projectile = new Projectile({
|
||||
onCollide: exposeCollision,
|
||||
owner: caster,
|
||||
owner: caster.id,
|
||||
position: caster.position.clone(),
|
||||
radius: ability.radius,
|
||||
speed: ability.speed,
|
||||
|
||||
+12
-5
@@ -16,7 +16,8 @@ export default class Entity {
|
||||
health = null
|
||||
height = 40
|
||||
maxHealth = 1
|
||||
memory = {} // TODO: hide from reports but keep public
|
||||
memory = {}
|
||||
pathfindingCooldown = 0
|
||||
position = null
|
||||
radius = 0
|
||||
rotation = 0
|
||||
@@ -31,6 +32,7 @@ export default class Entity {
|
||||
#logic = null
|
||||
#moving = false
|
||||
#path = []
|
||||
#noPathfindingUntil = 0
|
||||
#spawnPosition = new Vector2()
|
||||
|
||||
static collider(x, y, radius) {
|
||||
@@ -495,6 +497,7 @@ export default class Entity {
|
||||
|
||||
#move(distanceTraveled = 0) {
|
||||
if (this.casting != null) { return false }
|
||||
const currentTick = this.game?.currentTick ?? 0
|
||||
|
||||
if (this.#attacking) {
|
||||
const cursor = this.#dest ?? this.position
|
||||
@@ -504,8 +507,7 @@ export default class Entity {
|
||||
if (target != null && this.distanceTo(target.position) < basicAttack.range + this.radius + target.radius) {
|
||||
const cooldown = this.game?.secToTick(basicAttack.cooldown) ?? 0
|
||||
const lastCast = this.cooldowns[basicAttack.id]
|
||||
const timestamp = this.game?.currentTick ?? 0
|
||||
if (lastCast != null && lastCast + cooldown > timestamp) { return false }
|
||||
if (lastCast != null && lastCast + cooldown > currentTick) { return false }
|
||||
|
||||
this.castAction('a', target.id, false)
|
||||
return true
|
||||
@@ -516,8 +518,9 @@ export default class Entity {
|
||||
if (!this.#moving || this.#dest == null) { return false }
|
||||
|
||||
const fixedDest = this.fixFuturePosition(this.#dest)
|
||||
const pathfinding = this.#noPathfindingUntil <= currentTick
|
||||
|
||||
if (this.#path.length > 0) {
|
||||
if (pathfinding && this.#path.length > 0) {
|
||||
const sectionDest = this.#path.at(0)
|
||||
const lineOfSight = this.isInLineOfSight(sectionDest)
|
||||
if (!lineOfSight) {
|
||||
@@ -532,7 +535,7 @@ export default class Entity {
|
||||
}
|
||||
}
|
||||
|
||||
if ((this.#path.length < 1 || (this.#path.at(-1)?.distanceTo(fixedDest) ?? 0) > 0.01)) {
|
||||
if (pathfinding && (this.#path.length < 1 || (this.#path.at(-1)?.distanceTo(fixedDest) ?? 0) > 0.01)) {
|
||||
const start = SATX.vectorToFloat32Array(this.position)
|
||||
const goal = SATX.vectorToFloat32Array(fixedDest)
|
||||
const obstacles = []
|
||||
@@ -574,6 +577,10 @@ export default class Entity {
|
||||
}
|
||||
}
|
||||
|
||||
if (pathfinding && this.pathfindingCooldown > 0) {
|
||||
this.#noPathfindingUntil = currentTick + (this.game?.secToTick(this.pathfindingCooldown) ?? 0)
|
||||
}
|
||||
|
||||
if (this.#path.length > 0) {
|
||||
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled
|
||||
const destination = this.#path.at(0)
|
||||
|
||||
+6
-4
@@ -18,6 +18,7 @@ export default class Game {
|
||||
tickRate = 30
|
||||
width = 10000 * 1.6
|
||||
|
||||
#behindMs = 0
|
||||
#currentTiming = 0
|
||||
#eventEmitter = new EventEmitter()
|
||||
#logic = null
|
||||
@@ -103,9 +104,8 @@ export default class Game {
|
||||
}
|
||||
|
||||
#gameLoop() {
|
||||
const tickBudget = this.#tickBudget
|
||||
|
||||
if (this.#nextTickAt != null) {
|
||||
const tickBudget = this.#tickBudget
|
||||
const nextTickAt = this.#nextTickAt
|
||||
this.#nextTickAt = null
|
||||
|
||||
@@ -114,9 +114,11 @@ export default class Game {
|
||||
|
||||
const before = performance.now()
|
||||
this.update()
|
||||
this.#nextTickAt = start + tickBudget
|
||||
const after = performance.now()
|
||||
const taken = (after - before)
|
||||
const prevBehind = this.#behindMs
|
||||
this.#behindMs = Math.max(0, this.#behindMs + taken - tickBudget)
|
||||
this.#nextTickAt = start + tickBudget - prevBehind
|
||||
|
||||
this.#timings[this.#currentTiming] = taken
|
||||
if (this.#currentTiming++ > this.#timings.length) {
|
||||
@@ -124,7 +126,7 @@ export default class Game {
|
||||
}
|
||||
|
||||
if (after - before > tickBudget) {
|
||||
console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms. Entity count: ${this.entities.length}`)
|
||||
console.warn(`Can't keep up! A tick took ${taken.toFixed(1)} ms / ${tickBudget} ms. (Behind ${this.#behindMs} ms)`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -126,7 +126,7 @@ function laneScenario() {
|
||||
].map((p) => p.multiplyScalar(1.6))
|
||||
|
||||
if (game.entities.length < 100) {
|
||||
if ([(0 * game.tickRate), (1 * game.tickRate), (2 * game.tickRate)].includes(game.currentTick % (6 * game.tickRate))) {
|
||||
if ([(0 * game.tickRate), (1 * game.tickRate), (2 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) {
|
||||
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false, route: topRoute })))
|
||||
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false, route: midRoute })))
|
||||
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: false, route: botRoute })))
|
||||
@@ -136,7 +136,7 @@ function laneScenario() {
|
||||
game.spawnEntity(new Entity(Template.minion(Team.red, { ranged: false, route: botRoute.toReversed() })))
|
||||
}
|
||||
|
||||
if ([(3 * game.tickRate), (4 * game.tickRate), (5 * game.tickRate)].includes(game.currentTick % (6 * game.tickRate))) {
|
||||
if ([(3 * game.tickRate), (4 * game.tickRate), (5 * game.tickRate)].includes(game.currentTick % (30 * game.tickRate))) {
|
||||
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true, route: topRoute })))
|
||||
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true, route: midRoute })))
|
||||
game.spawnEntity(new Entity(Template.minion(Team.blue, { ranged: true, route: botRoute })))
|
||||
|
||||
+24
-11
@@ -5,25 +5,35 @@ import Entity from './entity.js'
|
||||
|
||||
export default class Projectile {
|
||||
id = crypto.randomUUID()
|
||||
after = null // TODO: hide from reports but keep public
|
||||
|
||||
bbox = new Float32Array(4)
|
||||
height = 50
|
||||
memory = {} // TODO: hide from reports but keep public
|
||||
onCollide = null // TODO: hide from reports but keep public
|
||||
owner = null // TODO: only keep an ID
|
||||
memory = {}
|
||||
owner = null
|
||||
position = new Vector2()
|
||||
radius = 5
|
||||
speed = 1000
|
||||
visualRadius = null
|
||||
|
||||
#after = null
|
||||
#dest = null
|
||||
#homingTarget = null
|
||||
#logic = null
|
||||
#onCollide = null
|
||||
#game = null
|
||||
|
||||
get after() { return this.#after }
|
||||
get game() { return this.#game }
|
||||
set game(value) { this.#game = value }
|
||||
get homingTarget() { return this.#homingTarget }
|
||||
get logic() { return this.#logic }
|
||||
get onCollide() { return this.#onCollide }
|
||||
|
||||
set after(value) { this.#after = value }
|
||||
set destination(value) { this.#dest = value }
|
||||
set game(value) { this.#game = value }
|
||||
set homingTarget(value) { this.#homingTarget = value }
|
||||
set logic(value) { this.#logic = value }
|
||||
set onCollide(value) { this.#onCollide = value }
|
||||
|
||||
get destination() {
|
||||
return this.#dest ?? this.#homingTarget?.position
|
||||
@@ -53,6 +63,9 @@ export default class Projectile {
|
||||
this.#move()
|
||||
this.#checkStationaryCollisions()
|
||||
this.#checkIfArrived()
|
||||
if (this.#logic != null) {
|
||||
this.#logic()
|
||||
}
|
||||
}
|
||||
|
||||
#calculateBbox() {
|
||||
@@ -66,15 +79,15 @@ export default class Projectile {
|
||||
if (this.destination == null) { return }
|
||||
if (!this.position.equals(this.destination)) { return }
|
||||
|
||||
if (this.after != null) {
|
||||
this.after(this, this.#homingTarget)
|
||||
if (this.#after != null) {
|
||||
this.#after(this, this.#homingTarget)
|
||||
}
|
||||
|
||||
this.despawn()
|
||||
}
|
||||
|
||||
#checkStationaryCollisions() {
|
||||
if (this.onCollide == null) { return }
|
||||
if (this.#onCollide == null) { return }
|
||||
|
||||
const bbox = this.bbox
|
||||
const entitiesAndTerrains = this.game?.entities ?? []
|
||||
@@ -82,7 +95,7 @@ export default class Projectile {
|
||||
if (bboxCheckedObstacles.length > 0) {
|
||||
const colliders = bboxCheckedObstacles.map((it) => it.colliders()).flat()
|
||||
const collider = this.collider()
|
||||
colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.onCollide(this, it))
|
||||
colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.#onCollide(this, it))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,14 +112,14 @@ export default class Projectile {
|
||||
this.position.add(step)
|
||||
}
|
||||
|
||||
if (this.onCollide != null) {
|
||||
if (this.#onCollide != null) {
|
||||
const bbox = Entity.tunnelBbox(position.x, position.y, destination.x, destination.y, this.radius)
|
||||
const entitiesAndTerrains = this.game?.entities ?? []
|
||||
const bboxCheckedObstacles = entitiesAndTerrains.filter((it) => SATX.bboxCheck(bbox, it.bbox))
|
||||
if (bboxCheckedObstacles.length > 0) {
|
||||
const colliders = bboxCheckedObstacles.map((it) => it.colliders()).flat()
|
||||
const collider = Entity.tunnelCollider(prevPos.x, prevPos.y, this.position.x, this.position.y, this.radius)
|
||||
colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.onCollide(this, it))
|
||||
colliders.filter((it) => SATX.collideObject(collider, it)).forEach((it) => this.#onCollide(this, it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default class Template {
|
||||
height: options.ranged ? 40 : 38,
|
||||
logic: this.#minionLogic(options.route),
|
||||
maxHealth: options.ranged ? 300 : 450,
|
||||
pathfindingCooldown: 0.2,
|
||||
position: options.route?.at(0) ?? new Vector2(0, 0),
|
||||
radius: 48,
|
||||
speed: 325,
|
||||
|
||||
Reference in New Issue
Block a user