GGJ22/Engine/src/states/Game.ts
2022-01-29 21:43:20 -08:00

387 lines
11 KiB
TypeScript

import { ContextI } from "../ContextI"
import { segments } from "../shared/segments"
import { audio, playSong, disableSound, enableSound } from '../shared/audio'
import { StateI } from "./StateI"
import * as PIXI from 'pixi.js'
import * as planck from 'planck'
import { DecorationInstance } from "../shared/decors"
import { SpriteInstance, sprites } from "../shared/sprites"
import { ShapeCircle, ShapePoints } from "../data/sprite"
import { SegmentZone } from "../data/segment"
import { Zone } from "../live/Zone"
import { Entity } from "../live/Entity"
import { isPlayerEntity, PlayerEntity } from "../live/PlayerEntity"
import { Action } from "../live/Action"
import { WorldContext } from "../live/World"
export interface PIXIMissingColorMatrix extends PIXI.Filter {
night(intensity: number, multiply: boolean): void
contrast(amount: number, multiply: boolean): void
brightness(amount: number, multiply: boolean): void
saturate(amount: number, multiply: boolean): void
hue(rotation: number, multiply: boolean): void
colorTone(desaturation: number, toned: number, lightColor: number, darkColor: number, multiple: boolean): void
reset(): void
}
export interface Layer {
title: string
container: PIXI.Container
decorations: DecorationInstance[]
colorMatrix: PIXIMissingColorMatrix
}
export function GameState(ctx: ContextI): StateI {
//disableSound()
let isNight = false
let modeTimer = 0
let nightTime = 10 * 1000
let dayTime = 10 * 1000
let lastTime: number = performance.now()
let world: planck.World = planck.World({
gravity: planck.Vec2(0, 0),
})
let worldBody: planck.Body = world.createBody({
type: 'static',
})
world.on('begin-contact', (contact: planck.Contact) => {
let a = contact.getFixtureA().getUserData()
let b = contact.getFixtureB().getUserData()
if (a instanceof Zone) {
if (b instanceof Entity) {
b.addZoneContact(a)
}
} else if (a instanceof Entity) {
if (b instanceof Entity) {
a.addEntityContact(b)
b.addEntityContact(a)
}
}
})
world.on('end-contact', (contact: planck.Contact) => {
let a = contact.getFixtureA().getUserData()
let b = contact.getFixtureB().getUserData()
if (a instanceof Zone) {
if (b instanceof Entity) {
b.removeZoneContact(a)
}
} else if (a instanceof Entity) {
if (b instanceof Entity) {
a.removeEntityContact(b)
b.removeEntityContact(a)
}
}
})
let rootContainer = new PIXI.Container()
let playLayer: Layer
let layers: Layer[] = []
let entities: Entity[] = []
let zones: Zone[] = []
let enter = () => {
lastTime = performance.now()
hookKeyboard()
// Load the world segment.
let w = segments.world
if (!w) return ctx.pop()
rootContainer.width = w.width
rootContainer.height = w.height
rootContainer.scale.set(2, 2)
for (let l of w.layers) {
let layer: Layer = {
title: l.title,
container: new PIXI.Container(),
decorations: [],
colorMatrix: new PIXI.filters.ColorMatrixFilter()
}
layer.container.filters = [layer.colorMatrix]
layer.container.width = w.width
layer.container.height = w.height
if (l.title === 'objects') {
playLayer = layer
layer.container.sortableChildren = true
}
layers.push(layer)
for (let d of l.decorations) {
let di = new DecorationInstance(d.decor, d.decoration)
di.elapsed = d.timeOffset
di.container.x = d.x
di.container.y = d.y
if (d.rotation !== 0) {
di.container.angle = d.rotation
}
if (d.flip) {
di.container.pivot.y = 1
di.container.scale.y *= -1
if (d.rotation === 0) {
di.container.position.y--
}
}
if (d.mirror) {
di.container.pivot.x = 1
di.container.scale.x *= -1
di.container.position.x--
if (d.rotation !== 0) {
di.container.angle = -d.rotation
di.container.position.x++
di.container.position.y--
}
}
if (l.title === 'objects') {
di.container.zIndex = di.container.position.y + di.container.height/4
}
layer.container.addChild(di.container)
layer.decorations.push(di)
}
rootContainer.addChild(layer.container)
}
for (let z of w.zones) {
addZone(new Zone(z))
}
// Add bogus entity
addEntity(new PlayerEntity('deer.animal.stand.west.0'), 300, 100)
ctx.app.stage.addChild(rootContainer)
}
let leave = () => {
unhookKeyboard()
for (let entity of entities) {
removeEntity(entity)
}
ctx.app.stage.removeChild(rootContainer)
}
let elapsed: number = 0
let update = (delta: number) => {
let time = performance.now()
let realDelta = time - lastTime
lastTime = time
checkGamepads()
elapsed += delta
modeTimer += realDelta
if (isNight && modeTimer >= nightTime) {
modeTimer = 0
nightfall(false)
} else if (!isNight && modeTimer >= dayTime) {
modeTimer = 0
nightfall(true)
}
// Run world sim.
while (elapsed >= 1/60) {
world.step(1/60)
elapsed -= 1/60
}
// Update/render.
for (let layer of layers) {
for (let decoration of layer.decorations) {
decoration.update(realDelta)
}
}
for (let entity of entities) {
if (isPlayerEntity(entity)) {
entity.act(desiredActions)
// FIXME: This isn't the right place for this.
rootContainer.position.set(Math.round(ctx.app.renderer.width/2), Math.round(ctx.app.renderer.height/2))
rootContainer.pivot.set(Math.max(rootContainer.width/6, Math.min(entity.x, rootContainer.width/3)), Math.max(rootContainer.height/6, Math.min(entity.y, rootContainer.height/3)))
}
entity.update(realDelta, worldContext)
// I guess...
entity.sprite.container.x = entity.x
entity.sprite.container.y = entity.y
entity.sprite.container.zIndex = entity.y + (entity.sprite.frame?entity.sprite.frame.originY:0)
}
}
let addEntity = (entity: Entity, x: number, y: number) => {
if (entities.find(v=>v===entity)) return
console.log('add somethin', entity)
entities.push(entity)
playLayer.container.addChild(entity.sprite.container)
// I guess this is a fair enough place to create physics and add it to the entity.
let spriteShape = entity.sprite.getBodyShape()
if (spriteShape) {
let shape: planck.Shape|undefined
if (spriteShape instanceof ShapeCircle) {
shape = planck.Circle(planck.Vec2(spriteShape.x, spriteShape.y), spriteShape.radius)
} else if (spriteShape instanceof ShapePoints) {
shape = planck.Polygon(spriteShape.points.map(v=>planck.Vec2(v[0], v[1])))
}
if (shape !== undefined) {
let body = world.createDynamicBody({
position: planck.Vec2(entity.x, entity.y),
fixedRotation: true,
})
let fixture = body.createFixture({
shape,
density: 1,
friction: 0.9,
restitution: 0.05,
})
fixture.setUserData(entity)
entity.body = body
body.setUserData(entity)
}
}
entity.x = x
entity.y = y
}
let removeEntity = (entity: Entity) => {
entities = entities.filter(v=>v!==entity)
playLayer.container.removeChild(entity.sprite.container)
if (entity.body) {
world.destroyBody(entity.body)
entity.body = undefined
}
}
// Zonage
let addZone = (zone: Zone) => {
if (zones.find(v=>v===zone)) return
let shape = planck.Chain(zone.points.map(v=>planck.Vec2(v[0],v[1])))
zone.fixture = worldBody.createFixture({
shape: shape,
})
if (zone.type === 'fluid') {
zone.fixture.setSensor(true)
}
zone.fixture.setUserData(zone)
}
let removeZone = (zone: Zone) => {
zones = zones.filter(v=>v!==zone)
if (zone.fixture) {
worldBody.destroyFixture(zone.fixture)
}
}
let worldContext: WorldContext = {
addEntity,
removeEntity,
}
let desiredActions: Action[] = []
let adjustAction = (type: string, v: number, inc?: boolean) => {
let action = desiredActions.find(v=>v.type===type)
if (!action) {
if (v === 0) return
desiredActions.push({
type: type,
priority: v,
})
} else {
action.priority = inc?action.priority+v:v
if (action.priority <= 0) {
desiredActions = desiredActions.filter(v=>v!==action)
}
}
}
let keyup = (e: KeyboardEvent) => {
if (e.key === 'ArrowLeft') {
adjustAction('west', 0)
} else if (e.key === 'ArrowRight') {
adjustAction('east', 0)
} else if (e.key === 'ArrowUp') {
adjustAction('north', 0)
} else if (e.key === 'ArrowDown') {
adjustAction('south', 0)
}
}
let keydown = (e: KeyboardEvent) => {
if (e.repeat) return
if (e.key === 'ArrowLeft') {
adjustAction('west', 1)
} else if (e.key === 'ArrowRight') {
adjustAction('east', 1)
} else if (e.key === 'ArrowUp') {
adjustAction('north', 1)
} else if (e.key === 'ArrowDown') {
adjustAction('south', 1)
}
}
let hookKeyboard = () => {
window.addEventListener('keyup', keyup)
window.addEventListener('keydown', keydown)
}
let unhookKeyboard = () => {
window.removeEventListener('keyup', keyup)
window.removeEventListener('keydown', keydown)
}
let gamepads: Record<string, Gamepad> = {}
window.addEventListener('gamepadconnected', (ev: GamepadEvent) => {
gamepads[ev.gamepad.id] = ev.gamepad
})
window.removeEventListener('gamepaddisconnected', (ev: GamepadEvent) => {
delete gamepads[ev.gamepad.id]
})
function checkGamepads() {
//for (let gp of Object.values(gamepads)) {
for (let gp of navigator.getGamepads()) {
if (!gp || !gp.connected) break
if (gp.axes[0] < 0) {
adjustAction('west', Math.abs(gp.axes[0]))
} else if (gp.axes[0] > 0) {
adjustAction('east', gp.axes[0])
} else {
adjustAction('east', 0)
adjustAction('west', 0)
}
if (gp.axes[1] < 0) {
adjustAction('north', Math.abs(gp.axes[1]))
} else if (gp.axes[1] > 0) {
adjustAction('south', gp.axes[1])
} else {
adjustAction('north', 0)
adjustAction('south', 0)
}
//console.log(gp.axes, gp.buttons)
let attackHeld = false
for (let i = 0; i < 4; i++) {
let btn = gp.buttons[i]
if (btn.pressed) {
attackHeld = true
}
}
if (attackHeld) {
adjustAction('attack', 2)
} else if (desiredActions.find(v=>v.type==='attack')) {
adjustAction('attack', 0)
}
}
}
let nightfall = (b: boolean) => {
if (b) {
playSong('GGJ-ScaryMusic')
for (let l of layers) {
//l.colorMatrix.brightness(0.25, false)
l.colorMatrix.night(0.2, true)
l.colorMatrix.saturate(-0.75, true)
//l.colorMatrix.hue(160, true)
}
for (let e of entities) {
}
} else {
playSong('GGJ-HappyMusic')
for (let l of layers) {
l.colorMatrix.reset()
}
for (let e of entities) {
}
}
isNight = b
}
nightfall(false)
return {
enter,
leave,
update,
}
}