mirror of https://github.com/mode777/rayjs.git
little game framework
This commit is contained in:
parent
7603ba55dd
commit
10bded75db
|
@ -701,7 +701,10 @@ function main(){
|
||||||
// Font loading/unloading
|
// Font loading/unloading
|
||||||
core.addApiFunctionByName("GetFontDefault")
|
core.addApiFunctionByName("GetFontDefault")
|
||||||
core.addApiFunctionByName("LoadFont")
|
core.addApiFunctionByName("LoadFont")
|
||||||
// core.addApiFunctionByName("LoadFontEx")
|
const lfx = apiDesc.getFunction("LoadFontEx")
|
||||||
|
lfx?.params!.pop()
|
||||||
|
lfx?.params!.pop()
|
||||||
|
core.addApiFunction(lfx!, null, { customizeCall: "Font returnVal = LoadFontEx(fileName, fontSize, NULL, 0);" })
|
||||||
core.addApiFunctionByName("LoadFontFromImage")
|
core.addApiFunctionByName("LoadFontFromImage")
|
||||||
// core.addApiFunctionByName("LoadFontFromMemory")
|
// core.addApiFunctionByName("LoadFontFromMemory")
|
||||||
core.addApiFunctionByName("IsFontReady")
|
core.addApiFunctionByName("IsFontReady")
|
||||||
|
|
|
@ -12,9 +12,10 @@ export interface StructBindingOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FuncBindingOptions {
|
export interface FuncBindingOptions {
|
||||||
before?: (gen: QuickJsGenerator) => void
|
before?: (gen: QuickJsGenerator) => void,
|
||||||
after?: (gen: QuickJsGenerator) => void
|
after?: (gen: QuickJsGenerator) => void,
|
||||||
body?: (gen: QuickJsGenerator) => void
|
customizeCall?: string,
|
||||||
|
body?: (gen: QuickJsGenerator) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,17 +44,19 @@ export class RayLibHeader extends QuickJsHeader {
|
||||||
fun.jsToC(para.type,para.name,"argv["+i+"]", this.structLookup)
|
fun.jsToC(para.type,para.name,"argv["+i+"]", this.structLookup)
|
||||||
}
|
}
|
||||||
// call c function
|
// call c function
|
||||||
fun.call(api.name, api.params.map(x => x.name), api.returnType === "void" ? null : {type: api.returnType, name: "returnVal"})
|
if(options.customizeCall) fun.line(options.customizeCall)
|
||||||
|
else fun.call(api.name, api.params.map(x => x.name), api.returnType === "void" ? null : {type: api.returnType, name: "returnVal"})
|
||||||
// clean up parameters
|
// clean up parameters
|
||||||
for (const param of api.params) {
|
for (const param of api.params) {
|
||||||
fun.jsCleanUpParameter(param.type, param.name)
|
fun.jsCleanUpParameter(param.type, param.name)
|
||||||
}
|
}
|
||||||
if(options.after) options.after(fun)
|
|
||||||
// return result
|
// return result
|
||||||
if(api.returnType === "void"){
|
if(api.returnType === "void"){
|
||||||
|
if(options.after) options.after(fun)
|
||||||
fun.statement("return JS_UNDEFINED")
|
fun.statement("return JS_UNDEFINED")
|
||||||
} else {
|
} else {
|
||||||
fun.jsToJs(api.returnType, "ret", "returnVal", this.structLookup)
|
fun.jsToJs(api.returnType, "ret", "returnVal", this.structLookup)
|
||||||
|
if(options.after) options.after(fun)
|
||||||
fun.returnExp("ret")
|
fun.returnExp("ret")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -885,6 +885,8 @@ declare function getPixelDataSize(width: number, height: number, format: number)
|
||||||
declare function getFontDefault(): Font;
|
declare function getFontDefault(): Font;
|
||||||
/** Load font from file into GPU memory (VRAM) */
|
/** Load font from file into GPU memory (VRAM) */
|
||||||
declare function loadFont(fileName: string): Font;
|
declare function loadFont(fileName: string): Font;
|
||||||
|
/** Load font from file with extended parameters, use NULL for fontChars and 0 for glyphCount to load the default character set */
|
||||||
|
declare function loadFontEx(fileName: string, fontSize: number): Font;
|
||||||
/** Load font from Image (XNA style) */
|
/** Load font from Image (XNA style) */
|
||||||
declare function loadFontFromImage(image: Image, key: Color, firstChar: number): Font;
|
declare function loadFontFromImage(image: Image, key: Color, firstChar: number): Font;
|
||||||
/** Check if a font is ready */
|
/** Check if a font is ready */
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
"name": "bindings",
|
"name": "bindings",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"inkjs": "^2.2.1"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ts-loader": "^9.4.2",
|
"ts-loader": "^9.4.2",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
@ -801,6 +804,11 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/inkjs": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/inkjs/-/inkjs-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-Y3M8GsM8J2iL2RT+rtK2e6uTMgP+glGc7V4iJQELhclxdR3rEVQd40x4yopOBkIjIBZ4gpHnFbKWfdZe6GhEMA=="
|
||||||
|
},
|
||||||
"node_modules/interpret": {
|
"node_modules/interpret": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
|
||||||
|
|
|
@ -16,5 +16,8 @@
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"webpack": "^5.82.0",
|
"webpack": "^5.82.0",
|
||||||
"webpack-cli": "^5.0.2"
|
"webpack-cli": "^5.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"inkjs": "^2.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
import { HasColor } from "./entity"
|
import { Clickable, HasColor } from "./entity"
|
||||||
import { makeUpdateablePromise } from "./game"
|
import { makeUpdateablePromise } from "./game"
|
||||||
|
|
||||||
export type easeFunc = (t: number, a: number, b: number, d: number) => number
|
export type easeFunc = (t: number, a: number, b: number, d: number) => number
|
||||||
|
@ -17,11 +17,10 @@ export const interpolate = (a: number, b: number, d: number, setter: (v: number)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const wait = (time: number) => {
|
export const waitCondition = (predicate: () => boolean) => {
|
||||||
const start = getTime()
|
const start = getTime()
|
||||||
return makeUpdateablePromise(() => {
|
return makeUpdateablePromise(() => {
|
||||||
const cur = getTime()-start
|
if(predicate()){
|
||||||
if(cur < time){
|
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
|
@ -29,5 +28,16 @@ export const wait = (time: number) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const wait = (time: number) => {
|
||||||
|
const start = getTime()
|
||||||
|
return waitCondition(() => (getTime()-start) < time)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const waitFrame = (frames = 1) => waitCondition(() => !!(frames--) || frames <= 0)
|
||||||
|
|
||||||
|
export const waitKeyPressed = (key: number) => waitCondition(() => !isKeyPressed(key))
|
||||||
|
export const waitClick = (button: number = MOUSE_BUTTON_LEFT) => waitCondition(() => !isMouseButtonDown(button))
|
||||||
|
export const waitEntityClicked = (entity: Clickable) => waitCondition(() => !entity.isClicked)
|
||||||
|
|
||||||
export const fadeIn = (c: HasColor, time: number, easeFunc = easeLinearNone) => interpolate(0, 1, time, (v) => c.color = fade(c.color, v), easeFunc)
|
export const fadeIn = (c: HasColor, time: number, easeFunc = easeLinearNone) => interpolate(0, 1, time, (v) => c.color = fade(c.color, v), easeFunc)
|
||||||
export const fadeOut = (c: HasColor, time: number, easeFunc = easeLinearNone) => interpolate(0, 1, time, (v) => c.color = fade(c.color, 1-v), easeFunc)
|
export const fadeOut = (c: HasColor, time: number, easeFunc = easeLinearNone) => interpolate(0, 1, time, (v) => c.color = fade(c.color, 1-v), easeFunc)
|
|
@ -1,21 +1,29 @@
|
||||||
|
import { makeText } from "./text"
|
||||||
|
|
||||||
|
export type Creator<A,B> = (objIn: A) => B
|
||||||
|
export type Builder<A> = Creator<Partial<A>, A>
|
||||||
|
export type Extender<A,B> = Creator<A & Partial<B>, B>
|
||||||
|
|
||||||
|
export function makeCombined<A>(fn1: Builder<A>): Builder<A>
|
||||||
|
export function makeCombined<A,B>(fn1: Builder<A>, fn2: Extender<A,B>): Builder<A&B>
|
||||||
|
export function makeCombined<A,B,C>(fn1: Builder<A>, fn2: Extender<A,B>, fn3: Extender<A&B,C>): Builder<A&B&C>
|
||||||
|
export function makeCombined<A,B,C,D>(fn1: Builder<A>, fn2: Extender<A,B>, fn3: Extender<A&B,C>, fn4: Extender<A&B&C,D>): Builder<A&B&C&D>
|
||||||
|
export function makeCombined<A,B,C,D,E>(fn1: Builder<A>, fn2: Extender<A,B>, fn3: Extender<A&B,C>, fn4: Extender<A&B&C,D>, fn5: Extender<A&B&C&D,E>): Builder<A&B&C&D&E>
|
||||||
|
export function makeCombined<A,B,C,D,E,F>(fn1: Builder<A>, fn2: Extender<A,B>, fn3: Extender<A&B,C>, fn4: Extender<A&B&C,D>, fn5: Extender<A&B&C&D,E>, fn6: Extender<A&B&C&D&E,F>): Builder<A&B&C&D&E&F>
|
||||||
|
export function makeCombined<A,B,C,D,E,F>(fn1: Builder<A>, fn2?: Extender<A,B>, fn3?: Extender<A&B,C>, fn4?: Extender<A&B&C,D>, fn5?: Extender<A&B&C&D,E>, fn6?: Extender<A&B&C&D&E,F>): Builder<A> | Builder<A&B> | Builder<A&B&C> | Builder<A&B&C&D> | Builder<A&B&C&D&E> | Builder<A&B&C&D&E&F>
|
||||||
|
{
|
||||||
|
if(fn2 && fn3 && fn4 && fn5 && fn6) return makeCombined(makeCombined(fn1, fn2, fn3, fn4, fn5), fn6)
|
||||||
|
if(fn2 && fn3 && fn4 && fn5) return makeCombined(makeCombined(fn1, fn2, fn3, fn4), fn5)
|
||||||
|
if(fn2 && fn3 && fn4) return makeCombined(makeCombined(fn1, fn2, fn3), fn4)
|
||||||
|
if(fn2 && fn3) return makeCombined(makeCombined(fn1, fn2), fn3)
|
||||||
|
if(fn2) return (objIn: Partial<A&B>) => <A&B>fn2(<A&Partial<B>>fn1(objIn))
|
||||||
|
return fn1
|
||||||
|
}
|
||||||
|
|
||||||
export interface HasIdentity {
|
export interface HasIdentity {
|
||||||
id: number
|
id: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Drawable<T> {
|
|
||||||
draw: (entity: T) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Updatable<T> {
|
|
||||||
update: (entity: T) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HasResources<T> {
|
|
||||||
load: (entity: T) => void
|
|
||||||
unload: (entity: T) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HasPosition {
|
export interface HasPosition {
|
||||||
position: Vector2
|
position: Vector2
|
||||||
}
|
}
|
||||||
|
@ -24,24 +32,107 @@ export interface HasColor {
|
||||||
color: Color
|
color: Color
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EntityOf<T> = HasIdentity & Partial<HasResources<EntityOf<T>>> & Partial<Updatable<EntityOf<T>>> & Partial<Drawable<EntityOf<T>>> & T
|
export interface HasBoundingBox {
|
||||||
|
boundingBox: Rectangle
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Behaviour<T> {
|
||||||
|
load?: (entity: T) => void,
|
||||||
|
unload?: (entity: T) => void,
|
||||||
|
update?: (entity: T) => void,
|
||||||
|
draw?: (entity: T) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addBehaviour<T extends HasBehaviour>(obj: T, behaviour: Behaviour<T>){
|
||||||
|
obj.behaviours.push(behaviour)
|
||||||
|
}
|
||||||
|
export function removeBehaviour<T extends HasBehaviour>(obj: T, behaviour: Behaviour<T>){
|
||||||
|
const idx = obj.behaviours.findIndex(x => x === behaviour)
|
||||||
|
if(idx !== -1) obj.behaviours.splice(idx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HasBehaviour {
|
||||||
|
behaviours: Behaviour<any>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Entity = HasIdentity & HasBehaviour
|
||||||
|
export type EntityOf<T> = Entity & T
|
||||||
|
|
||||||
|
export const setPropFn = <T>(obj: T, key: keyof T, valueFn: () => any) => { if(obj[key] === undefined) obj[key] = valueFn() }
|
||||||
|
export const setProp = <T>(obj: T, key: keyof T, value: any) => { if(obj[key] === undefined) obj[key] = value }
|
||||||
|
|
||||||
let ID = 0
|
let ID = 0
|
||||||
export const makeEntity = () => ({
|
|
||||||
id: ID++
|
|
||||||
})
|
|
||||||
|
|
||||||
export const makePosition = (x = 0, y = 0) => ({
|
export const makeIdentity = (obj: Partial<HasIdentity>) => {
|
||||||
position: new Vector2(x, y)
|
setPropFn(obj, 'id', () => ID++)
|
||||||
})
|
return <HasIdentity>obj
|
||||||
|
}
|
||||||
|
|
||||||
export const makeColorRgb = (r = 255, g = 255, b = 255, a = 255) => ({
|
export const makeBehaviour = (obj: Partial<HasBehaviour>) => {
|
||||||
color: new Color(r, g, b, a)
|
setPropFn(obj, 'behaviours', () => [])
|
||||||
})
|
return <HasBehaviour>obj
|
||||||
|
}
|
||||||
|
|
||||||
export const makeColorClone = (c = WHITE) => ({
|
export const makeEntity: Builder<Entity> = makeCombined(makeIdentity, makeBehaviour)
|
||||||
color: new Color(c.r, c.g, c.b, c.a)
|
|
||||||
|
export const makePosition = (obj: Partial<HasPosition>,x = 0, y = 0) => {
|
||||||
|
setPropFn(obj, 'position', () => new Vector2(x,y))
|
||||||
|
return <HasPosition>obj
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeColorRgb = (obj: Partial<HasColor>, r = 255, g = 255, b = 255, a = 255) => {
|
||||||
|
setPropFn(obj, 'color', () => new Color(r, g, b, a))
|
||||||
|
return <HasColor>obj
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeColor = (obj: Partial<HasColor>, c = WHITE) => {
|
||||||
|
setPropFn(obj, 'color', () => new Color(c.r, c.g, c.b, c.a))
|
||||||
|
return <HasColor>obj
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeBoundingBox = (obj: Partial<HasBoundingBox>, x = 0, y = 0, width = 0, height = 0) => {
|
||||||
|
setPropFn(obj, 'boundingBox', () => new Rectangle(x,y,width,height))
|
||||||
|
return <HasBoundingBox>obj
|
||||||
|
}
|
||||||
|
|
||||||
|
export const debugRectDrawFn = (obj: HasBoundingBox, color = GREEN) => drawRectangleLines(obj.boundingBox.x, obj.boundingBox.y, obj.boundingBox.width, obj.boundingBox.height, color)
|
||||||
|
export const debugRectDrawBehaviour = { draw: debugRectDrawFn }
|
||||||
|
|
||||||
|
export interface Clickable extends HasBoundingBox, HasBehaviour {
|
||||||
|
isClicked: boolean
|
||||||
|
hasMouseOver: boolean,
|
||||||
|
hasMouseEntered: boolean,
|
||||||
|
hasMouseLeft: boolean
|
||||||
|
debugClickable: boolean
|
||||||
|
}
|
||||||
|
export const clickableBehaviour: Behaviour<Clickable> = {
|
||||||
|
update: (obj: Clickable) => {
|
||||||
|
const over = checkCollisionPointRec(getMousePosition(), obj.boundingBox)
|
||||||
|
obj.hasMouseEntered = !obj.hasMouseOver && over
|
||||||
|
obj.hasMouseLeft = obj.hasMouseOver && !over
|
||||||
|
obj.hasMouseOver = over
|
||||||
|
obj.isClicked = obj.hasMouseOver && isMouseButtonPressed(MOUSE_BUTTON_LEFT)
|
||||||
|
if(obj.hasMouseEntered) setMouseCursor(MOUSE_CURSOR_POINTING_HAND)
|
||||||
|
if(obj.hasMouseLeft) setMouseCursor(MOUSE_CURSOR_DEFAULT)
|
||||||
|
},
|
||||||
|
draw: (obj: Clickable) => {
|
||||||
|
if(obj.debugClickable){
|
||||||
|
debugRectDrawFn(obj, obj.hasMouseOver ? RED : GREEN)
|
||||||
|
drawCircle(getMouseX(), getMouseY(), 10, YELLOW)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unload: obj => setMouseCursor(MOUSE_CURSOR_DEFAULT)
|
||||||
|
}
|
||||||
|
export const makeClickable: Builder<Clickable> = makeCombined(makeBehaviour, makeBoundingBox, (obj: HasBehaviour & HasBoundingBox & Partial<Clickable>) => {
|
||||||
|
setProp(obj, 'hasMouseOver', false)
|
||||||
|
setProp(obj, 'isClicked', false)
|
||||||
|
setProp(obj, 'hasMouseEntered', false)
|
||||||
|
setProp(obj, 'hasMouseLeft', false)
|
||||||
|
setProp(obj, 'debugClickable', false)
|
||||||
|
addBehaviour(<Clickable>obj, clickableBehaviour)
|
||||||
|
return <Clickable>obj
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { EntityOf } from "./entity";
|
import { Behaviour, Entity, EntityOf } from "./entity";
|
||||||
|
import { forEachReverse } from "./helpers";
|
||||||
import { resourceUnloadAll } from "./resource";
|
import { resourceUnloadAll } from "./resource";
|
||||||
|
|
||||||
const promiseUpdateList: (()=>boolean)[] = []
|
const promiseUpdateList: (()=>boolean)[] = []
|
||||||
const entitiyList: EntityOf<any>[] = []
|
const entitiyList: Entity[] = []
|
||||||
|
|
||||||
|
|
||||||
const dispatchPromises = () => {
|
const dispatchPromises = () => {
|
||||||
|
@ -16,49 +17,65 @@ const dispatchPromises = () => {
|
||||||
|
|
||||||
export const makeUpdateablePromise = (updateFn: () => boolean) => {
|
export const makeUpdateablePromise = (updateFn: () => boolean) => {
|
||||||
let resFn: () => void
|
let resFn: () => void
|
||||||
|
let rejFn: (reason: any) => void
|
||||||
const promise = new Promise<void>((resolve, reject) => {
|
const promise = new Promise<void>((resolve, reject) => {
|
||||||
resFn = resolve
|
resFn = resolve
|
||||||
|
rejFn = reject
|
||||||
});
|
});
|
||||||
const update = () => {
|
const update = () => {
|
||||||
const res = updateFn()
|
try {
|
||||||
if(res) resFn()
|
const res = updateFn()
|
||||||
return res
|
if(res) resFn()
|
||||||
|
return res
|
||||||
|
} catch(e: any){
|
||||||
|
traceLog(LOG_INFO, "ERROR!")
|
||||||
|
rejFn(e)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
promiseUpdateList.unshift(update)
|
promiseUpdateList.unshift(update)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
export const entityAdd = (entity: EntityOf<any>) => {
|
export const entityAdd = (entity: Entity) => {
|
||||||
if (entity.load) entity.load(entity)
|
entity.behaviours.forEach(b => b.load ? b.load(entity) : undefined)
|
||||||
entitiyList.push(entity)
|
entitiyList.push(entity)
|
||||||
|
traceLog(LOG_INFO, `GAME: [ID ${entity.id}] loaded entity`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const entityRemove = (entity: EntityOf<any>) => {
|
export const entityUnload = (entity: Entity) => {
|
||||||
|
forEachReverse(entity.behaviours, (b, i) => b.unload ? b.unload(entity) : undefined);
|
||||||
|
traceLog(LOG_INFO, `GAME: [ID ${entity.id}] unloaded entity`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const entityRemove = (entity: Entity) => {
|
||||||
// TODO: Do this cached
|
// TODO: Do this cached
|
||||||
const i = entitiyList.findIndex(x => x.id === entity.id)
|
const i = entitiyList.findIndex(x => x.id === entity.id)
|
||||||
if (i !== -1) {
|
if (i !== -1) {
|
||||||
const e = entitiyList[i]
|
entityUnload(entity)
|
||||||
if (e.unload) e.unload(entity)
|
|
||||||
entitiyList.splice(i, 1)
|
entitiyList.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const runGame = (width: number, height: number, title: string, startupCallback: () => void) => {
|
export const runGame = (width: number, height: number, title: string, startupCallback: (quit: () => void) => void | Promise<void>) => {
|
||||||
initWindow(width, height, title)
|
initWindow(width, height, title)
|
||||||
setTargetFPS(60)
|
setTargetFPS(60)
|
||||||
startupCallback()
|
let quit = false
|
||||||
|
let exception: any = null
|
||||||
|
const p = startupCallback(() => quit = true)
|
||||||
|
if(p) p.catch(e => { exception = e })
|
||||||
while(!windowShouldClose()){
|
while(!windowShouldClose()){
|
||||||
dispatchPromises()
|
dispatchPromises()
|
||||||
for (const entity of entitiyList) {
|
if(exception) throw exception
|
||||||
if (entity.update) entity.update(entity)
|
entitiyList.forEach(e => e.behaviours.forEach(b => b.update ? b.update(e) : undefined))
|
||||||
}
|
|
||||||
beginDrawing()
|
beginDrawing()
|
||||||
clearBackground(BLACK)
|
clearBackground(BLACK)
|
||||||
for (const entity of entitiyList) {
|
drawText("Active promises: "+ promiseUpdateList.length, 10,10, 8, RAYWHITE)
|
||||||
if (entity.draw) entity.draw(entity)
|
entitiyList.forEach(e => e.behaviours.forEach(b => b.draw ? b.draw(e) : undefined))
|
||||||
}
|
|
||||||
endDrawing()
|
endDrawing()
|
||||||
}
|
}
|
||||||
|
entitiyList.forEach(x => entityUnload(x))
|
||||||
|
entitiyList.splice(0,entitiyList.length)
|
||||||
resourceUnloadAll()
|
resourceUnloadAll()
|
||||||
closeWindow()
|
closeWindow()
|
||||||
}
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export function forEachReverse<T>(arr: T[], fn: (el: T, i: number) => void){
|
||||||
|
for (var i = arr.length - 1; i >= 0; i--) {
|
||||||
|
fn(arr[i], i)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,67 @@
|
||||||
import { fadeIn, fadeOut, wait } from "./animation";
|
import { Choice } from "inkjs/engine/Choice";
|
||||||
|
import { fadeIn, fadeOut, wait, waitClick, waitEntityClicked, waitFrame, waitKeyPressed } from "./animation";
|
||||||
|
import { Builder, Clickable, addBehaviour, debugRectDrawBehaviour, makeCombined, setProp } from "./entity";
|
||||||
import { entityAdd, entityRemove, runGame } from "./game";
|
import { entityAdd, entityRemove, runGame } from "./game";
|
||||||
import { TextEntity, makeTextEntity } from "./text";
|
import { ClickableText as ClickableText, Text, makeClickableText as makeClickableText, makeParagraph, makeText as makeText } from "./text";
|
||||||
|
import { Compiler } from "inkjs";
|
||||||
|
|
||||||
runGame(800,400, "Typescript Game", async () => {
|
|
||||||
const text = <TextEntity>{
|
|
||||||
...makeTextEntity("Welcome to rayjs!"),
|
runGame(800,400, "Typescript Game", async (quit) => {
|
||||||
size: 80,
|
const source = loadFileText("resources/intercept.ink")
|
||||||
position: new Vector2(100, getScreenHeight()/2 - 40),
|
const c = new Compiler(source)
|
||||||
font: 'resources/anonymous_pro_bold.ttf'
|
const story = c.Compile()
|
||||||
|
traceLog(LOG_INFO, "[INK] Story loaded")
|
||||||
|
|
||||||
|
const textTemplate = {
|
||||||
|
size: 16,
|
||||||
|
font: 'resources/anonymous_pro_bold.ttf',
|
||||||
|
color: fade(WHITE, 0)
|
||||||
}
|
}
|
||||||
let quit = false
|
|
||||||
|
interface HasChoice { choice: Choice }
|
||||||
|
type ChoiceEntity = ClickableText & HasChoice
|
||||||
|
const makeChoice: Builder<ChoiceEntity> = makeCombined(makeClickableText, (obj: ClickableText & Partial<HasChoice>) => {
|
||||||
|
setProp(obj, "choice", null)
|
||||||
|
return <ChoiceEntity>obj
|
||||||
|
})
|
||||||
|
|
||||||
|
const text = makeParagraph({
|
||||||
|
...textTemplate,
|
||||||
|
position: new Vector2(32,32),
|
||||||
|
maxWidth: getScreenWidth()/2 - 64
|
||||||
|
})
|
||||||
entityAdd(text)
|
entityAdd(text)
|
||||||
while(!quit){
|
|
||||||
await fadeIn(text, 2)
|
while(true){
|
||||||
await wait(2)
|
while(story.canContinue){
|
||||||
await fadeOut(text, 2)
|
const txt = story.Continue()
|
||||||
text.text = "This Summer!"
|
if(txt?.trim() !== ''){
|
||||||
await fadeIn(text, 2)
|
await fadeOut(text, 1, easeCubicOut)
|
||||||
await fadeOut(text, 3)
|
text.text = txt!
|
||||||
entityRemove(text)
|
await fadeIn(text, 1, easeCubicInOut)
|
||||||
|
await waitClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(story.currentChoices.length == 0) break;
|
||||||
|
const choices = story.currentChoices.map((v,i) => makeChoice({
|
||||||
|
...textTemplate,
|
||||||
|
choice: v,
|
||||||
|
color: RAYWHITE,
|
||||||
|
//debugClickable: true,
|
||||||
|
position: new Vector2(getScreenWidth()/2,getScreenHeight()/2+(textTemplate.size + 10)*i),
|
||||||
|
text: `(${i}) ${v.text}`,
|
||||||
|
}))
|
||||||
|
for (const choice of choices) {
|
||||||
|
entityAdd(choice)
|
||||||
|
fadeIn(choice, 1)
|
||||||
|
await wait(0.5)
|
||||||
|
}
|
||||||
|
let choiceIdx = -1
|
||||||
|
await Promise.race(choices.map(x => waitEntityClicked(x).then(() => choiceIdx = x.choice.index)))
|
||||||
|
traceLog(LOG_INFO, "Clicked: " + choiceIdx)
|
||||||
|
choices.forEach(x => entityRemove(x))
|
||||||
|
story.ChooseChoiceIndex(choiceIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,9 +16,7 @@ function loadResourceFunc<T>(loader: (filename: string) => T, unloader: (resourc
|
||||||
res!.refcount++
|
res!.refcount++
|
||||||
return <T>res?.resource
|
return <T>res?.resource
|
||||||
} else {
|
} else {
|
||||||
traceLog(LOG_INFO, "here")
|
|
||||||
const resource = loader(filename)
|
const resource = loader(filename)
|
||||||
traceLog(LOG_INFO, <string>resource)
|
|
||||||
resourceList.set(filename, {
|
resourceList.set(filename, {
|
||||||
refcount: 1,
|
refcount: 1,
|
||||||
id: filename,
|
id: filename,
|
||||||
|
@ -45,4 +43,7 @@ export const resourceUnloadAll = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const textureLoad = loadResourceFunc<Texture>(loadTexture,unloadTexture)
|
export const textureLoad = loadResourceFunc<Texture>(loadTexture,unloadTexture)
|
||||||
export const fontLoad = loadResourceFunc<Font>(loadFont,unloadFont)
|
export const fontLoad = loadResourceFunc<Font>((id: string) => {
|
||||||
|
const split = id.split(':')
|
||||||
|
return loadFontEx(split[0], parseInt(split[1])!*getWindowScaleDPI().x)
|
||||||
|
},unloadFont)
|
|
@ -1,41 +1,120 @@
|
||||||
import { EntityOf, HasColor, HasPosition, makeColorRgb, makeEntity, makePosition } from "./entity"
|
import { Behaviour, Clickable, Entity, EntityOf, HasBehaviour, HasBoundingBox, HasColor, HasPosition, addBehaviour, makeBehaviour, makeBoundingBox, makeClickable, makeColor, makeColorRgb, makeCombined, makeEntity, makePosition, removeBehaviour, setProp, setPropFn } from "./entity"
|
||||||
import { fontLoad, resourceUnload } from "./resource"
|
import { fontLoad, resourceUnload, textureLoad } from "./resource"
|
||||||
|
|
||||||
export interface Text extends HasPosition, HasColor {
|
// FONT
|
||||||
text: string,
|
export interface HasFont extends HasBehaviour, HasColor {
|
||||||
font?: string,
|
font?: string,
|
||||||
size: number,
|
fontSize: number,
|
||||||
spacing: number
|
fontSpacing: number
|
||||||
}
|
|
||||||
|
|
||||||
export interface TextEntity extends EntityOf<Text> {
|
|
||||||
type: "text"
|
|
||||||
fontResource?: Font
|
fontResource?: Font
|
||||||
|
fontResourceId?: string
|
||||||
}
|
}
|
||||||
|
export const fontLoadBehaviour: Behaviour<HasFont> = {
|
||||||
export const makeText = (text = "", size = 20, font: string | null = null, spacing = 1) => (<Text>{
|
load: t => {
|
||||||
...makePosition(),
|
t.fontResourceId = t.font ? t.font + ":" + t.fontSize : undefined
|
||||||
...makeColorRgb(),
|
t.fontResource = (t.font ? fontLoad(t.fontResourceId!) : getFontDefault())
|
||||||
text: text,
|
},
|
||||||
font: font,
|
unload: t => t.font ? resourceUnload(t.fontResourceId!) : undefined
|
||||||
size: size,
|
}
|
||||||
spacing: spacing
|
export const makeFont = makeCombined(makeBehaviour, makeColor, (obj: HasBehaviour & Partial<HasFont>) => {
|
||||||
|
setProp(obj, 'font', undefined)
|
||||||
|
setProp(obj, 'fontSize', 20)
|
||||||
|
setProp(obj, 'fontSpacing', 1)
|
||||||
|
addBehaviour(<HasFont>obj, fontLoadBehaviour)
|
||||||
|
return <HasFont>obj
|
||||||
})
|
})
|
||||||
|
|
||||||
export const textDrawFn = (t: TextEntity) => drawTextEx(t.fontResource!, t.text, t.position, t.size, t.spacing, t.color);
|
// TEXT
|
||||||
export const textLoadFn = (t: TextEntity) => {
|
export interface Text extends Entity, HasFont, HasPosition {
|
||||||
t.fontResource = (t.font ? fontLoad(t.font) : getFontDefault())
|
text: string,
|
||||||
return
|
}
|
||||||
|
export const makeText = makeCombined(makeEntity, makeFont, makePosition, (obj: Partial<Text>) => {
|
||||||
|
setProp(obj, 'text', "")
|
||||||
|
addBehaviour(<Text>obj, textBehaviour)
|
||||||
|
return <Text>obj
|
||||||
|
})
|
||||||
|
export const textDrawFn = (t: Text, text?: string, position?: Vector2) => drawTextEx(t.fontResource!, text ?? t.text, position ?? t.position, t.fontSize, t.fontSpacing, t.color);
|
||||||
|
export const textBehaviour: Behaviour<Text> = {
|
||||||
|
draw: textDrawFn
|
||||||
}
|
}
|
||||||
export const textUnloadFn = (t: TextEntity) => t.font ? resourceUnload(t.font) : undefined
|
|
||||||
|
|
||||||
export const makeTextEntity = (text: string = "") => (<TextEntity>{
|
// PARAGRAPH
|
||||||
...makeEntity(),
|
export interface Line {
|
||||||
...makeText(text),
|
text: string,
|
||||||
type: "text",
|
width: number
|
||||||
draw: textDrawFn,
|
}
|
||||||
load: textLoadFn,
|
export interface Paragraph extends Text {
|
||||||
unload: textUnloadFn
|
lines: Line[],
|
||||||
|
maxWidth: number,
|
||||||
|
_textCached: string
|
||||||
|
}
|
||||||
|
export const breakTextLinesFn = (p: Paragraph) => {
|
||||||
|
const words = p.text.split(" ");
|
||||||
|
const lines: Line[] = [];
|
||||||
|
let currentLine = words[0];
|
||||||
|
|
||||||
|
let lastw = 0;
|
||||||
|
for (let i = 1; i < words.length; i++) {
|
||||||
|
const word = words[i];
|
||||||
|
const width = measureTextEx(p.fontResource!, currentLine + " " + word, p.fontSize, p.fontSpacing).x
|
||||||
|
if (width < p.maxWidth) {
|
||||||
|
currentLine += " " + word;
|
||||||
|
} else {
|
||||||
|
lines.push({ text: currentLine, width: lastw });
|
||||||
|
currentLine = word;
|
||||||
|
//lastw = width;
|
||||||
|
}
|
||||||
|
lastw = width;
|
||||||
|
if (i === words.length - 1)
|
||||||
|
lastw = measureTextEx(p.fontResource!, currentLine, p.fontSize, p.fontSpacing).x;
|
||||||
|
}
|
||||||
|
if (words.length === 1) lastw = measureTextEx(p.fontResource!, currentLine, p.fontSize, p.fontSpacing).x
|
||||||
|
lines.push({ text: currentLine, width: lastw });
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
export const paragraphBehaviour: Behaviour<Paragraph> = {
|
||||||
|
update: p => {
|
||||||
|
if (p._textCached !== p.text) {
|
||||||
|
p.lines = breakTextLinesFn(p)
|
||||||
|
p._textCached = p.text
|
||||||
|
}
|
||||||
|
},
|
||||||
|
draw: p => {
|
||||||
|
const v2 = new Vector2(p.position.x, p.position.y)
|
||||||
|
for (const line of p.lines) {
|
||||||
|
textDrawFn(p, line.text, v2)
|
||||||
|
v2.y += p.fontSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const makeParagraph = makeCombined(makeText, (obj: Partial<Paragraph>) => {
|
||||||
|
setProp(obj, 'lines', [])
|
||||||
|
setProp(obj, 'maxWidth', 100)
|
||||||
|
setProp(obj, "_textCached", "")
|
||||||
|
removeBehaviour(<Paragraph>obj, textBehaviour)
|
||||||
|
addBehaviour(<Paragraph>obj, paragraphBehaviour)
|
||||||
|
return <Paragraph>obj
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// CLICKABLE TEXT
|
||||||
|
export interface ClickableText extends Text, Clickable {
|
||||||
|
underlineWidth: number
|
||||||
|
}
|
||||||
|
export const calcTextBoundsFn = (obj: Text & HasBoundingBox) => {
|
||||||
|
const rec = obj.boundingBox = new Rectangle(obj.position.x, obj.position.y, 0, 0)
|
||||||
|
if (obj.fontResource) {
|
||||||
|
const size = measureTextEx(obj.fontResource!, obj.text, obj.fontSize, obj.fontSpacing)
|
||||||
|
rec.width = size.x
|
||||||
|
rec.height = size.y
|
||||||
|
}
|
||||||
|
obj.boundingBox = rec
|
||||||
|
}
|
||||||
|
export const clickableTextBehaviour: Behaviour<ClickableText> = {
|
||||||
|
load: calcTextBoundsFn,
|
||||||
|
draw: t => t.hasMouseOver ? drawRectangle(t.boundingBox.x, t.boundingBox.y + t.boundingBox.height, t.boundingBox.width, t.underlineWidth, t.color) : undefined
|
||||||
|
}
|
||||||
|
export const makeClickableText = makeCombined(makeText, makeClickable, (obj: Entity & Clickable & Partial<ClickableText>) => {
|
||||||
|
setProp(obj, 'underlineWidth', 1)
|
||||||
|
addBehaviour(<ClickableText>obj, clickableTextBehaviour)
|
||||||
|
return <ClickableText>obj
|
||||||
|
})
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"lib": ["ES2020"],
|
||||||
"types": [
|
"types": [
|
||||||
"../lib.raylib"
|
"../lib.raylib"
|
||||||
]
|
]
|
||||||
|
|
|
@ -709,19 +709,24 @@ class RayLibHeader extends quickjs_1.QuickJsHeader {
|
||||||
fun.jsToC(para.type, para.name, "argv[" + i + "]", this.structLookup);
|
fun.jsToC(para.type, para.name, "argv[" + i + "]", this.structLookup);
|
||||||
}
|
}
|
||||||
// call c function
|
// call c function
|
||||||
fun.call(api.name, api.params.map(x => x.name), api.returnType === "void" ? null : { type: api.returnType, name: "returnVal" });
|
if (options.customizeCall)
|
||||||
|
fun.line(options.customizeCall);
|
||||||
|
else
|
||||||
|
fun.call(api.name, api.params.map(x => x.name), api.returnType === "void" ? null : { type: api.returnType, name: "returnVal" });
|
||||||
// clean up parameters
|
// clean up parameters
|
||||||
for (const param of api.params) {
|
for (const param of api.params) {
|
||||||
fun.jsCleanUpParameter(param.type, param.name);
|
fun.jsCleanUpParameter(param.type, param.name);
|
||||||
}
|
}
|
||||||
if (options.after)
|
|
||||||
options.after(fun);
|
|
||||||
// return result
|
// return result
|
||||||
if (api.returnType === "void") {
|
if (api.returnType === "void") {
|
||||||
|
if (options.after)
|
||||||
|
options.after(fun);
|
||||||
fun.statement("return JS_UNDEFINED");
|
fun.statement("return JS_UNDEFINED");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fun.jsToJs(api.returnType, "ret", "returnVal", this.structLookup);
|
fun.jsToJs(api.returnType, "ret", "returnVal", this.structLookup);
|
||||||
|
if (options.after)
|
||||||
|
options.after(fun);
|
||||||
fun.returnExp("ret");
|
fun.returnExp("ret");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1623,7 +1628,10 @@ function main() {
|
||||||
// Font loading/unloading
|
// Font loading/unloading
|
||||||
core.addApiFunctionByName("GetFontDefault");
|
core.addApiFunctionByName("GetFontDefault");
|
||||||
core.addApiFunctionByName("LoadFont");
|
core.addApiFunctionByName("LoadFont");
|
||||||
// core.addApiFunctionByName("LoadFontEx")
|
const lfx = apiDesc.getFunction("LoadFontEx");
|
||||||
|
lfx?.params.pop();
|
||||||
|
lfx?.params.pop();
|
||||||
|
core.addApiFunction(lfx, null, { customizeCall: "Font returnVal = LoadFontEx(fileName, fontSize, NULL, 0);" });
|
||||||
core.addApiFunctionByName("LoadFontFromImage");
|
core.addApiFunctionByName("LoadFontFromImage");
|
||||||
// core.addApiFunctionByName("LoadFontFromMemory")
|
// core.addApiFunctionByName("LoadFontFromMemory")
|
||||||
core.addApiFunctionByName("IsFontReady");
|
core.addApiFunctionByName("IsFontReady");
|
||||||
|
|
|
@ -2648,8 +2648,8 @@ static JSValue js_loadFileText(JSContext * ctx, JSValueConst this_val, int argc,
|
||||||
const char * fileName = (const char *)JS_ToCString(ctx, argv[0]);
|
const char * fileName = (const char *)JS_ToCString(ctx, argv[0]);
|
||||||
char * returnVal = LoadFileText(fileName);
|
char * returnVal = LoadFileText(fileName);
|
||||||
JS_FreeCString(ctx, fileName);
|
JS_FreeCString(ctx, fileName);
|
||||||
UnloadFileText(returnVal);
|
|
||||||
JSValue ret = JS_NewString(ctx, returnVal);
|
JSValue ret = JS_NewString(ctx, returnVal);
|
||||||
|
UnloadFileText(returnVal);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5189,6 +5189,19 @@ static JSValue js_loadFont(JSContext * ctx, JSValueConst this_val, int argc, JSV
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JSValue js_loadFontEx(JSContext * ctx, JSValueConst this_val, int argc, JSValueConst * argv) {
|
||||||
|
const char * fileName = (const char *)JS_ToCString(ctx, argv[0]);
|
||||||
|
int fontSize;
|
||||||
|
JS_ToInt32(ctx, &fontSize, argv[1]);
|
||||||
|
Font returnVal = LoadFontEx(fileName, fontSize, NULL, 0);
|
||||||
|
JS_FreeCString(ctx, fileName);
|
||||||
|
Font* ret_ptr = (Font*)js_malloc(ctx, sizeof(Font));
|
||||||
|
*ret_ptr = returnVal;
|
||||||
|
JSValue ret = JS_NewObjectClass(ctx, js_Font_class_id);
|
||||||
|
JS_SetOpaque(ret, ret_ptr);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static JSValue js_loadFontFromImage(JSContext * ctx, JSValueConst this_val, int argc, JSValueConst * argv) {
|
static JSValue js_loadFontFromImage(JSContext * ctx, JSValueConst this_val, int argc, JSValueConst * argv) {
|
||||||
Image* image_ptr = (Image*)JS_GetOpaque2(ctx, argv[0], js_Image_class_id);
|
Image* image_ptr = (Image*)JS_GetOpaque2(ctx, argv[0], js_Image_class_id);
|
||||||
if(image_ptr == NULL) return JS_EXCEPTION;
|
if(image_ptr == NULL) return JS_EXCEPTION;
|
||||||
|
@ -9694,6 +9707,7 @@ static const JSCFunctionListEntry js_raylib_core_funcs[] = {
|
||||||
JS_CFUNC_DEF("getPixelDataSize",3,js_getPixelDataSize),
|
JS_CFUNC_DEF("getPixelDataSize",3,js_getPixelDataSize),
|
||||||
JS_CFUNC_DEF("getFontDefault",0,js_getFontDefault),
|
JS_CFUNC_DEF("getFontDefault",0,js_getFontDefault),
|
||||||
JS_CFUNC_DEF("loadFont",1,js_loadFont),
|
JS_CFUNC_DEF("loadFont",1,js_loadFont),
|
||||||
|
JS_CFUNC_DEF("loadFontEx",2,js_loadFontEx),
|
||||||
JS_CFUNC_DEF("loadFontFromImage",3,js_loadFontFromImage),
|
JS_CFUNC_DEF("loadFontFromImage",3,js_loadFontFromImage),
|
||||||
JS_CFUNC_DEF("isFontReady",1,js_isFontReady),
|
JS_CFUNC_DEF("isFontReady",1,js_isFontReady),
|
||||||
JS_CFUNC_DEF("unloadFont",1,js_unloadFont),
|
JS_CFUNC_DEF("unloadFont",1,js_unloadFont),
|
||||||
|
|
Loading…
Reference in New Issue