mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:10:42 +00:00
Smooth build-ghost range circle and nuke trajectory to cursor
The build-ghost icon already tracked the cursor at sub-tile precision, but the range circle (defense post / SAM / nuke radius) and the nuke trajectory arc still snapped to the hover tile, making them look jagged as the cursor moved. Range circle: cursorLoop now smooths radiusTileX/Y the same way as the icon, except when upgrading an existing structure (the circle stays anchored to that structure's real tile). Nuke trajectory: split the work by cadence. The throttled renderGhost path caches the static inputs (nearest silo + threatening SAMs) in nukeTrajectoryStatic; cursorLoop rebuilds the Bezier each frame with the live cursor as the destination. Source stays on the silo's tile; only the endpoint follows the cursor. All three previews now use the same tile-center (+0.5) convention.
This commit is contained in:
@@ -52,6 +52,16 @@ export class BuildPreviewController implements Controller {
|
||||
// frame with the current cursor world position.
|
||||
private lastGhostData: GhostPreviewData | null = null;
|
||||
|
||||
// Static inputs for the nuke trajectory preview (source silo + threatening
|
||||
// SAMs). Recomputed in the throttled renderGhost path; cursorLoop rebuilds
|
||||
// the Bezier each frame with the live cursor position as the destination so
|
||||
// the arc tracks the cursor smoothly instead of snapping tile-to-tile.
|
||||
private nukeTrajectoryStatic: {
|
||||
srcX: number;
|
||||
srcY: number;
|
||||
sams: SAMInfo[];
|
||||
} | null = null;
|
||||
|
||||
constructor(
|
||||
private game: GameView,
|
||||
private eventBus: EventBus,
|
||||
@@ -78,16 +88,45 @@ export class BuildPreviewController implements Controller {
|
||||
// integer tile coord centers on that tile), so we subtract 0.5 here to
|
||||
// place the icon exactly under the cursor.
|
||||
const cursorLoop = () => {
|
||||
if (this.lastGhostData !== null) {
|
||||
const ghost = this.lastGhostData;
|
||||
const traj = this.nukeTrajectoryStatic;
|
||||
if (ghost !== null || traj !== null) {
|
||||
const w = this.transformHandler.screenToWorldCoordinatesFloat(
|
||||
this.mousePos.x,
|
||||
this.mousePos.y,
|
||||
);
|
||||
this.view.updateGhostPreview({
|
||||
...this.lastGhostData,
|
||||
tileX: w.x - 0.5,
|
||||
tileY: w.y - 0.5,
|
||||
});
|
||||
if (ghost !== null) {
|
||||
// The range circle (defense post / SAM / nuke radius) normally
|
||||
// follows the cursor, so smooth it the same way as the icon. When
|
||||
// upgrading, the circle is anchored to the existing structure's tile
|
||||
// (stationary, correctly snapped) — leave it alone in that case.
|
||||
const radiusFollowsCursor = !(
|
||||
ghost.canUpgrade && ghost.upgradeTargetTile !== null
|
||||
);
|
||||
this.view.updateGhostPreview({
|
||||
...ghost,
|
||||
tileX: w.x - 0.5,
|
||||
tileY: w.y - 0.5,
|
||||
...(radiusFollowsCursor
|
||||
? { radiusTileX: w.x - 0.5, radiusTileY: w.y - 0.5 }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
if (traj !== null) {
|
||||
// Rebuild the arc with the live cursor as the destination (same
|
||||
// tile-center convention as the icon: shader adds +0.5).
|
||||
this.view.updateNukeTrajectory(
|
||||
buildNukeTrajectory(
|
||||
traj.srcX,
|
||||
traj.srcY,
|
||||
w.x - 0.5,
|
||||
w.y - 0.5,
|
||||
this.game.height(),
|
||||
this.uiState.rocketDirectionUp,
|
||||
traj.sams,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(cursorLoop);
|
||||
};
|
||||
@@ -228,17 +267,17 @@ export class BuildPreviewController implements Controller {
|
||||
*/
|
||||
private updateNukeTrajectoryPreview(tileRef: TileRef | undefined): void {
|
||||
if (!this.ghostUnit || tileRef === undefined) {
|
||||
this.view.updateNukeTrajectory(null);
|
||||
this.clearNukeTrajectory();
|
||||
return;
|
||||
}
|
||||
const type = this.ghostUnit.buildableUnit.type;
|
||||
if (type !== UnitType.AtomBomb && type !== UnitType.HydrogenBomb) {
|
||||
this.view.updateNukeTrajectory(null);
|
||||
this.clearNukeTrajectory();
|
||||
return;
|
||||
}
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (!myPlayer) {
|
||||
this.view.updateNukeTrajectory(null);
|
||||
this.clearNukeTrajectory();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -246,7 +285,7 @@ export class BuildPreviewController implements Controller {
|
||||
.units(UnitType.MissileSilo)
|
||||
.filter((u) => u.isActive());
|
||||
if (silos.length === 0) {
|
||||
this.view.updateNukeTrajectory(null);
|
||||
this.clearNukeTrajectory();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -285,17 +324,14 @@ export class BuildPreviewController implements Controller {
|
||||
});
|
||||
}
|
||||
|
||||
this.view.updateNukeTrajectory(
|
||||
buildNukeTrajectory(
|
||||
srcX,
|
||||
srcY,
|
||||
dstX,
|
||||
dstY,
|
||||
this.game.height(),
|
||||
this.uiState.rocketDirectionUp,
|
||||
sams,
|
||||
),
|
||||
);
|
||||
// Stash the static inputs; cursorLoop rebuilds the Bezier each frame with
|
||||
// the live cursor as the destination so the arc tracks smoothly.
|
||||
this.nukeTrajectoryStatic = { srcX, srcY, sams };
|
||||
}
|
||||
|
||||
private clearNukeTrajectory(): void {
|
||||
this.nukeTrajectoryStatic = null;
|
||||
this.view.updateNukeTrajectory(null);
|
||||
}
|
||||
|
||||
private buildGhostPreviewData(
|
||||
@@ -446,7 +482,7 @@ export class BuildPreviewController implements Controller {
|
||||
this.ghostUnit = null;
|
||||
this.lastGhostData = null;
|
||||
this.view.updateGhostPreview(null);
|
||||
this.view.updateNukeTrajectory(null);
|
||||
this.clearNukeTrajectory();
|
||||
}
|
||||
|
||||
private removeGhostStructure() {
|
||||
|
||||
Reference in New Issue
Block a user