master
Denis Ranneft 1 month ago
parent 1523b38c9f
commit 097a417cc2

@ -56,6 +56,13 @@ const MAX_STATIC_GRAPHICS_CACHE_AREA = 2_500_000;
const TERRAIN_SEAM_BLEED_SCALE = 1.002; const TERRAIN_SEAM_BLEED_SCALE = 1.002;
/**
* Snap camera center (px, same space as worldToScreen) when computing visible tile indices.
* Without this, sub-pixel camera follow changes floor(minWX)/ceil(maxWX) often and forces a full
* tile/object pass + pool scans every frame while walking.
*/
const GROUND_CAMERA_BOUNDS_QUANTIZE_PX = TILE_HEIGHT;
/** Convert world (tile) coordinates to screen (pixel) coordinates */ /** Convert world (tile) coordinates to screen (pixel) coordinates */
export function worldToScreen(wx: number, wy: number): ScreenPoint { export function worldToScreen(wx: number, wy: number): ScreenPoint {
return { return {
@ -136,6 +143,8 @@ export class GameRenderer {
endY: number; endY: number;
} }
| null = null; | null = null;
/** Incremented on each full ground rebuild; used to throttle eviction passes. */
private _groundRebuildCounter = 0;
// Reusable Graphics (avoid GC). UI/combat overlays; world tiles are sprite-only. // Reusable Graphics (avoid GC). UI/combat overlays; world tiles are sprite-only.
private _heroGfx: Graphics | null = null; private _heroGfx: Graphics | null = null;
@ -173,7 +182,8 @@ export class GameRenderer {
private _usedNearbyHeroSprites = new Set<string>(); private _usedNearbyHeroSprites = new Set<string>();
private _lastEntitySortMs = 0; private _lastEntitySortMs = 0;
private _entitySortIntervalMs = 120; /** Depth sort is O(n log n) on entityLayer; ~8 Hz is enough for walking fights. */
private _entitySortIntervalMs = 200;
private _townDrawDirty = true; private _townDrawDirty = true;
private _lastTownDrawMs = 0; private _lastTownDrawMs = 0;
private _townDrawIntervalMs = 120; private _townDrawIntervalMs = 120;
@ -502,17 +512,20 @@ export class GameRenderer {
drawGround(camera: Camera, screenWidth: number, screenHeight: number): void { drawGround(camera: Camera, screenWidth: number, screenHeight: number): void {
const cx = camera.finalX; const cx = camera.finalX;
const cy = camera.finalY; const cy = camera.finalY;
const q = GROUND_CAMERA_BOUNDS_QUANTIZE_PX;
const qcx = Math.round(cx / q) * q;
const qcy = Math.round(cy / q) * q;
const halfW = screenWidth / (2 * MAP_ZOOM) + TILE_WIDTH * 2; const halfW = screenWidth / (2 * MAP_ZOOM) + TILE_WIDTH * 2;
const halfH = screenHeight / (2 * MAP_ZOOM) + TILE_HEIGHT * 2; const halfH = screenHeight / (2 * MAP_ZOOM) + TILE_HEIGHT * 2;
const worldCorners = [ const worldCorners = [
screenToWorld(cx - halfW, cy - halfH), screenToWorld(qcx - halfW, qcy - halfH),
screenToWorld(cx + halfW, cy - halfH), screenToWorld(qcx + halfW, qcy - halfH),
screenToWorld(cx - halfW, cy + halfH), screenToWorld(qcx - halfW, qcy + halfH),
screenToWorld(cx + halfW, cy + halfH), screenToWorld(qcx + halfW, qcy + halfH),
]; ];
const renderPaddingTiles = 4; const renderPaddingTiles = 5;
let minWX = Number.POSITIVE_INFINITY; let minWX = Number.POSITIVE_INFINITY;
let maxWX = Number.NEGATIVE_INFINITY; let maxWX = Number.NEGATIVE_INFINITY;
let minWY = Number.POSITIVE_INFINITY; let minWY = Number.POSITIVE_INFINITY;
@ -548,6 +561,8 @@ export class GameRenderer {
this._groundDirty = false; this._groundDirty = false;
this._lastGroundBounds = { startX, endX, startY, endY }; this._lastGroundBounds = { startX, endX, startY, endY };
this._groundRebuildCounter += 1;
const runTileEviction = (this._groundRebuildCounter & 1) === 0;
const hw = TILE_WIDTH / 2; const hw = TILE_WIDTH / 2;
const hh = TILE_HEIGHT / 2; const hh = TILE_HEIGHT / 2;
@ -556,10 +571,10 @@ export class GameRenderer {
const usedObjectSprites = this._usedObjectSprites; const usedObjectSprites = this._usedObjectSprites;
usedTileSprites.clear(); usedTileSprites.clear();
usedObjectSprites.clear(); usedObjectSprites.clear();
const tileMinX = cx - (halfW + hw); const tileMinX = qcx - (halfW + hw);
const tileMaxX = cx + (halfW + hw); const tileMaxX = qcx + (halfW + hw);
const tileMinY = cy - (halfH + hh); const tileMinY = qcy - (halfH + hh);
const tileMaxY = cy + (halfH + hh); const tileMaxY = qcy + (halfH + hh);
// Pass 2 needs slightly expanded bounds for oversized props. // Pass 2 needs slightly expanded bounds for oversized props.
const objectPaddingTiles = 4; const objectPaddingTiles = 4;
@ -623,10 +638,10 @@ export class GameRenderer {
// Pass 2: objects (drawn after tiles so they layer on top) // Pass 2: objects (drawn after tiles so they layer on top)
// Slightly expanded object bounds prevent enlarged props from edge clipping. // Slightly expanded object bounds prevent enlarged props from edge clipping.
const objectMinX = cx - (halfW + TILE_WIDTH * 1.5); const objectMinX = qcx - (halfW + TILE_WIDTH * 1.5);
const objectMaxX = cx + (halfW + TILE_WIDTH * 1.5); const objectMaxX = qcx + (halfW + TILE_WIDTH * 1.5);
const objectMinY = cy - (halfH + TILE_HEIGHT * 2); const objectMinY = qcy - (halfH + TILE_HEIGHT * 2);
const objectMaxY = cy + (halfH + TILE_HEIGHT * 2); const objectMaxY = qcy + (halfH + TILE_HEIGHT * 2);
for (let wx = objectStartX; wx <= objectEndX; wx++) { for (let wx = objectStartX; wx <= objectEndX; wx++) {
let isoX = (wx - objectStartY) * hw; let isoX = (wx - objectStartY) * hw;
let isoY = (wx + objectStartY) * hh; let isoY = (wx + objectStartY) * hh;
@ -684,25 +699,27 @@ export class GameRenderer {
this._objectSpriteFreeList, this._objectSpriteFreeList,
1800, 1800,
); );
const evictPaddingTiles = 8; if (runTileEviction) {
this._evictSpritesOutsideTileBounds( const evictPaddingTiles = 8;
this._tileSpritePool, this._evictSpritesOutsideTileBounds(
startX - evictPaddingTiles, this._tileSpritePool,
endX + evictPaddingTiles, startX - evictPaddingTiles,
startY - evictPaddingTiles, endX + evictPaddingTiles,
endY + evictPaddingTiles, startY - evictPaddingTiles,
this._tileSpriteFreeList, endY + evictPaddingTiles,
2800, this._tileSpriteFreeList,
); 2800,
this._evictSpritesOutsideTileBounds( );
this._objectSpritePool, this._evictSpritesOutsideTileBounds(
objectStartX - evictPaddingTiles, this._objectSpritePool,
objectEndX + evictPaddingTiles, objectStartX - evictPaddingTiles,
objectStartY - evictPaddingTiles, objectEndX + evictPaddingTiles,
objectEndY + evictPaddingTiles, objectStartY - evictPaddingTiles,
this._objectSpriteFreeList, objectEndY + evictPaddingTiles,
1800, this._objectSpriteFreeList,
); 1800,
);
}
} }
/** /**
@ -717,7 +734,6 @@ export class GameRenderer {
let cy = iso.y; let cy = iso.y;
if (texture) { if (texture) {
gfx.clear();
const entry = this._ensureSprite( const entry = this._ensureSprite(
this._characterSpritePool, this._characterSpritePool,
'hero', 'hero',
@ -766,7 +782,6 @@ export class GameRenderer {
return; return;
} }
gfx.clear();
const entry = this._ensureSprite( const entry = this._ensureSprite(
this._characterSpritePool, this._characterSpritePool,
'meet_partner', 'meet_partner',
@ -824,7 +839,6 @@ export class GameRenderer {
return; return;
} }
gfx.clear();
const place = ( const place = (
poolKey: string, poolKey: string,
textureKey: string, textureKey: string,

Loading…
Cancel
Save