From 5b861cc34119dbc64eb77ea1e12e10da0ab10bde Mon Sep 17 00:00:00 2001 From: asraelite Date: Tue, 6 Mar 2018 10:35:54 +0000 Subject: [PATCH] Add rotation view and path prediction toggles --- js/game/control.mjs | 11 ++++++++++ js/game/events.mjs | 32 +++++++++++++++++++++++++++ js/game/index.mjs | 2 ++ js/graphics/index.mjs | 35 +++++++++++++++++++++++++++-- js/graphics/world.mjs | 20 +++++++++++++++++ js/gui/modules.mjs | 12 +++++++++- js/world/body.mjs | 26 +++++++++++++++------- js/world/entity.mjs | 9 ++++---- js/world/index.mjs | 7 ++++-- js/world/ship.mjs | 1 + js/world/spawn.mjs | 8 ++++++- js/world/tracer.mjs | 51 +++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 195 insertions(+), 19 deletions(-) create mode 100644 js/world/tracer.mjs diff --git a/js/game/control.mjs b/js/game/control.mjs index 9f0cdd4..232a6cb 100644 --- a/js/game/control.mjs +++ b/js/game/control.mjs @@ -11,6 +11,8 @@ export const mapping = { right: 'KeyD', exitEdit: 'Escape', inventory: 'KeyE', + cycleRotation: 'KeyC', + toggleTrace: 'KeyT' }; let held, pressed; @@ -49,6 +51,15 @@ function tickPlaying() { state.inventory = !state.inventory; } + if (pressed[mapping.cycleRotation]) { + events.cycleRotationMode(); + } + + if (pressed[mapping.toggleTrace]) { + events.toggleTrace(); + } + + // For debugging. if (pressed['KeyR']) events.startGame(); } diff --git a/js/game/events.mjs b/js/game/events.mjs index 20efbb5..d1f87a4 100644 --- a/js/game/events.mjs +++ b/js/game/events.mjs @@ -8,6 +8,23 @@ import * as edit from './edit.mjs'; export let shipLanded = false; +let notification; +let notLife = 0; + +function notify(message) { + notification.text = message; + notLife = 60; +} + +export function tick() { + if (notLife-- <= 0) + notification.text = ''; +} + +export function setNotificationElement(el) { + notification = el; +} + export function startGame() { game.changeView('game'); graphics.perspective.focusPlayer(); @@ -33,6 +50,21 @@ export function toggleEdit() { edit.init(); } +export function toggleTrace() { + let trace = graphics.toggleTrace(); + notify('Path prediction: ' + (trace ? 'on' : 'off')); +} + +export function cycleRotationMode() { + let message = { + parent: 'planet', + local: 'ship', + universe: 'universe' + }[graphics.cycleRotationMode()]; + + notify('Rotation view: ' + message); +} + export function endEditing() { let {valid, reason} = edit.end(); diff --git a/js/game/index.mjs b/js/game/index.mjs index b088914..d99d9be 100644 --- a/js/game/index.mjs +++ b/js/game/index.mjs @@ -51,6 +51,8 @@ export function changeView(view) { } function tick() { + events.tick(); + if (state.view == 'game') { world.tick(); control.tick(); diff --git a/js/graphics/index.mjs b/js/graphics/index.mjs index 689b3cb..03756ae 100644 --- a/js/graphics/index.mjs +++ b/js/graphics/index.mjs @@ -8,6 +8,7 @@ import * as consts from '../consts.mjs'; export let canvas, context, tempCanvas, tempContext; export let perspective; +export let trace = false; export function init() { canvas = document.querySelector('#main'); @@ -50,6 +51,23 @@ export function changePerspective(rotationMode, shiftX = 0, shiftY = 0) { perspective.transition = 1; } +export function cycleRotationMode() { + if (perspective.rotationMode === 'parent') { + perspective.changeRotationMode('local'); + } else if (perspective.rotationMode === 'local') { + perspective.changeRotationMode('universe'); + } else { + perspective.changeRotationMode('parent'); + } + perspective.transition = 1; + return perspective.rotationMode; +} + +export function toggleTrace() { + trace = !trace; + return trace; +} + export function changeZoom(delta) { perspective.zoomDelta(delta); } @@ -77,6 +95,7 @@ class Perspective { } changeRotationMode(mode) { + this.oldShift = this.currentShift; this.oldTarget = this.currentRotation; this.rotationMode = mode; } @@ -111,6 +130,18 @@ class Perspective { return (old * x + cur * (1 - x)); } + interpolateAngles(cur, old, x = this.transition) { + let a = cur % (Math.PI * 2); + let b = old % (Math.PI * 2); + + let sum = a + b; + + if (sum > (Math.PI * 2) && sum < (Math.PI * 3)) + sum %= Math.PI; + + return sum / 2; + } + tick() { if (this.focus !== null) [this.x, this.y] = this.focus.com; @@ -129,6 +160,8 @@ class Perspective { this.targetRotation = this.focus.r; } + this.normalize(); + let dif = Math.abs(this.targetRotation - this.rotation); this.rotationMet = dif < (this.rotationMet ? 0.3 : 0.05); @@ -137,8 +170,6 @@ class Perspective { this.transition *= 0.9; this.zoomTransition *= 0.9; - - this.normalize(); } reset() { diff --git a/js/graphics/world.mjs b/js/graphics/world.mjs index 57ac29e..059851f 100644 --- a/js/graphics/world.mjs +++ b/js/graphics/world.mjs @@ -1,4 +1,5 @@ import {canvas, context} from './index.mjs'; +import * as graphics from './index.mjs'; import {images as assets} from '../assets.mjs'; import * as world from '../world/index.mjs'; import {state} from '../game/index.mjs'; @@ -6,6 +7,7 @@ import {state} from '../game/index.mjs'; export function render() { world.particles.forEach(renderParticle); world.celestials.forEach(renderCelestial); + if (graphics.trace) world.tracers.forEach(renderTracer); world.ships.forEach(renderShip); world.entities.forEach(renderEntity); } @@ -47,3 +49,21 @@ function renderCelestial(cel) { context.drawImage(cel.image, cel.x, cel.y, cel.diameter, cel.diameter); } + +function renderTracer(tracer) { + context.lineWidth = 0.1; + context.strokeStyle = '#fff'; + context.beginPath(); + context.moveTo(...tracer.pos); + let path = tracer.path; + + for (let i = 0; i < path.length; i++) { + context.lineTo(...path[i]); + if (i % 5 === 0 || i == path.length - 1) { + context.stroke(); + context.globalAlpha = (1 - (i / path.length)) * 0.5; + } + } + + context.globalAlpha = 1; +} diff --git a/js/gui/modules.mjs b/js/gui/modules.mjs index 2328055..4a02d5e 100644 --- a/js/gui/modules.mjs +++ b/js/gui/modules.mjs @@ -56,7 +56,6 @@ export function game() { } } - let editShadow = root(); shadow.append(editShadow); editShadow.posRelative({x: 0.45, y: 0, w: 0.55, h: 0.6}); @@ -97,5 +96,16 @@ export function game() { edit.guiInventory = inventory; + + let notification = new GuiText('', 0, 0, 0, 0, { + size: 12, + align: 'center', + valign: 'top' + }); + shadow.append(notification); + notification.posRelative({x: 0.5}); + notification.y += 10; + events.setNotificationElement(notification); + return shadow; } diff --git a/js/world/body.mjs b/js/world/body.mjs index d781f30..b065b4a 100644 --- a/js/world/body.mjs +++ b/js/world/body.mjs @@ -55,20 +55,20 @@ export default class Body { return this.rotateVector(x, y, this.r); } - tickMotion() { - this.x += this.xvel; - this.y += this.yvel; - this.r += this.rvel; - this.rvel *= this.rfriction; + tickMotion(speed = 1) { + this.x += this.xvel * speed; + this.y += this.yvel * speed; + this.r += this.rvel * speed; + this.rvel *= this.rfriction * speed; } - tickGravity(bodies) { + tickGravity(bodies, speed = 1) { bodies.forEach(b => { let force = b.mass / (this.distanceTo(b) ** 2) * G; let [[ax, ay], [bx, by]] = [this.com, b.com]; let angle = Math.atan2(by - ay, bx - ax); - this.xvel += Math.cos(angle) * force; - this.yvel += Math.sin(angle) * force; + this.xvel += Math.cos(angle) * force * speed; + this.yvel += Math.sin(angle) * force * speed; }); } @@ -100,4 +100,14 @@ export default class Body { this.yvel += vy; this.rvel += r / this.mass; } + + orbit(cel, altitude) { + this.gravity = true; + let speed = Math.sqrt(G * cel.mass / (altitude + cel.radius)); + let [cx, cy] = cel.com; + this.x = cx; + this.y = cy - (altitude + cel.radius); + this.yvel = 0; + this.xvel = speed; + } } diff --git a/js/world/entity.mjs b/js/world/entity.mjs index 4320ac7..1fa448d 100644 --- a/js/world/entity.mjs +++ b/js/world/entity.mjs @@ -13,7 +13,7 @@ export default class Entity extends Body { yvel = 0, gravity = false } = {}) { - super(x, y, 100); + super(x, y, 0.1); this.xvel = xvel; this.yvel = yvel; @@ -31,10 +31,6 @@ export default class Entity extends Body { return [this.x + this.width / 2, this.y + this.height / 2]; } - orbit(celestial, radius) { - this.gravity = true; - } - remove() { entities.delete(this); } @@ -50,10 +46,13 @@ export default class Entity extends Body { } if (playerShip.colliding(this.com, this.radius)) { + if (this.touched) return; let success = events.collectItem(this.type, this.id); if (!success) return; particle.createPickupBurst(playerShip, this.com); this.remove(); + } else { + this.touched = false; } } } diff --git a/js/world/index.mjs b/js/world/index.mjs index 17bd2b4..8441d4c 100644 --- a/js/world/index.mjs +++ b/js/world/index.mjs @@ -7,6 +7,7 @@ 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 let playerShip = null; @@ -19,9 +20,10 @@ export function init() { celestials.clear(); ships.clear(); particles.clear(); + tracers.clear(); spawn.player(); - spawn.startPlanet(); - spawn.testEntity(); + let p = spawn.startPlanet(); + spawn.testEntity(p); } export function tick() { @@ -29,4 +31,5 @@ export function tick() { celestials.forEach(c => c.tick()); entities.forEach(e => e.tick()); ships.forEach(s => s.tick()); + tracers.forEach(t => t.tick()); } diff --git a/js/world/ship.mjs b/js/world/ship.mjs index 72697b1..39aed32 100644 --- a/js/world/ship.mjs +++ b/js/world/ship.mjs @@ -4,6 +4,7 @@ import * as world from './index.mjs'; import * as consts from '../consts.mjs'; import * as particle from './particle.mjs'; import * as events from '../game/events.mjs'; +import Tracer from './tracer.mjs'; import {state} from '../game/index.mjs'; export default class Ship extends Body { diff --git a/js/world/spawn.mjs b/js/world/spawn.mjs index ee14ff4..919bc32 100644 --- a/js/world/spawn.mjs +++ b/js/world/spawn.mjs @@ -2,6 +2,7 @@ import Ship from './ship.mjs'; import Module from './module.mjs'; import Celestial from './celestial.mjs'; import Entity from './entity.mjs'; +import Tracer from './tracer.mjs'; import {modules} from '../data.mjs'; import * as world from './index.mjs'; @@ -12,6 +13,10 @@ export function player() { ship.addModule(0, 2, modules.thruster.light); world.ships.add(ship); world.setPlayerShip(ship); + + let tracer = new Tracer(ship); + world.tracers.add(tracer); + return ship; } @@ -22,9 +27,10 @@ export function startPlanet() { }); } -export function testEntity() { +export function testEntity(parent) { let entity = new Entity(0, -50); world.entities.add(entity); + entity.orbit(parent, 10); return entity; } diff --git a/js/world/tracer.mjs b/js/world/tracer.mjs new file mode 100644 index 0000000..d55c64f --- /dev/null +++ b/js/world/tracer.mjs @@ -0,0 +1,51 @@ +import Body from './body.mjs'; +import {modules} from '../data.mjs'; +import {playerShip} from './index.mjs'; +import {images as assets} from '../assets.mjs'; +import {celestials, particles, entities} from './index.mjs'; +import * as particle from './particle.mjs'; +import * as consts from '../consts.mjs'; +import * as events from '../game/events.mjs'; + +export default class Tracer extends Body { + constructor(ship) { + super(...ship.pos, 0.1); + + this.ship = ship; + this.path = []; + } + + run(distance) { + this.path = []; + [this.x, this.y] = this.ship.com; + [this.xvel, this.yvel] = [this.ship.xvel, this.ship.yvel]; + let dis = 0; + let last = this.pos; + let factor = 5; + + for (let i = 0; dis < distance; i++) { + if (this.tickPath(factor)) break; + this.path.push(this.pos); + + if (i % 10 === 0) { + let [lx, ly] = last; + dis += Math.sqrt((this.x - lx) ** 2 + (this.y - ly) ** 2); + last = this.pos; + } + + if (i > distance * 5) break; + } + + [this.x, this.y] = this.ship.com; + } + + tick() { + this.run(100); + } + + tickPath(speed) { + this.tickMotion(speed); + this.tickGravity(celestials, speed); + return !!this.getCelestialCollision(celestials); + } +}