Hi all,
I want to put together a little snake game to teach myself PixiJs. What I'm trying to achieve is a smooth gliding effect when moving this block around. (press left/right to start moving).
Unfortunately, I simply cannot get rid of the occasional 'stuttering' when rendering - so I'm starting to think I might be going about it the wrong way. Could somebody tell me what I'm doing wrong here?
see: fiddle
const graphics = new PIXI.Graphics();
const app = new PIXI.Application();
const movementSpeed = 150;
const stage = app.stage;
const blocks = [];
stage.addChild(graphics);
class Block {
constructor(position=[0, 0], direction=[0, 0]) {
this.width = 20;
this.position = position;
this.prevPosition = position;
this.direction = direction;
}
render (alpha = 0) {
let offsetX = (this.position[0] * alpha) + (this.prevPosition[0] * (1 - alpha));
let offsetY = (this.position[1] * alpha) + (this.prevPosition[1] * (1 - alpha));
graphics.drawRect(offsetX, offsetY, this.width, this.width);
}
update (delta = 0) {
this.prevPosition = this.position;
this.position = this.calculateNewPosition(delta);
}
calculateNewPosition (delta) {
let movementOffset = movementSpeed * (delta / 1000);
return [
this.position[0] + (movementOffset * this.direction[0]),
this.position[1] + (movementOffset * this.direction[1])
];
}
};
function render (delta) {
graphics.clear();
graphics.beginFill(0xFF0000);
blocks.forEach((block) => block.render(delta));
graphics.endFill();
};
function update (delta) {
blocks.forEach((block) => block.update(delta));
};
function changeDirection (direction) {
blocks.forEach((block) => { block.direction = direction });
}
const fps = 30; // Frames per second
const frameDuration = 1000 / fps; // Maximum time per frame.
const maxFrameDelta = 1000; // Upper limit on how long the frame takes to render.
let previousTimestamp = 0; // Last timestamp.
let accumulator = 0; // Remainder of time that we could not simulate in our fixed-step physics. (used to smooth out temporal aliasing)
function gameLoop(timestamp = 0) {
requestAnimationFrame(gameLoop);
//Calculate the time that has elapsed since the last frame
let frameDelta = timestamp - previousTimestamp;
//Optionally correct any unexpected huge gaps in the elapsed time
if (frameDelta > maxFrameDelta) elapsed = maxFrameDelta;
//Add the elapsed time to the accumulator
accumulator += frameDelta;
//Update the physics simulation in discrete chunks, and keep whatever remainder is left in the accumulator.
while (accumulator >= frameDuration) {
//Update the physics simulation
update(frameDuration);
accumulator -= frameDuration;
}
// After performing the physics steps, we might have some remaining time in the accumulator.
// This causes 'stuttering' (temporal aliasing), so we interpolate where the block should be rendered.
// That is: we don't draw the block exactly where the physics simulation has placed it, but where it should be to get the smoothest animation.
// (Thank you Glenn Fiedler!)
let renderOffset = accumulator / frameDuration;
render(renderOffset);
previousTimestamp = timestamp;
}
function initStage() {
document.body.appendChild(app.view);
blocks.push(new Block());
// Keyboard input.
document.addEventListener('keydown', function(event) {
if(event.keyCode == 37) {
changeDirection([-1, 0]);
}
else if(event.keyCode == 39) {
changeDirection([1, 0]);
}
});
gameLoop();
};
document.addEventListener("DOMContentLoaded", initStage);