import { ContextI } from "../ContextI" import { segments } from "../shared/segments" 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" interface Action { type: string priority: number } class Entity { sprite: SpriteInstance body?: planck.Body velocity: [number, number] = [0, 0] acceleration: number = 0.5 maxSpeed: number = 4 direction: number = 0 constructor(ctor: string) { this.sprite = new SpriteInstance(ctor) } update(delta: number) { // TODO: Update sprite. } get position(): [number, number] { if (this.body) { let p = this.body.getPosition() return [p.x, p.y] } return [this.sprite.container.x, this.sprite.container.y] } get x(): number { if (this.body) { let p = this.body.getPosition() return p.x } return this.sprite.container.x } set x(v: number) { if (this.body) { let p = this.body.getPosition() this.body.setPosition( planck.Vec2( v, p.y, ) ) } else { this.sprite.container.y = v } } get y(): number { if (this.body) { let p = this.body.getPosition() return p.y } return this.sprite.container.y } set y(v: number) { if (this.body) { let p = this.body.getPosition() this.body.setPosition( planck.Vec2( p.x, v, ) ) } else { this.sprite.container.y = v } } } class PlayerEntity extends Entity { action?: Action constructor(ctor: string) { super(ctor) } act(actions: Action[]) { this.action = actions.sort((a, b) => { if (a.priority < b.priority) { return -1 } if (a.priority > b.priority) { return 1 } return 0 })[0] } update(delta: number) { if (this.action) { // FIXME: Use physics. switch(this.action.type) { case 'west': if (this.velocity[0] > -this.maxSpeed) { this.velocity[0] -= this.acceleration } break case 'east': if (this.velocity[0] < this.maxSpeed) { this.velocity[0] += this.acceleration } break case 'north': if (this.velocity[1] > -this.maxSpeed) { this.velocity[1] -= this.acceleration } break case 'south': if (this.velocity[1] < this.maxSpeed) { this.velocity[1] += this.acceleration } break } } // this.velocity[0] *= 0.5 this.velocity[1] *= 0.5 // Eh... let's manually handle velocity this.body?.setLinearVelocity(planck.Vec2(this.velocity[0], this.velocity[1])) } } function isPlayerEntity(o: any): o is PlayerEntity { return o.act } export function GameState(ctx: ContextI): StateI { let world: planck.World = planck.World({ gravity: planck.Vec2(0, 0), }) let rootContainer = new PIXI.Container() let decorations: DecorationInstance[] = [] let entities: Entity[] = [] let enter = () => { 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 container = new PIXI.Container() container.width = w.width container.height = w.height 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-- } } container.addChild(di.container) decorations.push(di) } rootContainer.addChild(container) } // Add bogus entity //addEntity(new PlayerEntity('animals.deer.animal.west.0'), 0, 0) addEntity(new PlayerEntity('bogus-arrows.player.normal.e.0'), 0, 0) 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) => { elapsed += delta // Run world sim. while (elapsed >= 1/60) { world.step(1/60) elapsed -= 1/60 } // Update/render. for (let decoration of decorations) { decoration.update(delta) } 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(delta) // I guess... entity.sprite.container.x = entity.x entity.sprite.container.y = entity.y } } let addEntity = (entity: Entity, x: number, y: number) => { if (entities.find(v=>v===entity)) return entities.push(entity) rootContainer.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, }) entity.body = body body.setUserData(entity) } } entity.x = x entity.y = y } let removeEntity = (entity: Entity) => { entities = entities.filter(v=>v!==entity) rootContainer.removeChild(entity.sprite.container) if (entity.body) { world.destroyBody(entity.body) entity.body = undefined } } let desiredActions: Action[] = [] let adjustAction = (type: string, v: number) => { let action = desiredActions.find(v=>v.type===type) if (!action) { desiredActions.push({ type: type, priority: v, }) } else { action.priority += v if (action.priority <= 0) { desiredActions = desiredActions.filter(v=>v!==action) } } } let keyup = (e: KeyboardEvent) => { 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 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) } return { enter, leave, update, } }