|
|
|
@ -37,6 +37,8 @@ export interface WorldPoint {
|
|
|
|
type SpritePoolEntry = {
|
|
|
|
type SpritePoolEntry = {
|
|
|
|
sprite: Sprite;
|
|
|
|
sprite: Sprite;
|
|
|
|
textureKey: string;
|
|
|
|
textureKey: string;
|
|
|
|
|
|
|
|
worldX?: number;
|
|
|
|
|
|
|
|
worldY?: number;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const TERRAIN_SEAM_BLEED_SCALE = 1.002;
|
|
|
|
const TERRAIN_SEAM_BLEED_SCALE = 1.002;
|
|
|
|
@ -101,6 +103,10 @@ export class GameRenderer {
|
|
|
|
private _objectSpritePool = new Map<string, SpritePoolEntry>();
|
|
|
|
private _objectSpritePool = new Map<string, SpritePoolEntry>();
|
|
|
|
private _tileSpriteFreeList: Sprite[] = [];
|
|
|
|
private _tileSpriteFreeList: Sprite[] = [];
|
|
|
|
private _objectSpriteFreeList: Sprite[] = [];
|
|
|
|
private _objectSpriteFreeList: Sprite[] = [];
|
|
|
|
|
|
|
|
private _usedTileSprites = new Set<string>();
|
|
|
|
|
|
|
|
private _usedObjectSprites = new Set<string>();
|
|
|
|
|
|
|
|
private _emptySpriteSet = new Set<string>();
|
|
|
|
|
|
|
|
private _groundTerrainCache = new Map<string, string>();
|
|
|
|
private _buildingSpritePool = new Map<string, SpritePoolEntry>();
|
|
|
|
private _buildingSpritePool = new Map<string, SpritePoolEntry>();
|
|
|
|
private _characterSpritePool = new Map<string, SpritePoolEntry>();
|
|
|
|
private _characterSpritePool = new Map<string, SpritePoolEntry>();
|
|
|
|
private _npcSpritePool = new Map<string, SpritePoolEntry>();
|
|
|
|
private _npcSpritePool = new Map<string, SpritePoolEntry>();
|
|
|
|
@ -321,6 +327,8 @@ export class GameRenderer {
|
|
|
|
textureKey: string,
|
|
|
|
textureKey: string,
|
|
|
|
texture: SpritePoolEntry['sprite']['texture'],
|
|
|
|
texture: SpritePoolEntry['sprite']['texture'],
|
|
|
|
layer: Container,
|
|
|
|
layer: Container,
|
|
|
|
|
|
|
|
worldX?: number,
|
|
|
|
|
|
|
|
worldY?: number,
|
|
|
|
freeList?: Sprite[],
|
|
|
|
freeList?: Sprite[],
|
|
|
|
): SpritePoolEntry {
|
|
|
|
): SpritePoolEntry {
|
|
|
|
let entry = pool.get(poolKey);
|
|
|
|
let entry = pool.get(poolKey);
|
|
|
|
@ -332,7 +340,7 @@ export class GameRenderer {
|
|
|
|
sprite.anchor.set(0.5, 1);
|
|
|
|
sprite.anchor.set(0.5, 1);
|
|
|
|
sprite.roundPixels = true;
|
|
|
|
sprite.roundPixels = true;
|
|
|
|
if (!sprite.parent) layer.addChild(sprite);
|
|
|
|
if (!sprite.parent) layer.addChild(sprite);
|
|
|
|
entry = { sprite, textureKey };
|
|
|
|
entry = { sprite, textureKey, worldX, worldY };
|
|
|
|
pool.set(poolKey, entry);
|
|
|
|
pool.set(poolKey, entry);
|
|
|
|
return entry;
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -340,6 +348,10 @@ export class GameRenderer {
|
|
|
|
entry.sprite.texture = texture;
|
|
|
|
entry.sprite.texture = texture;
|
|
|
|
entry.textureKey = textureKey;
|
|
|
|
entry.textureKey = textureKey;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof worldX === 'number' && typeof worldY === 'number') {
|
|
|
|
|
|
|
|
entry.worldX = worldX;
|
|
|
|
|
|
|
|
entry.worldY = worldY;
|
|
|
|
|
|
|
|
}
|
|
|
|
return entry;
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -359,10 +371,8 @@ export class GameRenderer {
|
|
|
|
maxFreeListSize: number,
|
|
|
|
maxFreeListSize: number,
|
|
|
|
): void {
|
|
|
|
): void {
|
|
|
|
for (const [key, entry] of pool) {
|
|
|
|
for (const [key, entry] of pool) {
|
|
|
|
const sep = key.indexOf(',');
|
|
|
|
const wx = entry.worldX ?? Number.NaN;
|
|
|
|
if (sep < 0) continue;
|
|
|
|
const wy = entry.worldY ?? Number.NaN;
|
|
|
|
const wx = Number(key.slice(0, sep));
|
|
|
|
|
|
|
|
const wy = Number(key.slice(sep + 1));
|
|
|
|
|
|
|
|
if (!Number.isFinite(wx) || !Number.isFinite(wy)) continue;
|
|
|
|
if (!Number.isFinite(wx) || !Number.isFinite(wy)) continue;
|
|
|
|
if (wx >= minX && wx <= maxX && wy >= minY && wy <= maxY) continue;
|
|
|
|
if (wx >= minX && wx <= maxX && wy >= minY && wy <= maxY) continue;
|
|
|
|
pool.delete(key);
|
|
|
|
pool.delete(key);
|
|
|
|
@ -627,8 +637,20 @@ export class GameRenderer {
|
|
|
|
const hw = TILE_WIDTH / 2;
|
|
|
|
const hw = TILE_WIDTH / 2;
|
|
|
|
const hh = TILE_HEIGHT / 2;
|
|
|
|
const hh = TILE_HEIGHT / 2;
|
|
|
|
const terrainCtx = this._worldTerrainContext;
|
|
|
|
const terrainCtx = this._worldTerrainContext;
|
|
|
|
const usedTileSprites = new Set<string>();
|
|
|
|
const usedTileSprites = this._usedTileSprites;
|
|
|
|
const usedObjectSprites = new Set<string>();
|
|
|
|
const usedObjectSprites = this._usedObjectSprites;
|
|
|
|
|
|
|
|
usedTileSprites.clear();
|
|
|
|
|
|
|
|
usedObjectSprites.clear();
|
|
|
|
|
|
|
|
const terrainCache = this._groundTerrainCache;
|
|
|
|
|
|
|
|
terrainCache.clear();
|
|
|
|
|
|
|
|
const terrainAt = (wx: number, wy: number): string => {
|
|
|
|
|
|
|
|
const key = `${wx},${wy}`;
|
|
|
|
|
|
|
|
const cached = terrainCache.get(key);
|
|
|
|
|
|
|
|
if (cached) return cached;
|
|
|
|
|
|
|
|
const terrain = proceduralTerrain(wx, wy, terrainCtx);
|
|
|
|
|
|
|
|
terrainCache.set(key, terrain);
|
|
|
|
|
|
|
|
return terrain;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Pass 1: tiles
|
|
|
|
// Pass 1: tiles
|
|
|
|
for (let wx = startX; wx <= endX; wx++) {
|
|
|
|
for (let wx = startX; wx <= endX; wx++) {
|
|
|
|
@ -640,7 +662,7 @@ export class GameRenderer {
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const terrain = proceduralTerrain(wx, wy, terrainCtx);
|
|
|
|
const terrain = terrainAt(wx, wy);
|
|
|
|
const dark = (wx + wy) % 2 === 0;
|
|
|
|
const dark = (wx + wy) % 2 === 0;
|
|
|
|
const textureKey = spritesReady ? terrainToTextureKey(terrain) : null;
|
|
|
|
const textureKey = spritesReady ? terrainToTextureKey(terrain) : null;
|
|
|
|
const texture = textureKey ? this._spriteRegistry.getTexture(textureKey) : null;
|
|
|
|
const texture = textureKey ? this._spriteRegistry.getTexture(textureKey) : null;
|
|
|
|
@ -654,6 +676,8 @@ export class GameRenderer {
|
|
|
|
textureKey,
|
|
|
|
textureKey,
|
|
|
|
texture,
|
|
|
|
texture,
|
|
|
|
this._groundSpriteLayer,
|
|
|
|
this._groundSpriteLayer,
|
|
|
|
|
|
|
|
wx,
|
|
|
|
|
|
|
|
wy,
|
|
|
|
this._tileSpriteFreeList,
|
|
|
|
this._tileSpriteFreeList,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
entry.sprite.x = iso.x;
|
|
|
|
entry.sprite.x = iso.x;
|
|
|
|
@ -705,7 +729,7 @@ export class GameRenderer {
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const terrainHere = proceduralTerrain(wx, wy, terrainCtx);
|
|
|
|
const terrainHere = terrainAt(wx, wy);
|
|
|
|
const obj = proceduralObject(wx, wy, terrainHere, terrainCtx);
|
|
|
|
const obj = proceduralObject(wx, wy, terrainHere, terrainCtx);
|
|
|
|
if (!obj) continue;
|
|
|
|
if (!obj) continue;
|
|
|
|
const variant = tileHash(wx, wy, 999);
|
|
|
|
const variant = tileHash(wx, wy, 999);
|
|
|
|
@ -720,6 +744,8 @@ export class GameRenderer {
|
|
|
|
objTextureKey,
|
|
|
|
objTextureKey,
|
|
|
|
objTexture,
|
|
|
|
objTexture,
|
|
|
|
this._objectSpriteLayer,
|
|
|
|
this._objectSpriteLayer,
|
|
|
|
|
|
|
|
wx,
|
|
|
|
|
|
|
|
wy,
|
|
|
|
this._objectSpriteFreeList,
|
|
|
|
this._objectSpriteFreeList,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
entry.sprite.x = iso.x;
|
|
|
|
entry.sprite.x = iso.x;
|
|
|
|
@ -767,8 +793,8 @@ export class GameRenderer {
|
|
|
|
1800,
|
|
|
|
1800,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
this._hideUnusedSprites(this._tileSpritePool, new Set());
|
|
|
|
this._hideUnusedSprites(this._tileSpritePool, this._emptySpriteSet);
|
|
|
|
this._hideUnusedSprites(this._objectSpritePool, new Set());
|
|
|
|
this._hideUnusedSprites(this._objectSpritePool, this._emptySpriteSet);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -1926,7 +1952,7 @@ export class GameRenderer {
|
|
|
|
for (const lbl of this._npcLabels) {
|
|
|
|
for (const lbl of this._npcLabels) {
|
|
|
|
lbl.visible = false;
|
|
|
|
lbl.visible = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._hideUnusedSprites(this._npcSpritePool, new Set());
|
|
|
|
this._hideUnusedSprites(this._npcSpritePool, this._emptySpriteSet);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
|