From b159b589736146b6db3f09295d1fdeb426b70dfe Mon Sep 17 00:00:00 2001 From: asraelite Date: Mon, 13 May 2024 18:58:16 +0200 Subject: [PATCH] Use variable physics tick rate --- .gitignore | 1 + src/game/control.ts | 16 ++++++++-------- src/game/events.ts | 1 - src/game/index.ts | 33 +++++++++++++++++++++------------ src/graphics/index.ts | 11 +++++------ src/world/body.ts | 20 ++++++++++---------- src/world/celestial.ts | 2 +- src/world/entity.ts | 6 +++--- src/world/index.ts | 29 +++++++++++++++++------------ src/world/particle.ts | 8 ++++---- src/world/ship.ts | 19 +++++++++++-------- src/world/tracer.ts | 7 +++++-- 12 files changed, 86 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index ebe1430..655f2e3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.map dist/improcket.min.js node_modules +.DS_Store diff --git a/src/game/control.ts b/src/game/control.ts index eab0834..6355585 100644 --- a/src/game/control.ts +++ b/src/game/control.ts @@ -27,29 +27,29 @@ export const mapping = { let held, pressed; -export function tick() { +export function tick(delta: number) { held = input.keyCode.held; pressed = input.keyCode.pressed; if (state.editing) { tickEditing(); } else if (state.playing && !state.gameOver && !state.paused) { - tickPlaying(); + tickPlaying(delta); } if (!state.editing) { if (input.mouse.scroll !== 0) { // Fix for Firefox. - let delta = input.mouse.scroll > 0 ? -50 : 50; - graphics.changeZoom(delta); + let scrollDelta = input.mouse.scroll > 0 ? -50 : 50; + graphics.changeZoom(scrollDelta); } if (held[mapping.zoomIn]) { - graphics.changeZoom(-10); + graphics.changeZoom(-10 * delta); } if (held[mapping.zoomOut]) { - graphics.changeZoom(10); + graphics.changeZoom(10 * delta); } if (pressed[mapping.togglePause] && !state.gameOver) { @@ -74,8 +74,8 @@ export function tick() { } } -function tickPlaying() { - let power = held[mapping.reduce] ? 0.3 : 1; +function tickPlaying(delta: number) { + let power = (held[mapping.reduce] ? 0.3 : 1) * delta; if (held[mapping.thrust] && playerShip.fuel !== 0) { playerShip.applyThrust({ forward: power }); diff --git a/src/game/events.ts b/src/game/events.ts index 57a6b21..ab930e6 100644 --- a/src/game/events.ts +++ b/src/game/events.ts @@ -64,7 +64,6 @@ export function toMenu() { } export function togglePause() { - console.log(game.state.paused); game.state.paused = !game.state.paused; audio.play('pause'); if (game.state.paused) { diff --git a/src/game/index.ts b/src/game/index.ts index c87e477..0207634 100644 --- a/src/game/index.ts +++ b/src/game/index.ts @@ -25,7 +25,7 @@ export async function init() { gui.init(); input.init(); - events.playMusic(); + // events.playMusic(); //events.startGame(); loop(tick); @@ -52,27 +52,36 @@ export function changeView(view) { } } -function loop(fn, fps = 60) { - let then = Date.now(); - let interval = 1000 / fps; +async function loop(fn) { + let then = Date.now(); - (function loop(time) { - fn(); + while (true) { + const delta = (Date.now() - then) * 60; + fn(delta / 1000); + then = Date.now(); - requestAnimationFrame(loop); - })(0); + await new Promise(resolve => requestAnimationFrame(resolve)); + // await new Promise(resolve => requestAnimationFrame(resolve)); + // await new Promise(resolve => requestAnimationFrame(resolve)); + // await new Promise(resolve => requestAnimationFrame(resolve)); + // await new Promise(resolve => requestAnimationFrame(resolve)); + // await new Promise(resolve => requestAnimationFrame(resolve)); + // await new Promise(resolve => requestAnimationFrame(resolve)); + // await new Promise(resolve => requestAnimationFrame(resolve)); + // await new Promise(resolve => requestAnimationFrame(resolve)); + } }; -function tick() { +function tick(delta: number) { events.tick(); if (state.view == 'game' && !state.paused) { - world.tick(); + world.tick(delta); } - control.tick(); + control.tick(delta); gui.tick(); - graphics.render(); + graphics.render(delta); input.tick(); } diff --git a/src/graphics/index.ts b/src/graphics/index.ts index 8bbe2fd..9dd66c6 100644 --- a/src/graphics/index.ts +++ b/src/graphics/index.ts @@ -9,7 +9,7 @@ import * as consts from '../consts'; const TAU = consts.TAU; export let canvas, context, tempCanvas, tempContext; -export let perspective; +export let perspective: Perspective; export let trace = true; export let markers = true; @@ -36,7 +36,7 @@ export function init() { context.fillText('Loading...', canvas.width / 2, canvas.height / 2); } -export function render() { +export function render(delta: number) { context.clearRect(0, 0, canvas.width, canvas.height); context.fillStyle = '#000'; context.fillRect(0, 0, canvas.width, canvas.height); @@ -46,7 +46,7 @@ export function render() { context.clip(); context.save(); - perspective.tick(); + perspective.tick(delta); perspective.transformRotate(); renderBackground(perspective.rotation); perspective.transformCanvas(); @@ -171,7 +171,7 @@ class Perspective { return Math.atan2(Math.sin(b - a), Math.cos(b - a)); } - tick() { + tick(delta: number) { if (this.focus !== null) [this.x, this.y] = this.focus.com; @@ -192,12 +192,11 @@ class Perspective { this.normalize(); let dif = Math.abs(this.targetRotation - this.rotation); - this.rotationMet = dif < (this.rotationMet ? 0.3 : 0.05); this.rotation = this.currentRotation; this.zoom = this.currentZoom; - this.transition *= 0.9; + this.transition *= 0.9 ** delta; this.zoomTransition *= this.zoomTransitionSpeed; } diff --git a/src/world/body.ts b/src/world/body.ts index 687c20a..cc914a3 100644 --- a/src/world/body.ts +++ b/src/world/body.ts @@ -64,22 +64,22 @@ export default class Body { return this.rotateVector(x, y, this.r); } - tickMotion(speed = 1) { - this.x += this.xvel * speed; - this.y += this.yvel * speed; - this.r += this.rvel * speed; - this.rvel *= this.rfriction * speed; + tickMotion(delta: number) { + this.x += this.xvel * delta; + this.y += this.yvel * delta; + this.r += this.rvel * delta; + this.rvel *= this.rfriction ** delta; } - tickGravity(bodies, speed = 1) { + tickGravity(delta: number, bodies) { for (let body of bodies) { const distanceSquared = this.distanceToSquared(body); if (distanceSquared > (1000 ** 2)) continue; let force = body.mass / distanceSquared * G; let [[ax, ay], [bx, by]] = [this.com, body.com]; let angle = Math.atan2(by - ay, bx - ax); - this.xvel += Math.cos(angle) * force * speed; - this.yvel += Math.sin(angle) * force * speed; + this.xvel += Math.cos(angle) * force * delta; + this.yvel += Math.sin(angle) * force * delta; } } @@ -111,11 +111,11 @@ export default class Body { this.yvel = 0; } - applyDirectionalForce(x, y, r) { + applyDirectionalForce(x, y, rotation: number) { let [vx, vy] = this.rotateVector(x, y); this.xvel += vx / this.mass; this.yvel += vy / this.mass; - this.rvel += r / this.mass; + this.rvel += rotation / this.mass; } orbit(cel, altitude, angle = 0) { diff --git a/src/world/celestial.ts b/src/world/celestial.ts index 626c4b1..8d377b9 100644 --- a/src/world/celestial.ts +++ b/src/world/celestial.ts @@ -32,7 +32,7 @@ export default class Celestial extends Body { } - tick() { + tick(delta: number) { } diff --git a/src/world/entity.ts b/src/world/entity.ts index 3d406a4..0aceeb3 100644 --- a/src/world/entity.ts +++ b/src/world/entity.ts @@ -46,12 +46,12 @@ export default class Entity extends Body { entities.delete(this); } - tick() { + tick(delta: number) { if (Math.abs(playerShip.x - this.x) > 500 || Math.abs(playerShip.y - this.y) > 500) return; this.r += consts.ENTITY_ROTATION_RATE; - this.tickMotion(); - if (this.gravity) this.tickGravity(celestials); + this.tickMotion(delta); + if (this.gravity) this.tickGravity(delta, celestials); let col = this.getCelestialCollision(celestials); if (col !== false) { diff --git a/src/world/index.ts b/src/world/index.ts index 78b7373..a2dc96a 100644 --- a/src/world/index.ts +++ b/src/world/index.ts @@ -1,13 +1,18 @@ import * as spawn from './spawn'; import * as graphics from '../graphics/index'; +import { Particle } from './particle'; +import Tracer from './tracer'; +import Ship from './ship'; +import Celestial from './celestial'; +import Entity from './entity'; -export const entities = new Set(); -export const celestials = new Set(); -export const ships = new Set(); -export const particles = new Set(); -export const tracers = new Set(); +export const entities: Set = new Set(); +export const celestials: Set = new Set(); +export const ships: Set = new Set(); +export const particles: Set = new Set(); +export const tracers: Set = new Set(); -export let playerShip = null; +export let playerShip: Ship = null; export let speed = 1; @@ -43,14 +48,14 @@ export function decreaseSpeed() { if (speed > 1) speed -= 1; } -export function tick() { +export function tick(delta: number) { for (let i = 0; i < speed; i++) { - particles.forEach(p => p.tick()); - celestials.forEach(c => c.tick()); - entities.forEach(e => e.tick()); - ships.forEach(s => s.tick()); + particles.forEach(p => p.tick(delta)); + celestials.forEach(c => c.tick(delta)); + entities.forEach(e => e.tick(delta)); + ships.forEach(s => s.tick(delta)); } spawn.tick(); - if (graphics.trace) tracers.forEach(t => t.tick()); + if (graphics.trace) tracers.forEach(t => t.tick(delta)); } diff --git a/src/world/particle.ts b/src/world/particle.ts index 676f847..ea340d4 100644 --- a/src/world/particle.ts +++ b/src/world/particle.ts @@ -83,7 +83,7 @@ export function createItemToss(ship) { })); } -class Particle extends Body { +export class Particle extends Body { constructor(x, y, { xvel = 0, yvel = 0, @@ -113,14 +113,14 @@ class Particle extends Body { return [this.x - this.size / 2, this.y - this.size / 2]; } - tick() { + tick(delta: number) { if (this.life-- <= 0) { particles.delete(this); return; } - this.tickMotion(); - if (this.gravity) this.tickGravity(celestials); + this.tickMotion(delta); + if (this.gravity) this.tickGravity(delta, celestials); this.xvel *= this.friction; this.yvel *= this.friction; diff --git a/src/world/ship.ts b/src/world/ship.ts index 0f1bf7c..871e1ee 100644 --- a/src/world/ship.ts +++ b/src/world/ship.ts @@ -5,7 +5,7 @@ import * as consts from '../consts'; import * as particle from './particle'; import * as events from '../game/events'; import Tracer from './tracer'; -import {state} from '../game/index'; +import { state } from '../game/index'; export default class Ship extends Body { constructor(x, y) { @@ -50,15 +50,18 @@ export default class Ship extends Body { return closest; } - tick() { + tick(delta: number) { if (this.crashed) return; - if (!state.editing) this.tickMotion(); - if (!this.landed) this.tickGravity(world.celestials); + if (!state.editing) this.tickMotion(delta); + if (!this.landed) this.tickGravity(delta, world.celestials); if (!state.editing) this.resolveCollisions(); this.modules.forEach(m => { if (m.type == 'thruster' && m.power !== 0) { - for (let i = 0; i < 2; i++) particle.createThrustExhaust(m); + for (let i = 0; i < 20; i++) { + if (Math.random() > (delta / 10)) continue; + particle.createThrustExhaust(m); + } } }); @@ -89,7 +92,7 @@ export default class Ship extends Body { } addModule(x, y, properties, options) { - let module = new Module(x, y, this, {...properties, ...options}); + let module = new Module(x, y, this, { ...properties, ...options }); this.modules.add(module); this.refreshShape(); } @@ -102,7 +105,7 @@ export default class Ship extends Body { refreshShape() { let points = []; this.modules.forEach(m => points.push([...m.localCom, m.mass])); - this.mass = points.reduce((a, [,,b]) => a + b, 0); + this.mass = points.reduce((a, [, , b]) => a + b, 0); this.localCom = points.reduce(([ax, ay], [bx, by, bm]) => [ax + bx * bm, ay + by * bm], [0, 0]) .map(x => x / this.mass); @@ -214,7 +217,7 @@ export default class Ship extends Body { } applyThrust({ forward = 0, left = 0, right = 0, back = 0, - turnLeft = 0, turnRight = 0}) { + turnLeft = 0, turnRight = 0 }) { let thrustForce = -forward * consts.THRUST_POWER * this.thrust; let turnForce = (turnRight - turnLeft) * consts.TURN_POWER; diff --git a/src/world/tracer.ts b/src/world/tracer.ts index dea7eef..ca4b968 100644 --- a/src/world/tracer.ts +++ b/src/world/tracer.ts @@ -6,8 +6,11 @@ import {celestials, particles, entities} from './index'; import * as particle from './particle'; import * as consts from '../consts'; import * as events from '../game/events'; +import Ship from './ship'; export default class Tracer extends Body { + ship: Ship; + constructor(ship) { super(...ship.pos, 0.1); @@ -39,13 +42,13 @@ export default class Tracer extends Body { [this.x, this.y] = this.ship.com; } - tick() { + tick(delta: number) { this.run(this.ship.computation); } tickPath(speed) { this.tickMotion(speed); - this.tickGravity(celestials, speed); + this.tickGravity(speed, celestials); return !!this.getCelestialCollision(celestials); } }