GGJ22/Engine/src/states/Game.ts

381 lines
10 KiB
TypeScript
Raw Normal View History

2022-01-29 01:37:01 -08:00
import { ContextI } from "../ContextI"
2022-01-29 03:44:34 -08:00
import { segments } from "../shared/segments"
2022-01-29 01:37:01 -08:00
import { StateI } from "./StateI"
2022-01-29 03:44:34 -08:00
import * as PIXI from 'pixi.js'
2022-01-29 16:31:40 -08:00
import * as planck from 'planck'
2022-01-29 03:44:34 -08:00
import { DecorationInstance } from "../shared/decors"
2022-01-29 16:31:40 -08:00
import { SpriteInstance, sprites } from "../shared/sprites"
import { ShapeCircle, ShapePoints } from "../data/sprite"
2022-01-29 04:57:01 -08:00
interface Action {
type: string
priority: number
}
class Entity {
sprite: SpriteInstance
2022-01-29 16:31:40 -08:00
body?: planck.Body
velocity: [number, number] = [0, 0]
acceleration: number = 0.5
maxSpeed: number = 4
direction: number = 0
2022-01-29 17:06:13 -08:00
turnRate: number = 10
2022-01-29 04:57:01 -08:00
constructor(ctor: string) {
this.sprite = new SpriteInstance(ctor)
}
update(delta: number) {
// TODO: Update sprite.
}
2022-01-29 16:31:40 -08:00
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]
}
2022-01-29 04:57:01 -08:00
get x(): number {
2022-01-29 16:31:40 -08:00
if (this.body) {
let p = this.body.getPosition()
return p.x
}
2022-01-29 04:57:01 -08:00
return this.sprite.container.x
}
set x(v: number) {
2022-01-29 16:31:40 -08:00
if (this.body) {
let p = this.body.getPosition()
this.body.setPosition(
planck.Vec2(
v,
p.y,
)
)
} else {
this.sprite.container.y = v
}
2022-01-29 04:57:01 -08:00
}
get y(): number {
2022-01-29 16:31:40 -08:00
if (this.body) {
let p = this.body.getPosition()
return p.y
}
2022-01-29 04:57:01 -08:00
return this.sprite.container.y
}
set y(v: number) {
2022-01-29 16:31:40 -08:00
if (this.body) {
let p = this.body.getPosition()
this.body.setPosition(
planck.Vec2(
p.x,
v,
)
)
} else {
this.sprite.container.y = v
}
2022-01-29 04:57:01 -08:00
}
}
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.
2022-01-29 17:06:13 -08:00
let shouldMove = false
2022-01-29 04:57:01 -08:00
switch(this.action.type) {
case 'west':
2022-01-29 17:06:13 -08:00
if (this.direction !== 0) {
if (this.direction < 180) {
this.direction -= this.turnRate
} else {
this.direction += this.turnRate
}
2022-01-29 16:31:40 -08:00
}
2022-01-29 17:06:13 -08:00
shouldMove = true
2022-01-29 04:57:01 -08:00
break
case 'east':
2022-01-29 17:06:13 -08:00
if (this.direction !== 180) {
if (this.direction < 180) {
this.direction += this.turnRate
} else {
this.direction -= this.turnRate
}
2022-01-29 16:31:40 -08:00
}
2022-01-29 17:06:13 -08:00
shouldMove = true
2022-01-29 04:57:01 -08:00
break
case 'north':
2022-01-29 17:06:13 -08:00
if (this.direction !== 90) {
if (this.direction < 90 || this.direction > 270) {
this.direction += this.turnRate
} else {
this.direction -= this.turnRate
}
2022-01-29 16:31:40 -08:00
}
2022-01-29 17:06:13 -08:00
shouldMove = true
2022-01-29 04:57:01 -08:00
break
case 'south':
2022-01-29 17:06:13 -08:00
if (this.direction !== 270) {
if (this.direction > 90 && this.direction < 270) {
this.direction += this.turnRate
} else {
this.direction -= this.turnRate
}
2022-01-29 16:31:40 -08:00
}
2022-01-29 17:06:13 -08:00
shouldMove = true
2022-01-29 04:57:01 -08:00
break
}
2022-01-29 17:06:13 -08:00
if (this.direction > 360) {
this.direction = 0
} else if (this.direction < 0) {
this.direction = 360
}
if (shouldMove) {
let r = this.direction * (Math.PI/180)
if (Math.abs(this.velocity[0]) < this.maxSpeed) {
this.velocity[0] -= Math.cos(r) * this.acceleration
}
if (Math.abs(this.velocity[1]) < this.maxSpeed) {
this.velocity[1] -= Math.sin(r) * this.acceleration
}
let cardinal = this.getCardinal()
if (this.sprite.subsetKey !== cardinal) {
this.sprite.setCtor(`${this.sprite.spriteKey}.${this.sprite.animationKey}.${this.sprite.setKey}.${cardinal}.${this.sprite.frameIndex}`)
}
}
2022-01-29 04:57:01 -08:00
}
2022-01-29 17:06:13 -08:00
2022-01-29 16:31:40 -08:00
//
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]))
2022-01-29 04:57:01 -08:00
}
2022-01-29 17:06:13 -08:00
getCardinal(): string {
const degreesPerDirection = 360 / 8
const angle = this.direction + degreesPerDirection / 2
if (angle >= 0 * degreesPerDirection && angle < 1 * degreesPerDirection) {
return 'w'
} else if (angle >= 1 * degreesPerDirection && angle < 2 * degreesPerDirection) {
return 'nw'
} else if (angle >= 2 * degreesPerDirection && angle < 3 * degreesPerDirection) {
return 'n'
} else if (angle >= 3 * degreesPerDirection && angle < 4 * degreesPerDirection) {
return 'ne'
} else if (angle >= 4 * degreesPerDirection && angle < 5 * degreesPerDirection) {
return 'e'
} else if (angle >= 5 * degreesPerDirection && angle < 6 * degreesPerDirection) {
return 'se'
} else if (angle >= 6 * degreesPerDirection && angle < 7 * degreesPerDirection) {
return 's'
}
return 'sw'
}
2022-01-29 04:57:01 -08:00
}
function isPlayerEntity(o: any): o is PlayerEntity {
return o.act
}
2022-01-29 01:37:01 -08:00
export function GameState(ctx: ContextI): StateI {
2022-01-29 16:31:40 -08:00
let world: planck.World = planck.World({
gravity: planck.Vec2(0, 0),
})
2022-01-29 01:37:01 -08:00
2022-01-29 03:44:34 -08:00
let rootContainer = new PIXI.Container()
let decorations: DecorationInstance[] = []
2022-01-29 04:57:01 -08:00
let entities: Entity[] = []
2022-01-29 03:44:34 -08:00
2022-01-29 01:37:01 -08:00
let enter = () => {
2022-01-29 04:57:01 -08:00
hookKeyboard()
2022-01-29 03:44:34 -08:00
// 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
2022-01-29 16:31:40 -08:00
if (d.rotation !== 0) {
di.container.angle = d.rotation
}
2022-01-29 03:44:34 -08:00
if (d.flip) {
di.container.pivot.y = 1
di.container.scale.y *= -1
2022-01-29 16:31:40 -08:00
if (d.rotation === 0) {
di.container.position.y--
}
2022-01-29 03:44:34 -08:00
}
if (d.mirror) {
di.container.pivot.x = 1
di.container.scale.x *= -1
di.container.position.x--
2022-01-29 16:31:40 -08:00
if (d.rotation !== 0) {
di.container.angle = -d.rotation
di.container.position.x++
di.container.position.y--
}
2022-01-29 03:44:34 -08:00
}
container.addChild(di.container)
decorations.push(di)
}
rootContainer.addChild(container)
}
2022-01-29 04:57:01 -08:00
// Add bogus entity
2022-01-29 16:31:40 -08:00
//addEntity(new PlayerEntity('animals.deer.animal.west.0'), 0, 0)
2022-01-29 17:06:13 -08:00
addEntity(new PlayerEntity('bogus-arrows.player.normal.w.0'), 0, 0)
2022-01-29 04:57:01 -08:00
2022-01-29 03:44:34 -08:00
ctx.app.stage.addChild(rootContainer)
2022-01-29 01:37:01 -08:00
}
let leave = () => {
2022-01-29 04:57:01 -08:00
unhookKeyboard()
for (let entity of entities) {
removeEntity(entity)
}
2022-01-29 03:44:34 -08:00
ctx.app.stage.removeChild(rootContainer)
2022-01-29 01:37:01 -08:00
}
2022-01-29 16:31:40 -08:00
let elapsed: number = 0
2022-01-29 01:37:01 -08:00
let update = (delta: number) => {
2022-01-29 16:31:40 -08:00
elapsed += delta
// Run world sim.
while (elapsed >= 1/60) {
world.step(1/60)
elapsed -= 1/60
}
// Update/render.
2022-01-29 03:44:34 -08:00
for (let decoration of decorations) {
decoration.update(delta)
}
2022-01-29 04:57:01 -08:00
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))
2022-01-29 05:18:51 -08:00
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)))
2022-01-29 04:57:01 -08:00
}
entity.update(delta)
2022-01-29 16:31:40 -08:00
// I guess...
entity.sprite.container.x = entity.x
entity.sprite.container.y = entity.y
2022-01-29 04:57:01 -08:00
}
}
2022-01-29 16:31:40 -08:00
let addEntity = (entity: Entity, x: number, y: number) => {
2022-01-29 04:57:01 -08:00
if (entities.find(v=>v===entity)) return
entities.push(entity)
rootContainer.addChild(entity.sprite.container)
2022-01-29 16:31:40 -08:00
// 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
2022-01-29 04:57:01 -08:00
}
let removeEntity = (entity: Entity) => {
entities = entities.filter(v=>v!==entity)
rootContainer.removeChild(entity.sprite.container)
2022-01-29 16:31:40 -08:00
if (entity.body) {
world.destroyBody(entity.body)
entity.body = undefined
}
2022-01-29 04:57:01 -08:00
}
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)
2022-01-29 01:37:01 -08:00
}
return {
enter,
leave,
update,
}
}