diff --git a/game/actor.ts b/game/actor.ts index a49f574b..368c650c 100644 --- a/game/actor.ts +++ b/game/actor.ts @@ -3,8 +3,7 @@ import { btn, Button } from "./input"; import { Vec2, new_v2, vadd, vnorm, vmul } from "./vector"; import { spr, use_texture, Texture } from "./graphics"; -interface ActorSnapshot { - __type__: string; +export interface ActorProps { velocity: Vec2; friction: number; id: string; @@ -12,23 +11,33 @@ interface ActorSnapshot { bounds: Vec2; } -export class Actor { - velocity: Vec2 = new_v2(0); - friction: number = 0.6; - id: string; - position: Vec2; - bounds: Vec2; +export function new_actor_props( + id: string, + position: Vec2, + bounds: Vec2 +): ActorProps { + return { + velocity: new_v2(0), + friction: 0.6, + id, + position, + bounds, + }; +} - constructor(id: string, position: Vec2, bounds: Vec2) { - this.id = id; - this.position = position; - this.bounds = bounds; +export class Actor { + type: ActorType; + props: ActorProps; + + constructor(type: ActorType, props: ActorProps) { + this.type = type; + this.props = props; } update() {} update_physics() { - const velocity = vmul(this.velocity, this.friction); + const velocity = vmul(this.props.velocity, this.props.friction); // Zero if we're smaller than some epsilon. if (Math.abs(velocity.x) < 0.01) { velocity.x = 0; @@ -37,36 +46,22 @@ export class Actor { velocity.y = 0; } - const new_position = vadd(this.position, velocity); + const new_position = vadd(this.props.position, velocity); // TODO: Collision detection // const { w, h } = this.bounds; - this.velocity = velocity; - this.position = new_position; + this.props.velocity = velocity; + this.props.position = new_position; } draw(_clock: number) {} bonk(_other: Actor) {} - - snapshot(): ActorSnapshot { - return { ...this, __type__: "??" }; - } - - assign_snapshot(s: ActorSnapshot) { - this.id = s.id; - this.position = s.position; - this.bounds = s.bounds; - this.velocity = s.velocity; - this.friction = s.friction; - return this; - } } const robo_info = { - width: 32, - height: 32, + bounds: new_v2(32), sprite: "./bot.png", animations: [ { start: 0, length: 1, speed: 20 }, @@ -74,15 +69,11 @@ const robo_info = { ], }; -interface RoboSnapshot extends ActorSnapshot { - __type__: "robo"; -} - export class Robo extends Actor { bot_sprite: Texture | undefined = undefined; - constructor(pos: Vec2) { - super("robo", pos, new_v2(robo_info.width, robo_info.height)); + constructor(props: ActorProps) { + super("robo", props); load_texture(robo_info.sprite).then((texture) => { this.bot_sprite = texture; }); @@ -104,43 +95,36 @@ export class Robo extends Actor { a.x += 1; } vnorm(a); - this.velocity = vadd(this.velocity, vmul(a, 1.5)); + this.props.velocity = vadd(this.props.velocity, vmul(a, 1.5)); } draw(clock: number) { if (this.bot_sprite != undefined) { use_texture(this.bot_sprite); - const vel = this.velocity; + const vel = this.props.velocity; const moving = vel.x != 0 || vel.y != 0; const anim = robo_info.animations[moving ? 1 : 0]; - const { x: w, y: h } = this.bounds; - const { x, y } = this.position; + const { x: w, y: h } = this.props.bounds; + const { x, y } = this.props.position; const frame = (anim.start + ((clock / anim.speed) % anim.length)) >> 0; spr(x, y, w, h, frame * w, 0, 32, 32); } } - - snapshot(): RoboSnapshot { - return { ...super.snapshot(), __type__: "robo" }; - } - - assign_snapshot(s: RoboSnapshot) { - super.assign_snapshot(s); - return this; - } } -const SNAPSHOT_TABLE: { [key: string]: (s: any) => Actor } = { - robo: (s: any) => new Robo(new_v2(0)).assign_snapshot(s), +const ACTOR_TABLE = { + robo: (s: ActorProps) => new Robo(s), }; -export function actor_from_snapshot(s: ActorSnapshot): Actor { - const f = SNAPSHOT_TABLE[s.__type__]; - if (f == undefined) { - throw new Error("No handler for " + s.__type__); - } - return f(s); +export type ActorType = keyof typeof ACTOR_TABLE; + +export function is_actor_type(type: string): type is ActorType { + return ACTOR_TABLE.hasOwnProperty(type); +} + +export function spawn_actor(type: ActorType, props: ActorProps): Actor { + return ACTOR_TABLE[type](props); } diff --git a/game/level.ts b/game/level.ts index 63d8f168..dfedf755 100644 --- a/game/level.ts +++ b/game/level.ts @@ -23,14 +23,26 @@ export type TileLayer = { tiles: Tile[]; }; -export type Layer = TileLayer; +export type Entity = { + type: string; + id: string; + px: [number, number]; + bounds: [number, number]; + // TODO: More props here. +}; + +export type EntityLayer = { + type: "entity"; + entities: Entity[]; +}; export type Level = { world_x: number; world_y: number; width: number; height: number; - layers: Layer[]; + tile_layers: TileLayer[]; + entity_layers: EntityLayer[]; }; export type TileSet = { id: number; texture: Texture }; @@ -52,6 +64,74 @@ async function load_tileset(def: { return { id: def.uid, texture }; } +type TileLayerInstance = { + __type: "Tiles"; + __gridSize: number; + __pxTotalOffsetX: number; + __pxTotalOffsetY: number; + __tilesetDefUid: number; + gridTiles: { + px: [number, number]; + src: [number, number]; + f: number; + t: number; + a: number; + }[]; +}; + +function load_tile_layer_instance( + tile_sets: Map, + li: TileLayerInstance +): TileLayer { + const tileset = tile_sets.get(li.__tilesetDefUid); + if (!tileset) { + throw new Error("Unable to find texture!!! " + li.__tilesetDefUid); + } + + return { + type: "tile", + texture: tileset.texture, + grid_size: li.__gridSize, + offset: [li.__pxTotalOffsetX, li.__pxTotalOffsetY], + tiles: li.gridTiles, + }; +} + +type EntityLayerInstance = { + __type: "Entities"; + entityInstances: { + __identifier: string; + iid: string; + width: number; + height: number; + px: [number, number]; + }[]; +}; + +function load_entity_layer_instance(li: EntityLayerInstance): EntityLayer { + return { + type: "entity", + entities: li.entityInstances.map((ei) => { + return { + type: ei.__identifier, + id: ei.iid, + px: ei.px, + bounds: [ei.width, ei.height], + }; + }), + }; +} + +type LayerInstance = TileLayerInstance | EntityLayerInstance; + +function is_tile_layer_instance(x: LayerInstance): x is TileLayerInstance { + return x.__type == "Tiles"; +} + +function is_entity_layer_instance(x: LayerInstance): x is EntityLayerInstance { + return x.__type == "Entities"; +} + function load_level( tile_sets: Map, def: { @@ -59,41 +139,27 @@ function load_level( worldY: number; pxWid: number; pxHei: number; - layerInstances: { - __gridSize: number; - __pxTotalOffsetX: number; - __pxTotalOffsetY: number; - __tilesetDefUid: number; - gridTiles: { - px: [number, number]; - src: [number, number]; - f: number; - t: number; - a: number; - }[]; - }[]; + layerInstances: LayerInstance[]; } ): Level { - return { + const result: Level = { world_x: def.worldX, world_y: def.worldY, width: def.pxWid, height: def.pxHei, - layers: def.layerInstances.map((li) => { - const tileset = tile_sets.get(li.__tilesetDefUid); - if (!tileset) { - throw new Error("Unable to find texture!!! " + li.__tilesetDefUid); - } - - return { - type: "tile", - texture: tileset.texture, - grid_size: li.__gridSize, - offset: [li.__pxTotalOffsetX, li.__pxTotalOffsetY], - tiles: li.gridTiles, - }; - }), + tile_layers: [], + entity_layers: [], }; + + for (const li of def.layerInstances) { + if (is_tile_layer_instance(li)) { + result.tile_layers.push(load_tile_layer_instance(tile_sets, li)); + } else if (is_entity_layer_instance(li)) { + result.entity_layers.push(load_entity_layer_instance(li)); + } + } + + return result; } export async function load_world(path: string): Promise { @@ -120,7 +186,7 @@ export function draw_level( offset_x: number = 0, offset_y: number = 0 ) { - for (const layer of level.layers) { + for (const layer of level.tile_layers) { use_texture(layer.texture); let [ofx, ofy] = layer.offset; diff --git a/game/main.ts b/game/main.ts index 4fcda58e..4cd8d5ba 100644 --- a/game/main.ts +++ b/game/main.ts @@ -2,13 +2,21 @@ import { cls, print } from "./graphics"; import { since_start } from "./time"; import { new_v2 } from "./vector"; import { load_world, World, Level, draw_level } from "./level"; -import { Actor, Robo, ActorSnapshot, actor_from_snapshot } from "./actor"; +import { + Actor, + ActorProps, + ActorType, + new_actor_props, + is_actor_type, + spawn_actor, +} from "./actor"; /// A nice looping frame counter. let clock = 0; let world: World | undefined = undefined; let level: Level | undefined = undefined; +let actors: Actor[] = []; // Note zelda overworld is 16x8 screens // zelda screen is 16x11 tiles @@ -27,6 +35,19 @@ function load_assets() { } // TODO: SPAWN ACTORS BASED ON LEVEL. + actors.length = 0; + for (const entity_layer of level.entity_layers) { + for (const entity of entity_layer.entities) { + if (is_actor_type(entity.type)) { + const [x, y] = entity.px; + const [w, h] = entity.bounds; + const props = new_actor_props(entity.id, new_v2(x, y), new_v2(w, h)); + actors.push(spawn_actor(entity.type, props)); + } else { + print("WARNING: Ignoring entity of type", entity.type); + } + } + } }); Promise.all([map_load]).then(() => { @@ -34,32 +55,31 @@ function load_assets() { }); } -let actors: Actor[] = []; - // TODO: Build a system whereby the signatures of the fundamental functions can be checked. export function init() { print("Hello world!"); load_assets(); - actors.push(new Robo(new_v2(10, 10))); } interface Snapshot { clock: number; - actors: ActorSnapshot[]; + actors: { type: ActorType; props: ActorProps }[]; } export function suspend(): Snapshot { return { clock, - actors: actors.map((a) => a.snapshot()), + actors: actors.map((a) => { + return { type: a.type, props: a.props }; + }), }; } export function resume(snapshot: Snapshot | undefined) { if (snapshot) { clock = snapshot.clock || 0; - actors = snapshot.actors.map((a) => actor_from_snapshot(a)); + actors = snapshot.actors.map((s) => spawn_actor(s.type, s.props)); } } diff --git a/game/overworld.ldtk b/game/overworld.ldtk index 1c46c27f..ec24255d 100644 --- a/game/overworld.ldtk +++ b/game/overworld.ldtk @@ -11,8 +11,8 @@ "iid": "315bbfe0-1460-11ee-be24-3324961fe10c", "jsonVersion": "1.3.3", "appBuildId": 467698, - "nextUid": 9, - "identifierStyle": "Capitalize", + "nextUid": 11, + "identifierStyle": "Lowercase", "toc": [], "worldLayout": "GridVania", "worldGridWidth": 320, @@ -39,9 +39,39 @@ "customCommands": [], "flags": [], "defs": { "layers": [ + { + "__type": "Entities", + "identifier": "entities", + "type": "Entities", + "uid": 10, + "doc": null, + "uiColor": null, + "gridSize": 16, + "guideGridWid": 0, + "guideGridHei": 0, + "displayOpacity": 1, + "inactiveOpacity": 0.6, + "hideInList": false, + "hideFieldsWhenInactive": true, + "canSelectWhenInactive": true, + "renderInWorldView": true, + "pxOffsetX": 0, + "pxOffsetY": 0, + "parallaxFactorX": 0, + "parallaxFactorY": 0, + "parallaxScaling": true, + "requiredTags": [], + "excludedTags": [], + "intGridValues": [], + "autoRuleGroups": [], + "autoSourceLayerDefUid": null, + "tilesetDefUid": null, + "tilePivotX": 0, + "tilePivotY": 0 + }, { "__type": "Tiles", - "identifier": "Base", + "identifier": "base", "type": "Tiles", "uid": 2, "doc": null, @@ -69,11 +99,45 @@ "tilePivotX": 0, "tilePivotY": 0 } - ], "entities": [], "tilesets": [ + ], "entities": [ + { + "identifier": "robo", + "uid": 9, + "tags": [], + "exportToToc": false, + "doc": "Where robo starts", + "width": 32, + "height": 32, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": true, + "color": "#000000", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "nineSliceBorders": [], + "maxCount": 1, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [] + } + ], "tilesets": [ { "__cWid": 8, "__cHei": 8, - "identifier": "Overworld", + "identifier": "overworld", "uid": 1, "relPath": "overworld.aseprite", "embedAtlas": null, @@ -84,7 +148,7 @@ "padding": 0, "tags": [], "tagsSourceEnumUid": 8, - "enumTags": [{ "enumValueId": "Collide", "tileIds": [] }], + "enumTags": [{ "enumValueId": "collide", "tileIds": [] }], "customData": [], "savedSelections": [], "cachedPixelData": { @@ -95,7 +159,7 @@ { "__cWid": 32, "__cHei": 64, - "identifier": "Internal_Icons", + "identifier": "internal_icons", "uid": 5, "relPath": null, "embedAtlas": "LdtkIcons", @@ -114,10 +178,10 @@ "averageColors": "00004b344233459b423349a959a9379c688769758ca4bc9489aab9aa58cc58bc42d74d2244ce428f4c7e4fb34abb45564ffe7dda78880000000000000000000069a969a97a99999999989a85998699767a7579667ccc7ccc7bcb7caa7ccc7ccc22d72d2224ce228f2c7e2fb32abb25562ffe000000000000000000000000000059764b97599868ac679a69ab4a84477756787688475347532a932a934a837a83f2b6fb22f3acf15afa6cfc93f899f334fccc000000000000000000000000000059aa49aa59996999699969aa489949995999799a499949992999299948997889a385a823a379a248a749a864a667a223a8880000000000000000000000000000189919991999199939994778166727772889289948993aaa389949a959a959a932b63b2233ad315a395c3c83389933343ccc00000000000000000000000000008aaa8aaa8aaa8aaa8aaa7bbb8aaa7bbb8bcb7aaa8bcb7bcb69aa8aaa8aaa69aa6abb6abb6abb6abb6a226a226a226a2261a661a661a661a600000000000000006c526c426c926c91659b649c66a566a46a7b6a7b667766776aba6abb676367636b746b746b746b74616c616c616c616c8abb8abb8abb8abb00000000000000006ba5579a6689598658875cb66abb9aa989aa98ac7abc6678968a88877c87cba952755823536952475648586354455223599900000000000000000000000000003ec63da76db79dc7554885498969b4377fa29e8289cdb9ce5ade5ade49ce49ce82a68a22839b8259885b8b73855683238aab00000000000000000000000000005d745d867da87e75448c458b86ad76ae68ac679c779b78ce3c9378867ca6adb784858933847a844788498963854584348989000000000000000000000000000057a668b899b8449396534493858364836853697769436667755667776c73498800000000000000000000000000000000000000000000000000000000000000006bba79b87d9679ad776a7b988abc8abc4aceaace4bba4bba6b8c4c9c4cac5b7c000000000000000000000000000000000000000000000000000000000000000059aaada7a9bdcdbd59aaada7a9bdcdbd8cb8a9b98ac889b8aabaacc79ea498bd000000000000000000000000000000000000000000000000000000000000000057ac596b55946abb5abb8ca65d8677ac437b5a3368886934547a595897a57b230000000000000000000000000000000000000000000000000000000000000000799a5c817b9b3a886abb8464676a7a967a857a857977898889882a954a956b950000000000000000000000000000000000000000000000000000000000000000499977997868799579875a6465995a8957a66a735ba53a935969479a576a467700000000000000000000000000000000000000000000000000000000000000005744985596659b747a659a76768a7a5676754777388735665976987794459465000000000000000000000000000000000000000000000000000000000000000088668a66868a9b8577666a4467846987778a7789797a87888b8676667a767ca50000000000000000000000000000000000000000000000000000000000000000449374934c957c9574847a438475a3958695768565956853b9447a777493a493000000000000000000000000000000000000000000000000000000000000000079547a838394689a49547a6357636975786383848997b384655873748974588400000000000000000000000000000000000000000000000000000000000000007da48ca769768b554b976cba3a824a82696259526a758c986963694268478b850000000000000000000000000000000000000000000000000000000000000000696559555579557458598674573353635677575579667a8758538b848a44838b0000000000000000000000000000000000000000000000000000000000000000385437883b95534549555a855877997598772b953b9529a939a95aa84b949a840000000000000000000000000000000000000000000000000000000000000000897687898776878578998485878b789a847b8b6579998a55886998788a879b9700000000000000000000000000000000000000000000000000000000000000006ba97988897469646b987a876a997a987b987955766777765c958a858777867700000000000000000000000000000000000000000000000000000000000000005a747b947b967866a855788928884566578879a98864a579233433343334633400000000000000000000000000000000000000000000000000000000000000006a747b846a844997598669987bb8b8aabaa96ba67cba9854687669864a864b86000000000000000000000000000000000000000000000000000000000000000038ab389b48ab47ac49ab48ac579b48ac49ab38ab58bc4b8659aa5c8457ac586a0000000000000000000000000000000000000000000000000000000000000000299b2999389a379b38893955589a79bc8c9588bc7a8c599a689a5b8558ac597a00000000000000000000000000000000000000000000000000000000000000002888378936773975579b389a579b488938884b74469a465747785b75568b586a000000000000000000000000000000000000000000000000000000000000000038553865285428444755566455763a64356746743779397445674c63469b585a0000000000000000000000000000000000000000000000000000000000000000284437643a7629641555297938874879385438664665355536775a85569a785a00000000000000000000000000000000000000000000000000000000000000005789789b779b6a75668a897b64558555876576798855845694749b74a68a986a000000000000000000000000000000000000000000000000000000000000000047776766678867667799798698768866976685673755387638763b74358b387a00000000000000000000000000000000000000000000000000000000000000005777686569874944498846774677685568646987677778775a456a65ab66ca550000000000000000000000000000000000000000000000000000000000000000355656666656455546455345634558655854aa749854775577737b64777a7a7900000000000000000000000000000000000000000000000000000000000000005955895598546c758c75ba76b88797749b75a98967888789978857888788a78800000000000000000000000000000000000000000000000000000000000000006977897799776a748a749a747987ba97aa998ba8a78bab75a87ab89cbb74b97b000000000000000000000000000000000000000000000000000000000000000059645788598858546a7569996a767a766887649c767476797a54766977667976000000000000000000000000000000000000000000000000000000000000000078887a75796577777a869976987799865777667787668a53857a885a98659546000000000000000000000000000000000000000000000000000000000000000087559877a96586779788b9769866888899877576777879647759a8659888a7440000000000000000000000000000000000000000000000000000000000000000785477887a55747b7585795b7999a9667456878889aa58997888797b56776855000000000000000000000000000000000000000000000000000000000000000048545854617b644557448744537b85565899899a39994a7a58998999a5558988000000000000000000000000000000000000000000000000000000000000000089659744a6559555a55698889486a57aab43a96b9556a665a854a579a744a5550000000000000000000000000000000000000000000000000000000000000000596587556677777777778578876687778974867787668876988897779876a744000000000000000000000000000000000000000000000000000000000000000067536556875448225922415851595456654587459456947b48997a86764585560000000000000000000000000000000000000000000000000000000000000000a854a89989998556a7559766a7779976a975997596749a64968a9779a55595450000000000000000000000000000000000000000000000000000000000000000674487549854885594558445a777a7778373579b5a32675584456975958b9944000000000000000000000000000000000000000000000000000000000000000077449754b674b469b964b658a766a864a777a975a566a754a677a875b777b9650000000000000000000000000000000000000000000000000000000000000000775577547445755676558744697377637766785334556566577859755877887600000000000000000000000000000000000000000000000000000000000000002789287328772a7436793a9457795a84368a3334323364555a757b856aaa9a5500000000000000000000000000000000000000000000000000000000000000005888516b5a3349a95964797778987a5375696a536668796577887a847a74797500000000000000000000000000000000000000000000000000000000000000007b537a53767b6769748775767a9a7988759c768a7b957a847775776478647854000000000000000000000000000000000000000000000000000000000000000098999788988998889b879a869a869a86696565676965667767446854677877880000000000000000000000000000000000000000000000000000000000000000678a77997ba647887a7589999ca59ba889aa9999655667bd6ba979a967bc6c7300000000000000000000000000000000000000000000000000000000000000006aaa6556518566775965485438985888576546854ca547775999699989997a9900000000000000000000000000000000000000000000000000000000000000006678526466335644769c5a7888547a785c4454a658885c946285627b6c54674a000000000000000000000000000000000000000000000000000000000000000033843b33359c337c395c3b853899355653745a33558b536b585b5a7557885445000000000000000000000000000000000000000000000000000000000000000026551566274525664a85486546564656377756664655465545454656516a656700000000000000000000000000000000000000000000000000000000000000004964696468553a86485437443645896588548856895477446a7569547a757954000000000000000000000000000000000000000000000000000000000000000036678566399988993b968b955ba658995566588859645a986ca7796477887ca6000000000000000000000000000000000000000000000000000000000000000019562a554c665c55156a256a468c557b1a8429744a845a83196b285a496b595b00000000000000000000000000000000000000000000000000000000000000001486248645a7549615782578469a5689187629764a875a861a692a694b7a5b79000000000000000000000000000000000000000000000000000000000000000017772777489858881555255546665556199528854884588411122112411251120000000000000000000000000000000000000000000000000000000000000000" } } - ], "enums": [{ "identifier": "Tile_flag", "uid": 8, "values": [{ "id": "Collide", "tileRect": null, "tileId": -1, "color": 12470831, "__tileSrcRect": null }], "iconTilesetUid": null, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }], "externalEnums": [], "levelFields": [] }, + ], "enums": [{ "identifier": "tile_flag", "uid": 8, "values": [{ "id": "collide", "tileRect": null, "tileId": -1, "color": 12470831, "__tileSrcRect": null }], "iconTilesetUid": null, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }], "externalEnums": [], "levelFields": [] }, "levels": [ { - "identifier": "Level_0", + "identifier": "level_0", "iid": "315be6f1-1460-11ee-be24-4d9c21f00752", "uid": 0, "worldX": 0, @@ -138,7 +202,47 @@ "fieldInstances": [], "layerInstances": [ { - "__identifier": "Base", + "__identifier": "entities", + "__type": "Entities", + "__cWid": 20, + "__cHei": 15, + "__gridSize": 16, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "9c951a30-3b70-11ee-bcfe-35afbfcd1d9c", + "levelId": 0, + "layerDefUid": 10, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 1588631, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "robo", + "__grid": [17,12], + "__pivot": [0,0], + "__tags": [], + "__tile": null, + "__smartColor": "#000000", + "iid": "ab3eeac0-3b70-11ee-bcfe-2f66db6f9ef4", + "width": 32, + "height": 32, + "defUid": 9, + "px": [272,192], + "fieldInstances": [] + } + ] + }, + { + "__identifier": "base", "__type": "Tiles", "__cWid": 20, "__cHei": 15, @@ -469,4 +573,4 @@ ], "worlds": [], "dummyWorldIid": "315be6f0-1460-11ee-be24-1f7d7ad3f341" -} +} \ No newline at end of file