From 8c1ab4c5756ac43d52bc8773f5e72dde90f79e77 Mon Sep 17 00:00:00 2001 From: Joris Date: Tue, 5 Jul 2022 22:11:00 +0200 Subject: WIP --- README.md | 6 +++++ public/index.html | 18 +------------ src/controls.ts | 8 +++--- src/main.ts | 7 +++++ src/model/size.ts | 8 ++++++ src/model/vec2.ts | 73 ++++++++++++++++++++++++++++++++++++++++++++++---- src/screen.ts | 11 +++++--- src/util/physics.ts | 40 ++++++++++++++++++++++++++++ src/view/colors.ts | 2 +- src/view/scene.ts | 32 +++++++++++++++++----- src/view/ship.ts | 76 +++++++++++++++++++++++++++++++++-------------------- tsconfig.json | 2 +- 12 files changed, 216 insertions(+), 67 deletions(-) create mode 100644 src/model/size.ts create mode 100644 src/util/physics.ts diff --git a/README.md b/README.md index 47c92d0..2b7f37e 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,9 @@ nix develop --command bin/watch.sh ``` Then, open your browser at `http://localhost:8000`. + +# Todo + +[ ] Screen size equivalence +[ ] Add rocks to avoid +[ ] Destroy rocks with missiles diff --git a/public/index.html b/public/index.html index bcedaf9..32f4bc7 100644 --- a/public/index.html +++ b/public/index.html @@ -6,22 +6,6 @@ - - - + diff --git a/src/controls.ts b/src/controls.ts index b575b3f..8ae0dbf 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -3,7 +3,7 @@ export interface Controls { right: boolean, down: boolean, left: boolean, - space: boolean, + spaceCount: number, } export let current = { @@ -11,7 +11,7 @@ export let current = { right: false, down: false, left: false, - space: false, + spaceCount: 0, } document.addEventListener('keydown', event => { @@ -31,8 +31,8 @@ function update(current: Controls, key: string, isDown: boolean): Controls { return { ...current, down: isDown } else if (key === 'ArrowLeft') return { ...current, left: isDown } - else if (key === ' ') - return { ...current, space: isDown } + else if (key === ' ' && !isDown) + return { ...current, spaceCount: current.spaceCount + 1 } else return current } diff --git a/src/main.ts b/src/main.ts index 53bc487..c235929 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,3 +9,10 @@ function loop(timestamp: number) { } window.requestAnimationFrame(loop) + +window.onresize = () => Scene.resize( + scene, + { width: window.innerWidth, + height: window.innerHeight + } +) diff --git a/src/model/size.ts b/src/model/size.ts new file mode 100644 index 0000000..5e8a778 --- /dev/null +++ b/src/model/size.ts @@ -0,0 +1,8 @@ +export interface Size { + width: number, + height: number, +} + +export function diagonal(size: Size): number { + return Math.sqrt(size.width ** 2 + size.height ** 2) +} diff --git a/src/model/vec2.ts b/src/model/vec2.ts index 4bec53d..46ffc7e 100644 --- a/src/model/vec2.ts +++ b/src/model/vec2.ts @@ -1,15 +1,78 @@ +import * as Number from 'util/number' +import * as Size from 'model/size' + export interface Vec2 { x: number, y: number, } -export function zero(): Vec2 { - return { - x: 0, - y: 0, - } +export const zero: Vec2 = { + x: 0, + y: 0, } export function equals(v1: Vec2, v2: Vec2): boolean { return v1.x === v2.x && v1.y === v2.y } + +export function length(v: Vec2): number { + return Math.sqrt(v.x ** 2 + v.y ** 2) +} + +export function normalize(v: Vec2): Vec2 { + if (v.x === 0 && v.y === 0) { + return zero + } else { + const im = 1 / length(v) + return { + x: v.x * im, + y: v.y * im + } + } +} + +export function applyOnLength(f: (n: number) => number, v: Vec2): Vec2 { + return scale(normalize(v), f(length(v))) +} + +export function sub(v1: Vec2, v2: Vec2): Vec2 { + return { + x: v1.x - v2.x, + y: v1.y - v2.y + } +} + +export function div(v: Vec2, k: number): Vec2 { + return { + x: v.x / k, + y: v.y / k + } +} + +export function add(v1: Vec2, v2: Vec2): Vec2 { + return { + x: v1.x + v2.x, + y: v1.y + v2.y + } +} + +export function scale(v: Vec2, k: number): Vec2 { + return { + x: v.x * k, + y: v.y * k + } +} + +export function clamp(v: Vec2, vMin: Vec2, vMax: Vec2): Vec2 { + return { + x: Number.clamp(v.x, vMin.x, vMax.x), + y: Number.clamp(v.y, vMin.y, vMax.y) + } +} + +export function project(from: Size.Size, to: Size.Size, v: Vec2): Vec2 { + return { + x: v.x / from.width * to.width, + y: v.y / from.height * to.height + } +} diff --git a/src/screen.ts b/src/screen.ts index 930534e..0567ad9 100644 --- a/src/screen.ts +++ b/src/screen.ts @@ -1,4 +1,9 @@ -let canvas = document.querySelector('canvas') as HTMLCanvasElement +export let size = { + x: window.innerWidth, + y: window.innerHeight +} -export let width: number = canvas.width -export let height: number = canvas.height +window.onresize = () => { + size.x = window.innerWidth + size.y = window.innerHeight +} diff --git a/src/util/physics.ts b/src/util/physics.ts new file mode 100644 index 0000000..cbc82f7 --- /dev/null +++ b/src/util/physics.ts @@ -0,0 +1,40 @@ +import * as Vec2 from 'model/vec2' + +interface AccParams { + dt: number, + speed: Vec2.Vec2, + dir: Vec2.Vec2 +} + +// max_acc = v_length(board_size) / 90000 +const maxAcc: number = 0.04 +const inertia: number = 200 + +export function acc({ dt, speed, dir }: AccParams): Vec2.Vec2 { + const v = Vec2.applyOnLength((x) => x * maxAcc, dir) + return Vec2.div(Vec2.sub(Vec2.scale(v, dt), speed), inertia) +} + +interface SpeedParams { + dt: number, + acc: Vec2.Vec2, + speed: Vec2.Vec2 +} + +export function speed({ dt, acc, speed }: SpeedParams): Vec2.Vec2 { + return Vec2.add(Vec2.scale(acc, dt), speed) +} + +interface PosParams { + dt: number, + acc: Vec2.Vec2, + speed: Vec2.Vec2, + pos: Vec2.Vec2 +} + +export function pos({ dt, acc, speed, pos }: PosParams): Vec2.Vec2 { + return Vec2.add( + Vec2.scale(acc, (dt ** 2) / 2), + Vec2.add(Vec2.scale(speed, dt), pos) + ) +} diff --git a/src/view/colors.ts b/src/view/colors.ts index c663d25..725e981 100644 --- a/src/view/colors.ts +++ b/src/view/colors.ts @@ -1,4 +1,4 @@ -export let colors = { +export const colors = { blue: "#333388", red: "#CC3333", } diff --git a/src/view/scene.ts b/src/view/scene.ts index fe88c12..6d35edc 100644 --- a/src/view/scene.ts +++ b/src/view/scene.ts @@ -1,35 +1,53 @@ import * as Ship from 'view/ship' import * as Colors from 'view/colors' -import * as Screen from 'screen' +import * as Vec2 from 'model/vec2' +import * as Size from 'model/size' export interface State { + canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, timestamp: number, ship: Ship.State, + windowSize: Size.Size, } export function init(): State { - let canvas = document.querySelector('canvas') as HTMLCanvasElement - let context = canvas.getContext("2d") as CanvasRenderingContext2D + const canvas = document.querySelector('canvas') as HTMLCanvasElement + const context = canvas.getContext("2d") as CanvasRenderingContext2D + const windowSize = { + width: window.innerWidth, + height: window.innerHeight + } + canvas.width = windowSize.width + canvas.height = windowSize.height return { + canvas, context, timestamp: 0, - ship: Ship.init(), + ship: Ship.init(windowSize), + windowSize } } export function update(state: State, timestamp: number) { - let delta = timestamp - state.timestamp + const dt = timestamp - state.timestamp state.timestamp = timestamp - Ship.update(state.ship, state.timestamp, delta) + Ship.update(state.ship, dt, state.windowSize) +} + +export function resize(state: State, size: Size.Size) { + Ship.project(state.ship, state.windowSize, size) + state.windowSize = size + state.canvas.width = size.width + state.canvas.height = size.height } export function view(state: State) { // Clear state.context.fillStyle = Colors.colors.blue - state.context.fillRect(0, 0, Screen.width, Screen.height) + state.context.fillRect(0, 0, state.windowSize.width, state.windowSize.height) Ship.view(state.context, state.ship) } diff --git a/src/view/ship.ts b/src/view/ship.ts index 92590d3..5a8b86f 100644 --- a/src/view/ship.ts +++ b/src/view/ship.ts @@ -1,51 +1,64 @@ import * as Controls from 'controls' import * as Vec2 from 'model/vec2' +import * as Size from 'model/size' import * as Number from 'util/number' -import * as Screen from 'screen' import * as Colors from 'view/colors' +import * as Physics from 'util/physics' -export const radius: number = 30 -export const fireDelay: number = 200 export const missileWidth: number = 10 export const missileHeight: number = 5 export interface State { + speed: Vec2.Vec2, pos: Vec2.Vec2, + radius: number, missiles: Array, lastFired: number, } -export function init(): State { +export function init(windowSize: Size.Size): State { return { + speed: { x: 0, y: 0 }, pos: { - x: Screen.width / 6, - y: Screen.height / 2, + x: windowSize.width / 6, + y: windowSize.height / 2, }, + radius: Size.diagonal(windowSize) / 30, missiles: [], lastFired: 0, } } -export function update(state: State, timestamp: number, delta: number) { - move(state, delta) - updateMissiles(state, timestamp, delta) +export function update( + state: State, + dt: number, + windowSize: Size.Size +) { + move(state, dt, windowSize) + updateMissiles(state, dt, windowSize) } -function move(state: State, delta: number) { - let dir = controlsDir(Controls.current) +function move(state: State, dt: number, windowSize: Size.Size) { + const dir = controlsDir(Controls.current) - if (!Vec2.equals(dir, Vec2.zero())) { - let teta = Math.atan2(dir.y, dir.x) - state.pos.x += Math.cos(teta) * delta / 3 - state.pos.y += Math.sin(teta) * delta / 3 - } + const unitDt = 0.5 + const steps = Math.round(dt / unitDt) + + const acc = Physics.acc({ dt, speed: state.speed, dir }) + Array(steps).fill(1).forEach(_ => { + state.speed = Physics.speed({ dt: unitDt, acc, speed: state.speed }) + state.pos = Physics.pos({ dt: unitDt, acc, speed: state.speed, pos: state.pos }) + }) - state.pos.x = Number.clamp(state.pos.x, radius, Screen.width - radius) - state.pos.y = Number.clamp(state.pos.y, radius, Screen.height - radius) + state.pos = Vec2.clamp( + state.pos, + { x: state.radius, y: state.radius }, + { x: windowSize.width - state.radius, y: windowSize.height - state.radius } + ) } function controlsDir(c: Controls.Controls): Vec2.Vec2 { - let dir = Vec2.zero() + let dir = { x: 0, y: 0 } if (c.up && !c.down) dir.y = -1 @@ -57,26 +70,31 @@ function controlsDir(c: Controls.Controls): Vec2.Vec2 { else if (c.left && !c.right) dir.x = -1 - return dir + return Vec2.normalize(dir) } -function updateMissiles(state: State, timestamp: number, delta: number) { - if (Controls.current.space && state.lastFired + fireDelay < timestamp) { - state.missiles.push({x: state.pos.x + radius, y: state.pos.y}) - state.lastFired = timestamp +function updateMissiles(state: State, dt: number, windowSize: Size.Size) { + if (Controls.current.spaceCount > state.lastFired) { + state.missiles.push({x: state.pos.x + state.radius, y: state.pos.y}) + state.lastFired = Controls.current.spaceCount } state.missiles = state.missiles - .map(missile => ({ ...missile, x: missile.x + delta})) - .filter(missile => missile.x < Screen.width) + .map(missile => ({ ...missile, x: missile.x + dt})) + .filter(missile => missile.x < windowSize.width) +} + +export function project(state: State, from: Size.Size, to: Size.Size) { + state.pos = Vec2.project(from, to, state.pos) + state.radius = state.radius / Size.diagonal(from) * Size.diagonal(to) } export function view(context: CanvasRenderingContext2D, state: State) { context.fillStyle = Colors.colors.red context.beginPath() - context.moveTo(state.pos.x - radius, state.pos.y - radius) - context.lineTo(state.pos.x + radius, state.pos.y) - context.lineTo(state.pos.x - radius, state.pos.y + radius) + context.moveTo(state.pos.x - state.radius, state.pos.y - state.radius) + context.lineTo(state.pos.x + state.radius, state.pos.y) + context.lineTo(state.pos.x - state.radius, state.pos.y + state.radius) context.closePath() context.fill() diff --git a/tsconfig.json b/tsconfig.json index 3e7f32b..85c0a86 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "amd", - "target": "es5", + "target": "es2016", "baseUrl": "src", "outFile": "public/main.js", "noImplicitAny": true, -- cgit v1.2.3