Ничего толкового из этого не выходит, здесь я не за генерациями, иначе давно отметил бы ответом.
function getInterpolatedPosition(player, renderTime) {
const buffer = player.stateBuffer;
if (!buffer || buffer.length === 0) return null;
if (buffer.length === 1) {
return buffer[0].pos.copy();
}
for (let i = 0; i < buffer.length - 1; i++) {
const older = buffer[i];
const newer = buffer[i + 1];
if (renderTime >= older.time && renderTime <= newer.time) {
const dt = newer.time - older.time;
if (dt <= 0) return newer.pos.copy();
let alpha = (renderTime - older.time) / dt;
alpha = constrain(alpha, 0, 1);
return p5.Vector.lerp(older.pos, newer.pos, alpha);
}
}
const last = buffer[buffer.length - 1];
const MAX_EXTRAPOLATION = updateInterval * 2;
if (renderTime > last.time) {
const dt = (renderTime - last.time);
const clampedDt = Math.min(dt, MAX_EXTRAPOLATION) / 1000;
return createVector(
last.pos.x + last.vel.x * clampedDt,
last.pos.y + last.vel.y * clampedDt
);
}
return buffer[0].pos.copy();
} const runUpdate = async () => {
if(!isRunning) return;
onUpdateTick(remotePlayer);
updateTimeout = setTimeout(runUpdate, updateTickRate * 1000 + ping);
};const physicsTickRate = 1 / 60;
let physicsAccumulator = 0;
let localPlayer;
let lastInputId = 0;
let pendingInputs = [];
const inputTickRate = 1 / 60;
function getInput() {
let x = 0;
let y = 0;
// if (keyIsDown(65)) x -= 1; // A
// if (keyIsDown(68)) x += 1; // D
if (keyIsDown(87)) y -= 1; // W
if (keyIsDown(83)) y += 1; // S
return {id: lastInputId++, x, y};
}
function physicsTick(player, input) {
const playerInput = input || player.input;
if (player.pos.y <= 100) {
player.pos.x += player.speed * playerInput.x * physicsTickRate;
player.pos.y += player.speed * playerInput.y * physicsTickRate;
} else {
player.pos.y = 100;
if (player.vel.y > 0) player.vel.y = 0;
}
}
function mockServer(ping, updateTickRate, physicsTickRate, onUpdateTick) {
let isRunning = false;
let physicsTimeout;
let updateTimeout;
const speed = 700;
const createInitialRemotePlayer = () => ({
id: 1,
pos: {x: 0, y: 0},
vel: {x: 0, y: 0},
speed,
input: {id: 0, x: 0, y: 0},
});
let remotePlayer = createInitialRemotePlayer();
return {
async start() {
if(isRunning) return;
isRunning = true;
const runPhysics = async () => {
if(!isRunning) return;
physicsTick(remotePlayer);
physicsTimeout = setTimeout(runPhysics, physicsTickRate * 1000);
};
const runUpdate = async () => {
if(!isRunning) return;
onUpdateTick(remotePlayer);
updateTimeout = setTimeout(runUpdate, updateTickRate * 1000);
};
runPhysics();
runUpdate();
},
stop(){
isRunning = false;
clearTimeout(physicsTimeout);
clearTimeout(updateTimeout);
remotePlayer = createInitialRemotePlayer();
},
restart() {
this.stop();
this.start();
},
sendInput: input => remotePlayer.input = input,
};
}
function onUpdateTick(remotePlayer) {
if(!localPlayer) {
localPlayer = {
id: remotePlayer.id,
pos: createVector(remotePlayer.pos.x, remotePlayer.pos.y),
lastPos: createVector(remotePlayer.pos.x, remotePlayer.pos.y),
renderPos: createVector(remotePlayer.pos.x, remotePlayer.pos.y),
offset: createVector(0, 0),
vel: createVector(remotePlayer.vel.x, remotePlayer.vel.y),
speed: remotePlayer.speed,
input: remotePlayer.input,
};
} else {
localPlayer.lastPos = localPlayer.pos.copy();
localPlayer.speed = remotePlayer.speed;
localPlayer.vel.set(remotePlayer.vel.x, remotePlayer.vel.y);
localPlayer.pos.set(remotePlayer.pos.x, remotePlayer.pos.y);
pendingInputs = pendingInputs.filter(
input => input.id > remotePlayer.input.id
);
for (const input of pendingInputs) {
physicsTick(localPlayer, input);
}
localPlayer.offset.add(p5.Vector.sub(localPlayer.lastPos, localPlayer.pos))
}
}
const server = mockServer(0, 1 / 60, physicsTickRate, onUpdateTick);
function setup() {
frameRate(240);
createCanvas(windowWidth, windowHeight);
const buttons = [
createButton('Start Server'),
createButton('Restart Server'),
createButton('Stop Server')
];
for(const [i, btn] of buttons.entries()) {
btn.style('width', '100px');
btn.style('height', '50px');
btn.position(width - 125, 75 * (i + 1));
}
buttons[0].mousePressed(server.start);
buttons[1].mousePressed(server.restart.bind(server));
buttons[2].mousePressed(server.stop);
server.start();
setInterval(() => {
if (localPlayer) {
localPlayer.input = getInput();
server.sendInput(localPlayer.input);
pendingInputs.push(localPlayer.input);
}
}, inputTickRate);
}
function draw() {
background(220);
if (!localPlayer) return;
physicsAccumulator += deltaTime / 1000;
while (physicsAccumulator >= physicsTickRate) {
localPlayer.lastPos = localPlayer.pos.copy();
physicsTick(localPlayer);
physicsAccumulator -= physicsTickRate;
}
const alpha = physicsAccumulator / physicsTickRate;
localPlayer.renderPos.lerp(p5.Vector.add(localPlayer.pos, localPlayer.offset), alpha);
localPlayer.offset.mult(pow(0.001, deltaTime/1000));
translate(width / 2, height / 2);
line(-width / 2, 100, width / 2, 100);
push();
translate(localPlayer.renderPos);
rectMode(CENTER);
line(-width / 2, 0, width / 2, 0);
rect(0, 0, 100, 50);
pop();
}
window.setup = setup;
window.draw = draw;
window.onresize = () => resizeCanvas(windowWidth, windowHeight);const step = 1 / 30; // Шаг физики
let acc = 0; // Аккумулятор
let pos1; // Верхний прямоугольник
let lastPos1; // Последняя позиция верхнего прямоугольника
let pos2; // Нижний прямоугольник
let lastPos2;
// Функция вызывается один раз (init)
function setup() {
createCanvas(windowWidth, windowHeight); // Создаём канвас
//frameRate(144); // Устанавливаем кол-во кадров / сек
// Инициализируем позиции прямоугольников
pos1 = createVector(0, height/2 - 100);
lastPos1 = pos1.copy();
pos2 = createVector(0, height/2);
}
// Функция вызывается каждый кадр
function draw() {
background(220); // Заливаем фон
acc += deltaTime / 1000; // Увеличиваем аккумулятор
// Сохраняем последнюю позицию верхнего прямоугольника
lastPos2 = pos2.copy();
while(acc >= step) { // Пока аккумулятор больше шага физики
lastPos1 = pos1.copy();
pos1.x += 100 * step; // Двигаем верхний прямоугольник вправо
acc -= step; // Уменьшаем аккумулятор
}
pos2.x += 100 * (deltaTime / 1000); // Двигаем нижний прямоугольник вправо
const alpha = acc / step; // Коэффициент интерполяции
// Интерполяция позиции
const interpPos1 = p5.Vector.lerp(
createVector(lastPos1.x, lastPos1.y),
createVector(pos1.x, pos1.y),
alpha
);
// Верхний прямоугольник (двигает: физики)
rect(interpPos1.x, interpPos1.y, 200, 50);
// Интерполяция позиции
const interpPos2 = p5.Vector.lerp(lastPos2, pos2, alpha);
// Нижний прямоугольник (двигает: рендер)
rect(pos2.x, pos2.y, 200, 50);
} const step = 1 / 60; // Шаг физики
let acc = 0; // Аккумулятор
let pos1; // Верхний прямоугольник
let lastPos1; // Последняя позиция верхнего прямоугольника
let pos2; // Нижний прямоугольник
let lastPos2;
// Функция вызывается один раз (init)
function setup() {
createCanvas(windowWidth, windowHeight); // Создаём канвас
frameRate(144); // Устанавливаем кол-во кадров / сек
// Инициализируем позиции прямоугольников
pos1 = createVector(0, height/2 - 100);
pos2 = createVector(0, height/2);
}
// Функция вызывается каждый кадр
function draw() {
background(220); // Заливаем фон
acc += deltaTime / 1000; // Увеличиваем аккумулятор
lastPos1 = pos1.copy(); // Сохраняем последнюю позицию верхнего прямоугольника
lastPos2 = pos2.copy();
while(acc >= step) { // Пока аккумулятор больше шага физики
pos1.x += 300 * step; // Двигаем верхний прямоугольник вправо
acc -= step; // Уменьшаем аккумулятор
}
pos2.x += 300 * (deltaTime / 1000); // Двигаем нижний прямоугольник вправо
const alpha = acc / step; // Коэффициент интерполяции
// Интерполяция позиции
const interpPos1 = p5.Vector.lerp(
createVector(lastPos1.x, lastPos1.y),
createVector(pos1.x, pos1.y),
alpha
);
// Верхний прямоугольник (двигает: физики)
rect(interpPos1.x, interpPos1.y, 200, 50);
// Интерполяция позиции
const interpPos2 = p5.Vector.lerp(lastPos2, pos2, alpha);
// Нижний прямоугольник (двигает: рендер)
rect(pos2.x, pos2.y, 200, 50);
} const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
const step = 1 / 60;
let acc = 0;
let lastTime = 0;
let pos1 = { x: 0, y: 0 };
let lastPos1 = { ...pos1 };
let pos2 = { x: 0, y: 0 };
let lastPos2 = { ...pos2 };
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
pos1.y = canvas.height / 2 - 100;
pos2.y = canvas.height / 2;
lastPos1.y = pos1.y;
lastPos2.y = pos2.y;
}
window.addEventListener('resize', resize);
resize();
function lerp(start, end, t) {
return start + (end - start) * t;
}
function update(dt) {
acc += dt;
lastPos1 = { ...pos1 };
lastPos2 = { ...pos2 };
while (acc >= step) {
pos1.x += 100 * step;
acc -= step;
}
pos2.x += 100 * dt;
}
function draw() {
ctx.fillStyle = 'rgb(220, 220, 220)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const alpha = acc / step;
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
const ix1 = lerp(lastPos1.x, pos1.x, alpha);
ctx.fillRect(ix1, pos1.y, 200, 50);
ctx.strokeRect(ix1, pos1.y, 200, 50);
const ix2 = lerp(lastPos2.x, pos2.x, alpha);
ctx.fillRect(ix2, pos2.y, 200, 50);
ctx.strokeRect(ix2, pos2.y, 200, 50);
}
function loop(currentTime) {
const dt = (currentTime - lastTime) / 1000;
lastTime = currentTime;
update(dt);
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop); [package]
name = "Example"
version = "0.1.0"
edition = "2021"
[dependencies]
macroquad = "0.4.13"use interpolation::lerp;
use macroquad::color::{BLACK, LIGHTGRAY, WHITE};
use macroquad::prelude::{
clear_background, draw_rectangle, draw_rectangle_lines, next_frame, screen_width,
};
use macroquad::time::get_frame_time;
use macroquad::window::screen_height;
use miniquad::conf::Conf;
#[derive(Clone, Copy)]
struct Pos {
x: f32,
y: f32,
}
pub fn conf() -> Conf {
Conf::default()
}
#[macroquad::main(conf)]
async fn main() {
const STEP: f32 = 1. / 60.;
let mut acc: f32 = 0.;
let mut pos1 = Pos {
x: screen_width() / 2.0,
y: screen_height() / 2. - 100.,
};
let mut last_pos1 = pos1;
let mut pos2 = Pos {
x: screen_width() / 2.0,
y: screen_height() / 2.,
};
loop {
clear_background(LIGHTGRAY);
acc += get_frame_time();
last_pos1 = pos1;
while acc >= STEP {
pos1.x += 100. * STEP;
acc -= STEP;
}
pos2.x += 100. * get_frame_time();
let alpha = acc / STEP;
draw_rectangle(
lerp(&last_pos1.x, &pos1.x, &alpha),
lerp(&last_pos1.y, &pos1.y, &alpha),
200.,
50.,
WHITE,
);
draw_rectangle_lines(
lerp(&last_pos1.x, &pos1.x, &alpha),
lerp(&last_pos1.y, &pos1.y, &alpha),
200.,
50.,
2.,
BLACK,
);
draw_rectangle(pos2.x, pos2.y, 200., 50., WHITE);
draw_rectangle_lines(pos2.x, pos2.y, 200., 50., 2., BLACK);
next_frame().await
}
}