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) 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) document.getElementById('state').innerHTML = JSON.stringify(state, null, 2)
} }
} }
@@ -291,9 +320,6 @@ window.addEventListener('load', () => {
if (event.code == 'KeyE') { if (event.code == 'KeyE') {
websocket.send(JSON.stringify({ action: 'cast', slot: 3, id: playerId, x, y })) 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') { if (event.code == 'KeyD') {
websocket.send(JSON.stringify({ action: 'teleport', id: playerId, x, y })) websocket.send(JSON.stringify({ action: 'teleport', id: playerId, x, y }))
+69 -1
View File
@@ -56,6 +56,53 @@
border-bottom: none; border-bottom: none;
border-right: 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> </style>
</head> </head>
<body> <body>
@@ -63,7 +110,28 @@
<p>Connection: <span id="connection"></span></p> <p>Connection: <span id="connection"></span></p>
<pre id="state"></pre> <pre id="state"></pre>
</div> </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 --> <!-- TODO: cast indicator -->
<script type="module" src="client.js"></script> <script type="module" src="client.js"></script>
</body> </body>
+6 -2
View File
@@ -4,12 +4,16 @@ export default class Ability {
id = crypto.randomUUID() id = crypto.randomUUID()
name = 'Ability' name = 'Ability'
cooldown = 0 cooldown = 0
effect = () => {}
castTime = 0 castTime = 0
#effect = () => {}
get effect() { return this.#effect }
set effect(value) { this.#effect = value }
constructor(options) { constructor(options) {
if (options.name != null) { this.name = options.name } 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.cooldown != null) { this.cooldown = options.cooldown }
if (options.castTime != null) { this.castTime = options.castTime } 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 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 Effect from './effect.js'
export default class Entity { export default class Entity {
id = crypto.randomUUID() id = crypto.randomUUID()
@@ -16,7 +15,6 @@ export default class Entity {
Ability.straightShot(800, 7, 3000), Ability.straightShot(800, 7, 3000),
() => {}, () => {},
() => {}, () => {},
() => {},
] ]
casting = null casting = null
// TODO: teams // TODO: teams
+19 -4
View File
@@ -8,12 +8,14 @@ export default class Game {
currentTick = 0 currentTick = 0
width = 2000 width = 2000
height = 2000 height = 2000
running = false
nextTick = 0
#entities = [] #entities = []
#eventEmitter = new EventEmitter() #eventEmitter = new EventEmitter()
#projectiles = [] #projectiles = []
#terrains = [] #terrains = []
#tickBudget = Math.floor(1000 / this.tickRate) #tickBudget = 1000 / this.tickRate
get entities() { return this.#entities } get entities() { return this.#entities }
get eventEmitter() { return this.#eventEmitter } get eventEmitter() { return this.#eventEmitter }
@@ -80,10 +82,23 @@ export default class Game {
} }
} }
start() { gameLoop() {
const start = performance.now() 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.update()
setTimeout(() => this.start(), Math.max(0, this.#tickBudget - (performance.now() - start))) this.nextTick = start + tickBudget
}
}
start() {
setInterval(() => this.gameLoop(), 1)
} }
update() { update() {
-1
View File
@@ -3,7 +3,6 @@ import { WebSocketExpress } from 'websocket-express'
import Game from './game.js' 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 SATX from './satx.js'
import { Vector2 } from 'three' import { Vector2 } from 'three'
const app = new WebSocketExpress() const app = new WebSocketExpress()