Use variable physics tick rate

This commit is contained in:
asraelite 2024-05-13 18:58:16 +02:00 committed by Markus Scully
parent bf71f55130
commit b159b58973
12 changed files with 86 additions and 67 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
*.map *.map
dist/improcket.min.js dist/improcket.min.js
node_modules node_modules
.DS_Store

View file

@ -27,29 +27,29 @@ export const mapping = {
let held, pressed; let held, pressed;
export function tick() { export function tick(delta: number) {
held = input.keyCode.held; held = input.keyCode.held;
pressed = input.keyCode.pressed; pressed = input.keyCode.pressed;
if (state.editing) { if (state.editing) {
tickEditing(); tickEditing();
} else if (state.playing && !state.gameOver && !state.paused) { } else if (state.playing && !state.gameOver && !state.paused) {
tickPlaying(); tickPlaying(delta);
} }
if (!state.editing) { if (!state.editing) {
if (input.mouse.scroll !== 0) { if (input.mouse.scroll !== 0) {
// Fix for Firefox. // Fix for Firefox.
let delta = input.mouse.scroll > 0 ? -50 : 50; let scrollDelta = input.mouse.scroll > 0 ? -50 : 50;
graphics.changeZoom(delta); graphics.changeZoom(scrollDelta);
} }
if (held[mapping.zoomIn]) { if (held[mapping.zoomIn]) {
graphics.changeZoom(-10); graphics.changeZoom(-10 * delta);
} }
if (held[mapping.zoomOut]) { if (held[mapping.zoomOut]) {
graphics.changeZoom(10); graphics.changeZoom(10 * delta);
} }
if (pressed[mapping.togglePause] && !state.gameOver) { if (pressed[mapping.togglePause] && !state.gameOver) {
@ -74,8 +74,8 @@ export function tick() {
} }
} }
function tickPlaying() { function tickPlaying(delta: number) {
let power = held[mapping.reduce] ? 0.3 : 1; let power = (held[mapping.reduce] ? 0.3 : 1) * delta;
if (held[mapping.thrust] && playerShip.fuel !== 0) { if (held[mapping.thrust] && playerShip.fuel !== 0) {
playerShip.applyThrust({ forward: power }); playerShip.applyThrust({ forward: power });

View file

@ -64,7 +64,6 @@ export function toMenu() {
} }
export function togglePause() { export function togglePause() {
console.log(game.state.paused);
game.state.paused = !game.state.paused; game.state.paused = !game.state.paused;
audio.play('pause'); audio.play('pause');
if (game.state.paused) { if (game.state.paused) {

View file

@ -25,7 +25,7 @@ export async function init() {
gui.init(); gui.init();
input.init(); input.init();
events.playMusic(); // events.playMusic();
//events.startGame(); //events.startGame();
loop(tick); loop(tick);
@ -52,27 +52,36 @@ export function changeView(view) {
} }
} }
function loop(fn, fps = 60) { async function loop(fn) {
let then = Date.now(); let then = Date.now();
let interval = 1000 / fps;
(function loop(time) { while (true) {
fn(); const delta = (Date.now() - then) * 60;
fn(delta / 1000);
then = Date.now();
requestAnimationFrame(loop); await new Promise(resolve => requestAnimationFrame(resolve));
})(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));
}
}; };
function tick() { function tick(delta: number) {
events.tick(); events.tick();
if (state.view == 'game' && !state.paused) { if (state.view == 'game' && !state.paused) {
world.tick(); world.tick(delta);
} }
control.tick(); control.tick(delta);
gui.tick(); gui.tick();
graphics.render(); graphics.render(delta);
input.tick(); input.tick();
} }

View file

@ -9,7 +9,7 @@ import * as consts from '../consts';
const TAU = consts.TAU; const TAU = consts.TAU;
export let canvas, context, tempCanvas, tempContext; export let canvas, context, tempCanvas, tempContext;
export let perspective; export let perspective: Perspective;
export let trace = true; export let trace = true;
export let markers = true; export let markers = true;
@ -36,7 +36,7 @@ export function init() {
context.fillText('Loading...', canvas.width / 2, canvas.height / 2); 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.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = '#000'; context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height); context.fillRect(0, 0, canvas.width, canvas.height);
@ -46,7 +46,7 @@ export function render() {
context.clip(); context.clip();
context.save(); context.save();
perspective.tick(); perspective.tick(delta);
perspective.transformRotate(); perspective.transformRotate();
renderBackground(perspective.rotation); renderBackground(perspective.rotation);
perspective.transformCanvas(); perspective.transformCanvas();
@ -171,7 +171,7 @@ class Perspective {
return Math.atan2(Math.sin(b - a), Math.cos(b - a)); return Math.atan2(Math.sin(b - a), Math.cos(b - a));
} }
tick() { tick(delta: number) {
if (this.focus !== null) if (this.focus !== null)
[this.x, this.y] = this.focus.com; [this.x, this.y] = this.focus.com;
@ -192,12 +192,11 @@ class Perspective {
this.normalize(); this.normalize();
let dif = Math.abs(this.targetRotation - this.rotation); let dif = Math.abs(this.targetRotation - this.rotation);
this.rotationMet = dif < (this.rotationMet ? 0.3 : 0.05);
this.rotation = this.currentRotation; this.rotation = this.currentRotation;
this.zoom = this.currentZoom; this.zoom = this.currentZoom;
this.transition *= 0.9; this.transition *= 0.9 ** delta;
this.zoomTransition *= this.zoomTransitionSpeed; this.zoomTransition *= this.zoomTransitionSpeed;
} }

View file

@ -64,22 +64,22 @@ export default class Body {
return this.rotateVector(x, y, this.r); return this.rotateVector(x, y, this.r);
} }
tickMotion(speed = 1) { tickMotion(delta: number) {
this.x += this.xvel * speed; this.x += this.xvel * delta;
this.y += this.yvel * speed; this.y += this.yvel * delta;
this.r += this.rvel * speed; this.r += this.rvel * delta;
this.rvel *= this.rfriction * speed; this.rvel *= this.rfriction ** delta;
} }
tickGravity(bodies, speed = 1) { tickGravity(delta: number, bodies) {
for (let body of bodies) { for (let body of bodies) {
const distanceSquared = this.distanceToSquared(body); const distanceSquared = this.distanceToSquared(body);
if (distanceSquared > (1000 ** 2)) continue; if (distanceSquared > (1000 ** 2)) continue;
let force = body.mass / distanceSquared * G; let force = body.mass / distanceSquared * G;
let [[ax, ay], [bx, by]] = [this.com, body.com]; let [[ax, ay], [bx, by]] = [this.com, body.com];
let angle = Math.atan2(by - ay, bx - ax); let angle = Math.atan2(by - ay, bx - ax);
this.xvel += Math.cos(angle) * force * speed; this.xvel += Math.cos(angle) * force * delta;
this.yvel += Math.sin(angle) * force * speed; this.yvel += Math.sin(angle) * force * delta;
} }
} }
@ -111,11 +111,11 @@ export default class Body {
this.yvel = 0; this.yvel = 0;
} }
applyDirectionalForce(x, y, r) { applyDirectionalForce(x, y, rotation: number) {
let [vx, vy] = this.rotateVector(x, y); let [vx, vy] = this.rotateVector(x, y);
this.xvel += vx / this.mass; this.xvel += vx / this.mass;
this.yvel += vy / this.mass; this.yvel += vy / this.mass;
this.rvel += r / this.mass; this.rvel += rotation / this.mass;
} }
orbit(cel, altitude, angle = 0) { orbit(cel, altitude, angle = 0) {

View file

@ -32,7 +32,7 @@ export default class Celestial extends Body {
} }
tick() { tick(delta: number) {
} }

View file

@ -46,12 +46,12 @@ export default class Entity extends Body {
entities.delete(this); entities.delete(this);
} }
tick() { tick(delta: number) {
if (Math.abs(playerShip.x - this.x) > 500 || if (Math.abs(playerShip.x - this.x) > 500 ||
Math.abs(playerShip.y - this.y) > 500) return; Math.abs(playerShip.y - this.y) > 500) return;
this.r += consts.ENTITY_ROTATION_RATE; this.r += consts.ENTITY_ROTATION_RATE;
this.tickMotion(); this.tickMotion(delta);
if (this.gravity) this.tickGravity(celestials); if (this.gravity) this.tickGravity(delta, celestials);
let col = this.getCelestialCollision(celestials); let col = this.getCelestialCollision(celestials);
if (col !== false) { if (col !== false) {

View file

@ -1,13 +1,18 @@
import * as spawn from './spawn'; import * as spawn from './spawn';
import * as graphics from '../graphics/index'; 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 entities: Set<Entity> = new Set();
export const celestials = new Set(); export const celestials: Set<Celestial> = new Set();
export const ships = new Set(); export const ships: Set<Ship> = new Set();
export const particles = new Set(); export const particles: Set<Particle> = new Set();
export const tracers = new Set(); export const tracers: Set<Tracer> = new Set();
export let playerShip = null; export let playerShip: Ship = null;
export let speed = 1; export let speed = 1;
@ -43,14 +48,14 @@ export function decreaseSpeed() {
if (speed > 1) speed -= 1; if (speed > 1) speed -= 1;
} }
export function tick() { export function tick(delta: number) {
for (let i = 0; i < speed; i++) { for (let i = 0; i < speed; i++) {
particles.forEach(p => p.tick()); particles.forEach(p => p.tick(delta));
celestials.forEach(c => c.tick()); celestials.forEach(c => c.tick(delta));
entities.forEach(e => e.tick()); entities.forEach(e => e.tick(delta));
ships.forEach(s => s.tick()); ships.forEach(s => s.tick(delta));
} }
spawn.tick(); spawn.tick();
if (graphics.trace) tracers.forEach(t => t.tick()); if (graphics.trace) tracers.forEach(t => t.tick(delta));
} }

View file

@ -83,7 +83,7 @@ export function createItemToss(ship) {
})); }));
} }
class Particle extends Body { export class Particle extends Body {
constructor(x, y, { constructor(x, y, {
xvel = 0, xvel = 0,
yvel = 0, yvel = 0,
@ -113,14 +113,14 @@ class Particle extends Body {
return [this.x - this.size / 2, this.y - this.size / 2]; return [this.x - this.size / 2, this.y - this.size / 2];
} }
tick() { tick(delta: number) {
if (this.life-- <= 0) { if (this.life-- <= 0) {
particles.delete(this); particles.delete(this);
return; return;
} }
this.tickMotion(); this.tickMotion(delta);
if (this.gravity) this.tickGravity(celestials); if (this.gravity) this.tickGravity(delta, celestials);
this.xvel *= this.friction; this.xvel *= this.friction;
this.yvel *= this.friction; this.yvel *= this.friction;

View file

@ -50,15 +50,18 @@ export default class Ship extends Body {
return closest; return closest;
} }
tick() { tick(delta: number) {
if (this.crashed) return; if (this.crashed) return;
if (!state.editing) this.tickMotion(); if (!state.editing) this.tickMotion(delta);
if (!this.landed) this.tickGravity(world.celestials); if (!this.landed) this.tickGravity(delta, world.celestials);
if (!state.editing) this.resolveCollisions(); if (!state.editing) this.resolveCollisions();
this.modules.forEach(m => { this.modules.forEach(m => {
if (m.type == 'thruster' && m.power !== 0) { 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);
}
} }
}); });

View file

@ -6,8 +6,11 @@ import {celestials, particles, entities} from './index';
import * as particle from './particle'; import * as particle from './particle';
import * as consts from '../consts'; import * as consts from '../consts';
import * as events from '../game/events'; import * as events from '../game/events';
import Ship from './ship';
export default class Tracer extends Body { export default class Tracer extends Body {
ship: Ship;
constructor(ship) { constructor(ship) {
super(...ship.pos, 0.1); super(...ship.pos, 0.1);
@ -39,13 +42,13 @@ export default class Tracer extends Body {
[this.x, this.y] = this.ship.com; [this.x, this.y] = this.ship.com;
} }
tick() { tick(delta: number) {
this.run(this.ship.computation); this.run(this.ship.computation);
} }
tickPath(speed) { tickPath(speed) {
this.tickMotion(speed); this.tickMotion(speed);
this.tickGravity(celestials, speed); this.tickGravity(speed, celestials);
return !!this.getCelestialCollision(celestials); return !!this.getCelestialCollision(celestials);
} }
} }