Compare commits
No commits in common. "0c10347c78a634616368a2b0e8dd1710d24ed71c" and "5796f914e3e4fa0b671c59259e24582b1d711ca2" have entirely different histories.
0c10347c78
...
5796f914e3
|
|
@ -188,9 +188,7 @@ export class TextDisplay {
|
|||
}
|
||||
|
||||
private setCharRaw(x: number, y: number, char: string, fg: IColorLike, bg: IColorLike) {
|
||||
if (x < this.clipLeft || y < this.clipTop || x >= this.clipRight || y >= this.clipBottom) return;
|
||||
if (!char || char === '\0') return;
|
||||
|
||||
if (x < this.clipLeft || y < this.clipTop || x >= this.clipRight || y >= this.clipBottom || !char) return;
|
||||
const i = (y | 0) * this.width + (x | 0);
|
||||
let dirty = false;
|
||||
if (this.chars[i] !== char) {
|
||||
|
|
@ -241,9 +239,7 @@ export class TextDisplay {
|
|||
const dispBase = row * this.width + x0;
|
||||
const regRowStr = chars[regRow];
|
||||
for (let i = 0; i < copyW; i++) {
|
||||
const ch = regRowStr[regColOffset + i];
|
||||
if (ch === '\0') continue;
|
||||
this.chars[dispBase + i] = ch;
|
||||
this.chars[dispBase + i] = regRowStr[regColOffset + i];
|
||||
this.fgs[dispBase + i] = fgs[regBase + i];
|
||||
this.bgs[dispBase + i] = bgs[regBase + i];
|
||||
this.drawCell(x0 + i, row);
|
||||
|
|
@ -456,18 +452,6 @@ export class TextRegion {
|
|||
}
|
||||
}
|
||||
|
||||
get(x: number, y: number): IDefinedChar {
|
||||
const i = y * this.width + x;
|
||||
return [this.#chars[y][x], this.#fgs[i], this.#bgs[i]];
|
||||
}
|
||||
|
||||
set(x: number, y: number, char: IChar) {
|
||||
const ch = parseChar(char);
|
||||
this.#chars[y] = this.#chars[y].slice(0, x) + ch[0] + this.#chars[y].slice(x + 1);
|
||||
this.#fgs[y * this.width + x] = ch[1];
|
||||
this.#bgs[y * this.width + x] = ch[2];
|
||||
}
|
||||
|
||||
get [REGION_DATA]() {
|
||||
return {
|
||||
chars: this.#chars,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { Component, Entity } from "../core/world";
|
|||
import { component } from "../utils/decorators";
|
||||
|
||||
@component({ variables: ['x', 'y', 'z'] })
|
||||
export class Position extends Component<{ x: number, y: number, z: number, absolute: boolean }> {
|
||||
constructor(x = 0, y = 0, z = 0, absolute = false) {
|
||||
super({ x, y, z, absolute });
|
||||
export class Position extends Component<{ x: number, y: number, z: number }> {
|
||||
constructor(x = 0, y = 0, z = 0) {
|
||||
super({ x, y, z });
|
||||
}
|
||||
|
||||
get x() {
|
||||
|
|
@ -25,19 +25,6 @@ export class Position extends Component<{ x: number, y: number, z: number, absol
|
|||
set z(z: number) {
|
||||
this.state.z = z;
|
||||
}
|
||||
get absolute() {
|
||||
return this.state.absolute;
|
||||
}
|
||||
set absolute(absolute: boolean) {
|
||||
this.state.absolute = absolute;
|
||||
}
|
||||
}
|
||||
|
||||
export const getPosition = (entity?: Entity, key?: string) => entity?.get(Position, key)?.state;
|
||||
export const move = (entity: Entity, dx: number, dy: number, dz = 0) => {
|
||||
const pos = getPosition(entity);
|
||||
if (!pos) return;
|
||||
pos.x += dx;
|
||||
pos.y += dy;
|
||||
pos.z += dz;
|
||||
};
|
||||
|
|
@ -37,6 +37,14 @@ export class Viewport extends Component<void> {
|
|||
get worldY(): number {
|
||||
return Math.round(getPosition(this.entity)?.y ?? 0);
|
||||
}
|
||||
|
||||
move(dx: number, dy: number) {
|
||||
const pos = getPosition(this.entity);
|
||||
if (!pos) return;
|
||||
|
||||
pos.x += dx;
|
||||
pos.y += dy;
|
||||
}
|
||||
}
|
||||
|
||||
export const createViewport = (world: World, viewportData: ViewportData) => {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,9 @@ import { Resources } from "@common/rpg/utils/resources";
|
|||
export class TextDisplaySystem extends System {
|
||||
public readonly display: TextDisplay;
|
||||
|
||||
constructor(display: TextDisplay);
|
||||
constructor(width?: number, height?: number);
|
||||
constructor(displayOrWidth?: TextDisplay | number, height?: number) {
|
||||
constructor(display?: TextDisplay) {
|
||||
super();
|
||||
this.display = displayOrWidth instanceof TextDisplay ? displayOrWidth : new TextDisplay(displayOrWidth, height);
|
||||
this.display = display ?? new TextDisplay();
|
||||
}
|
||||
|
||||
override update(world: World) {
|
||||
|
|
@ -21,12 +19,16 @@ export class TextDisplaySystem extends System {
|
|||
x: viewport.worldX - viewport.screenX,
|
||||
y: viewport.worldY - viewport.screenY,
|
||||
} : { x: 0, y: 0 };
|
||||
const clipRect = this.display.getClipRect();
|
||||
if (viewport) {
|
||||
this.display.setClipRect({ x: viewport.screenX, y: viewport.screenY, width: viewport.width, height: viewport.height });
|
||||
}
|
||||
|
||||
const sprites = Array.from(world.query(Sprite, Position)).sort((a, b) => a[2].state.z - b[2].state.z);
|
||||
for (const [e, sprite, pos] of sprites) {
|
||||
if (e.has(Hidden)) continue;
|
||||
const image = sprite.image;
|
||||
const { x, y, absolute } = pos;
|
||||
const { x, y } = pos.state;
|
||||
|
||||
const data =
|
||||
Resources.get(TextRegion, image)
|
||||
|
|
@ -34,17 +36,9 @@ export class TextDisplaySystem extends System {
|
|||
?? image;
|
||||
|
||||
const region = data instanceof TextRegion ? data : new TextRegion(data);
|
||||
|
||||
if (absolute) {
|
||||
this.display.setRegion(x, y, region);
|
||||
} else {
|
||||
const clipRect = this.display.getClipRect();
|
||||
if (viewport) {
|
||||
this.display.setClipRect({ x: viewport.screenX, y: viewport.screenY, width: viewport.width, height: viewport.height });
|
||||
}
|
||||
this.display.setRegion(x - offset.x, y - offset.y, region);
|
||||
}
|
||||
|
||||
this.display.setClipRect(clipRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +1,60 @@
|
|||
import { Color, TextRegion } from "@common/display/text";
|
||||
import { TextRegion } from "@common/display/text";
|
||||
import { gameLoop } from "@common/game";
|
||||
import type { Point } from "@common/geometry";
|
||||
import Input from "@common/input";
|
||||
import { BSP } from "@common/level/bsp";
|
||||
import { bresenhamCircleGen } from "@common/navigation/bresenham";
|
||||
import { SeededRandom } from "@common/random";
|
||||
import { getPosition, move, Position } from "@common/rpg/components/position";
|
||||
import { createViewport } from "@common/rpg/components/render/viewport";
|
||||
import { Position } from "@common/rpg/components/position";
|
||||
import { createViewport, Viewport } from "@common/rpg/components/render/viewport";
|
||||
import { Sprite } from "@common/rpg/components/sprite";
|
||||
import { World } from "@common/rpg/core/world";
|
||||
import { TextDisplaySystem } from "@common/rpg/systems/render/text";
|
||||
import { Resources } from "@common/rpg/utils/resources";
|
||||
|
||||
const WALL = '#';
|
||||
const PLAYER = '@';
|
||||
|
||||
function createMap(world: World, random: SeededRandom) {
|
||||
const MAP_SIZE = 100;
|
||||
const mapData = new Array(MAP_SIZE * MAP_SIZE).fill(WALL);
|
||||
const mapSize = 100;
|
||||
const mapData = new Array(mapSize * mapSize).fill('#');
|
||||
|
||||
BSP.generateLevel(MAP_SIZE, MAP_SIZE, (x, y) => { mapData[x + y * MAP_SIZE] = '.'; }, {
|
||||
BSP.generateLevel(mapSize, mapSize, (x, y) => { mapData[x + y * mapSize] = '.'; }, {
|
||||
minWidth: 8,
|
||||
minHeight: 8,
|
||||
depth: 8,
|
||||
random,
|
||||
});
|
||||
const mapDataString: string[] = [];
|
||||
const maskDataString: string[] = [];
|
||||
for (let i = 0; i < MAP_SIZE; i++) {
|
||||
mapDataString.push(mapData.slice(i * MAP_SIZE, (i + 1) * MAP_SIZE).join(''));
|
||||
}
|
||||
for (let i = 0; i < MAP_SIZE * 3; i++) {
|
||||
maskDataString.push(Array.from({ length: MAP_SIZE * 3 }, () => ' ').join(''));
|
||||
}
|
||||
|
||||
const map = world.createEntity('map');
|
||||
map.add(new Sprite(Resources.add('map', new TextRegion(mapDataString))));
|
||||
map.add(new Position(0, 0, -2));
|
||||
|
||||
const mask = world.createEntity('mask');
|
||||
mask.add(new Sprite(Resources.add('mask', new TextRegion(maskDataString))));
|
||||
mask.add(new Position(-MAP_SIZE, -MAP_SIZE, 9));
|
||||
|
||||
return { map, mask };
|
||||
const mapDataString: string[] = [];
|
||||
for (let i = 0; i < mapSize; i++) {
|
||||
mapDataString.push(mapData.slice(i * mapSize, (i + 1) * mapSize).join(''));
|
||||
}
|
||||
Resources.add('map', new TextRegion(mapDataString));
|
||||
map.add(new Sprite('map'));
|
||||
map.add(new Position(0, 0));
|
||||
|
||||
function createPlayer(world: World, x = 0, y = 0) {
|
||||
const player = world.createEntity('player');
|
||||
player.add(new Position(x, y, 10));
|
||||
player.add(new Sprite(Resources.add('player', new TextRegion(PLAYER, Color.YELLOW))));
|
||||
;
|
||||
return player;
|
||||
}
|
||||
|
||||
function createFPS(world: World, x = 0, y = 0) {
|
||||
const fps = world.createEntity('fps');
|
||||
fps.add(new Position(x, y, 100, true));
|
||||
fps.add(new Sprite(Resources.add('fps', '60')));
|
||||
return fps;
|
||||
return map;
|
||||
}
|
||||
|
||||
export default gameLoop(() => {
|
||||
const world = new World();
|
||||
const display = world.addSystem(new TextDisplaySystem(100, 25)).display;
|
||||
const display = world.addSystem(new TextDisplaySystem()).display;
|
||||
|
||||
const random = new SeededRandom();
|
||||
const { map, mask } = createMap(world, random);
|
||||
|
||||
const mapData = Resources.get(TextRegion, map.get(Sprite)!.image)!;
|
||||
const emptyCells: Point[] = [];
|
||||
for (let x = 0; x < mapData.width; x++) {
|
||||
for (let y = 0; y < mapData.height; y++) {
|
||||
if (mapData.get(x, y)[0] === '.') {
|
||||
emptyCells.push({ x, y });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const maskData = Resources.get(TextRegion, mask.get(Sprite)!.image)!;
|
||||
const startCell = random.choice(emptyCells);
|
||||
|
||||
const viewport = createViewport(world, {
|
||||
width: display.width,
|
||||
const random = new SeededRandom('awoorwa');
|
||||
const map = createMap(world, random);
|
||||
const viewportEntity = createViewport(world, {
|
||||
width: display.width >> 1,
|
||||
height: display.height,
|
||||
worldX: startCell.x - (display.width >> 1),
|
||||
worldY: startCell.y - (display.height >> 1),
|
||||
screenX: 0,
|
||||
worldX: 0,
|
||||
worldY: 0,
|
||||
screenX: display.width >> 1,
|
||||
screenY: 0,
|
||||
});
|
||||
const player = createPlayer(world, startCell.x, startCell.y);
|
||||
createFPS(world, 0, 0);
|
||||
const viewport = viewportEntity.get(Viewport)!;
|
||||
return { world, map, random, viewport };
|
||||
}, (dt, { world, viewport }) => {
|
||||
const dx = Input.getHorizontal();
|
||||
const dy = Input.getVertical();
|
||||
|
||||
return {
|
||||
world,
|
||||
map, mapData,
|
||||
mask, maskData, maskDirty: true,
|
||||
fps: 0,
|
||||
fpsTimer: 0,
|
||||
random,
|
||||
viewport,
|
||||
player,
|
||||
lastMove: 0, now: 0,
|
||||
isWall: (x: number, y: number): boolean => {
|
||||
const [ch] = mapData.get(x, y);
|
||||
return ch === WALL;
|
||||
},
|
||||
};
|
||||
}, (dt, state) => {
|
||||
const { world, viewport, player, lastMove, now, mapData, maskData, isWall } = state;
|
||||
|
||||
let dx = -Math.sign(Input.getHorizontal());
|
||||
let dy = -Math.sign(Input.getVertical());
|
||||
const playerPos = getPosition(player)!;
|
||||
|
||||
if (now - lastMove > 0.03) {
|
||||
if (isWall(playerPos.x + dx, playerPos.y)) {
|
||||
dx = 0;
|
||||
}
|
||||
if (isWall(playerPos.x, playerPos.y + dy)) {
|
||||
dy = 0;
|
||||
}
|
||||
if (isWall(playerPos.x + dx, playerPos.y + dy)) {
|
||||
dx = 0
|
||||
dy = 0;
|
||||
}
|
||||
} else {
|
||||
dx = 0;
|
||||
dy = 0;
|
||||
}
|
||||
|
||||
if (dx || dy) {
|
||||
move(player, dx, dy);
|
||||
move(viewport, dx, dy);
|
||||
|
||||
playerPos.x + dx;
|
||||
playerPos.y + dy;
|
||||
|
||||
state.maskDirty = true;
|
||||
state.lastMove = now;
|
||||
}
|
||||
|
||||
if (state.maskDirty) {
|
||||
for (let x = 0; x < mapData.width; x++) {
|
||||
for (let y = 0; y < mapData.height; y++) {
|
||||
const [ch, fg] = maskData.get(x + mapData.width, y + mapData.height);
|
||||
if (ch !== ' ' && fg !== Color.GRAY) {
|
||||
maskData.set(x + mapData.width, y + mapData.height, [mapData.get(x, y)[0], Color.GRAY]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const { x, y } of bresenhamCircleGen(playerPos.x, playerPos.y, 10, { fill: 'shadow', breaker: isWall })) {
|
||||
maskData.set(x + mapData.width, y + mapData.height, '\0');
|
||||
}
|
||||
state.maskDirty = false;
|
||||
}
|
||||
viewport.move(dx * dt * 32, dy * dt * 16);
|
||||
|
||||
world.update(dt);
|
||||
|
||||
state.fpsTimer += dt;
|
||||
if (state.fpsTimer >= 0.5) {
|
||||
Resources.update('fps', (state.fps * 2).toString());
|
||||
state.fps = 0;
|
||||
state.fpsTimer = 0;
|
||||
} else {
|
||||
state.fps++;
|
||||
};
|
||||
|
||||
state.now += dt;
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue