From a6507af1e289d409aa595b1143f242338fedc4df Mon Sep 17 00:00:00 2001 From: Avinash Vora Date: Tue, 4 Jul 2023 23:55:17 +0400 Subject: [PATCH] Player movement implemented. --- README.md | 6 +++-- src/game-states.ts | 7 ++++++ src/main.ts | 61 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 src/game-states.ts diff --git a/README.md b/README.md index a14c333..ddce749 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Built using Typescript and [Malwoden](https://malwoden.com), partially following This repository is participating in the 2023 edition of the [RoguelikeDev event](https://www.reddit.com/r/roguelikedev/comments/14kz7al/roguelikedev_does_the_complete_roguelike_tutorial/), running from 4 July 2023 till 22 August 2023. -### Week 1 - 4 July 2023 - Setup, drawing an @, and moving around +### [Week 1](https://www.reddit.com/r/roguelikedev/comments/14q58js/roguelikedev_does_the_complete_roguelike_tutorial/) - 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. @@ -21,6 +21,8 @@ This repository is participating in the 2023 edition of the [RoguelikeDev event] - 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. +- Lots of mistakes in the tutorial--in Chapter 3, for example, the entire input handling code is missing imports and declarations. +- I prefered implementing game states in `game-states.ts` as an enum to reference. ### Week 2 - 11 July 2023 - The object system and generating your first map @@ -52,7 +54,7 @@ Your hometown is suffering from a cataclysm of monters. You have been tasked wit ### MVP -- [ ] Player can walk around +- [x] Player can walk around - [ ] Create a basic procedural dungeon map - [ ] Player has field-of-view - [ ] Spawn monsters diff --git a/src/game-states.ts b/src/game-states.ts new file mode 100644 index 0000000..df7672d --- /dev/null +++ b/src/game-states.ts @@ -0,0 +1,7 @@ +/** + * Possible game states + */ +export enum GameStates { + INIT, + PLAYER_TURN, +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 0818662..4c4991c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,9 @@ -import { Terminal, Color } from 'malwoden'; +import { Terminal, Color, Input, Vector2 } from 'malwoden'; import { World, Entity } from 'ecsy'; import * as Components from './components'; import * as Systems from './systems'; +import { GameStates } from './game-states'; /** * The main game engine. @@ -19,6 +20,9 @@ export class Game { lastTime: number; player: Entity; + input = new Input.KeyboardHandler(); + + gameState: GameStates; constructor() { this.world = new World(); @@ -27,6 +31,8 @@ export class Game { this.registerECS(); this.player = this.initWorld(); + + this.gameState = GameStates.INIT; } /** @@ -78,23 +84,70 @@ export class Game { * @param delta Time passed since last tick * @param time Current time */ - tick(delta: number, time: number) { + async tick(delta: number, time: number) { + // PLAYER_TURN + if (this.gameState == GameStates.PLAYER_TURN) { + // handle player input + const key = await this.input.waitForKeyDown(); + + // create an actions map of potential keypresses + const actions = new Map void>(); + actions.set(Input.KeyCode.LeftArrow, () => + tryMoveEntity(this.player, { x: -1, y: 0 }) + ); + actions.set(Input.KeyCode.RightArrow, () => + tryMoveEntity(this.player, { x: 1, y: 0 }) + ); + actions.set(Input.KeyCode.UpArrow, () => + tryMoveEntity(this.player, { x: 0, y: -1 }) + ); + actions.set(Input.KeyCode.DownArrow, () => + tryMoveEntity(this.player, { x: 0, y: 1 }) + ); + + // if the keypair in the map matches an input key, run the command + const command = actions.get(key); + if (command) command(); + } + + // execute game systems this.world.execute(delta, time); + + if (this.gameState == GameStates.INIT) { + this.gameState = GameStates.PLAYER_TURN; + } } /** * The engine's bootstrap function. Sets up the main game loop and runs it. */ - run() { + async run() { const time = performance.now(); const delta = time - this.lastTime; - this.tick(delta, this.lastTime); + await this.tick(delta, this.lastTime); window.requestAnimationFrame(this.run.bind(this)); } } +/** + * Attempts to move an Entity in position. + * @param entity Entity with a Position component + * @param delta Movement vector delta + */ +function tryMoveEntity(entity: Entity, delta: Vector2) { + const position = entity.getMutableComponent(Components.Position); + + if (!position) { + console.warn('WARNING: Can\'t move an entity without a Position!'); + return; + } + + position.x += delta.x; + position.y += delta.y; +} + // Modify the Window to include the Game object. declare global { interface Window {