1
0
Fork 0

Move the logic to mainloop

This commit is contained in:
Pabloader 2026-05-03 08:57:31 +00:00
parent 1c22edaac1
commit 90bac08e0b
4 changed files with 73 additions and 75 deletions

View File

@ -10,13 +10,7 @@ the attacker/source that CombatSystem folds into the final damage value.
RNG layer on top of damage calculation (crit chance, crit multiplier, random range). RNG layer on top of damage calculation (crit chance, crit multiplier, random range).
### Rendering ### Rendering
- Sprite rendering system.
- Generic, use `resourceId` to link image.
- `framesCount`
- `currentFrame`
- `animationSpeed`
- Animation support for sprites.
- Different display systems - Different display systems
- TextDisplay - [x] TextDisplay
- BrickDisplay - [ ] BrickDisplay
- Canvas - [ ] Canvas

View File

@ -8,4 +8,4 @@ export class Position extends Component<{ x: number, y: number, z: number }> {
} }
} }
export const getPosition = (entity: Entity) => entity.get(Position)?.state; export const getPosition = (entity?: Entity, key?: string) => entity?.get(Position, key)?.state;

View File

@ -3,7 +3,7 @@ import Input from '@common/input';
import { Inventory } from '@common/rpg/components/inventory'; import { Inventory } from '@common/rpg/components/inventory';
import { Item } from '@common/rpg/components/item'; import { Item } from '@common/rpg/components/item';
import { Position } from '@common/rpg/components/position'; import { Position } from '@common/rpg/components/position';
import { Entity, System, World } from '@common/rpg/core/world'; import { World } from '@common/rpg/core/world';
import { TextDisplaySystem } from '@common/rpg/systems/render/text'; import { TextDisplaySystem } from '@common/rpg/systems/render/text';
import { nextFrame } from '@common/utils'; import { nextFrame } from '@common/utils';
import './assets/style.css'; import './assets/style.css';
@ -11,47 +11,37 @@ import { Direction, getOppositeDirection, MAP_HEIGHT, MAP_WIDTH, MAP_X, MAP_Y, R
import { getPossibleRoomsCount, getRoom, getRoomsCount, getRoomsForLayer, getMapRoomChar, Room, Door } from './room'; import { getPossibleRoomsCount, getRoom, getRoomsCount, getRoomsForLayer, getMapRoomChar, Room, Door } from './room';
import { createPlayer, Player } from './player'; import { createPlayer, Player } from './player';
let lastMove = Date.now();
class GameSystem extends System { export default async function main() {
private readonly display = new TextDisplay(); const world = new World();
private room: Entity | undefined; const display = new TextDisplay();
private player: Entity | undefined; world.addSystem(new TextDisplaySystem(display));
override onAdd(world: World) { display.drawTextInBox(8, 11, 'Use arrow keys to move\nSHIFT to run\nSPACE to activate\nAWOO!', { fg: Color.CYAN });
world.addSystem(new TextDisplaySystem(this.display));
this.display.drawTextInBox(8, 11, 'Use arrow keys to move\nSHIFT to run\nSPACE to activate\nAWOO!', { fg: Color.CYAN }); let roomEntity = getRoom(world, 0, 0, 0);
let room = roomEntity?.get(Room);
if (!room) {
throw new Error('No room found');
}
room.onEnter();
this.room = getRoom(world, 0, 0, 0); const playerEntity = createPlayer(world, (room.x + room.width / 2) | 0, (room.y + room.height / 2) | 0);
const room = this.room?.get(Room); const player = playerEntity.get(Player);
if (room) { if (!player) {
room.onEnter(); throw new Error('No player found');
this.player = createPlayer(world, (room.x + room.width / 2) | 0, (room.y + room.height / 2) | 0);
}
} }
override update(world: World) { function drawRoom() {
this.handleInput(world);
this.drawRoom();
this.drawMap(world);
this.drawInfo(world);
}
private drawRoom() {
const room = this.room?.get(Room);
if (!room) return; if (!room) return;
this.display.drawBox(ROOM_AREA_X, ROOM_AREA_Y, ROOM_AREA_WIDTH - 2, ROOM_AREA_HEIGHT - 2, { fill: [' '] }); display.drawBox(ROOM_AREA_X, ROOM_AREA_Y, ROOM_AREA_WIDTH - 2, ROOM_AREA_HEIGHT - 2, { fill: ' ' });
this.display.drawBox(room.x, room.y, room.width, room.height, { fill: ['.', Color.GRAY] }); display.drawBox(room.x, room.y, room.width, room.height, { fill: ['.', Color.GRAY] });
} }
private drawMap(world: World) { function drawMap() {
if (!this.room) return; display.drawBox(MAP_X, MAP_Y, MAP_WIDTH - 2, MAP_HEIGHT - 2, { fill: [' '], title: 'Map' });
const worldPos = roomEntity.get(Position, 'world');
this.display.drawBox(MAP_X, MAP_Y, MAP_WIDTH - 2, MAP_HEIGHT - 2, { fill: [' '], title: 'Map' });
const worldPos = this.room.get(Position, 'world');
const centerX = worldPos ? worldPos.state.x - MAP_X - MAP_WIDTH / 2 : 0; const centerX = worldPos ? worldPos.state.x - MAP_X - MAP_WIDTH / 2 : 0;
const centerY = worldPos ? worldPos.state.y - MAP_Y - MAP_HEIGHT / 2 : 0; const centerY = worldPos ? worldPos.state.y - MAP_Y - MAP_HEIGHT / 2 : 0;
const worldZ = worldPos ? worldPos.state.z : 0; const worldZ = worldPos ? worldPos.state.z : 0;
@ -65,31 +55,27 @@ class GameSystem extends System {
if (x > MAP_X && x < MAP_X + MAP_WIDTH - 1 && y > MAP_Y && y < MAP_Y + MAP_HEIGHT - 1) { if (x > MAP_X && x < MAP_X + MAP_WIDTH - 1 && y > MAP_Y && y < MAP_Y + MAP_HEIGHT - 1) {
const char = getMapRoomChar(room); const char = getMapRoomChar(room);
this.display.setChar(x, y, [char, room === this.room ? Color.YELLOW : Color.WHITE]); display.setChar(x, y, [char, room === roomEntity ? Color.YELLOW : Color.WHITE]);
} }
} }
} }
private drawInfo(world: World) { function drawInfo() {
if (!this.room || !this.player) return; const worldPos = roomEntity.get(Position, 'world');
const worldPos = this.room.get(Position, 'world');
const coords = worldPos ? `${worldPos.state.x},${worldPos.state.y},${worldPos.state.z}`.padStart(9) : '0,0,0'; const coords = worldPos ? `${worldPos.state.x},${worldPos.state.y},${worldPos.state.z}`.padStart(9) : '0,0,0';
const foundRooms = getRoomsCount(world); const foundRooms = getRoomsCount(world);
const totalRooms = getPossibleRoomsCount(world); const totalRooms = getPossibleRoomsCount(world);
const rooms = `${foundRooms}/${totalRooms}${foundRooms === totalRooms ? '' : '+'}`.padStart(coords.length - 2); const rooms = `${foundRooms}/${totalRooms}${foundRooms === totalRooms ? '' : '+'}`.padStart(coords.length - 2);
const inv = this.player.get(Inventory); const inv = playerEntity.get(Inventory);
const foundItems = inv ? Object.values(inv.state.slots).length : 0; const foundItems = inv ? Object.values(inv.state.slots).length : 0;
const totalItems = Array.from(world.query(Item)).length; const totalItems = Array.from(world.query(Item)).length;
const items = `${foundItems}/${totalItems}`.padStart(coords.length - 2); const items = `${foundItems}/${totalItems}`.padStart(coords.length - 2);
this.display.drawTextInBox(0, 0, `Pos: ${coords}\nRooms: ${rooms}\nItems: ${items}`, { fg: Color.YELLOW, title: 'Info' }); display.drawTextInBox(0, 0, `Pos: ${coords}\nRooms: ${rooms}\nItems: ${items}`, { fg: Color.YELLOW, title: 'Info' });
} }
private handleInput(world: World) { let lastMove = Date.now();
const player = this.player?.get(Player); function handleInput() {
let room = this.room?.get(Room); if (!room || !player) return;
if (!player || !room) return;
Input.updateKeys(); Input.updateKeys();
const isSpacePressed = Input.isPressed(Input.KeyCode.SPACE); const isSpacePressed = Input.isPressed(Input.KeyCode.SPACE);
@ -122,17 +108,17 @@ class GameSystem extends System {
} }
if (moved) { if (moved) {
const activatedDoor = room.getActivatedDoor(newX, newY)?.get(Door); const activatedDoor = room.getActivatedDoor(newX, newY);
if (activatedDoor) { if (activatedDoor) {
const shouldTravel = ![Direction.UP, Direction.DOWN].includes(activatedDoor.direction) || isSpacePressed; const shouldTravel = ![Direction.UP, Direction.DOWN].includes(activatedDoor.direction) || isSpacePressed;
if (shouldTravel) { if (shouldTravel) {
room.onLeave(); room.onLeave();
this.room = getRoom(world, activatedDoor.worldX, activatedDoor.worldY, activatedDoor.worldZ); roomEntity = getRoom(world, activatedDoor.worldX, activatedDoor.worldY, activatedDoor.worldZ);
room = this.room.get(Room); room = roomEntity.get(Room);
if (!room) return; if (!room) return;
room.onEnter(); room.onEnter();
const oppositeDoor = room.doors[getOppositeDirection(activatedDoor.direction)]?.get(Door); const oppositeDoor = room.getDoor(getOppositeDirection(activatedDoor.direction));
if (oppositeDoor) { if (oppositeDoor) {
switch (activatedDoor.direction) { switch (activatedDoor.direction) {
@ -181,15 +167,15 @@ class GameSystem extends System {
player.y = newY; player.y = newY;
} }
} }
}
export default async function main() {
const world = new World();
world.addSystem(new GameSystem());
while (true) { while (true) {
const dt = await nextFrame(); const dt = await nextFrame();
handleInput();
drawRoom();
drawMap();
drawInfo();
world.update(dt); world.update(dt);
} }
} }

View File

@ -10,7 +10,7 @@ import { spawnItem } from "./item";
import { Resources } from "@common/rpg/utils/resources"; import { Resources } from "@common/rpg/utils/resources";
import { TextRegion } from "@common/display/text"; import { TextRegion } from "@common/display/text";
type IDoors = [Entity?, Entity?, Entity?, Entity?, Entity?, Entity?]; type IDoors = [string?, string?, string?, string?, string?, string?];
@component @component
export class Room extends Component<{ doors: IDoors }> { export class Room extends Component<{ doors: IDoors }> {
@ -45,8 +45,11 @@ export class Room extends Component<{ doors: IDoors }> {
get doors() { get doors() {
return this.state.doors; return this.state.doors;
} }
getActivatedDoor(px: number, py: number): Entity | undefined { getActivatedDoor(px: number, py: number): Door | undefined {
return this.state.doors.find((door) => { const id = this.state.doors.find((doorId) => {
if (!doorId) return false;
const door = this.entity.world.getEntity(doorId);
const doorPos = door?.get(Position); const doorPos = door?.get(Position);
if (!doorPos) return false; if (!doorPos) return false;
@ -56,14 +59,24 @@ export class Room extends Component<{ doors: IDoors }> {
const { x, y } = doorPos.state; const { x, y } = doorPos.state;
return px === x - dx && py === y - dy; return px === x - dx && py === y - dy;
}); });
if (!id) return undefined;
return this.entity.world.getEntity(id)?.get(Door);
}
getDoor(direction: Direction): Door | undefined {
const id = this.doors[direction];
if (!id) return undefined;
return this.entity.world.getEntity(id)?.get(Door);
} }
onEnter() { onEnter() {
this.doors.forEach(door => door?.remove(Hidden)); this.doors.forEach(doorId => doorId && this.entity.world.getEntity(doorId)?.remove(Hidden));
} }
onLeave() { onLeave() {
this.doors.forEach(door => door?.add(new Hidden())); this.doors.forEach(doorId => doorId && this.entity.world.getEntity(doorId)?.add(new Hidden()));
} }
} }
@ -110,7 +123,7 @@ interface IRoomBlank {
const roomId = (worldX: number, worldY: number, worldZ: number) => `room_${worldX}_${worldY}_${worldZ}`; const roomId = (worldX: number, worldY: number, worldZ: number) => `room_${worldX}_${worldY}_${worldZ}`;
const createDoor = (world: World, x: number, y: number, direction: Direction, worldX: number, worldY: number, worldZ: number): Entity => { const createDoor = (world: World, x: number, y: number, direction: Direction, worldX: number, worldY: number, worldZ: number): string => {
const door = world.createEntity('door_*'); const door = world.createEntity('door_*');
door.add(new Position(x, y)); door.add(new Position(x, y));
door.add(new Position(worldX, worldY, worldZ), 'world'); door.add(new Position(worldX, worldY, worldZ), 'world');
@ -121,7 +134,7 @@ const createDoor = (world: World, x: number, y: number, direction: Direction, wo
if (spr) { if (spr) {
door.add(new Position((-spr.width / 2) | 0, (-spr.height / 2) | 0), 'offset'); door.add(new Position((-spr.width / 2) | 0, (-spr.height / 2) | 0), 'offset');
} }
return door; return door.id;
} }
const generateDoors = (world: World, { x, y, width, height, worldX, worldY, worldZ }: IRoomBlank): IDoors => { const generateDoors = (world: World, { x, y, width, height, worldX, worldY, worldZ }: IRoomBlank): IDoors => {
@ -195,8 +208,12 @@ const generateItems = (world: World, { x, y, width, height, doors }: IRoomBlank)
} }
const hasObjectAt = (x: number, y: number) => const hasObjectAt = (x: number, y: number) =>
doors.flat().filter(x => x != null).map(getPosition).some((d) => d?.x === x && d.y === y) || doors
items.map(getPosition).some((i) => i?.x === x && i.y === y); .filter(x => x != null)
.map((id) => getPosition(world.getEntity(id)))
.some((d) => d?.x === x && d.y === y)
|| items.map((i) => getPosition(i))
.some((i) => i?.x === x && i.y === y);
const oneDoor = doors.filter((d) => Boolean(d)).length === 1; const oneDoor = doors.filter((d) => Boolean(d)).length === 1;
@ -258,9 +275,10 @@ export const getPossibleRoomsCount = (world: World) => {
for (const room of rooms) { for (const room of rooms) {
const roomComp = room.get(Room); const roomComp = room.get(Room);
roomComp?.state.doors.forEach((d) => { roomComp?.state.doors.forEach((d) => {
const worldPos = d?.get(Position, 'world'); if (!d) return;
const worldPos = getPosition(world.getEntity(d) ,'world');
if (worldPos) { if (worldPos) {
const id = roomId(worldPos.state.x, worldPos.state.y, worldPos.state.z); const id = roomId(worldPos.x, worldPos.y, worldPos.z);
roomsSet.add(id); roomsSet.add(id);
} }
}); });