reworking reconcilation and improved debug
This commit is contained in:
parent
cdaa7c678d
commit
f1c9af7b6b
|
@ -12,9 +12,11 @@
|
||||||
<script type="module" src="/js/main.js"></script>
|
<script type="module" src="/js/main.js"></script>
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<label for="fakeping">Fake Latency (ms): </label>
|
<label for="fakeping">Fake Latency (ms): </label>
|
||||||
<input type="number" id="fakeping" value="0" min="0" max="1000" title="This feature is broken!" disabled>
|
<input type="number" id="fakeping" value="0" min="0" max="1000">
|
||||||
<label for="interpolation">Interpolation: </label>
|
<label for="interpolation">Interpolation: </label>
|
||||||
<input type="checkbox" id="interpolation">
|
<input type="checkbox" id="interpolation">
|
||||||
|
<label for="show-authority">Show Authority Positions: </label>
|
||||||
|
<input type="checkbox" id="show-authority">
|
||||||
</div>
|
</div>
|
||||||
<div id="chatbox"></div>
|
<div id="chatbox"></div>
|
||||||
<div id="compose">
|
<div id="compose">
|
||||||
|
|
|
@ -22,32 +22,47 @@ var players = {};
|
||||||
var props = {};
|
var props = {};
|
||||||
var client_id;
|
var client_id;
|
||||||
var delta = 0.0;
|
var delta = 0.0;
|
||||||
var last_update = 0.0;
|
var lastUpdate = 0.0;
|
||||||
var frames = 0;
|
var frames = 0;
|
||||||
var ticks = 0;
|
var ticks = 0;
|
||||||
var server_tick = 0;
|
var serverTick = 0;
|
||||||
var server_ping = 0;
|
var serverPing = 0;
|
||||||
|
var reconciliations = 0;
|
||||||
|
var lastServerState = 0;
|
||||||
var ws;
|
var ws;
|
||||||
var predictions = {};
|
|
||||||
|
|
||||||
const interpolationToggle = document.getElementById("interpolation");
|
const BUFFER_SIZE = TICK_RATE * 5; // 5 seconds of buffer
|
||||||
var enable_interpolation = new Stateful(localStorage.getItem("interpolation") || true);
|
var stateBuffer = new Array(BUFFER_SIZE);
|
||||||
interpolationToggle.checked = enable_interpolation.get();
|
var inputBuffer = new Array(BUFFER_SIZE);
|
||||||
enable_interpolation.onUpdate(val => {
|
|
||||||
localStorage.setItem("interpolation", val);
|
|
||||||
});
|
|
||||||
interpolationToggle.addEventListener("change", () => {
|
|
||||||
enable_interpolation.set(interpolationToggle.checked);
|
|
||||||
});
|
|
||||||
|
|
||||||
const fakePingInput = document.getElementById("fakeping");
|
const fakePingInput = document.getElementById("fakeping");
|
||||||
var fake_ping = new Stateful(localStorage.getItem("fakeping") || 0);
|
var fakePing = new Stateful(localStorage.getItem("fakeping") || 0);
|
||||||
fakePingInput.value = fake_ping.get();
|
fakePingInput.value = fakePing.get();
|
||||||
fake_ping.onUpdate(val => {
|
fakePing.onUpdate(val => {
|
||||||
localStorage.setItem("fakeping", val);
|
localStorage.setItem("fakeping", val);
|
||||||
});
|
});
|
||||||
fakePingInput.addEventListener("change", () => {
|
fakePingInput.addEventListener("change", () => {
|
||||||
fake_ping.set(fakePingInput.value);
|
fakePing.set(fakePingInput.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const interpolationToggle = document.getElementById("interpolation");
|
||||||
|
var interpolationEnabled = new Stateful(localStorage.getItem("interpolation") || true);
|
||||||
|
interpolationToggle.checked = interpolationEnabled.get();
|
||||||
|
interpolationEnabled.onUpdate(val => {
|
||||||
|
localStorage.setItem("interpolation", val);
|
||||||
|
});
|
||||||
|
interpolationToggle.addEventListener("change", () => {
|
||||||
|
interpolationEnabled.set(interpolationToggle.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
const authorityPositionToggle = document.getElementById("show-authority");
|
||||||
|
var showAuthorityPositions = new Stateful(localStorage.getItem("show-authority") || false);
|
||||||
|
authorityPositionToggle.checked = showAuthorityPositions.get();
|
||||||
|
showAuthorityPositions.onUpdate(val => {
|
||||||
|
localStorage.setItem("show-authority", val);
|
||||||
|
});
|
||||||
|
authorityPositionToggle.addEventListener("change", () => {
|
||||||
|
showAuthorityPositions.set(authorityPositionToggle.checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
var input = {
|
var input = {
|
||||||
|
@ -72,7 +87,6 @@ function start() {
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener("message", packet => {
|
ws.addEventListener("message", packet => {
|
||||||
setTimeout(() => {
|
|
||||||
var data = JSON.parse(packet.data);
|
var data = JSON.parse(packet.data);
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
@ -107,73 +121,9 @@ function start() {
|
||||||
chatbox.scrollTop = chatbox.scrollHeight;
|
chatbox.scrollTop = chatbox.scrollHeight;
|
||||||
players[data.id] = new Player(data.name, data.x, data.y, data.col);
|
players[data.id] = new Player(data.name, data.x, data.y, data.col);
|
||||||
break;
|
break;
|
||||||
case "update": {
|
case "update":
|
||||||
server_tick = data.tick;
|
processServerTick(data);
|
||||||
var prediction = predictions[data.tick];
|
|
||||||
if (prediction) {
|
|
||||||
Object.keys(predictions).forEach(tick => {
|
|
||||||
if (tick < data.tick) delete predictions[tick];
|
|
||||||
});
|
|
||||||
server_ping = new Date() - prediction.time;
|
|
||||||
}
|
|
||||||
Object.keys(data.players).forEach(id => {
|
|
||||||
const player = players[id];
|
|
||||||
const update = data.players[id];
|
|
||||||
if (id == client_id) {
|
|
||||||
if (!prediction) return;
|
|
||||||
const predicted = prediction.player;
|
|
||||||
// clear all predictions prior to this tick
|
|
||||||
if (predicted != update) {
|
|
||||||
var diff_x = predicted.x - update.x;
|
|
||||||
var diff_y = predicted.y - update.y;
|
|
||||||
// apply difference to all predictions
|
|
||||||
Object.values(predictions).forEach(p => {
|
|
||||||
p.x -= diff_x;
|
|
||||||
p.y -= diff_y;
|
|
||||||
});
|
|
||||||
// update client state to reflect
|
|
||||||
player.x -= diff_x;
|
|
||||||
player.y -= diff_y;
|
|
||||||
}
|
|
||||||
delete predictions[data.tick];
|
|
||||||
} else {
|
|
||||||
player.x = update.x;
|
|
||||||
player.y = update.y;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Object.keys(data.props).forEach(id => {
|
|
||||||
const prop = props[id];
|
|
||||||
const update = data.props[id];
|
|
||||||
if (!prediction) {
|
|
||||||
prop.x = update.x;
|
|
||||||
prop.y = update.y;
|
|
||||||
prop.xv = update.xv;
|
|
||||||
prop.yv = update.yv;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const predicted = prediction.props[id];
|
|
||||||
if (predicted != update) {
|
|
||||||
var diff_x = predicted.x - update.x;
|
|
||||||
var diff_y = predicted.y - update.y;
|
|
||||||
var diff_xv = predicted.xv - update.xv;
|
|
||||||
var diff_yv = predicted.yv - update.yv;
|
|
||||||
// apply difference to all predictions
|
|
||||||
Object.values(predictions).forEach(p => {
|
|
||||||
p.x -= diff_x;
|
|
||||||
p.y -= diff_y;
|
|
||||||
p.xv -= diff_xv;
|
|
||||||
p.yv -= diff_yv;
|
|
||||||
});
|
|
||||||
// update client state to reflect
|
|
||||||
prop.x -= diff_x;
|
|
||||||
prop.y -= diff_y;
|
|
||||||
prop.xv -= diff_xv;
|
|
||||||
prop.yv -= diff_yv;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case "chat": {
|
case "chat": {
|
||||||
const player = players[data.player];
|
const player = players[data.player];
|
||||||
|
|
||||||
|
@ -214,7 +164,6 @@ function start() {
|
||||||
console.warn("Unknown message received from the server.");
|
console.warn("Unknown message received from the server.");
|
||||||
console.warn(msg);
|
console.warn(msg);
|
||||||
}
|
}
|
||||||
}, fake_ping.get() / 2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener("error", error => {
|
ws.addEventListener("error", error => {
|
||||||
|
@ -242,14 +191,135 @@ function start() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processServerTick(data) {
|
||||||
|
lastServerState = data;
|
||||||
|
serverTick = data.tick;
|
||||||
|
var localState = stateBuffer[data.tick % BUFFER_SIZE]
|
||||||
|
if (localState) serverPing = new Date() - localState.time;
|
||||||
|
|
||||||
|
// update players
|
||||||
|
Object.keys(data.players).forEach(id => {
|
||||||
|
const player = players[id];
|
||||||
|
const serverPlayerState = data.players[id];
|
||||||
|
if (!localState) {
|
||||||
|
player.x = serverPlayerState.x;
|
||||||
|
player.y = serverPlayerState.y;
|
||||||
|
player.in_x = serverPlayerState.in_x;
|
||||||
|
player.in_y = serverPlayerState.in_y;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const localPlayerState = localState.players[id];
|
||||||
|
|
||||||
|
// calculate position difference from authority
|
||||||
|
var dist = Math.sqrt(
|
||||||
|
Math.pow(localPlayerState.x - serverPlayerState.x, 2) +
|
||||||
|
Math.pow(localPlayerState.y - serverPlayerState.y, 2));
|
||||||
|
|
||||||
|
if (dist > 10.0) {
|
||||||
|
return;
|
||||||
|
reconciliations++;
|
||||||
|
|
||||||
|
console.warn("Player#" + id + ": Reconciling " + dist + " units of error.");
|
||||||
|
console.warn("\tclient said (" + localPlayerState.x.toPrecision(4) + "," + localPlayerState.y.toPrecision(4) + ")");
|
||||||
|
console.warn("\tserver said (" + serverPlayerState.x.toPrecision(4) + "," + serverPlayerState.y.toPrecision(4) + ")");
|
||||||
|
|
||||||
|
// resync to authority and roll-forward
|
||||||
|
localState.players[id] = serverPlayerState;
|
||||||
|
for (var tick = serverTick + 1; tick < ticks; tick++) {
|
||||||
|
const state = stateBuffer[(tick - 1) % BUFFER_SIZE].players[id];
|
||||||
|
const input = id == client_id ? inputBuffer[tick % BUFFER_SIZE] : {
|
||||||
|
x: state.in_x,
|
||||||
|
y: state.in_y,
|
||||||
|
};
|
||||||
|
|
||||||
|
player.x = state.x;
|
||||||
|
player.y = state.y;
|
||||||
|
player.in_x = input.x;
|
||||||
|
player.in_y = input.y;
|
||||||
|
|
||||||
|
player.update(1000 / TICK_RATE / 1000);
|
||||||
|
stateBuffer[tick % BUFFER_SIZE].player = {
|
||||||
|
x: player.x,
|
||||||
|
y: player.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastState = stateBuffer[ticks % BUFFER_SIZE];
|
||||||
|
if (lastState) {
|
||||||
|
player.x = lastState.players[id].x;
|
||||||
|
player.y = lastState.players[id].y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// update props
|
||||||
|
Object.keys(data.props).forEach(id => {
|
||||||
|
const serverPropState = data.props[id];
|
||||||
|
const prop = props[id];
|
||||||
|
|
||||||
|
prop.x = serverPropState.x;
|
||||||
|
prop.y = serverPropState.y;
|
||||||
|
prop.xv = serverPropState.xv;
|
||||||
|
prop.yv = serverPropState.yv;
|
||||||
|
|
||||||
|
return; // TODO: reimplement this once player reconciliation is stable
|
||||||
|
if (!localState) {
|
||||||
|
prop.x = serverPropState.x;
|
||||||
|
prop.y = serverPropState.y;
|
||||||
|
prop.xv = serverPropState.xv;
|
||||||
|
prop.yv = serverPropState.yv;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const localPropState = localState.props[id];
|
||||||
|
|
||||||
|
// calculate position difference from authority
|
||||||
|
var dist = Math.sqrt(
|
||||||
|
Math.pow(localPropState.x - serverPropState.x, 2) +
|
||||||
|
Math.pow(localPropState.y - serverPropState.y, 2));
|
||||||
|
|
||||||
|
if (dist > 10.0) {
|
||||||
|
reconciliations++;
|
||||||
|
|
||||||
|
console.warn("Prop#" + id + ": Reconciling " + dist + " units of error.");
|
||||||
|
|
||||||
|
// resync to authority and roll-forward
|
||||||
|
localState.props[id] = serverPropState;
|
||||||
|
for (var tick = serverTick; tick < ticks; tick++) {
|
||||||
|
const state = stateBuffer[(tick - 1) % BUFFER_SIZE];
|
||||||
|
const propState = state.props[id];
|
||||||
|
prop.x = propState.x;
|
||||||
|
prop.y = propState.y;
|
||||||
|
prop.xv = propState.xv;
|
||||||
|
prop.yv = propState.yv;
|
||||||
|
prop.update(1000 / TICK_RATE / 1000, Object.values(state.players));
|
||||||
|
stateBuffer[tick % BUFFER_SIZE].props[id] = {
|
||||||
|
x: prop.x,
|
||||||
|
y: prop.y,
|
||||||
|
xv: prop.xv,
|
||||||
|
yv: prop.yv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastState = stateBuffer[ticks % BUFFER_SIZE];
|
||||||
|
if (lastState) {
|
||||||
|
prop.x = lastState.props[id].x;
|
||||||
|
prop.y = lastState.props[id].y;
|
||||||
|
prop.xv = lastState.props[id].xv;
|
||||||
|
prop.yv = lastState.props[id].yv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
canvas.addEventListener("keypress", event => {
|
canvas.addEventListener("keypress", event => {
|
||||||
switch (event.key.toLowerCase()) {
|
switch (event.key.toLowerCase()) {
|
||||||
case 'i':
|
case 'i':
|
||||||
enable_interpolation.update(val => !val);
|
interpolationEnabled.update(val => !val);
|
||||||
interpolationToggle.checked = enable_interpolation.get();
|
interpolationToggle.checked = interpolationEnabled.get();
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
console.log(predictions);
|
showAuthorityPositions.update(val => !val);
|
||||||
|
authorityPositionToggle.checked = showAuthorityPositions.get();
|
||||||
break;
|
break;
|
||||||
case 'enter':
|
case 'enter':
|
||||||
composeBox.focus();
|
composeBox.focus();
|
||||||
|
@ -322,45 +392,38 @@ function sendChat(msg) {
|
||||||
type: "chat",
|
type: "chat",
|
||||||
msg: msg,
|
msg: msg,
|
||||||
}));
|
}));
|
||||||
}, fake_ping.get() / 2);
|
}, fakePing.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
function update(delta) {
|
function update(delta) {
|
||||||
const prediction = {
|
|
||||||
time: new Date(),
|
|
||||||
player: {},
|
|
||||||
props: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const clientPlayer = players[client_id];
|
const clientPlayer = players[client_id];
|
||||||
if (!clientPlayer) return;
|
if (!clientPlayer) return;
|
||||||
|
|
||||||
clientPlayer.in_x = input.move_right - input.move_left;
|
clientPlayer.in_x = input.move_right - input.move_left;
|
||||||
clientPlayer.in_y = input.move_down - input.move_up;
|
clientPlayer.in_y = input.move_down - input.move_up;
|
||||||
|
|
||||||
clientPlayer.update(delta);
|
inputBuffer[ticks % BUFFER_SIZE] = {
|
||||||
|
x: clientPlayer.in_x,
|
||||||
// insert prediction for the next server tick
|
y: clientPlayer.in_y,
|
||||||
prediction.player = {
|
|
||||||
x: clientPlayer.x,
|
|
||||||
y: clientPlayer.y,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var t = ticks;
|
var playerState = {};
|
||||||
setTimeout(() => {
|
Object.keys(players).forEach(id => {
|
||||||
if (!ws) return;
|
const player = players[id];
|
||||||
ws.send(JSON.stringify({
|
player.update(delta);
|
||||||
type: "update",
|
playerState[id] = {
|
||||||
tick: t,
|
x: player.x,
|
||||||
x: input.move_right - input.move_left,
|
y: player.y,
|
||||||
y: input.move_down - input.move_up,
|
in_x: player.in_x,
|
||||||
}));
|
in_y: player.in_y,
|
||||||
}, fake_ping.get() / 2);
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var propState = {};
|
||||||
Object.keys(props).forEach(id => {
|
Object.keys(props).forEach(id => {
|
||||||
const prop = props[id];
|
const prop = props[id];
|
||||||
prop.update(delta, Object.values(players));
|
prop.update(delta, Object.values(players));
|
||||||
prediction.props[id] = {
|
propState[id] = {
|
||||||
x: prop.x,
|
x: prop.x,
|
||||||
y: prop.y,
|
y: prop.y,
|
||||||
xv: prop.xv,
|
xv: prop.xv,
|
||||||
|
@ -368,15 +431,30 @@ function update(delta) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
predictions[ticks] = prediction;
|
stateBuffer[ticks % BUFFER_SIZE] = {
|
||||||
|
tick: ticks,
|
||||||
|
time: new Date(),
|
||||||
|
players: playerState,
|
||||||
|
props: propState,
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(ticks => {
|
||||||
|
if (!ws) return;
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: "update",
|
||||||
|
tick: ticks,
|
||||||
|
x: input.move_right - input.move_left,
|
||||||
|
y: input.move_down - input.move_up,
|
||||||
|
}));
|
||||||
|
}, fakePing.get() / 2, ticks);
|
||||||
|
|
||||||
ticks++;
|
ticks++;
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
delta = performance.now() - last_update;
|
delta = performance.now() - lastUpdate;
|
||||||
if (performance.now() - last_update >= 1000 / TICK_RATE) {
|
if (performance.now() - lastUpdate >= 1000 / TICK_RATE) {
|
||||||
last_update = performance.now();
|
lastUpdate = performance.now();
|
||||||
update(delta / 1000);
|
update(delta / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,34 +463,56 @@ function draw() {
|
||||||
ctx.fillStyle = "#f0f0f0";
|
ctx.fillStyle = "#f0f0f0";
|
||||||
ctx.fillRect(0, 0, WORLD_SIZE, WORLD_SIZE);
|
ctx.fillRect(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||||
|
|
||||||
|
// DEBUG: show authoritative position for client players and props
|
||||||
|
var authorityDistance = 0.0;
|
||||||
|
if (lastServerState && showAuthorityPositions.get()) {
|
||||||
|
const player = players[client_id];
|
||||||
|
const lastPlayerState = lastServerState.players[client_id];
|
||||||
|
authorityDistance = Math.sqrt(
|
||||||
|
Math.pow(players[client_id].x - lastPlayerState.x, 2) +
|
||||||
|
Math.pow(players[client_id].y - lastPlayerState.y, 2));
|
||||||
|
ctx.strokeStyle = player.colour;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(
|
||||||
|
lastPlayerState.x - Player.SIZE / 2,
|
||||||
|
lastPlayerState.y - Player.SIZE / 2,
|
||||||
|
Player.SIZE, Player.SIZE);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
Object.keys(props).forEach(id => {
|
||||||
|
const prop = props[id];
|
||||||
|
const lastPropState = lastServerState.props[id];
|
||||||
|
ctx.strokeStyle = prop.colour;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(
|
||||||
|
lastPropState.x - Prop.SIZE / 2,
|
||||||
|
lastPropState.y - Prop.SIZE / 2,
|
||||||
|
Prop.SIZE, Prop.SIZE);
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
drawPlayers();
|
drawPlayers();
|
||||||
drawProps();
|
drawProps();
|
||||||
|
|
||||||
// DEBUG: draw last known authoritative state
|
var debug = {
|
||||||
if (Object.values(predictions).length > 10) {
|
"ping": serverPing + "ms",
|
||||||
const server_state = Object.values(predictions)[0];
|
"fake ping": + fakePing.get() + "ms",
|
||||||
ctx.fillStyle = "#208020";
|
"ticks behind": ticks - serverTick,
|
||||||
ctx.beginPath();
|
"reconciliations": reconciliations,
|
||||||
ctx.rect(server_state.player.x - Player.SIZE / 2,
|
}
|
||||||
server_state.player.y - Player.SIZE / 2,
|
if (showAuthorityPositions.get()) {
|
||||||
Player.SIZE, Player.SIZE);
|
debug["authority distance"] = authorityDistance;
|
||||||
ctx.stroke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var debug = "ping: " + server_ping + "ms\n" +
|
|
||||||
"fake ping: " + fake_ping.get() + "ms\n" +
|
|
||||||
"buffer length: " + Object.keys(predictions).length + "\n" +
|
|
||||||
"delta: " + delta + "\n" +
|
|
||||||
"ticks behind: " + (ticks - server_tick);
|
|
||||||
ctx.fillStyle = "#101010";
|
ctx.fillStyle = "#101010";
|
||||||
ctx.font = "16px monospace";
|
ctx.font = "16px monospace";
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
ctx.textBaseline = "bottom";
|
ctx.textBaseline = "bottom";
|
||||||
var debug_lines = debug.split('\n');
|
var debug_y = WORLD_SIZE - 8 - (Object.keys(debug).length - 1) * 16;
|
||||||
var debug_y = WORLD_SIZE - 8 - (debug_lines.length - 1) * 16;
|
Object.keys(debug).forEach((key, i) => {
|
||||||
for (var i = 0; i < debug_lines.length; i++) {
|
ctx.fillText(key + ": " + debug[key], 8, debug_y + 16 * i);
|
||||||
ctx.fillText(debug_lines[i], 8, debug_y + 16 * i);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
frames++;
|
frames++;
|
||||||
requestAnimationFrame(draw);
|
requestAnimationFrame(draw);
|
||||||
|
@ -422,7 +522,7 @@ function drawPlayers() {
|
||||||
Object.keys(players).forEach((id, index) => {
|
Object.keys(players).forEach((id, index) => {
|
||||||
const player = players[id];
|
const player = players[id];
|
||||||
|
|
||||||
if (enable_interpolation.get()) {
|
if (interpolationEnabled.get()) {
|
||||||
player.draw_x = player.draw_x + 0.1 * (player.x - player.draw_x);
|
player.draw_x = player.draw_x + 0.1 * (player.x - player.draw_x);
|
||||||
player.draw_y = player.draw_y + 0.1 * (player.y - player.draw_y);
|
player.draw_y = player.draw_y + 0.1 * (player.y - player.draw_y);
|
||||||
} else {
|
} else {
|
||||||
|
@ -459,7 +559,7 @@ function drawProps() {
|
||||||
Object.keys(props).forEach(id => {
|
Object.keys(props).forEach(id => {
|
||||||
const prop = props[id];
|
const prop = props[id];
|
||||||
|
|
||||||
if (enable_interpolation.get()) {
|
if (interpolationEnabled.get()) {
|
||||||
prop.draw_x = prop.draw_x + 0.1 * (prop.x - prop.draw_x);
|
prop.draw_x = prop.draw_x + 0.1 * (prop.x - prop.draw_x);
|
||||||
prop.draw_y = prop.draw_y + 0.1 * (prop.y - prop.draw_y);
|
prop.draw_y = prop.draw_y + 0.1 * (prop.y - prop.draw_y);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import { WORLD_SIZE } from "./world.js";
|
||||||
|
|
||||||
export default class Player {
|
export default class Player {
|
||||||
static SPEED = 400.0;
|
static SPEED = 200.0;
|
||||||
static SIZE = 50.0;
|
static SIZE = 50.0;
|
||||||
|
|
||||||
constructor(name, x, y, colour) {
|
constructor(name, x, y, colour) {
|
||||||
|
@ -16,5 +18,8 @@ export default class Player {
|
||||||
update(delta) {
|
update(delta) {
|
||||||
if (this.in_x != 0) this.x += this.in_x * Player.SPEED * delta;
|
if (this.in_x != 0) this.x += this.in_x * Player.SPEED * delta;
|
||||||
if (this.in_y != 0) this.y += this.in_y * Player.SPEED * delta;
|
if (this.in_y != 0) this.y += this.in_y * Player.SPEED * delta;
|
||||||
|
|
||||||
|
this.x = Math.min(Math.max(this.x, 0.0), WORLD_SIZE);
|
||||||
|
this.y = Math.min(Math.max(this.y, 0.0), WORLD_SIZE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,8 @@ export function init(http_server) {
|
||||||
name: client.player.name,
|
name: client.player.name,
|
||||||
x: client.player.x,
|
x: client.player.x,
|
||||||
y: client.player.y,
|
y: client.player.y,
|
||||||
|
xv: client.player.xv,
|
||||||
|
yv: client.player.yv,
|
||||||
col: client.player.colour,
|
col: client.player.colour,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -168,17 +170,18 @@ export function init(http_server) {
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
var delta = (performance.now() - last_update) / 1000;
|
var delta = (performance.now() - last_update) / 1000;
|
||||||
|
last_update = performance.now();
|
||||||
|
|
||||||
// update players
|
// update players
|
||||||
var frame_players = {};
|
var frame_players = {};
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
if (!client.player) return;
|
if (!client.player) return;
|
||||||
client.player.update(delta);
|
client.player.update(delta);
|
||||||
client.player.x = Math.max(Math.min(client.player.x, WORLD_SIZE), 0);
|
|
||||||
client.player.y = Math.max(Math.min(client.player.y, WORLD_SIZE), 0);
|
|
||||||
frame_players[client.id] = {
|
frame_players[client.id] = {
|
||||||
x: client.player.x,
|
x: client.player.x,
|
||||||
y: client.player.y,
|
y: client.player.y,
|
||||||
|
xv: client.player.xv,
|
||||||
|
yv: client.player.yv,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -211,7 +214,6 @@ function update() {
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
|
|
||||||
last_update = performance.now();
|
|
||||||
ticks++;
|
ticks++;
|
||||||
setTimeout(update, 1000 / TICK_RATE);
|
setTimeout(update, 1000 / TICK_RATE);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue