Add start of ship editing
This commit is contained in:
parent
d85338d9f2
commit
b88c0eb358
18 changed files with 288 additions and 100 deletions
36
dist/img/celestials/green_0.svg
vendored
36
dist/img/celestials/green_0.svg
vendored
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 41 KiB |
2
dist/improcket.min.js.map
vendored
2
dist/improcket.min.js.map
vendored
File diff suppressed because one or more lines are too long
|
@ -2,13 +2,14 @@
|
|||
* Constants that do not change during gameplay.
|
||||
* This can kind of be treated like a configuration file, I guess.
|
||||
*
|
||||
* All le
|
||||
* All length units are relative to the size of a small ship module, which
|
||||
* is always 1x1.
|
||||
*/
|
||||
|
||||
// For fixing floating point rounding errors.
|
||||
export const EPSILON = 1e-8;
|
||||
// Unit length of sector. Only for internal representation.
|
||||
export const SECTOR_SIZE = 512;
|
||||
// Star count per sector.
|
||||
export const STAR_DENSITY = (SECTOR_SIZE ** 2) / 10000;
|
||||
// G, G-boy, The big G, Mr. G, g's big brother, G-dog
|
||||
export const GRAVITATIONAL_CONSTANT = 0.01;
|
||||
// Perspective constraints. Higher zoom value = closer.
|
||||
|
@ -16,3 +17,10 @@ export const MIN_ZOOM = 1;
|
|||
export const MAX_ZOOM = 100;
|
||||
export const DEFAULT_ZOOM = 10;
|
||||
export const ZOOM_SPEED = 0.01;
|
||||
// Ship landing. Angle in radians.
|
||||
export const TIP_ANGLE = 30.3;
|
||||
export const TIP_SPEED = 0.015;
|
||||
// Ship flight mechanics. Speed measured in units per tick.
|
||||
export const FUEL_BURN_RATE = 0.01;
|
||||
// Distance at which an orbited planet will not be considered a parent body.
|
||||
export const MAX_PARENT_CELESTIAL_DISTANCE = 120;
|
||||
|
|
|
@ -1,16 +1,36 @@
|
|||
import * as input from '../input.mjs';
|
||||
import * as events from './events.mjs';
|
||||
import * as player from './player.mjs';
|
||||
import * as graphics from '../graphics/index.mjs';
|
||||
import {state} from './index.mjs';
|
||||
|
||||
export const mapping = {
|
||||
thrust: 'KeyW',
|
||||
left: 'KeyA',
|
||||
right: 'KeyD'
|
||||
right: 'KeyD',
|
||||
exitEdit: 'Escape'
|
||||
};
|
||||
|
||||
export function tick() {
|
||||
let held = input.keyCode.held;
|
||||
let pressed = input.keyCode.pressed;
|
||||
let held, pressed;
|
||||
|
||||
export function tick() {
|
||||
held = input.keyCode.held;
|
||||
pressed = input.keyCode.pressed;
|
||||
|
||||
if (state.editing) {
|
||||
tickEditing();
|
||||
} else if (state.playing) {
|
||||
tickPlaying();
|
||||
}
|
||||
|
||||
if (!state.editing) {
|
||||
if (input.mouse.scroll !== 0) {
|
||||
graphics.changeZoom(-input.mouse.scroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tickPlaying() {
|
||||
if (held[mapping.thrust]) {
|
||||
player.ship.applyThrust({ forward: 1 });
|
||||
}
|
||||
|
@ -23,3 +43,9 @@ export function tick() {
|
|||
player.ship.applyThrust({ turnRight: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
function tickEditing() {
|
||||
if (held[mapping.exitEdit]) {
|
||||
events.endEditing();
|
||||
}
|
||||
}
|
||||
|
|
10
js/game/edit.mjs
Normal file
10
js/game/edit.mjs
Normal file
|
@ -0,0 +1,10 @@
|
|||
import * as game from './index.mjs';
|
||||
import * as graphics from '../graphics/index.mjs';
|
||||
|
||||
export function init() {
|
||||
graphics.setZoom(20);
|
||||
}
|
||||
|
||||
export function end() {
|
||||
|
||||
}
|
|
@ -2,8 +2,34 @@ import * as game from './index.mjs';
|
|||
import * as graphics from '../graphics/index.mjs';
|
||||
import * as world from '../world/index.mjs';
|
||||
import * as player from './player.mjs';
|
||||
import * as edit from './edit.mjs';
|
||||
|
||||
export let shipLanded = false;
|
||||
|
||||
export function startGame() {
|
||||
game.changeView('game');
|
||||
graphics.perspective.focusPlayer();
|
||||
}
|
||||
|
||||
export function landShip() {
|
||||
shipLanded = true;
|
||||
game.state.landed = true;
|
||||
}
|
||||
|
||||
export function launchShip() {
|
||||
shipLanded = false;
|
||||
game.state.landed = false;
|
||||
}
|
||||
|
||||
export function editShip() {
|
||||
console.log('a');
|
||||
graphics.changePerspective('parent', -5, 0);
|
||||
game.state.editing = true;
|
||||
edit.init();
|
||||
}
|
||||
|
||||
export function endEditing() {
|
||||
graphics.changePerspective('universe');
|
||||
game.state.editing = false;
|
||||
edit.end();
|
||||
}
|
||||
|
|
|
@ -6,12 +6,15 @@ import * as world from '../world/index.mjs';
|
|||
import * as events from './events.mjs';
|
||||
import * as control from './control.mjs';
|
||||
import * as player from './player.mjs';
|
||||
import * as edit from './edit.mjs';
|
||||
|
||||
export let state;
|
||||
|
||||
export async function init() {
|
||||
state = {
|
||||
view: 'menu',
|
||||
playing: false,
|
||||
editing: false,
|
||||
paused: false
|
||||
};
|
||||
|
||||
|
@ -36,6 +39,9 @@ export function changeView(view) {
|
|||
gui.changeView(view);
|
||||
|
||||
if (view == 'game') {
|
||||
state.playing = true;
|
||||
state.editing = false;
|
||||
state.paused = false;
|
||||
world.init();
|
||||
player.init();
|
||||
}
|
||||
|
|
0
js/game/inventory.mjs
Normal file
0
js/game/inventory.mjs
Normal file
|
@ -1,4 +1,5 @@
|
|||
import * as world from '../world/index.mjs';
|
||||
import * as inventory from './inventory.mjs';
|
||||
|
||||
export let ship;
|
||||
|
||||
|
|
|
@ -11,58 +11,23 @@ function init() {
|
|||
};
|
||||
}
|
||||
|
||||
export function render() {
|
||||
export function render(angle) {
|
||||
if (patterns === null) init();
|
||||
|
||||
renderLayer(patterns.back, 0.3, 1);
|
||||
renderLayer(patterns.middle, 0.5, 0.3);
|
||||
//renderLayer(patterns.front, 0.7, 0.3);
|
||||
renderLayer(patterns.back, 0.3, 1, angle);
|
||||
renderLayer(patterns.middle, 0.5, 0.3, angle);
|
||||
//renderLayer(patterns.front, 0.7, 0.3, angle);
|
||||
}
|
||||
|
||||
function renderLayer(pattern, speed = 1, scale = 1) {
|
||||
function renderLayer(pattern, speed = 1, scale = 1, angle = 0) {
|
||||
context.save();
|
||||
let outset = (Math.abs(Math.cos(angle)) + Math.abs(Math.sin(angle)));
|
||||
outset = ((outset - 1) * canvas.width) / scale;
|
||||
let [px, py] = [perspective.x * speed, perspective.y * speed];
|
||||
context.translate(-px, -py);
|
||||
context.scale(scale, scale);
|
||||
context.fillStyle = pattern;
|
||||
context.fillRect(px / scale, py / scale,
|
||||
canvas.width / scale, canvas.height / scale);
|
||||
context.fillRect(px / scale - outset / 2, py / scale - outset / 2,
|
||||
canvas.width / scale + outset, canvas.height / scale + outset);
|
||||
context.restore();
|
||||
}
|
||||
|
||||
/*
|
||||
function renderSectorStars(sector) {
|
||||
let rand = new SeededRandom(sector.numId);
|
||||
|
||||
context.fillStyle = '#fff';
|
||||
|
||||
for (let i = 0; i < STAR_DENSITY; i++) {
|
||||
let sx = rand.next() * sector.size + sector.wx;
|
||||
let sy = rand.next() * sector.size + sector.wy;
|
||||
context.fillRect(sx, sy, 1.5, 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
function tile(img, x, y, dx = 0, dy = 0, scale = 1) {
|
||||
let [sx, sy] = [x * scale, y * scale];
|
||||
let [ex, ey] = [(x + canvas.width) * scale, (y + canvas.height) * scale];
|
||||
for (let x = sx; x < ex;) {
|
||||
let nx = (Math.floor(x / img.width) + 1) * img.width;
|
||||
nx = Math.min(nx, ex);
|
||||
let w = nx - x;
|
||||
|
||||
for (let y = sy; y < ey;) {
|
||||
let ny = (Math.floor(y / img.height) + 1) * img.height;
|
||||
ny = Math.min(ny, ey);
|
||||
let h = ny - y;
|
||||
|
||||
context.drawImage(img, x % img.width, y % img.height, w, h,
|
||||
dx + x, dy + y, w, h);
|
||||
|
||||
y = ny;
|
||||
}
|
||||
|
||||
x = nx;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -29,10 +29,10 @@ export function render() {
|
|||
context.fillStyle = '#000';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
renderBackground();
|
||||
|
||||
context.save();
|
||||
perspective.tick();
|
||||
perspective.transformRotate();
|
||||
renderBackground(perspective.rotation);
|
||||
perspective.transformCanvas();
|
||||
renderWorld();
|
||||
context.restore();
|
||||
|
@ -44,41 +44,108 @@ export function getVisibleSectors() {
|
|||
return world.getContainedSectors(...perspective.bounds);
|
||||
}
|
||||
|
||||
export function changePerspective(rotationMode, shiftX = 0, shiftY = 0) {
|
||||
perspective.changeRotationMode(rotationMode);
|
||||
perspective.changeShift(shiftX, shiftY);
|
||||
perspective.transition = 1;
|
||||
}
|
||||
|
||||
export function changeZoom(delta) {
|
||||
perspective.zoomDelta(delta);
|
||||
}
|
||||
|
||||
export function setZoom(target) {
|
||||
perspective.changeZoom(target);
|
||||
}
|
||||
|
||||
class Perspective {
|
||||
constructor() {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.shiftX = 0;
|
||||
this.shiftY = 0;
|
||||
this.zoom = 0;
|
||||
this.bounds = [0, 0, canvas.width, canvas.height];
|
||||
this.rotationMode = 'universe';
|
||||
this.targetZoom = consts.DEFAULT_ZOOM;
|
||||
this.oldTarget = 0;
|
||||
this.oldShift = [0, 0];
|
||||
this.oldZoom = 0;
|
||||
this.transition = 0;
|
||||
this.zoomTransition = 0;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
changeRotationMode(mode) {
|
||||
this.oldTarget = this.targetRotation;
|
||||
this.rotationMode = mode;
|
||||
}
|
||||
|
||||
changeShift(x, y) {
|
||||
this.oldShift = this.currentShift;
|
||||
[this.shiftX, this.shiftY] = [x, y];
|
||||
}
|
||||
|
||||
changeZoom(zoom) {
|
||||
this.oldZoom = this.currentZoom;
|
||||
this.targetZoom = zoom;
|
||||
this.zoomTransition = 1;
|
||||
}
|
||||
|
||||
get currentShift() {
|
||||
let [ox, oy] = this.oldShift;
|
||||
return [this.interpolate(this.shiftX, ox),
|
||||
this.interpolate(this.shiftY, oy)];
|
||||
}
|
||||
|
||||
get currentRotation() {
|
||||
return this.interpolate(this.targetRotation, this.oldTarget);
|
||||
}
|
||||
|
||||
get currentZoom() {
|
||||
let t = this.zoomTransition;
|
||||
return (this.oldZoom * t + this.targetZoom * (1 - t));
|
||||
}
|
||||
|
||||
interpolate(cur, old, x = this.transition) {
|
||||
return (old * x + cur * (1 - x));
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (input.mouse.scroll !== 0) {
|
||||
this.zoomDelta(-input.mouse.scroll);
|
||||
}
|
||||
|
||||
if (this.focus !== null) {
|
||||
if (this.focus !== null)
|
||||
[this.x, this.y] = this.focus.com;
|
||||
}
|
||||
|
||||
if (this.rotationFocus !== null) {
|
||||
this.targetRotation = this.rotationFocus.r;
|
||||
} else {
|
||||
if (this.focus === null || this.rotationMode === 'universe') {
|
||||
this.targetRotation = 0;
|
||||
} else if (this.rotationMode === 'parent') {
|
||||
let parent = this.focus.parentCelestial;
|
||||
if (parent === null) {
|
||||
this.targetRotation = 0;
|
||||
} else {
|
||||
let a = this.focus.angleTo(...this.focus.com, ...parent.com);
|
||||
this.targetRotation = a - Math.PI / 2;
|
||||
}
|
||||
} else {
|
||||
this.targetRotation = this.focus.r;
|
||||
}
|
||||
|
||||
if (this.smoothRotation) {
|
||||
this.rotation = (this.rotation * 0.9 + this.targetRotation * 0.1);
|
||||
} else {
|
||||
this.rotation = this.targetRotation;
|
||||
}
|
||||
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.zoomTransition *= 0.9;
|
||||
|
||||
this.normalize();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.rotation = 0;
|
||||
this.targetRotation = 0;
|
||||
this.smoothRotation = false;
|
||||
this.zoom = consts.DEFAULT_ZOOM;
|
||||
this.targetZoom = this.zoom;
|
||||
this.focus = null;
|
||||
this.rotationFocus = null;
|
||||
}
|
||||
|
@ -86,24 +153,37 @@ class Perspective {
|
|||
focusPlayer() {
|
||||
this.focus = world.playerShip;
|
||||
this.rotationFocus = world.playerShip;
|
||||
this.smoothRotation = false;
|
||||
}
|
||||
|
||||
zoomDelta(delta) {
|
||||
let factor = 1 + (consts.ZOOM_SPEED * Math.abs(delta));
|
||||
this.zoom *= delta > 0 ? factor : 1 / factor;
|
||||
this.targetZoom *= delta > 0 ? factor : 1 / factor;
|
||||
this.normalize();
|
||||
}
|
||||
|
||||
normalize() {
|
||||
this.zoom = Math.max(consts.MIN_ZOOM,
|
||||
Math.min(consts.MAX_ZOOM, this.zoom));
|
||||
this.targetZoom = Math.max(consts.MIN_ZOOM,
|
||||
Math.min(consts.MAX_ZOOM, this.targetZoom));
|
||||
this.targetRotation %= (Math.PI * 2);
|
||||
}
|
||||
|
||||
transformRotate() {
|
||||
let [,,bw, bh] = this.bounds;
|
||||
context.translate(bw / 2, bh / 2);
|
||||
context.rotate(-this.rotation);
|
||||
context.translate(-bw / 2, -bh / 2);
|
||||
}
|
||||
|
||||
rotateVector(x, y, r = this.rotation) {
|
||||
return [(x * Math.cos(r) - y * Math.sin(r)),
|
||||
(y * Math.cos(r) - x * Math.sin(r))];
|
||||
}
|
||||
|
||||
transformCanvas() {
|
||||
let [,,bw, bh] = this.bounds;
|
||||
let tx = -this.x * this.zoom;
|
||||
let ty = -this.y * this.zoom;
|
||||
let [sx, sy] = this.rotateVector(...this.currentShift, -this.rotation);
|
||||
let tx = -(this.x + sx) * this.zoom;
|
||||
let ty = -(this.y + sy) * this.zoom;
|
||||
context.translate(tx + bw / 2, ty + bh / 2);
|
||||
context.scale(this.zoom, this.zoom);
|
||||
}
|
||||
|
|
|
@ -8,4 +8,9 @@ export default class GuiButton extends GuiElement {
|
|||
this.text = text;
|
||||
this.onclick = onclick;
|
||||
}
|
||||
|
||||
click() {
|
||||
if (this.options.draw)
|
||||
this.onclick();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import {Rect} from './misc.mjs';
|
||||
|
||||
const defaultOptions = {
|
||||
draw: true // Whether the element itself will be rendered.
|
||||
draw: true, // Whether the element itself will be rendered.
|
||||
drawChildren: true // Whether children will be rendered.
|
||||
}
|
||||
|
||||
export default class GuiElement extends Rect {
|
||||
|
@ -15,9 +16,14 @@ export default class GuiElement extends Rect {
|
|||
this.options = Object.assign({}, defaultOptions, options);
|
||||
}
|
||||
|
||||
tick() {
|
||||
tickElement() {
|
||||
this.tickMouse();
|
||||
this.children.forEach(c => c.tick());
|
||||
this.tick();
|
||||
this.children.forEach(c => c.tickElement());
|
||||
}
|
||||
|
||||
tick() {
|
||||
|
||||
}
|
||||
|
||||
append(element) {
|
||||
|
|
|
@ -11,7 +11,7 @@ export function init() {
|
|||
}
|
||||
|
||||
export function tick() {
|
||||
root.tick();
|
||||
root.tickElement();
|
||||
}
|
||||
|
||||
export function changeView(view) {
|
||||
|
|
|
@ -11,10 +11,11 @@ export class Rect {
|
|||
this.mouseHeld = false;
|
||||
}
|
||||
|
||||
click() {}
|
||||
|
||||
tickMouse() {
|
||||
if (this.mouseHeld == true && !input.mouse.held[0] && this.mouseOver)
|
||||
if (this.onclick !== null)
|
||||
this.onclick();
|
||||
this.click();
|
||||
if (!this.mouseHeld && input.mouse.pressed[0] && this.mouseOver)
|
||||
this.mouseHeld = true;
|
||||
if (!input.mouse.held[0])
|
||||
|
|
|
@ -5,6 +5,7 @@ import GuiFrame from './frame.mjs';
|
|||
import GuiImage from './image.mjs';
|
||||
import GuiButton from './button.mjs';
|
||||
import * as events from '../game/events.mjs';
|
||||
import {state} from '../game/index.mjs';
|
||||
|
||||
export function root() {
|
||||
return new GuiFrame(0, 0, canvas.width, canvas.height, {
|
||||
|
@ -36,6 +37,14 @@ export function title() {
|
|||
|
||||
export function game() {
|
||||
let shadow = root();
|
||||
|
||||
|
||||
let edit = new GuiButton('Edit rocket', events.editShip, 0, 0, 200);
|
||||
shadow.append(edit);
|
||||
edit.posRelative({ x: 0.5, xc: 0.5, y: 1 });
|
||||
edit.y -= 45;
|
||||
edit.tick = () => {
|
||||
edit.options.draw = state.landed && !state.editing;
|
||||
}
|
||||
|
||||
return shadow;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ export default class Celestial extends Body {
|
|||
return [this.x + this.radius, this.y + this.radius];
|
||||
}
|
||||
|
||||
get escapeVelocity() {
|
||||
|
||||
}
|
||||
|
||||
tick() {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import Module from './module.mjs';
|
||||
import Body from './body.mjs';
|
||||
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 {state} from '../game/index.mjs';
|
||||
|
||||
export default class Ship extends Body {
|
||||
constructor(x, y) {
|
||||
|
@ -10,6 +13,7 @@ export default class Ship extends Body {
|
|||
this.localCom = [0, 0];
|
||||
this.modules = new Set();
|
||||
this.maxRadius = 0;
|
||||
this.landed = false;
|
||||
}
|
||||
|
||||
get com() {
|
||||
|
@ -17,9 +21,27 @@ export default class Ship extends Body {
|
|||
return [this.x + lx, this.y + ly];
|
||||
}
|
||||
|
||||
get parentCelestial() {
|
||||
let closest = null;
|
||||
let closestDistance = 0;
|
||||
|
||||
world.celestials.forEach(c => {
|
||||
let dis = this.distanceTo(c);
|
||||
if (closest === null || dis < closestDistance) {
|
||||
closest = c;
|
||||
closestDistance = dis;
|
||||
}
|
||||
});
|
||||
|
||||
if (closestDistance > consts.MAX_PARENT_CELESTIAL_DISTANCE)
|
||||
return null;
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.tickMotion();
|
||||
this.tickGravity(world.celestials);
|
||||
if (!this.landed) this.tickGravity(world.celestials);
|
||||
this.resolveCollisions();
|
||||
|
||||
this.modules.forEach(m => {
|
||||
|
@ -29,6 +51,9 @@ export default class Ship extends Body {
|
|||
});
|
||||
|
||||
this.modules.forEach(m => m.reset());
|
||||
|
||||
if (events.shipLanded != this.landed)
|
||||
this.landed ? events.landShip() : events.launchShip();
|
||||
}
|
||||
|
||||
addModule(x, y, properties, options) {
|
||||
|
@ -55,6 +80,8 @@ export default class Ship extends Body {
|
|||
}
|
||||
|
||||
resolveCollisions() {
|
||||
this.landed = false;
|
||||
|
||||
world.celestials.forEach(c => {
|
||||
let dis = this.distanceTo(c);
|
||||
|
||||
|
@ -72,16 +99,22 @@ export default class Ship extends Body {
|
|||
let angleToCom = this.angleTo(...this.com, ...pos);
|
||||
let angle = angleToCom - theta;
|
||||
let [force] = this.rotateVector(0, 1, angle);
|
||||
if (Math.abs(angle) < 0.3) {
|
||||
if (Math.abs(angle) < consts.TIP_ANGLE) {
|
||||
force *= -1;
|
||||
}
|
||||
this.rvel -= force * 0.015;
|
||||
if (Math.abs(angle) < 0.003
|
||||
&& Math.abs(this.rvel) < 0.001) {
|
||||
this.landed = true;
|
||||
this.rvel = 0;
|
||||
this.r = theta - Math.PI / 2;
|
||||
}
|
||||
this.rvel -= force * consts.TIP_SPEED;
|
||||
}
|
||||
|
||||
checkModuleCollision(module, body) {
|
||||
let p = this.getWorldPoint(...module.localCom);
|
||||
let dis = body.distanceTo({ com: p });
|
||||
if (dis < body.radius + 0.5) {
|
||||
if (dis < body.radius + 0.5 + consts.EPSILON) {
|
||||
this.approach(body, dis - (body.radius + 0.5));
|
||||
this.halt();
|
||||
this.resolveCelestialCollision(p, body);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue