diff --git a/js/consts.mjs b/js/consts.mjs index 1bf711a..03e9d48 100644 --- a/js/consts.mjs +++ b/js/consts.mjs @@ -11,7 +11,7 @@ export const EPSILON = 1e-8; // Unit length of sector. Only for internal representation. export const SECTOR_SIZE = 512; // G, G-boy, The big G, Mr. G, g's big brother, G-dog -export const GRAVITATIONAL_CONSTANT = 0.01; +export const GRAVITATIONAL_CONSTANT = 0.002; // Perspective constraints. Higher zoom value = closer. export const MIN_ZOOM = 1; export const MAX_ZOOM = 100; @@ -22,7 +22,11 @@ export const TIP_ANGLE = 0.3; export const TIP_SPEED = 0.015; // Ship flight mechanics. Speed measured in units per tick. export const FUEL_BURN_RATE = 0.01; +export const THRUST_POWER = 0.007; +export const TURN_POWER = 0.05; // Distance at which an orbited planet will not be considered a parent body. export const MAX_PARENT_CELESTIAL_DISTANCE = 120; // Ship editing. export const EDIT_MARGIN = 2; +// Floating items. +export const ENTITY_ROTATION_RATE = 0.01; diff --git a/js/game/events.mjs b/js/game/events.mjs index 627ab9b..20efbb5 100644 --- a/js/game/events.mjs +++ b/js/game/events.mjs @@ -2,6 +2,8 @@ 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 inventory from './inventory.mjs'; +import * as particle from '../world/particle.mjs'; import * as edit from './edit.mjs'; export let shipLanded = false; @@ -48,3 +50,12 @@ export function invalidTilePlacement() { export function tilePlacement() { // TODO: Play some audio. } + +export function tossItem() { + particle.createItemToss(world.playerShip); +} + +export function collectItem(type, id) { + inventory.addItem(type, id); + return true; +} diff --git a/js/game/inventory.mjs b/js/game/inventory.mjs index 82f8636..ad563e4 100644 --- a/js/game/inventory.mjs +++ b/js/game/inventory.mjs @@ -1,5 +1,6 @@ import {modules} from '../data.mjs'; import {images as assets} from '../assets.mjs'; +import * as events from './events.mjs'; export const items = new Map(); export let currentItem = null; @@ -30,6 +31,7 @@ export function removeItem(type, id) { items.delete(mapId); currentItem = null; } + events.tossItem(); } export function selectItem(type, id) { diff --git a/js/graphics/world.mjs b/js/graphics/world.mjs index 8c76957..57ac29e 100644 --- a/js/graphics/world.mjs +++ b/js/graphics/world.mjs @@ -7,6 +7,7 @@ export function render() { world.particles.forEach(renderParticle); world.celestials.forEach(renderCelestial); world.ships.forEach(renderShip); + world.entities.forEach(renderEntity); } function renderParticle(particle) { @@ -14,6 +15,14 @@ function renderParticle(particle) { context.fillRect(...particle.com, particle.size, particle.size); } +function renderEntity(entity) { + context.save(); + context.translate(...entity.com); + context.rotate(entity.r); + context.drawImage(entity.image, -0.5, -0.5, 1, 1); + context.restore(); +} + function renderShip(ship) { context.save(); context.translate(...ship.com); diff --git a/js/world/body.mjs b/js/world/body.mjs index 7aa517a..d781f30 100644 --- a/js/world/body.mjs +++ b/js/world/body.mjs @@ -24,6 +24,15 @@ export default class Body { return Math.sqrt(this.xvel ** 2 + this.yvel ** 2); } + getCelestialCollision(celestials) { + let result = false; + celestials.forEach(c => { + let dis = this.distanceTo(c); + if (dis < c.radius) result = c; + }); + return result; + } + getWorldPoint(lx, ly) { let [cx, cy] = this.localCom; let [nx, ny] = this.rotateVector(lx - cx, ly - cy, this.r); @@ -55,7 +64,7 @@ export default class Body { tickGravity(bodies) { bodies.forEach(b => { - let force = b.mass / this.mass / (this.distanceTo(b) ** 2) * G; + 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; diff --git a/js/world/entity.mjs b/js/world/entity.mjs index 9b9443f..4320ac7 100644 --- a/js/world/entity.mjs +++ b/js/world/entity.mjs @@ -1,64 +1,59 @@ import Body from './body.mjs'; import {modules} from '../data.mjs'; -import {celestials, particles} from './index.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 function createThrustExhaust(thruster) { - let ship = thruster.ship; - let [fx, fy] = ship.relativeVector(0, 0.2); - let [dx, dy] = ship.relativeVector((Math.random() - 0.5) * 0.5, 0.5); - let [cx, cy] = thruster.com; - particles.add(new Particle(cx + dx, cy + dy, { - xvel: ship.xvel + fx, - yvel: ship.yvel + fy, - color: '#f4c542', - lifetime: 5, - size: 0.07 - })); -} - -class Particle extends Body { - constructor(x, y, { +export default class Entity extends Body { + constructor(x, y, type = 'fuel', id = 'small', { xvel = 0, yvel = 0, - spray = 0.1, - fizzle = 0, - maxFizzle = fizzle * 2, - color = '#fff', - gravity = false, - lifetime = 50, - size = 0.1, - friction = 0.99 - }) { - super(x, y, 0.1); + gravity = false + } = {}) { + super(x, y, 100); - this.size = size; - this.xvel = xvel + (Math.random() - 0.5) * spray; - this.yvel = yvel + (Math.random() - 0.5) * spray; - this.friction = friction; - this.fizzle = fizzle; - this.maxFizzle = maxFizzle; - this.color = color; + this.xvel = xvel; + this.yvel = yvel; + this.width = 1; + this.height = 1; + this.radius = (this.width + this.height) / 2; + this.type = type; + this.id = id; + this.image = assets.modules[type][id]; this.gravity = gravity; - this.life = lifetime; + this.touched = false; } get com() { - return [this.x - this.size / 2, this.y - this.size / 2]; + return [this.x + this.width / 2, this.y + this.height / 2]; + } + + orbit(celestial, radius) { + this.gravity = true; + } + + remove() { + entities.delete(this); } tick() { - if (!this.life--) { - particles.delete(this); - return; - } - + this.r += consts.ENTITY_ROTATION_RATE; this.tickMotion(); if (this.gravity) this.tickGravity(celestials); + let col = this.getCelestialCollision(celestials); - this.xvel *= this.friction; - this.yvel *= this.friction; - this.x += (Math.random() - 0.5) * this.fizzle; - this.y += (Math.random() - 0.5) * this.fizzle; - if (this.fizzle < this.mazFizzle) this.fizzle *= 1.05; + if (col !== false) { + this.remove(); + } + + if (playerShip.colliding(this.com, this.radius)) { + let success = events.collectItem(this.type, this.id); + if (!success) return; + particle.createPickupBurst(playerShip, this.com); + this.remove(); + } } } diff --git a/js/world/index.mjs b/js/world/index.mjs index 24e4701..17bd2b4 100644 --- a/js/world/index.mjs +++ b/js/world/index.mjs @@ -21,6 +21,7 @@ export function init() { particles.clear(); spawn.player(); spawn.startPlanet(); + spawn.testEntity(); } export function tick() { diff --git a/js/world/particle.mjs b/js/world/particle.mjs index f724b5a..0626902 100644 --- a/js/world/particle.mjs +++ b/js/world/particle.mjs @@ -15,6 +15,31 @@ export function createThrustExhaust(thruster) { })); } +export function createPickupBurst(ship, point) { + for (let i = 0; i < 20; i++) { + particles.add(new Particle(...point, { + xvel: ship.xvel, + yvel: ship.yvel, + color: '#eae55d', + lifetime: Math.random() * 20 + 15, + friction: 0.95, + size: Math.random() * 0.2 + 0.05, + spray: 0.3 + })); + } +} + +export function createItemToss(ship) { + particles.add(new Particle(...ship.com, { + xvel: ship.xvel, + yvel: ship.yvel, + color: '#a87234', + lifetime: 50, + size: 0.6, + spray: 0.4 + })); +} + class Particle extends Body { constructor(x, y, { xvel = 0, @@ -46,7 +71,7 @@ class Particle extends Body { } tick() { - if (!this.life--) { + if (this.life-- <= 0) { particles.delete(this); return; } diff --git a/js/world/ship.mjs b/js/world/ship.mjs index e929ad1..831257f 100644 --- a/js/world/ship.mjs +++ b/js/world/ship.mjs @@ -83,6 +83,19 @@ export default class Ship extends Body { Math.max(Math.sqrt((bx - lx) ** 2 + (by - ly) ** 2), a), 0) + 1; } + colliding(point, radius) { + let [px, py] = point; + let result = false; + + this.modules.forEach(m => { + let [mx, my] = this.getWorldPoint(...m.localCom); + let dis = Math.sqrt((py - my) ** 2 + (px - mx) ** 2); + if (dis < radius) result = true; + }); + + return result; + } + resolveCollisions() { this.landed = false; @@ -127,8 +140,11 @@ export default class Ship extends Body { applyThrust({ forward = 0, left = 0, right = 0, back = 0, turnLeft = 0, turnRight = 0}) { - let turnForce = (turnRight - turnLeft) / 20; - this.applyDirectionalForce(0, -forward / 30, turnForce); + + let thrustForce = -forward * consts.THRUST_POWER; + let turnForce = (turnRight - turnLeft) * consts.TURN_POWER; + + this.applyDirectionalForce(0, thrustForce, turnForce); this.modules.forEach(m => { if (m.type !== 'thruster') return; diff --git a/js/world/spawn.mjs b/js/world/spawn.mjs index 4203e45..ee14ff4 100644 --- a/js/world/spawn.mjs +++ b/js/world/spawn.mjs @@ -1,6 +1,7 @@ import Ship from './ship.mjs'; import Module from './module.mjs'; import Celestial from './celestial.mjs'; +import Entity from './entity.mjs'; import {modules} from '../data.mjs'; import * as world from './index.mjs'; @@ -16,11 +17,17 @@ export function player() { export function startPlanet() { return celestial(0, 0, 40, { - density: 10, + density: 3, type: 'green' }); } +export function testEntity() { + let entity = new Entity(0, -50); + world.entities.add(entity); + return entity; +} + export function celestial(x, y, radius, params) { let celestial = new Celestial(x - radius, y - radius, radius, params); world.celestials.add(celestial);