2 years ago
#22786
afterburn
How do I repeatedly interpolate linearly between two values within a requestAnimationFrame loop
I'm trying to implement multiplayer position interpolation for my canvas game but I'm having trouble using my linear interpolation (lerp) function. I experience slight jitter when using t = 0.1 so I'm looking for some alternative way to calculate t such that movement will appear smooth to the user - or a different solution altogether.
I render a canvas as follows (simplified):
function lerp (a, b, t) {
return a + (b - a) * t;
}
function tick() {
// Process position update(s).
const t = 0.1;
position.x = lerp(position.x, target_position.x, t);
position.y = lerp(position.y, target_position.y, t);
// Camera follow by offsetting canvas transform.
update_camera();
window.requestAnimationFrame(tick);
}
function update_camera() {
const position_delta_x = position.x - initial_position.x;
const position_delta_y = position.y - initial_position.y;
const offset_x = Math.round(position_delta_x);
const offset_y = Math.round(position_delta_y);
ctx.setTransform(1, 0, 0, 1, -offset_x, -offset_y);
}
tick();
I receive about 10 updates every second via a websocket that contains new position data:
function handle_position_update(new_position) {
target_position.x = new_position.x;
target_position.y = new_position.y;
}
I've noticed the jitter is coming from my camera follow logic but I'm sure as to why this is happening.
// Shared code.
const INITIAL_POSITION = {
x: 300,
y: 80
};
// Server-side code.
const server_state = {
x: INITIAL_POSITION.x,
y: INITIAL_POSITION.y
};
const UPDATE_TICK_RATE = 10;
const PHYSICS_TICK_RATE = 60;
setInterval(() => {
// Simulate server physics update.
const w = input[87] ? 1 : 0;
const a = input[65] ? 1 : 0;
const s = input[83] ? 1 : 0;
const d = input[68] ? 1 : 0;
const vertical = w ? 1 : s ? -1 : 0;
const horizontal = d ? 1 : a ? -1 : 0;
server_state.x += horizontal * 5;
server_state.y -= vertical * 5;
}, 1000 / PHYSICS_TICK_RATE)
setInterval(() => {
// Simulate server sending updates.
target_pos_x = server_state.x;
target_pos_y = server_state.y;
}, 1000 / UPDATE_TICK_RATE);
// Client-side code.
let target_pos_x = INITIAL_POSITION.x,
target_pos_y = INITIAL_POSITION.y;
let input = [];
window.addEventListener('keydown', e => input[e.keyCode] = true);
window.addEventListener('keyup', e => input[e.keyCode] = false);
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const W = canvas.width = 600;
const H = canvas.height = 160;
const circle = {
position: {
x: INITIAL_POSITION.x,
y: INITIAL_POSITION.y
},
tick: function() {
let t = 0.1;
this.position.x = lerp(this.position.x, target_pos_x, t);
this.position.y = lerp(this.position.y, target_pos_y, t);
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, 3, 0, 2 * Math.PI);
ctx.fillStyle = 'white'
ctx.fill();
}
}
const reference_point = {
position: {
x: 240,
y: 60
},
tick: function() {
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, 3, 0, 2 * Math.PI);
ctx.fillStyle = 'red'
ctx.fill()
}
}
function tick(now) {
clear();
circle.tick();
reference_point.tick();
camera_follow();
window.requestAnimationFrame(tick);
}
tick();
function clear() {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, W, H);
ctx.restore();
}
function camera_follow() {
const position_delta_x = circle.position.x - INITIAL_POSITION.x;
const position_delta_y = circle.position.y - INITIAL_POSITION.y;
const offset_x = Math.round(position_delta_x);
const offset_y = Math.round(position_delta_y);
ctx.setTransform(1, 0, 0, 1, -offset_x, -offset_y);
}
function lerp(a, b, t) {
return a + (b - a) * t;
}
<canvas></canvas>
<div>WASD to move</div>
javascript
canvas
multiplayer
requestanimationframe
linear-interpolation
javascript
canvas
multiplayer
requestanimationframe
linear-interpolation
0 Answers
Your Answer