fix game loop timer

This commit is contained in:
2025-01-12 10:57:22 +09:00
parent e0dd7dcaf3
commit d9849f770b
6 changed files with 123 additions and 13 deletions
+29 -3
View File
@@ -249,6 +249,35 @@ function connectWebSocket() {
terrain.position.set(t.position.x / 100, t.position.y / 100, 0)
}
if (playerId != null) {
const player = state.entities.find((e) => e.id == playerId)
if (player != null) {
for (let abilityIndex = 0; abilityIndex < 4; abilityIndex++) {
if (player.abilities[abilityIndex] != null) {
const ability = player.abilities[abilityIndex]
const lastCast = player.cooldowns[ability.id] ?? -99999
const cooldownDuration = (ability.cooldown * state.tickRate) ?? 0
const remainingCooldown = (lastCast + cooldownDuration) - state.currentTick
let cssPercentage = '100%'
let text = ''
if (remainingCooldown > 0) {
const cooldownPercentage = 1 - (remainingCooldown / cooldownDuration)
cssPercentage = `${Math.round(100 * cooldownPercentage)}%`
if (remainingCooldown / state.tickRate <= 5) {
text = `${(Math.round(10 * remainingCooldown / state.tickRate) / 10).toFixed(1)}`
}
else {
text = `${Math.round(remainingCooldown / state.tickRate)}`
}
}
document.getElementById(`ability-${abilityIndex}-cooldown`).style.clipPath = `polygon(0 ${cssPercentage}, 100% ${cssPercentage}, 100% 100%, 0 100%)`
document.getElementById(`ability-${abilityIndex}-cooldown-text`).innerHTML = text
}
}
}
}
document.getElementById('state').innerHTML = JSON.stringify(state, null, 2)
}
}
@@ -291,9 +320,6 @@ window.addEventListener('load', () => {
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 }))
+69 -1
View File
@@ -56,6 +56,53 @@
border-bottom: none;
border-right: none;
}
.hud {
position: fixed;
gap: 10px;
padding: 15px;
padding-bottom: 10px;
display: flex;
inset: auto 0 0 0;
width: fit-content;
margin: auto;
border: 5px solid gray;
background-color: black;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom: none;
}
.ability {
position: relative;
flex: 1 0 0;
border: 1px solid white;
width: 75px;
height: 75px;
color: white;
}
.cooldown {
position: absolute;
top: 0;
left: 0;
width: 73px;
height: 73px;
background-color: grey;
opacity: 0.4;
}
.cooldown-text {
position: absolute;
top: 0;
left: 0;
width: 73px;
height: 73px;
line-height: 73px;
text-align: center;
color: white;
font-family: monospace;
}
</style>
</head>
<body>
@@ -63,7 +110,28 @@
<p>Connection: <span id="connection"></span></p>
<pre id="state"></pre>
</div>
<!-- TODO: HUD -->
<div class="hud">
<div id="ability-0" class="ability">
A
<div id="ability-0-cooldown" class="cooldown"></div>
<div id="ability-0-cooldown-text" class="cooldown-text"></div>
</div>
<div id="ability-1" class="ability">
Q
<div id="ability-1-cooldown" class="cooldown"></div>
<div id="ability-1-cooldown-text" class="cooldown-text"></div>
</div>
<div id="ability-2" class="ability">
W
<div id="ability-2-cooldown" class="cooldown"></div>
<div id="ability-2-cooldown-text" class="cooldown-text"></div>
</div>
<div id="ability-3" class="ability">
E
<div id="ability-3-cooldown" class="cooldown"></div>
<div id="ability-3-cooldown-text" class="cooldown-text"></div>
</div>
</div>
<!-- TODO: cast indicator -->
<script type="module" src="client.js"></script>
</body>
+6 -2
View File
@@ -4,12 +4,16 @@ export default class Ability {
id = crypto.randomUUID()
name = 'Ability'
cooldown = 0
effect = () => {}
castTime = 0
#effect = () => {}
get effect() { return this.#effect }
set effect(value) { this.#effect = value }
constructor(options) {
if (options.name != null) { this.name = options.name }
if (options.effect != null) { this.effect = options.effect }
if (options.effect != null) { this.#effect = options.effect }
if (options.cooldown != null) { this.cooldown = options.cooldown }
if (options.castTime != null) { this.castTime = options.castTime }
}
-2
View File
@@ -3,7 +3,6 @@ import SAT from 'sat'
import SATX from './satx.js'
import Pathfind from './pathfind.js'
import Ability from './ability.js'
import Effect from './effect.js'
export default class Entity {
id = crypto.randomUUID()
@@ -16,7 +15,6 @@ export default class Entity {
Ability.straightShot(800, 7, 3000),
() => {},
() => {},
() => {},
]
casting = null
// TODO: teams
+19 -4
View File
@@ -8,12 +8,14 @@ export default class Game {
currentTick = 0
width = 2000
height = 2000
running = false
nextTick = 0
#entities = []
#eventEmitter = new EventEmitter()
#projectiles = []
#terrains = []
#tickBudget = Math.floor(1000 / this.tickRate)
#tickBudget = 1000 / this.tickRate
get entities() { return this.#entities }
get eventEmitter() { return this.#eventEmitter }
@@ -80,10 +82,23 @@ export default class Game {
}
}
gameLoop() {
const tickBudget = this.#tickBudget
if (this.nextTick != null) {
const nextTick = this.nextTick
this.nextTick = null
let start = performance.now()
while (start < nextTick) { start = performance.now() }
this.update()
this.nextTick = start + tickBudget
}
}
start() {
const start = performance.now()
this.update()
setTimeout(() => this.start(), Math.max(0, this.#tickBudget - (performance.now() - start)))
setInterval(() => this.gameLoop(), 1)
}
update() {
-1
View File
@@ -3,7 +3,6 @@ import { WebSocketExpress } from 'websocket-express'
import Game from './game.js'
import Entity from './entity.js'
import Terrain from './terrain.js'
import SATX from './satx.js'
import { Vector2 } from 'three'
const app = new WebSocketExpress()