mirror of https://github.com/mode777/rayjs.git
Improve framework
This commit is contained in:
parent
94324f5300
commit
da5ce36250
|
@ -1,43 +0,0 @@
|
||||||
import { Clickable, HasColor } from "./entity"
|
|
||||||
import { makeUpdateablePromise } from "./game"
|
|
||||||
|
|
||||||
export type easeFunc = (t: number, a: number, b: number, d: number) => number
|
|
||||||
|
|
||||||
export const interpolate = (a: number, b: number, d: number, setter: (v: number) => void, fn: easeFunc) => {
|
|
||||||
const start = getTime()
|
|
||||||
return makeUpdateablePromise(() => {
|
|
||||||
const cur = getTime()-start
|
|
||||||
if(cur < d){
|
|
||||||
setter(fn(cur, a, b, d))
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
setter(b)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const waitCondition = (predicate: () => boolean) => {
|
|
||||||
const start = getTime()
|
|
||||||
return makeUpdateablePromise(() => {
|
|
||||||
if(predicate()){
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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 fadeOut = (c: HasColor, time: number, easeFunc = easeLinearNone) => interpolate(0, 1, time, (v) => c.color = fade(c.color, 1-v), easeFunc)
|
|
|
@ -1,21 +1,36 @@
|
||||||
import { makeText } from "./text"
|
import { makeInlineText } from "./text"
|
||||||
|
|
||||||
|
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 type Creator<A,B> = (objIn: A) => B
|
export type Creator<A,B> = (objIn: A) => B
|
||||||
export type Builder<A> = Creator<Partial<A>, A>
|
export type Builder<A> = Creator<Partial<A>, A>
|
||||||
export type Extender<A,B> = Creator<A & Partial<B>, B>
|
export type Extender<A,B> = Creator<A & Partial<B>, B>
|
||||||
|
|
||||||
export function makeCombined<A>(fn1: Builder<A>): Builder<A>
|
export function combine<A>(fn1: Builder<A>): Builder<A>
|
||||||
export function makeCombined<A,B>(fn1: Builder<A>, fn2: Extender<A,B>): Builder<A&B>
|
export function combine<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 combine<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 combine<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 combine<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 combine<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>
|
export function combine<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 && fn6) return combine(combine(fn1, fn2, fn3, fn4, fn5), fn6)
|
||||||
if(fn2 && fn3 && fn4 && fn5) return makeCombined(makeCombined(fn1, fn2, fn3, fn4), fn5)
|
if(fn2 && fn3 && fn4 && fn5) return combine(combine(fn1, fn2, fn3, fn4), fn5)
|
||||||
if(fn2 && fn3 && fn4) return makeCombined(makeCombined(fn1, fn2, fn3), fn4)
|
if(fn2 && fn3 && fn4) return combine(combine(fn1, fn2, fn3), fn4)
|
||||||
if(fn2 && fn3) return makeCombined(makeCombined(fn1, fn2), fn3)
|
if(fn2 && fn3) return combine(combine(fn1, fn2), fn3)
|
||||||
if(fn2) return (objIn: Partial<A&B>) => <A&B>fn2(<A&Partial<B>>fn1(objIn))
|
if(fn2) return (objIn: Partial<A&B>) => <A&B>fn2(<A&Partial<B>>fn1(objIn))
|
||||||
return fn1
|
return fn1
|
||||||
}
|
}
|
||||||
|
@ -36,21 +51,6 @@ export interface HasBoundingBox {
|
||||||
boundingBox: Rectangle
|
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 {
|
export interface HasBehaviour {
|
||||||
behaviours: Behaviour<any>[]
|
behaviours: Behaviour<any>[]
|
||||||
}
|
}
|
||||||
|
@ -58,55 +58,33 @@ export interface HasBehaviour {
|
||||||
export type Entity = HasIdentity & HasBehaviour
|
export type Entity = HasIdentity & HasBehaviour
|
||||||
export type EntityOf<T> = Entity & T
|
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 hasDefaultFn = <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 }
|
export const hasDefault = <T>(obj: T, key: keyof T, value: any) => { if(obj[key] === undefined) obj[key] = value }
|
||||||
|
|
||||||
|
export const withComponent = <T>(fn?: (obj: Partial<T>) => void) => (obj: Partial<T>) => { if(fn){fn(obj)}; return <T>obj }
|
||||||
|
export const which = <T extends HasBehaviour>(behaviour: Behaviour<T>) => (obj: T) => { addBehaviour(obj,behaviour); return obj; }
|
||||||
let ID = 0
|
let ID = 0
|
||||||
|
export const withIdentity = withComponent<HasIdentity>(x => hasDefaultFn(x,'id', () => ID++))
|
||||||
|
export const withBehaviour = withComponent<HasBehaviour>(x => hasDefaultFn(x, 'behaviours', () => []))
|
||||||
|
export const withPosition = withComponent<HasPosition>(x => hasDefaultFn(x, 'position', () => new Vector2(0,0)))
|
||||||
|
export const withColor = withComponent<HasColor>(x => hasDefaultFn(x, 'color', () => new Color(255,255,255,255)))
|
||||||
|
export const withBoundingBox = withComponent<HasBoundingBox>(x => hasDefaultFn(x, 'boundingBox', () => new Rectangle(0,0,0,0)))
|
||||||
|
|
||||||
export const makeIdentity = (obj: Partial<HasIdentity>) => {
|
export const makeEntity: Builder<Entity> = combine(withIdentity, withBehaviour)
|
||||||
setPropFn(obj, 'id', () => ID++)
|
|
||||||
return <HasIdentity>obj
|
|
||||||
}
|
|
||||||
|
|
||||||
export const makeBehaviour = (obj: Partial<HasBehaviour>) => {
|
|
||||||
setPropFn(obj, 'behaviours', () => [])
|
|
||||||
return <HasBehaviour>obj
|
|
||||||
}
|
|
||||||
|
|
||||||
export const makeEntity: Builder<Entity> = makeCombined(makeIdentity, makeBehaviour)
|
|
||||||
|
|
||||||
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 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 const debugRectDrawBehaviour = { draw: debugRectDrawFn }
|
||||||
|
|
||||||
export interface Clickable extends HasBoundingBox, HasBehaviour {
|
export interface HasMouseInteraction {
|
||||||
isClicked: boolean
|
isClicked: boolean
|
||||||
hasMouseOver: boolean,
|
hasMouseOver: boolean,
|
||||||
hasMouseEntered: boolean,
|
hasMouseEntered: boolean,
|
||||||
hasMouseLeft: boolean
|
hasMouseLeft: boolean
|
||||||
debugClickable: boolean
|
debugClickable: boolean
|
||||||
}
|
}
|
||||||
export const clickableBehaviour: Behaviour<Clickable> = {
|
export const withMouseInteraction = withComponent<HasMouseInteraction>()
|
||||||
update: (obj: Clickable) => {
|
export const checksBoundingBoxClicks: Behaviour<HasMouseInteraction&HasBoundingBox> = {
|
||||||
|
update: obj => {
|
||||||
const over = checkCollisionPointRec(getMousePosition(), obj.boundingBox)
|
const over = checkCollisionPointRec(getMousePosition(), obj.boundingBox)
|
||||||
obj.hasMouseEntered = !obj.hasMouseOver && over
|
obj.hasMouseEntered = !obj.hasMouseOver && over
|
||||||
obj.hasMouseLeft = obj.hasMouseOver && !over
|
obj.hasMouseLeft = obj.hasMouseOver && !over
|
||||||
|
@ -115,23 +93,14 @@ export const clickableBehaviour: Behaviour<Clickable> = {
|
||||||
if(obj.hasMouseEntered) setMouseCursor(MOUSE_CURSOR_POINTING_HAND)
|
if(obj.hasMouseEntered) setMouseCursor(MOUSE_CURSOR_POINTING_HAND)
|
||||||
if(obj.hasMouseLeft) setMouseCursor(MOUSE_CURSOR_DEFAULT)
|
if(obj.hasMouseLeft) setMouseCursor(MOUSE_CURSOR_DEFAULT)
|
||||||
},
|
},
|
||||||
draw: (obj: Clickable) => {
|
draw: obj => {
|
||||||
if(obj.debugClickable){
|
if(obj.debugClickable){
|
||||||
debugRectDrawFn(obj, obj.hasMouseOver ? RED : GREEN)
|
debugRectDrawFn(obj, obj.hasMouseOver ? RED : GREEN)
|
||||||
drawCircle(getMouseX(), getMouseY(), 10, YELLOW)
|
drawCircle(getMouseX(), getMouseY(), 10, YELLOW)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unload: obj => setMouseCursor(MOUSE_CURSOR_DEFAULT)
|
unload: obj => obj.hasMouseOver && 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
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,38 +2,75 @@ import { Behaviour, Entity, EntityOf } from "./entity";
|
||||||
import { forEachReverse } from "./helpers";
|
import { forEachReverse } from "./helpers";
|
||||||
import { resourceUnloadAll } from "./resource";
|
import { resourceUnloadAll } from "./resource";
|
||||||
|
|
||||||
const promiseUpdateList: (()=>boolean)[] = []
|
const promiseUpdateList: PromiseContext<any>[] = []
|
||||||
const entitiyList: Entity[] = []
|
const entitiyList: Entity[] = []
|
||||||
|
|
||||||
|
|
||||||
const dispatchPromises = () => {
|
const dispatchPromises = () => {
|
||||||
for (var i = promiseUpdateList.length - 1; i >= 0; i--) {
|
for (var i = promiseUpdateList.length - 1; i >= 0; i--) {
|
||||||
const finished = promiseUpdateList[i]()
|
const p = promiseUpdateList[i]
|
||||||
if (finished) {
|
p.update()
|
||||||
|
if (p.isFinished) {
|
||||||
promiseUpdateList.splice(i, 1);
|
promiseUpdateList.splice(i, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeUpdateablePromise = (updateFn: () => boolean) => {
|
class PromiseContext<T> {
|
||||||
let resFn: () => void
|
|
||||||
let rejFn: (reason: any) => void
|
private _result: T | null = null;
|
||||||
const promise = new Promise<void>((resolve, reject) => {
|
public get result(): T | null {
|
||||||
resFn = resolve
|
return this._result;
|
||||||
rejFn = reject
|
}
|
||||||
});
|
private _error: any | null = null;
|
||||||
const update = () => {
|
public get error(): any | null {
|
||||||
try {
|
return this._error;
|
||||||
const res = updateFn()
|
}
|
||||||
if(res) resFn()
|
private _isFinished = false;
|
||||||
return res
|
public get isFinished() {
|
||||||
} catch(e: any){
|
return this._isFinished;
|
||||||
traceLog(LOG_INFO, "ERROR!")
|
}
|
||||||
rejFn(e)
|
private _isCancellationRequested = false;
|
||||||
return true
|
public get isCancellationRequested() {
|
||||||
|
return this._isCancellationRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly resolveFn: (val: T | PromiseLike<T>) => void,
|
||||||
|
private readonly rejectFn: (err: any) => void,
|
||||||
|
private readonly updateFn: (p: PromiseContext<T>) => void){}
|
||||||
|
|
||||||
|
update(){
|
||||||
|
if(!this.isFinished){
|
||||||
|
this.updateFn(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
promiseUpdateList.unshift(update)
|
|
||||||
|
resolve(val: T){
|
||||||
|
this._result = val
|
||||||
|
this._isFinished = true
|
||||||
|
this.resolveFn(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(reason: any){
|
||||||
|
this._error = reason
|
||||||
|
this._isFinished = true
|
||||||
|
this.rejectFn(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(){
|
||||||
|
this._isCancellationRequested = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export interface ExtendedPromise<T> extends Promise<T> {
|
||||||
|
context: PromiseContext<T>
|
||||||
|
}
|
||||||
|
export const makeUpdateablePromise = <T>(update: (ctx: PromiseContext<T>) => void) => {
|
||||||
|
let context: PromiseContext<T>
|
||||||
|
const promise = <ExtendedPromise<T>>new Promise<T>((resolve, reject) => {
|
||||||
|
context = new PromiseContext<T>(resolve,reject,update)
|
||||||
|
});
|
||||||
|
promise.context = context!
|
||||||
|
promiseUpdateList.unshift(context!)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { Choice } from "inkjs/engine/Choice";
|
import { Choice } from "inkjs/engine/Choice";
|
||||||
import { fadeIn, fadeOut, wait, waitClick, waitEntityClicked, waitFrame, waitKeyPressed } from "./animation";
|
import { fadeIn, fadeOut, move, wait, waitAnyClicked, waitClick } from "./timing";
|
||||||
import { Builder, Clickable, addBehaviour, debugRectDrawBehaviour, makeCombined, setProp } from "./entity";
|
import { Builder, combine, withComponent } from "./entity";
|
||||||
import { entityAdd, entityRemove, runGame } from "./game";
|
import { entityAdd, entityRemove, runGame } from "./game";
|
||||||
import { ClickableText as ClickableText, Text, makeClickableText as makeClickableText, makeParagraph, makeText as makeText } from "./text";
|
import { ClickableText, makeClickableText, makeParagraph } from "./text";
|
||||||
import { Compiler } from "inkjs";
|
import { Compiler } from "inkjs";
|
||||||
|
|
||||||
|
runGame(800,400, "The Intercept", async (quit) => {
|
||||||
|
|
||||||
runGame(800,400, "Typescript Game", async (quit) => {
|
|
||||||
const source = loadFileText("resources/intercept.ink")
|
const source = loadFileText("resources/intercept.ink")
|
||||||
const c = new Compiler(source)
|
const c = new Compiler(source)
|
||||||
const story = c.Compile()
|
const story = c.Compile()
|
||||||
|
@ -19,12 +17,7 @@ runGame(800,400, "Typescript Game", async (quit) => {
|
||||||
color: fade(WHITE, 0)
|
color: fade(WHITE, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HasChoice { choice: Choice }
|
const makeChoice = combine(makeClickableText, withComponent<{ 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({
|
const text = makeParagraph({
|
||||||
...textTemplate,
|
...textTemplate,
|
||||||
|
@ -37,9 +30,12 @@ runGame(800,400, "Typescript Game", async (quit) => {
|
||||||
while(story.canContinue){
|
while(story.canContinue){
|
||||||
const txt = story.Continue()
|
const txt = story.Continue()
|
||||||
if(txt?.trim() !== ''){
|
if(txt?.trim() !== ''){
|
||||||
await fadeOut(text, 1, easeCubicOut)
|
move(text, new Vector2(32,64), 1, easeCubicIn)
|
||||||
|
await fadeOut(text, 1)
|
||||||
text.text = txt!
|
text.text = txt!
|
||||||
await fadeIn(text, 1, easeCubicInOut)
|
text.position = new Vector2(32,0)
|
||||||
|
move(text, new Vector2(32,32), 1, easeCubicOut)
|
||||||
|
await fadeIn(text, 1)
|
||||||
await waitClick()
|
await waitClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,20 +44,22 @@ runGame(800,400, "Typescript Game", async (quit) => {
|
||||||
...textTemplate,
|
...textTemplate,
|
||||||
choice: v,
|
choice: v,
|
||||||
color: RAYWHITE,
|
color: RAYWHITE,
|
||||||
//debugClickable: true,
|
|
||||||
position: new Vector2(getScreenWidth()/2,getScreenHeight()/2+(textTemplate.size + 10)*i),
|
position: new Vector2(getScreenWidth()/2,getScreenHeight()/2+(textTemplate.size + 10)*i),
|
||||||
text: `(${i}) ${v.text}`,
|
text: `(${i}) ${v.text}`,
|
||||||
}))
|
}))
|
||||||
for (const choice of choices) {
|
for (const choice of choices) {
|
||||||
entityAdd(choice)
|
entityAdd(choice)
|
||||||
|
choice.position.x += 32
|
||||||
|
move(choice, new Vector2(choice.position.x-32,choice.position.y), 1, easeSineOut)
|
||||||
fadeIn(choice, 1)
|
fadeIn(choice, 1)
|
||||||
await wait(0.5)
|
await wait(0.5)
|
||||||
}
|
}
|
||||||
let choiceIdx = -1
|
const choiceIdx = await waitAnyClicked(choices)
|
||||||
await Promise.race(choices.map(x => waitEntityClicked(x).then(() => choiceIdx = x.choice.index)))
|
|
||||||
traceLog(LOG_INFO, "Clicked: " + choiceIdx)
|
traceLog(LOG_INFO, "Clicked: " + choiceIdx)
|
||||||
choices.forEach(x => entityRemove(x))
|
|
||||||
story.ChooseChoiceIndex(choiceIdx)
|
story.ChooseChoiceIndex(choiceIdx)
|
||||||
|
choices.forEach(x => { move(x, vector2Add(x.position,new Vector2(-32,0)), 1, easeSineIn); fadeOut(x, 1) })
|
||||||
|
await wait(1)
|
||||||
|
choices.forEach(x => entityRemove(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,42 +1,41 @@
|
||||||
import { Behaviour, Clickable, Entity, EntityOf, HasBehaviour, HasBoundingBox, HasColor, HasPosition, addBehaviour, makeBehaviour, makeBoundingBox, makeClickable, makeColor, makeColorRgb, makeCombined, makeEntity, makePosition, removeBehaviour, setProp, setPropFn } from "./entity"
|
import { Behaviour, HasMouseInteraction, Entity, HasBehaviour, HasBoundingBox, HasColor, HasPosition, withBehaviour, withBoundingBox, withColor, combine, makeEntity, withPosition, hasDefault, which, withComponent, Builder, withMouseInteraction, checksBoundingBoxClicks } from "./entity"
|
||||||
import { fontLoad, resourceUnload, textureLoad } from "./resource"
|
import { fontLoad, resourceUnload } from "./resource"
|
||||||
|
|
||||||
// FONT
|
// FONT
|
||||||
export interface HasFont extends HasBehaviour, HasColor {
|
export interface HasFont {
|
||||||
font?: string,
|
font?: string,
|
||||||
fontSize: number,
|
fontSize: number,
|
||||||
fontSpacing: number
|
fontSpacing: number
|
||||||
fontResource?: Font
|
fontResource?: Font
|
||||||
fontResourceId?: string
|
fontResourceId?: string
|
||||||
}
|
}
|
||||||
export const fontLoadBehaviour: Behaviour<HasFont> = {
|
export const loadsFont: Behaviour<HasFont> = {
|
||||||
load: t => {
|
load: t => {
|
||||||
t.fontResourceId = t.font ? t.font + ":" + t.fontSize : undefined
|
t.fontResourceId = t.font ? t.font + ":" + t.fontSize : undefined
|
||||||
t.fontResource = (t.font ? fontLoad(t.fontResourceId!) : getFontDefault())
|
t.fontResource = (t.font ? fontLoad(t.fontResourceId!) : getFontDefault())
|
||||||
},
|
},
|
||||||
unload: t => t.font ? resourceUnload(t.fontResourceId!) : undefined
|
unload: t => t.font ? resourceUnload(t.fontResourceId!) : undefined
|
||||||
}
|
}
|
||||||
export const makeFont = makeCombined(makeBehaviour, makeColor, (obj: HasBehaviour & Partial<HasFont>) => {
|
export const withFont = combine(withBehaviour, withComponent<HasFont>(x => {
|
||||||
setProp(obj, 'font', undefined)
|
hasDefault(x, 'fontSize', 20)
|
||||||
setProp(obj, 'fontSize', 20)
|
hasDefault(x, 'fontSpacing', 1)
|
||||||
setProp(obj, 'fontSpacing', 1)
|
}), which<HasFont&HasBehaviour>(loadsFont))
|
||||||
addBehaviour(<HasFont>obj, fontLoadBehaviour)
|
|
||||||
return <HasFont>obj
|
|
||||||
})
|
|
||||||
|
|
||||||
// TEXT
|
// TEXT
|
||||||
export interface Text extends Entity, HasFont, HasPosition {
|
export interface HasText {
|
||||||
text: string,
|
text: string
|
||||||
}
|
}
|
||||||
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 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> = {
|
export const withText = withComponent<Text>(x => hasDefault(x,'text', ''))
|
||||||
|
export type Text = Entity & HasFont & HasPosition & HasColor & HasText
|
||||||
|
export const makeText: Builder<Text> = combine(makeEntity, withFont, withPosition, withColor, withText)
|
||||||
|
|
||||||
|
// INLINE TEXT
|
||||||
|
export type InlineText = Entity & Text
|
||||||
|
export const drawsInlineText: Behaviour<InlineText> = {
|
||||||
draw: textDrawFn
|
draw: textDrawFn
|
||||||
}
|
}
|
||||||
|
export const makeInlineText: Builder<InlineText> = combine(makeText, which(drawsInlineText))
|
||||||
|
|
||||||
// PARAGRAPH
|
// PARAGRAPH
|
||||||
export interface Line {
|
export interface Line {
|
||||||
|
@ -72,7 +71,7 @@ export const breakTextLinesFn = (p: Paragraph) => {
|
||||||
lines.push({ text: currentLine, width: lastw });
|
lines.push({ text: currentLine, width: lastw });
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
export const paragraphBehaviour: Behaviour<Paragraph> = {
|
export const drawsParagraph: Behaviour<Paragraph> = {
|
||||||
update: p => {
|
update: p => {
|
||||||
if (p._textCached !== p.text) {
|
if (p._textCached !== p.text) {
|
||||||
p.lines = breakTextLinesFn(p)
|
p.lines = breakTextLinesFn(p)
|
||||||
|
@ -87,17 +86,14 @@ export const paragraphBehaviour: Behaviour<Paragraph> = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const makeParagraph = makeCombined(makeText, (obj: Partial<Paragraph>) => {
|
export const makeParagraph = combine(makeText, withComponent<Paragraph>(obj => {
|
||||||
setProp(obj, 'lines', [])
|
hasDefault(obj, 'lines', [])
|
||||||
setProp(obj, 'maxWidth', 100)
|
hasDefault(obj, 'maxWidth', 100)
|
||||||
setProp(obj, "_textCached", "")
|
hasDefault(obj, "_textCached", "")
|
||||||
removeBehaviour(<Paragraph>obj, textBehaviour)
|
}), which(drawsParagraph))
|
||||||
addBehaviour(<Paragraph>obj, paragraphBehaviour)
|
|
||||||
return <Paragraph>obj
|
|
||||||
})
|
|
||||||
|
|
||||||
// CLICKABLE TEXT
|
// CLICKABLE TEXT
|
||||||
export interface ClickableText extends Text, Clickable {
|
export interface HasTextDecoration {
|
||||||
underlineWidth: number
|
underlineWidth: number
|
||||||
}
|
}
|
||||||
export const calcTextBoundsFn = (obj: Text & HasBoundingBox) => {
|
export const calcTextBoundsFn = (obj: Text & HasBoundingBox) => {
|
||||||
|
@ -109,12 +105,14 @@ export const calcTextBoundsFn = (obj: Text & HasBoundingBox) => {
|
||||||
}
|
}
|
||||||
obj.boundingBox = rec
|
obj.boundingBox = rec
|
||||||
}
|
}
|
||||||
export const clickableTextBehaviour: Behaviour<ClickableText> = {
|
export type ClickableText = InlineText & HasMouseInteraction & HasTextDecoration & HasBoundingBox
|
||||||
|
export const drawsClickableText: Behaviour<ClickableText> = {
|
||||||
load: calcTextBoundsFn,
|
load: calcTextBoundsFn,
|
||||||
draw: t => t.hasMouseOver ? drawRectangle(t.boundingBox.x, t.boundingBox.y + t.boundingBox.height, t.boundingBox.width, t.underlineWidth, t.color) : undefined
|
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>) => {
|
export const makeClickableText: Builder<ClickableText> = combine(makeInlineText,
|
||||||
setProp(obj, 'underlineWidth', 1)
|
withBoundingBox,
|
||||||
addBehaviour(<ClickableText>obj, clickableTextBehaviour)
|
withMouseInteraction,
|
||||||
return <ClickableText>obj
|
withComponent<HasTextDecoration>(obj => hasDefault(obj,'underlineWidth',1)),
|
||||||
})
|
which<Entity&HasMouseInteraction&HasBoundingBox>(checksBoundingBoxClicks),
|
||||||
|
which(drawsClickableText))
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { HasMouseInteraction, HasColor, HasPosition } from "./entity"
|
||||||
|
import { makeUpdateablePromise } from "./game"
|
||||||
|
|
||||||
|
export type easeFunc = (t: number, a: number, b: number, d: number) => number
|
||||||
|
|
||||||
|
export const interpolate = (a: number, b: number, d: number, setter: (v: number) => void, fn: easeFunc) => {
|
||||||
|
const start = getTime()
|
||||||
|
const delta = b - a
|
||||||
|
return makeUpdateablePromise<void>(ctx => {
|
||||||
|
const cur = getTime()-start
|
||||||
|
if(cur < d && !ctx.isCancellationRequested){
|
||||||
|
setter(fn(cur, a, delta, d))
|
||||||
|
} else {
|
||||||
|
setter(b)
|
||||||
|
ctx.resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const interpolateVec2 = (a: Vector2, b: Vector2, d: number, setter: (v: Vector2) => void, fn: easeFunc) => {
|
||||||
|
const start = getTime()
|
||||||
|
const delta = vector2Subtract(b, a)
|
||||||
|
return makeUpdateablePromise<void>(ctx => {
|
||||||
|
const cur = getTime()-start
|
||||||
|
if(cur < d && !ctx.isCancellationRequested){
|
||||||
|
const x = fn(cur, a.x, delta.x, d)
|
||||||
|
const y = fn(cur, a.y, delta.y, d)
|
||||||
|
setter(new Vector2(x,y))
|
||||||
|
} else {
|
||||||
|
setter(b)
|
||||||
|
ctx.resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const waitCondition = (predicate: () => boolean) => makeUpdateablePromise<void>(ctx => {
|
||||||
|
if(predicate() || ctx.isCancellationRequested){
|
||||||
|
ctx.resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export const waitFirst = <T>(list: T[], predicate: (item: T) => boolean) => makeUpdateablePromise<number>(ctx => {
|
||||||
|
let idx = list.findIndex(x => predicate(x))
|
||||||
|
if(idx !== -1 || ctx.isCancellationRequested) ctx.resolve(idx)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const wait = (time: number) => {
|
||||||
|
const start = getTime()
|
||||||
|
return waitCondition(() => (getTime()-start) >= time)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const waitKeyPressed = (key: number) => waitCondition(() => isKeyPressed(key))
|
||||||
|
export const waitClick = (button: number = MOUSE_BUTTON_LEFT) => waitCondition(() => isMouseButtonDown(button))
|
||||||
|
export const waitClicked = (entity: HasMouseInteraction) => waitCondition(() => entity.isClicked)
|
||||||
|
export const waitAnyClicked = (entites: HasMouseInteraction[]) => waitFirst(entites, x => x.isClicked)
|
||||||
|
|
||||||
|
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(1, 0, time, (v) => c.color = fade(c.color, v), easeFunc)
|
||||||
|
export const move = (p: HasPosition, to: Vector2, time: number, easeFunc = easeLinearNone) => interpolateVec2(p.position, to, time, v2 => p.position = v2, easeFunc)
|
Loading…
Reference in New Issue