diff --git a/README.md b/README.md index a696436..a14c333 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,14 @@ This repository is participating in the 2023 edition of the [RoguelikeDev event] ### Week 1 - 4 July 2023 - Setup, drawing an @, and moving around +- Getting this done a bit in advance because I know I am going to be busy over this year's sprint! +- The Malwoden tutorial doesn't really spend any time explaining how to set up the development environment--it just asks for some files to be copy/pasted. I've used `vite` for packaging and am just using the `vanilla-ts` template. +- I actually don't have any experience with Typescript, and I learned Javascript during the pre-jQuery era, so modern front-end is pretty new to me. For now, it has been relatively straightforward to just learn as I've gone along. +- I don't understand typing strictness in Typescript. The `queries` in any inherited `System` class should be typed `SystemQueries` but the tutorial doesn't do this and it doesn't throw any errors. There also seems to be no requirement to have a consistent style for semicolon requirement! I presume the settings in `tsconfig.json` control this, but I don't understand why there is no singular standard for the language. +- A lot of logic keeps being shoved into the `main.ts` file and I am expecting a serious refactor down the road. +- Malwoden itself seems to get out of the way--I enjoy this. +- The ECS system is simple but seems sufficient--compared to Rust's `specs` or `legion` this feels much better for a beginner. + ### Week 2 - 11 July 2023 - The object system and generating your first map ### Week 3 - 18 July 2023 - Field of view, placing enemies, and attacking @@ -26,7 +34,7 @@ This repository is participating in the 2023 edition of the [RoguelikeDev event] ### Week 7 - 15 August 2023 - Monster/item progression and equipment -### Week 8 - 22 July 2023 - Sharing your game +### Week 8 - 22 July 2023 - Sharing your gamex ## Design Specification diff --git a/package-lock.json b/package-lock.json index bcf33c9..69347a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "yezriel", "version": "0.0.0", "dependencies": { + "ecsy": "^0.4.2", "malwoden": "^0.5.0" }, "devDependencies": { @@ -367,6 +368,11 @@ "node": ">=12" } }, + "node_modules/ecsy": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/ecsy/-/ecsy-0.4.2.tgz", + "integrity": "sha512-dVKOkuz1IsRvS7GlHcWghUtMZzXr70h6b+SQEHlKy2RgsCRNDzdYr6a43Q6HafuquA/67YFZCQt8zqadq/jlLA==" + }, "node_modules/esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", diff --git a/package.json b/package.json index 4899784..1f21d15 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "vite": "^4.3.9" }, "dependencies": { - "malwoden": "^0.5.0" + "malwoden": "^0.5.0", + "ecsy": "^0.4.2" } } diff --git a/src/components.ts b/src/components.ts new file mode 100644 index 0000000..7f6e2b3 --- /dev/null +++ b/src/components.ts @@ -0,0 +1,35 @@ +import { Component, ComponentSchema, Types } from 'ecsy'; +import { Terminal } from 'malwoden'; + +/** + * Position component + * @param x x-coordinate + * @param y y-coordinate + */ +export class Position extends Component { + x = 0; + y = 0; + + static schema: ComponentSchema = { + x: { type: Types.Number }, + y: { type: Types.Number }, + }; +} + +/** + * Renderable component + * @param glyph `Terminal.Glyph` for rendering on the canvas + */ +export class Renderable extends Component { + // glyph can never be undefined + glyph!: Terminal.Glyph; + + static schema: ComponentSchema = { + glyph: { type: Types.Ref }, + }; +} + +/** + * Player component + */ +export class Player extends Component {} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index cc74923..0818662 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,8 @@ -import { Terminal } from "malwoden"; +import { Terminal, Color } from 'malwoden'; +import { World, Entity } from 'ecsy'; + +import * as Components from './components'; +import * as Systems from './systems'; /** * The main game engine. @@ -10,12 +14,19 @@ export class Game { public static readonly CHAR_WIDTH = 16; public static readonly CHAR_HEIGHT = 16; + world: World; terminal: Terminal.RetroTerminal; lastTime: number; + player: Entity; + constructor() { + this.world = new World(); this.terminal = this.createTerminal(); this.lastTime = performance.now(); + + this.registerECS(); + this.player = this.initWorld(); } /** @@ -36,15 +47,39 @@ export class Game { }); } + /** + * Register the ECS system into the game World. + */ + registerECS() { + this.world + .registerComponent(Components.Position) + .registerComponent(Components.Renderable) + .registerComponent(Components.Player) + + .registerSystem(Systems.RenderSystem, this); + } + + /** + * Initializes the World with a player Entity. + * @returns Entity + */ + initWorld(): Entity { + const player = this.world + .createEntity() + .addComponent(Components.Player) + .addComponent(Components.Position, { x: 5, y: 5 }) + .addComponent(Components.Renderable, { glyph: new Terminal.Glyph('@', Color.Yellow) }); + + return player; + } + /** * The engine's update tick. * @param delta Time passed since last tick * @param time Current time */ tick(delta: number, time: number) { - this.terminal.clear(); - this.terminal.writeAt({ x: 1, y: 1 }, 'Hello, world!'); - this.terminal.render(); + this.world.execute(delta, time); } /** @@ -60,6 +95,7 @@ export class Game { } } +// Modify the Window to include the Game object. declare global { interface Window { game: Game; diff --git a/src/systems/index.ts b/src/systems/index.ts new file mode 100644 index 0000000..53597cc --- /dev/null +++ b/src/systems/index.ts @@ -0,0 +1 @@ +export { RenderSystem } from './render'; \ No newline at end of file diff --git a/src/systems/render.ts b/src/systems/render.ts new file mode 100644 index 0000000..a82685e --- /dev/null +++ b/src/systems/render.ts @@ -0,0 +1,44 @@ +import { World, System, SystemQueries } from 'ecsy'; + +import { Game } from '../main'; +import * as Components from '../components'; + +/** + * Rendering system + */ +export class RenderSystem extends System { + game: Game; + + constructor(world: World, game: Game) { + super(world, game); + this.game = game; + } + + static queries: SystemQueries = { + renderables: { + components: [Components.Renderable, Components.Position], + }, + }; + + /** + * Render any entities that have Position and Renderable components. + */ + execute() { + // run the query + const { results } = this.queries.renderables; + + // clear before the render pass + this.game.terminal.clear(); + + // draw each entity + for (const entity of results) { + const position = entity.getComponent(Components.Position)!; + const renderable = entity.getComponent(Components.Renderable)!; + + this.game.terminal.drawGlyph(position, renderable.glyph) + } + + // render to canvas + this.game.terminal.render(); + } +} \ No newline at end of file