Add editor

This commit is contained in:
Alexander Klingenbeck 2023-06-06 22:55:45 +02:00
parent 2f537f649c
commit 28122c54a3
5 changed files with 175 additions and 56 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,13 +1,14 @@
import { Behaviour, Builder, Creator, Entity, HasBoundingBox, HasMouseInteraction, HasPosition, HasSize, combine, hasDefault, hasDefaultFn, makeEntity, which, withBoundingBox, withComponent, withMouseInteraction, withPosition, withSize } from "./entity"
import { Behaviour, Builder, Creator, Entity, HasBehaviour, HasBoundingBox, HasMouseInteraction, HasPosition, HasSize, combine, hasDefault, hasDefaultFn, makeEntity, which, withBehaviour, withBoundingBox, withComponent, withMouseInteraction, withPosition, withSize } from "./entity"
import { entityAdd, gameRun, gameSetClearColor } from "./game"
import { resourceUnload, textureLoad } from "./resource"
import { HasText, withText } from "./text"
const withGuiSize = withComponent<HasSize>(x => hasDefaultFn(x, 'size', () => new Vector2(100,20)))
type UiControl = Entity & HasPosition & HasSize
const makeControl = combine(makeEntity, withPosition, withSize)
const makeControl = combine(makeEntity, withPosition, withGuiSize)
type Button = Entity & HasPosition & HasSize & HasMouseInteraction & HasText
type Button = UiControl & HasMouseInteraction & HasText
const drawsButton: Behaviour<Button> = {
draw: x => {
x.isClicked = guiButton(new Rectangle(x.position.x,x.position.y,x.size.x, x.size.y), x.text)
@ -15,9 +16,7 @@ const drawsButton: Behaviour<Button> = {
}
}
const makeButton: Builder<Button> = combine(
makeEntity,
withPosition,
withSize,
makeControl,
withMouseInteraction,
withComponent<HasText>(x => hasDefault(x, 'text', 'Button')),
which(drawsButton))
@ -30,7 +29,7 @@ interface HasChangedEvent {
onChange?: () => void
}
const withChangedEvent = withComponent<HasChangedEvent>()
type Textbox = Entity & HasPosition & HasSize & HasText & HasActive & HasChangedEvent
type Textbox = UiControl & HasText & HasActive & HasChangedEvent
const drawsTextbox: Behaviour<Textbox> = {
draw: x => {
if(guiTextBox(new Rectangle(x.position.x,x.position.y,x.size.x, x.size.y), x, x.active)){
@ -39,64 +38,77 @@ const drawsTextbox: Behaviour<Textbox> = {
}
}
}
const makeTextbox: Builder<Textbox> = combine(makeEntity,
withGuiBounds,
const makeTextbox: Builder<Textbox> = combine(
makeControl,
withText,
withActive,
withChangedEvent,
which(drawsTextbox))
type CheckBox = Entity & HasBoundingBox & HasText & HasActive & HasChangedEvent
type CheckBox = UiControl & HasText & HasActive & HasChangedEvent
const drawsCheckbox: Behaviour<CheckBox> = {
draw: c => {
const old = c.active
c.active = guiCheckBox(c.boundingBox, c.text, c.active)
if(old != c.active && c.onChange) c.onChange()
draw: x => {
const old = x.active
x.active = guiCheckBox(new Rectangle(x.position.x,x.position.y,x.size.x, x.size.y), x.text, x.active)
if(old != x.active && x.onChange) x.onChange()
}
}
const makeCheckbox: Builder<CheckBox> = combine(makeEntity,
withGuiBounds,
const makeCheckbox: Builder<CheckBox> = combine(
makeControl,
withText,
withActive,
withChangedEvent,
which(drawsCheckbox))
type Label = Entity & HasBoundingBox & HasText
const drawsLabel: Behaviour<Label> = { draw: c => guiLabel(c.boundingBox, c.text) }
const makeLabel: Builder<Label> = combine(makeEntity, withGuiBounds, withText, which(drawsLabel))
type Label = UiControl & HasText
const drawsLabel: Behaviour<Label> = { draw: x => guiLabel(new Rectangle(x.position.x,x.position.y,x.size.x, x.size.y), x.text) }
const makeLabel: Builder<Label> = combine(makeControl, withText, which(drawsLabel))
type WindowBox = Entity & HasBoundingBox & HasText
const drawsWindowBox: Behaviour<WindowBox> = { draw: c => guiWindowBox(c.boundingBox, c.text) }
const builderWindowBox: Builder<WindowBox> = combine(makeEntity, withGuiBounds, withText, which(drawsWindowBox))
type WindowBox = UiControl & HasText
const drawsWindowBox: Behaviour<WindowBox> = { draw: x => guiWindowBox(new Rectangle(x.position.x,x.position.y,x.size.x, x.size.y), x.text) }
const builderWindowBox: Builder<WindowBox> = combine(makeControl, withText, which(drawsWindowBox))
type GroupBox = Entity & HasBoundingBox & HasText
const drawsGroupBox: Behaviour<WindowBox> = { draw: c => guiGroupBox(c.boundingBox, c.text) }
const makeGroupBox: Builder<GroupBox> = combine(makeEntity, withGuiBounds, withText, which(drawsGroupBox))
type GroupBox = UiControl & HasText
const drawsGroupBox: Behaviour<WindowBox> = { draw: x => guiGroupBox(new Rectangle(x.position.x,x.position.y,x.size.x, x.size.y), x.text) }
const makeGroupBox: Builder<GroupBox> = combine(makeControl, withText, which(drawsGroupBox))
type Line = Entity & HasBoundingBox & HasText
const drawsLine: Behaviour<Line> = { draw: c => guiLine(c.boundingBox, c.text) }
const makeLine: Builder<Line> = combine(makeEntity, withGuiBounds, withText, which(drawsLine))
type Line = UiControl & HasText
const drawsLine: Behaviour<Line> = { draw: x => guiLine(new Rectangle(x.position.x,x.position.y,x.size.x, x.size.y), x.text) }
const makeLine: Builder<Line> = combine(makeControl, withText, which(drawsLine))
type Panel = Entity & HasBoundingBox & HasText
const drawsPanel: Behaviour<Panel> = { draw: c => guiPanel(c.boundingBox, c.text) }
const makePanel: Builder<Panel> = combine(makeEntity, withGuiBounds, withText, which(drawsPanel))
type Panel = UiControl & HasText
const drawsPanel: Behaviour<Panel> = { draw: x => guiPanel(new Rectangle(x.position.x,x.position.y,x.size.x, x.size.y), x.text) }
const makePanel: Builder<Panel> = combine(makeControl, withText, which(drawsPanel))
interface HasScrollView {
contentArea: Rectangle,
scroll: Vector2,
viewArea: Rectangle
}
const withScrollView = withComponent<HasScrollView & HasBoundingBox>(x => {
hasDefaultFn(x, 'contentArea', () => new Rectangle(x.boundingBox!.x, x.boundingBox!.y, x.boundingBox!.width, x.boundingBox!.height))
hasDefaultFn(x, 'viewArea', () => new Rectangle(x.boundingBox!.x, x.boundingBox!.y, x.boundingBox!.width, x.boundingBox!.height))
hasDefaultFn(x, 'scroll', () => new Vector2(0,0))
})
type ScrollPanel = Entity & HasBoundingBox & Partial<HasText> & HasScrollView
const drawsScrollView: Behaviour<ScrollPanel> = {
draw: s => {
s.viewArea = guiScrollPanel(s.boundingBox, s.text, s.contentArea, s.scroll)
}
}
const makeScollPanel: Builder<ScrollPanel> = combine(makeEntity, withComponent<Partial<HasText>>(), withGuiBounds, withScrollView, )
// interface HasScrollView {
// contentArea: Rectangle,
// scroll: Vector2,
// viewArea: Rectangle
// }
// const withScrollView = withComponent<HasScrollView & HasPosition & HasSize>(x => {
// hasDefaultFn(x, 'contentArea', () => new Rectangle(x.boundingBox!.x, x.boundingBox!.y, x.boundingBox!.width, x.boundingBox!.height))
// hasDefaultFn(x, 'viewArea', () => new Rectangle(x.boundingBox!.x, x.boundingBox!.y, x.boundingBox!.width, x.boundingBox!.height))
// hasDefaultFn(x, 'scroll', () => new Vector2(0,0))
// })
// type ScrollPanel = UiControl & Partial<HasText> & HasScrollView
// const drawsScrollView: Behaviour<ScrollPanel> = {
// draw: s => {
// s.viewArea = guiScrollPanel(s.boundingBox, s.text, s.contentArea, s.scroll)
// }
// }
// const makeScollPanel: Builder<ScrollPanel> = combine(makeEntity, withComponent<Partial<HasText>>(), withGuiBounds, withScrollView, )
// interface HasChildControls {
// controls: UiControl[]
// }
// const withChildControls = withComponent<HasChildControls>(x => hasDefault(x, 'controls', []))
// type StackPanel = UiControl & HasChildControls
// const drawsStackLayout: Behaviour<StackPanel> = {
// update: x => {
// }
// }
// const makeStackPanel = combine(makeControl, withChildControls)
interface HasGuiGlobalState {}
type GuiGlobalState = Entity & HasGuiGlobalState
@ -105,25 +117,122 @@ const appliesGuiGlobalState: Behaviour<GuiGlobalState> = {
}
const guiState: GuiGlobalState = combine(makeEntity,withComponent<HasGuiGlobalState>(), which(appliesGuiGlobalState))({})
interface HasTexture {
texture: string,
textureResource: Texture
}
const loadsTexture: Behaviour<HasTexture> = {
load: x => x.textureResource = textureLoad(x.texture),
unload: x => resourceUnload(x.texture)
}
const withTexture: Builder<HasTexture> = withComponent<HasTexture>()
interface HasTiles {
tileWidth: number,
tileHeight: number
}
const withTiles = withComponent<HasTiles>(x => {
hasDefault(x, 'tileWidth', 16)
hasDefault(x, 'tileHeight', 16)
})
interface HasTilemap {
width: number,
height: number,
tileData: number[]
}
const withTilemap = withComponent<HasTilemap>(x => {
hasDefault(x, 'width', 0)
hasDefault(x, 'height', 0)
hasDefaultFn(x, 'tileData', () => [])
})
type Tilemap = Entity & HasPosition & HasTexture & HasTiles & HasTilemap
const drawsTilemap: Behaviour<Tilemap> = {
draw: map => {
const position = new Vector2(0,0)
const src = new Rectangle(0,0,map.tileWidth,map.tileHeight)
const tilesX = Math.floor(map.textureResource.width/map.tileWidth)
for (let y = 0; y < map.width; y++) {
for (let x = 0; x < map.height; x++) {
let tileId = map.tileData[y*map.width+x]
if(tileId === 0) continue
tileId--
position.x = map.position.x + x * map.tileWidth
position.y = map.position.y + y * map.tileHeight
src.x = (tileId % tilesX) * map.tileWidth
src.y = Math.floor(tileId / tilesX) * map.tileHeight
drawTextureRec(map.textureResource, src, position, WHITE)
}
}
}
}
const makeTilemap: Builder<Tilemap> = combine(makeEntity,
withPosition,
withTexture,
withTiles,
withTilemap,
which<Tilemap>(loadsTexture),
which(drawsTilemap))
interface HasChildren {
children: Entity[]
}
const withChildren = withComponent<HasChildren>(x => hasDefaultFn(x, 'children', () => []))
type Container = Entity & HasChildren
const processesChildren: Behaviour<Container> = {
load: x => x.children.forEach(y => y.behaviours.forEach(z => z.load ? z.load(y) : undefined)),
update: x => x.children.forEach(y => y.behaviours.forEach(z => z.update ? z.update(y) : undefined)),
draw: x => x.children.forEach(y => {
y.behaviours.forEach(z => z.beforeDraw ? z.beforeDraw(y) : undefined)
y.behaviours.forEach(z => z.draw ? z.draw(y) : undefined)
y.behaviours.forEach(z => z.afterDraw ? z.afterDraw(y) : undefined)
}),
unload: x => x.children.forEach(y => y.behaviours.forEach(z => z.unload ? z.unload(y) : undefined))
}
const makeContainer = combine(makeEntity, withChildren, which(processesChildren))
interface HasCamera2D {
camera2D: Camera2D
}
const withCamera2D = withComponent<HasCamera2D>(x => hasDefaultFn(x,'camera2D',() => new Camera2D(new Vector2(getScreenWidth()/2.0, getScreenHeight()/2.0),new Vector2(0,0), 0, 1)))
type Space2D = Container & HasCamera2D
const applies2dSpace: Behaviour<Space2D> = {
beforeDraw: x => beginMode2D(x.camera2D),
afterDraw: x => endMode2D()
}
const makeSpace2D = combine(makeContainer, withCamera2D, which(applies2dSpace))
gameRun({ width: 800, height: 600, title: 'My Editor', flags: FLAG_WINDOW_RESIZABLE }, async (quit) => {
const map = makeTilemap({
texture: "resources/tilemap_packed.png",
width: 16,
height: 16,
tileData: new Array(16*16).fill(13)
})
const space2d = makeSpace2D({})
space2d.children.push(map)
space2d.camera2D.zoom = 2
space2d.camera2D.target = new Vector2((map.width/2)*map.tileWidth,(map.height/2)*map.tileHeight)
entityAdd(space2d)
const but = makeButton({
position: new Vector2(10,10),
text: 'Click Me!',
onClick: () => but.boundingBox.x += 20
onClick: () => but.position.x += 20
})
const tb = makeTextbox({
text: but.text,
boundingBox: new Rectangle(10, 50, 100, 20),
position: new Vector2(10,50),
onChange: () => but.text = tb.text
})
const cb = makeCheckbox({
boundingBox: new Rectangle(10, 75, 20, 20),
position: new Vector2(10,75),
size: new Vector2(20,20),
text: "Check Me!",
onChange: () => cb.text = "Checkbox is "+ (cb.active ? "checked" : "not checked")
})
const l = makeLabel({
boundingBox: new Rectangle(10, 100, 100, 20),
position: new Vector2(10,100),
text: "This is a label"
})
entityAdd(guiState)
@ -131,4 +240,6 @@ gameRun({ width: 800, height: 600, title: 'My Editor', flags: FLAG_WINDOW_RESIZA
entityAdd(tb)
entityAdd(cb)
entityAdd(l)
})

View File

@ -4,7 +4,9 @@ export interface Behaviour<T> {
load?: (entity: T) => void,
unload?: (entity: T) => void,
update?: (entity: T) => void,
beforeDraw?: (entity: T) => void
draw?: (entity: T) => void
afterDraw?: (entity: T) => void
}
export function addBehaviour<T extends HasBehaviour>(obj: T, behaviour: Behaviour<T>){
@ -25,13 +27,15 @@ export function combine<A,B,C>(fn1: Builder<A>, fn2: Extender<A,B>, fn3: Extende
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 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 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 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>
export function combine<A,B,C,D,E,F,G>(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>, fn7: Extender<A&B&C&D&E&F,G>): Builder<A&B&C&D&E&F&G>
export function combine<A,B,C,D,E,F,G>(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>, fn7?: Extender<A&B&C&D&E&F,G>): 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> | Builder<A&B&C&D&E&F&G>
{
if(fn2 && fn3 && fn4 && fn5 && fn6 && fn7) return combine(combine(fn1, fn2, fn3, fn4, fn5, fn6), fn7)
if(fn2 && fn3 && fn4 && fn5 && fn6) return combine(combine(fn1, fn2, fn3, fn4, fn5), fn6)
if(fn2 && fn3 && fn4 && fn5) return combine(combine(fn1, fn2, fn3, fn4), fn5)
if(fn2 && fn3 && fn4) return combine(combine(fn1, fn2, fn3), fn4)
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
}

View File

@ -128,7 +128,11 @@ export const gameRun = (options: Partial<WindowConfig>, startupCallback: (quit:
beginDrawing()
clearBackground(gameClearColor)
drawText("Active promises: "+ promiseUpdateList.length, 10,10, 8, RAYWHITE)
entitiyList.forEach(e => e.behaviours.forEach(b => b.draw ? b.draw(e) : undefined))
entitiyList.forEach(e => {
e.behaviours.forEach(b => b.beforeDraw ? b.beforeDraw(e) : undefined)
e.behaviours.forEach(b => b.draw ? b.draw(e) : undefined)
e.behaviours.forEach(b => b.afterDraw ? b.afterDraw(e) : undefined)
})
endDrawing()
}
entitiyList.forEach(x => entityUnload(x))

View File

@ -7,7 +7,7 @@ import { Compiler } from "inkjs";
gameRun({ width: 800, height: 400, title: "The Intercept" }, async (quit) => {
const source = loadFileText("resources/intercept.ink")
const c = new Compiler(source)
const c = new Compiler(source!)
const story = c.Compile()
traceLog(LOG_INFO, "[INK] Story loaded")