agentscape
Version:
Agentscape is a library for creating agent-based simulations. It provides a simple API for defining agents and their behavior, and for defining the environment in which the agents interact. Agentscape is designed to be flexible and extensible, allowing
156 lines (133 loc) • 4.05 kB
text/typescript
import Render2D, { Render2DConstructor } from './Render2D'
export type KeydownCallbackMap = {
[key: string]: {
callback: (e: KeyboardEvent) => void,
options: {
preventDefault?: boolean
}
}
}
export interface Animate2DConstructor extends Render2DConstructor {
frameRenderCallback: (tick: number, renderer: Render2D) => void
keydownCallbackMap?: KeydownCallbackMap
}
export default class Animate2D extends Render2D {
private frameRenderCallback: (tick: number, renderer: Render2D) => void
private runState: 'play'|'pause' = 'play'
public tick: number = 0
private defaultKeydownCallbackMap: KeydownCallbackMap = {
// space bar pauses animation
'Space': {
callback: () => {
if (this.runState === 'play') {
// emit a pause event
const event = new CustomEvent('pause')
window.dispatchEvent(event)
} else {
// emit a play event
const event = new CustomEvent('play')
window.dispatchEvent(event)
}
},
options: {preventDefault: true}
},
// return key steps animation forward
'Enter': {
callback: () => {
// emit a step event
const event = new CustomEvent('step')
window.dispatchEvent(event)
},
options: {preventDefault: true}
}
}
constructor(opts: Animate2DConstructor) {
const {
frameRenderCallback,
autoPlay = true,
frameRate = 60,
title = 'Canvas',
id = 'canvas_0',
} = opts
super({
...opts,
frameRate,
autoPlay,
title,
id
})
this.frameRenderCallback = frameRenderCallback
this.frameRate = frameRate
window.addEventListener('render', () => {
this.frameRenderCallback(this.tick, this)
})
window.addEventListener('stop', () => {
this.pause()
})
window.addEventListener('pause', () => {
this.pause()
})
window.addEventListener('play', () => {
this.play()
this.step()
})
window.addEventListener('step', () => {
if (this.runState === 'play') {
return
}
this.step()
})
window.addEventListener('fps', (e: Event) => {
const customEvent = e as CustomEvent
this.frameRate = customEvent.detail
})
// create a keydown event handler
window.addEventListener('keydown', (e: KeyboardEvent) => {
const mergedMaps = {...this.defaultKeydownCallbackMap, ...opts.keydownCallbackMap}
const codeAction = mergedMaps?.[e.code]
if (codeAction) {
const {callback, options} = codeAction
callback(e)
if (options.preventDefault) {
e.preventDefault()
}
}
})
if (autoPlay) {
this.runState = 'play'
this.play()
} else {
this.runState = 'pause'
this.pause()
}
this.step()
}
public step() {
this.frameRenderCallback(this.tick, this)
this.tick++
if (this.runState === 'play') {
setTimeout(
() => requestAnimationFrame(() => this.step()),
1000 / this.frameRate
)
}
}
public play() {
this.runState = 'play'
this.step()
}
public pause() {
this.runState = 'pause'
}
public toggle() {
if (this.runState === 'play') {
this.pause()
} else {
this.play()
}
}
public stepForward() {
this.pause()
this.step()
}
}