unit interpolation
This commit is contained in:
@@ -27,12 +27,12 @@ export class GameComponent implements OnInit {
|
||||
|
||||
async ngOnInit() {
|
||||
const initialState: GameState = {
|
||||
tick: 0,
|
||||
units: [{ id: 'u1', x: 0, y: 0 }],
|
||||
commandHistory: []
|
||||
};
|
||||
tick: 0,
|
||||
units: [{ id: 'u1', x: 0, y: 0 }],
|
||||
commandHistory: [],
|
||||
};
|
||||
|
||||
const json = await fetch('/assets/gameData.json').then(r => r.json());
|
||||
const json = await fetch('/assets/gameData.json').then((r) => r.json());
|
||||
this.engine = new SimulationEngine(initialState, GameRules, json);
|
||||
requestAnimationFrame(this.gameLoop.bind(this));
|
||||
}
|
||||
@@ -57,26 +57,64 @@ export class GameComponent implements OnInit {
|
||||
this.engine.fastForwardToEnd();
|
||||
}
|
||||
|
||||
async reloadGameData() {
|
||||
const json = await fetch('/assets/gameData.json').then(r => r.json());
|
||||
this.engine.reinitializeWithNewData(json);
|
||||
}
|
||||
|
||||
issueMove() {
|
||||
const cmd: Command = {
|
||||
step: this.engine.currentStep + 1,
|
||||
type: 'move',
|
||||
payload: { id: 'u1', dx: 1, dy: 0 }
|
||||
};
|
||||
this.engine.issueCommand(cmd);
|
||||
}
|
||||
async reloadGameData() {
|
||||
const json = await fetch('/assets/gameData.json').then((r) => r.json());
|
||||
this.engine.reinitializeWithNewData(json);
|
||||
}
|
||||
|
||||
issueMove() {
|
||||
const cmd: Command = {
|
||||
step: this.engine.currentStep + 1,
|
||||
type: 'move',
|
||||
payload: { id: 'u1', dx: 1, dy: 0 },
|
||||
};
|
||||
this.engine.issueCommand(cmd);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.resizeCanvas();
|
||||
requestAnimationFrame(this.render);
|
||||
window.addEventListener('resize', this.resizeCanvas.bind(this));
|
||||
}
|
||||
|
||||
render = (now: number) => {
|
||||
if (!this.engine) return;
|
||||
|
||||
const [prevState, currState] = this.engine.getRenderStates();
|
||||
|
||||
// Compute interpolation factor (0..1)
|
||||
const lastStepTime = this.engine.isRunning ? this.engine.lastStepTime : now;
|
||||
const t = this.engine.isRunning
|
||||
? Math.max(Math.min((now - lastStepTime) / this.engine.interval, 1), 0)
|
||||
: 1; // fully render the last known state
|
||||
|
||||
this.renderFrame(prevState, currState, t);
|
||||
|
||||
requestAnimationFrame(this.render);
|
||||
};
|
||||
|
||||
renderFrame(prev: GameState, curr: GameState, t: number) {
|
||||
const ctx = this.canvasRef.nativeElement.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (let i = 0; i < curr.units.length; i++) {
|
||||
const currUnit = curr.units[i];
|
||||
const prevUnit =
|
||||
prev.units.find((u) => u.id === currUnit.id) ?? currUnit;
|
||||
|
||||
const x = prevUnit.x + (currUnit.x - prevUnit.x) * t;
|
||||
const y = prevUnit.y + (currUnit.y - prevUnit.y) * t;
|
||||
|
||||
ctx.fillStyle = 'cyan';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x * 10, y * 10, 5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
resizeCanvas(): void {
|
||||
const wrapper = this.wrapperRef.nativeElement;
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
|
||||
@@ -4,23 +4,25 @@ import { GameRules } from './gameRules';
|
||||
import { GameState } from './gameState';
|
||||
|
||||
export class SimulationEngine {
|
||||
private interval = 50; // ms per step
|
||||
private lastTime = 0;
|
||||
interval: number = 50; // ms per step
|
||||
lastTime = 0;
|
||||
private frameRequestId: number | null = null;
|
||||
private data: GameData;
|
||||
private previousState: GameState | null = null;
|
||||
|
||||
currentStep = 0;
|
||||
initialState: GameState;
|
||||
state: GameState;
|
||||
isRunning = false;
|
||||
lastStepTime: number = 0;
|
||||
|
||||
constructor(
|
||||
initialState: GameState,
|
||||
private rules: typeof GameRules,
|
||||
initialData: GameData
|
||||
) {
|
||||
this.initialState = JSON.parse(JSON.stringify(initialState));
|
||||
this.state = JSON.parse(JSON.stringify(initialState));
|
||||
this.initialState = initialState;
|
||||
this.state = this.cloneState(this.initialState);
|
||||
this.data = initialData;
|
||||
}
|
||||
|
||||
@@ -40,7 +42,9 @@ export class SimulationEngine {
|
||||
}
|
||||
|
||||
fastForwardToEnd() {
|
||||
this.fastForwardTo(this.state.commandHistory[this.state.commandHistory.length - 1].step);
|
||||
this.fastForwardTo(
|
||||
this.state.commandHistory[this.state.commandHistory.length - 1].step
|
||||
);
|
||||
}
|
||||
|
||||
private fastForwardTo(target: number) {
|
||||
@@ -51,8 +55,10 @@ export class SimulationEngine {
|
||||
}
|
||||
|
||||
step() {
|
||||
this.state = this.rules.step(this.state, this.data);
|
||||
this.currentStep++;
|
||||
this.previousState = this.cloneState(this.state);
|
||||
this.rules.step(this.state, this.data);
|
||||
this.currentStep = this.state.tick;
|
||||
this.lastStepTime = performance.now();
|
||||
}
|
||||
|
||||
start() {
|
||||
@@ -72,7 +78,8 @@ export class SimulationEngine {
|
||||
|
||||
rewindToZero() {
|
||||
this.stop();
|
||||
this.state = JSON.parse(JSON.stringify(this.initialState));
|
||||
this.state = this.cloneState(this.initialState);
|
||||
this.previousState = this.cloneState(this.state);
|
||||
this.currentStep = 0;
|
||||
}
|
||||
|
||||
@@ -84,6 +91,18 @@ export class SimulationEngine {
|
||||
this.state.commandHistory.push(command);
|
||||
}
|
||||
|
||||
getRenderStates(): [GameState, GameState] {
|
||||
return [this.previousState ?? this.state, this.state];
|
||||
}
|
||||
|
||||
cloneState(state: GameState): GameState {
|
||||
return {
|
||||
tick: state.tick,
|
||||
commandHistory: [...state.commandHistory],
|
||||
units: state.units.map((u) => ({ ...u })),
|
||||
};
|
||||
}
|
||||
|
||||
private loop = () => {
|
||||
const now = performance.now();
|
||||
while (now - this.lastTime >= this.interval) {
|
||||
|
||||
@@ -437,6 +437,6 @@
|
||||
{
|
||||
"path": "manifest.json",
|
||||
"type": "other",
|
||||
"size": 7721
|
||||
"size": 7722
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user