add catching up mechanic for ticks

This commit is contained in:
2025-01-19 18:03:20 +09:00
parent 04cc3f951e
commit 072204b902
7 changed files with 51 additions and 27 deletions
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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))
}
}
}
+1
View File
@@ -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,