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;
/**
* 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 */
export function worldToScreen(wx: number, wy: number): ScreenPoint {
return {
@ -136,6 +143,8 @@ export class GameRenderer {
endY: number;
}
| 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.
private _heroGfx: Graphics | null = null;
@ -173,7 +182,8 @@ export class GameRenderer {
private _usedNearbyHeroSprites = new Set<string>();
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 _lastTownDrawMs = 0;
private _townDrawIntervalMs = 120;
@ -502,17 +512,20 @@ export class GameRenderer {
drawGround(camera: Camera, screenWidth: number, screenHeight: number): void {
const cx = camera.finalX;
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 halfH = screenHeight / (2 * MAP_ZOOM) + TILE_HEIGHT * 2;
const worldCorners = [
screenToWorld(cx - halfW, cy - halfH),
screenToWorld(cx + halfW, cy - halfH),
screenToWorld(cx - halfW, cy + halfH),
screenToWorld(cx + halfW, cy + halfH),
screenToWorld(qcx - halfW, qcy - halfH),
screenToWorld(qcx + halfW, qcy - halfH),
screenToWorld(qcx - halfW, qcy + halfH),
screenToWorld(qcx + halfW, qcy + halfH),
];
const renderPaddingTiles = 4;
const renderPaddingTiles = 5;
let minWX = Number.POSITIVE_INFINITY;
let maxWX = Number.NEGATIVE_INFINITY;
let minWY = Number.POSITIVE_INFINITY;
@ -548,6 +561,8 @@ export class GameRenderer {
this._groundDirty = false;
this._lastGroundBounds = { startX, endX, startY, endY };
this._groundRebuildCounter += 1;
const runTileEviction = (this._groundRebuildCounter & 1) === 0;
const hw = TILE_WIDTH / 2;
const hh = TILE_HEIGHT / 2;
@ -556,10 +571,10 @@ export class GameRenderer {
const usedObjectSprites = this._usedObjectSprites;
usedTileSprites.clear();
usedObjectSprites.clear();
const tileMinX = cx - (halfW + hw);
const tileMaxX = cx + (halfW + hw);
const tileMinY = cy - (halfH + hh);
const tileMaxY = cy + (halfH + hh);
const tileMinX = qcx - (halfW + hw);
const tileMaxX = qcx + (halfW + hw);
const tileMinY = qcy - (halfH + hh);
const tileMaxY = qcy + (halfH + hh);
// Pass 2 needs slightly expanded bounds for oversized props.
const objectPaddingTiles = 4;
@ -623,10 +638,10 @@ export class GameRenderer {
// Pass 2: objects (drawn after tiles so they layer on top)
// Slightly expanded object bounds prevent enlarged props from edge clipping.
const objectMinX = cx - (halfW + TILE_WIDTH * 1.5);
const objectMaxX = cx + (halfW + TILE_WIDTH * 1.5);
const objectMinY = cy - (halfH + TILE_HEIGHT * 2);
const objectMaxY = cy + (halfH + TILE_HEIGHT * 2);
const objectMinX = qcx - (halfW + TILE_WIDTH * 1.5);
const objectMaxX = qcx + (halfW + TILE_WIDTH * 1.5);
const objectMinY = qcy - (halfH + TILE_HEIGHT * 2);
const objectMaxY = qcy + (halfH + TILE_HEIGHT * 2);
for (let wx = objectStartX; wx <= objectEndX; wx++) {
let isoX = (wx - objectStartY) * hw;
let isoY = (wx + objectStartY) * hh;
@ -684,6 +699,7 @@ export class GameRenderer {
this._objectSpriteFreeList,
1800,
);
if (runTileEviction) {
const evictPaddingTiles = 8;
this._evictSpritesOutsideTileBounds(
this._tileSpritePool,
@ -704,6 +720,7 @@ export class GameRenderer {
1800,
);
}
}
/**
* Draw the hero (sprite only).
@ -717,7 +734,6 @@ export class GameRenderer {
let cy = iso.y;
if (texture) {
gfx.clear();
const entry = this._ensureSprite(
this._characterSpritePool,
'hero',
@ -766,7 +782,6 @@ export class GameRenderer {
return;
}
gfx.clear();
const entry = this._ensureSprite(
this._characterSpritePool,
'meet_partner',
@ -824,7 +839,6 @@ export class GameRenderer {
return;
}
gfx.clear();
const place = (
poolKey: string,
textureKey: string,

Loading…
Cancel
Save