make game framework more functional

This commit is contained in:
Alexander Klingenbeck 2023-05-27 15:03:29 +02:00
parent 9d7397b513
commit 5bd2820ec8
6 changed files with 121 additions and 80 deletions

Binary file not shown.

View File

@ -1,19 +1,19 @@
export interface HasIdentity {
export interface HasIdentity {
id: number
}
export interface Drawable<T> {
draw: (entity: T) => void
draw: (entity: T) => void
}
export interface Updatable<T> {
update: (entity: T) => void
update: (entity: T) => void
}
export interface HasResources<T> {
load: (entity: T) => void
unload: (entity: T) => void
load: (entity: T) => void
unload: (entity: T) => void
}
export interface HasPosition {
@ -24,7 +24,7 @@ export interface HasColor {
color: Color
}
export type EntityOf<T> = HasIdentity & Partial<HasResources<T>> & Partial<Updatable<T>> & Partial<Drawable<T>> & T
export type EntityOf<T> = HasIdentity & Partial<HasResources<EntityOf<T>>> & Partial<Updatable<EntityOf<T>>> & Partial<Drawable<EntityOf<T>>> & T
let ID = 0
export const makeEntity = () => ({
@ -32,13 +32,16 @@ export const makeEntity = () => ({
})
export const makePosition = (x = 0, y = 0) => ({
position: new Vector2(x,y)
position: new Vector2(x, y)
})
export const makeColorRgb = (r = 255, g = 255, b = 255, a = 255) => ({
color: new Color(r,g,b,a)
color: new Color(r, g, b, a)
})
export const makeColorClone = (c = WHITE) => ({
color: new Color(c.r,c.g,c.b,c.a)
color: new Color(c.r, c.g, c.b, c.a)
})

View File

@ -1,4 +1,9 @@
import { EntityOf } from "./entity";
import { resourceUnloadAll } from "./resource";
const promiseUpdateList: (()=>boolean)[] = []
const entitiyList: EntityOf<any>[] = []
const dispatchPromises = () => {
for (var i = promiseUpdateList.length - 1; i >= 0; i--) {
@ -23,34 +28,37 @@ export const makeUpdateablePromise = (updateFn: () => boolean) => {
return promise
}
export const entityAdd = (entity: EntityOf<any>) => {
if (entity.load) entity.load(entity)
entitiyList.push(entity)
}
export abstract class Game {
public clearColor = BLACK
private quit = false
constructor(public readonly width: number,
public readonly height: number,
public readonly title: string){
export const entityRemove = (entity: EntityOf<any>) => {
// TODO: Do this cached
const i = entitiyList.findIndex(x => x.id === entity.id)
if (i !== -1) {
const e = entitiyList[i]
if (e.unload) e.unload(entity)
entitiyList.splice(i, 1)
}
}
public run(){
initWindow(this.width,this.height,this.title)
setTargetFPS(60)
this.load()
while(!(this.quit = windowShouldClose())){
dispatchPromises()
this.update()
beginDrawing()
clearBackground(this.clearColor)
this.draw()
endDrawing()
export const runGame = (width: number, height: number, title: string, startupCallback: () => void) => {
initWindow(width, height, title)
setTargetFPS(60)
startupCallback()
while(!windowShouldClose()){
dispatchPromises()
for (const entity of entitiyList) {
if (entity.update) entity.update(entity)
}
this.unload()
closeWindow()
beginDrawing()
clearBackground(BLACK)
for (const entity of entitiyList) {
if (entity.draw) entity.draw(entity)
}
endDrawing()
}
abstract draw(): void;
abstract update(): void;
abstract load(): void;
abstract unload(): void;
resourceUnloadAll()
closeWindow()
}

View File

@ -1,47 +1,23 @@
import { fadeIn, fadeOut, wait } from "./animation";
import { EntityOf } from "./entity";
import { Game, makeUpdateablePromise } from "./game";
import { entityAdd, entityRemove, runGame } from "./game";
import { TextEntity, makeTextEntity } from "./text";
class MyGame<T> extends Game {
entities: EntityOf<T>[] = []
draw(): void {
for (const entity of this.entities) {
if(entity.draw) entity.draw(entity)
}
}
update(): void {
for (const entity of this.entities) {
if(entity.update) entity.update(entity)
}
}
load(): void {
}
unload(): void {
}
addEntity(entity: EntityOf<T>){
this.entities.push(entity)
}
}
const game = new MyGame<TextEntity>(800,450,"Typescript Game")
const main = async () => {
runGame(800,400, "Typescript Game", async () => {
const text = <TextEntity>{
...makeTextEntity("Welcome to rayjs!"),
size: 80,
position: new Vector2(100, game.height/2 - 40)
position: new Vector2(100, getScreenHeight()/2 - 40),
font: 'resources/anonymous_pro_bold.ttf'
}
game.addEntity(text)
await fadeIn(text, 2)
await wait(2)
await fadeOut(text, 2)
text.position.x += 75
text.text = "This Summer!"
await fadeIn(text, 2)
}
main()
game.run()
let quit = false
entityAdd(text)
while(!quit){
await fadeIn(text, 2)
await wait(2)
await fadeOut(text, 2)
text.text = "This Summer!"
await fadeIn(text, 2)
await fadeOut(text, 3)
entityRemove(text)
}
})

View File

@ -0,0 +1,47 @@
type ResourceType = 'texture' | 'image' | 'shader' | 'font'
const resourceList = new Map<string, Resource>()
interface Resource {
refcount: number,
id: string,
resource: any
unload: (t: any) => void
}
function loadResourceFunc<T>(loader: (filename: string) => T, unloader: (resource: T) => void){
return (filename: string) => {
if(resourceList.has(filename)){
const res = resourceList.get(filename)
res!.refcount++
return <T>res?.resource
} else {
traceLog(LOG_INFO, "here")
const resource = loader(filename)
traceLog(LOG_INFO, <string>resource)
resourceList.set(filename, {
refcount: 1,
id: filename,
resource: resource,
unload: unloader
})
return resource
}
}
}
export const resourceUnload = (id: string) => {
const res = resourceList.get(id)
if(res){
res.refcount--
if(res.refcount === 0){
res.unload(res.resource)
}
}
}
export const resourceUnloadAll = () => {
for (const res of resourceList.entries()) {
res[1].unload(res[1].resource)
}
}
export const textureLoad = loadResourceFunc<Texture>(loadTexture,unloadTexture)
export const fontLoad = loadResourceFunc<Font>(loadFont,unloadFont)

View File

@ -1,34 +1,41 @@
import { EntityOf, HasColor, HasPosition, makeColorRgb, makeEntity, makePosition } from "./entity"
import { fontLoad, resourceUnload } from "./resource"
export interface Text extends HasPosition, HasColor {
text: string,
font: Font,
font?: string,
size: number,
spacing: number
}
export interface TextEntity extends EntityOf<Text> {
type: "text"
fontResource?: Font
}
export const makeText = (text = "", size = 20, font = null, spacing = 1) => (<Text>{
export const makeText = (text = "", size = 20, font: string | null = null, spacing = 1) => (<Text>{
...makePosition(),
...makeColorRgb(),
text: text,
font: font === null ? getFontDefault() : loadFont(font),
font: font,
size: size,
spacing: spacing
})
export const textDraw = (t: Text) => {
return drawTextEx(t.font, t.text, t.position, t.size, t.spacing, t.color);
export const textDrawFn = (t: TextEntity) => drawTextEx(t.fontResource!, t.text, t.position, t.size, t.spacing, t.color);
export const textLoadFn = (t: TextEntity) => {
t.fontResource = (t.font ? fontLoad(t.font) : getFontDefault())
return
}
export const textUnloadFn = (t: TextEntity) => t.font ? resourceUnload(t.font) : undefined
export const makeTextEntity = (text: string = "") => (<TextEntity>{
...makeEntity(),
...makeText(text),
type: "text",
draw: textDraw,
draw: textDrawFn,
load: textLoadFn,
unload: textUnloadFn
})