I'm working on a multiplayer game and i'm having problem with synchronizing players.
When a player presses one of the move keys (W,A,S,D) then the client sends a packet about the pressed button, the server sets the velocity according to the pressed key and sends back to all nearby players the new velocity.
When the player release the key then the clients sends a packet, server sets the player velocity to 0,0 and sends the position and velocity to all nearby players.
So the problem is when i release the key, then most of the time the player jumps back.
How could i fix this?
i'm using socket.io.
Client side:
socket.on('positionEntity', function (data) {
console.log((data.x - entities[data.id].x)+" "+(data.y - entities[data.id].y));
entities[data.id].setPosition(data);
});
$(document).keyup(function(e) {
if (e.keyCode == 87) {
keys.W = false;
socket.emit("stopMove", {dir: 0, time: Date.now()});
}
if (e.keyCode == 65) {
keys.A = false;
socket.emit("stopMove", {dir: 1, time: Date.now()});
}
if (e.keyCode == 83) {
keys.S = false;
socket.emit("stopMove", {dir: 2, time: Date.now()});
}
if (e.keyCode == 68) {
keys.D = false;
socket.emit("stopMove", {dir: 3, time: Date.now()});
}
});
$(document).keydown(function(e) {
if (e.keyCode == 87 && !keys.W) {
keys.W = true;
socket.emit("startMove", {dir: 0, time: Date.now()});
}
if (e.keyCode == 65 && !keys.A) {
keys.A = true;
socket.emit("startMove", {dir: 1, time: Date.now()});
}
if (e.keyCode == 83 && !keys.S) {
keys.S = true;
socket.emit("startMove", {dir: 2, time: Date.now()});
}
if (e.keyCode == 68 && !keys.D) {
keys.D = true;
socket.emit("startMove", {dir: 3, time: Date.now()});
}
});
Server side:
socket.on('startMove', function(data) {
if (data.dir == 0) socket.player.setMotionY(-5);
if (data.dir == 1) socket.player.setMotionX(-5);
if (data.dir == 2) socket.player.setMotionY(5);
if (data.dir == 3) socket.player.setMotionX(5);
io.sockets.emit("positionEntity", socket.player.serializePosition());
});
socket.on('stopMove', function(dir) {
socket.player.setMotionX(0);
socket.player.setMotionY(0);
io.sockets.emit("positionEntity", socket.player.serializePosition());
});
This is a very complex task you are working on and something I've done as part of a pet project ;)
You're working on a client-server architecture game so the server is final authority on game logic and decisions. They way you are currently handling rendering will make sudden changes in velocity and direction apparent due to latency (as you have noticed!)
The trick is to buffer movement information of remote players so you always render player with a slight delay. I kept things primitive in my project and used only positional data, not acceleration or velocity. Example, when player A moves on his machine a command is not instantly sent to receive acknowledgement, he moves and on the next tick of my networking send loop (10 ticks per second) his position is fired to the server who updates all clients in the vicinity with this new position. These clients have a buffer for each "remote" player which stores each position for a time (100 milliseconds) before rendering the update. In this way the client is rendered with a slight delay but I can interpolate (smooth the transition of the sprite/model) between each positional co-ordinate to achieve smooth motion with the illusion of velocity and acceleration.
A BASIC interpolation function for client code. This system only queued two updates for each remote player at most, where index 0 in the update array was the older of two. So index 0 might be the remote player position 0ms and index 1 is the remote player position at 100ms.
interpolate: function() {
var timeDifference = new Date().getTime() - this.serverUpdates[1].time;
// Percentage of time passed since update was received (I use 100ms gaps)
var interPercent = (timeDifference) / 100;
// Latest updates (index 1 is the newest state)
var s1 = this.serverUpdates[0],
s2 = this.serverUpdates[1];
// Need to lerp between values provided in latest update and older one
var p = (new Vector3(s2.position)).subtract(new Vector3(s1.position));
p = p.timesScalar(interPercent);
// New position is the older lerped toward newer position where lerp
//percentage is the time passed
this.position = new Vector3(s1.position).add(p);
// Now update rotation in a smooth manner
var rotationDifference = (s2.rotation - s1.rotation);
if (rotationDifference && rotationDifference != 0) {
this.rotation = s1.rotation + (rotationDifference * interPercent);
}
},
In that code I was receiving updates that were approx 100ms apart, so at time 0 position is s1 and time 100ms s2 is the position. So if 50ms have passed since we received s2 then the entity is 50% between the two positions. This was fine for my need but may not work out in other types of games or might need tweaking.
These resources are an excellent start to explain networked games and dealing with latency, you'll be amazed at the difference implementing interpolation and extrapolation will have on your game smoothness in remote clients.
http://gafferongames.com/networking-for-game-programmers/what-every-programmer-needs-to-know-about-game-networking/
https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization <- REALLY GOOD!
https://developer.valvesoftware.com/wiki/Lag_compensation
Good luck :)
Related
I am new to THREEJS and at the moment I am trying to move a cube using arrow keys. Please see this fiddle: https://jsfiddle.net/mauricederegt/y6cw7foj/26/
All works, I can move the cube using the arrow keys and I even managed to rotate the cube around the correct axis when moving it around. The problem is with the animations. I can’t seem to get them to work. At the moment when you press the left arrow key, the cube moves to the left and also rolls around the axis. Well…at the moment it snaps into position, instead of smoothly transitioning.
What I want is that it smoothly moves to the left while it rotates, but how to do that? At the end of the code I do call for the
requestAnimationFrame
but that doesn’t do much. I have a fiddle here of my attempt doing this with CSS. Here the animations work (but never got the rotating direction correct): https://jsfiddle.net/mauricederegt/5ozqg9uL/3/ This does show what animations I want to have.
So what am I missing in the THREEjs? Thanks a lot
What you're looking for is something called "tweening," where you draw intermediary steps, rather than jumping immediately to the end result. There are several JavaScript libraries that will do this for you, but I'll cover some of the basics of implementing it yourself.
Take your example. When you tap the left arrow, you move the mesh 1 unit along the -x axis, and rotate -PI/2 about the +y axis. Rather than snapping to these positions/rotations, consider how long you want the animation to take, and start dividing out steps.
Let's say you want to to take 500ms (half a second). Your browser tries to run at about 60fps, so you have 30 frames (about 500ms) to work with at that rate. So for every frame, you can move the box 1/30 units, and rotate it by -PI/60. After 30 frames, the box should be in about the right place, give or take some rounding.
I use "about" when talking about the framerate of the browser because you aren't always guaranteed to get 60FPS. If your framerates dip, and you're locked to the framerate to draw your animation, then it too will slow down and take longer than you wanted. So what can be done about that?
Rather than relying on requestAnimationFrame as your timer, you can set a real timer to step through your animation. Toss calculating the frames you need to complete the animation, and instead calculate the steps needed.
We already know that 60fps is roughly 1 frame every 16.6ms, so that's the absolute maximum target that can expect the browser to draw. But when we do our updates by steps, nothing stops us from going faster. To make things easier to calculate, let's say we want to do 50 update steps rather than the 30 from before. This means that for the 500ms play time, we will need to perform an update every 10ms (slightly faster than the framerate). Also, because we are performing 50 steps, we will be updating the position by 1/50 units, and rotating by -PI/100.
let animationId = setInterval( ()=>{
// update position by 1/50 units
// update rotation by -PI/100
}, 10 ); // every 10 ms
As the interval runs, it will update the object. Meanwhile, the animation loop churns out new frames whenever it can.
Here's a full, running example, with only left-arrow support:
let W = window.innerWidth;
let H = window.innerHeight;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(0, 0, 50);
camera.lookAt(scene.position);
scene.add(camera);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 0, -1);
camera.add(light);
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1, 1, 1),
new THREE.MeshPhongMaterial({
color: "red"
})
);
cube.position.set(10, 0, 0);
scene.add(cube);
function render() {
renderer.render(scene, camera);
}
function resize() {
W = window.innerWidth;
H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
render();
}
window.addEventListener("resize", resize);
resize();
function animate() {
requestAnimationFrame(animate);
render();
}
requestAnimationFrame(animate);
const yAxis = new THREE.Vector3(0, 1, 0);
function updateCube() {
//cube.position.x -= 1;
//cube.rotateOnWorldAxis(yAxis, THREE.Math.degToRad(-90));
cube.position.x -= 1 / 50;
cube.rotateOnWorldAxis(yAxis, -(Math.PI / 100));
}
let step = 0;
let animationId = null;
function startStepping() {
animationId = setInterval(() => {
updateCube();
if (++step === 50) {
clearInterval(animationId);
animationId = null;
}
}, 10)
}
function handleKeyboard(e) {
//if (e.keyCode == 65 || e.keyCode == 37) {
// updateCube();
//}
if (animationId === null && (e.keyCode == 65 || e.keyCode == 37)) {
step = 0;
startStepping();
}
}
document.addEventListener("keydown", handleKeyboard, false);
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background: skyblue;
}
<script src="https://threejs.org/build/three.min.js"></script>
Now there is a downside to this method. Sometimes you may see updates skipped (if the browser's framerate drops), or the same position drawn twice (if your update rate is significantly lower than the browser's framerate). You could try to get the best of both worlds but computing the framerate live from your render loop, and adjusting your frame steps accordingly, but at that point you must ask whether the extra time spent computing those statistics are actually hurting trying to achieve a rock-steady framerate-locked draw rate.
Switching based on key input
Because your key input is now disjointed from drawing, you now need some kind of flag to determine the action being taken. Your key press handler will set that flag, and then updateCube will act based on that flag. Something like:
let action = null
function startStepping(){
// set up the interval...
// but then also ensure the action stops after the animation plays:
setTimeout( () => action = null, 500 );
}
function handleKeyboard(e){
if (animationId === null) {
step = 0;
switch(e.keyCode){
case 37:
case 65:
action = "left";
break;
// other keys...
}
startStepping();
}
}
function updateCube(){
switch(action){
case "left":
// move as if it's rolling left
break;
case "right":
// move as if it's rolling right
break;
// etc. for the other directions
}
}
Context:
The built-in heatmap layer in Google Maps API doesn't have native support for animations, so I've created my own animations using requestAnimationFrame with calls to the Maps API to set the next frame's data. Each frame uses ~2500 data points to render the heatmap and maxes out around 4000 data points.
The problem:
My animation has jank every 5-10 cycles. I don't need the animation to render 60fps, but I do want the framerate to be consistent and each step to be <=100ms. In Devtools, the janky animation frames give me this error at the top of call stack: "Warning: Recurring handler took # ms."
My proposed cause of the problem:
The dev console shows memory leaks,* minor GC, major GC, and DOM GC correlating to Maps API calls. The DOM GC in particular is correlated to the animation jank. (*JS Heap, Nodes, and Listeners increase linearly).
Why I'm asking the question:
I'm fairly new to Google Maps API, requestAnimationFrame, and memory management in JavaScript, so first of all I want to make sure I'm understanding the problem correctly. Secondly, I want to see if there is a way to optimize the heatmap layer for animations, or if my approach is misguided.
Most relevant resources I've consulted:
Google Maps API documentation
Google's guide to web performance
StackOverflow question that asks about very basic, short Maps API heatmap animations
Code
All within a d3.csv('data').
Establish recursive requestAnimationFrame:
const periodDuration = 100; // ms
const timeStart = performance.now();
const frameTick = timestamp => {
if (timestamp - timeStart >= periodDuration) {
timeStart = timestamp;
transitionToNextPeriod(); // shown in next code snippet
}
requestAnimationFrame(frameTick);
};
requestAnimationFrame(frameTick);
transitionToNextPeriod increments time, then filters data, and ends by setting heatmap with updated data:
function transitionToNextPeriod() {
let batchedData = [],
currPeriodData = [],
addMin = 0;
prevMin = min,
prevDate = date,
currMin = 0;
// batch the next 'x' # of mins for one batchedData call after `while` loop
while (++addMin <= 7){
if (prevMin + addMin === 60){
if (hour + 1 === 24){
hour = 0;
date++;
} else {
hour++
}
}
// Now with current minute, filter data and add to batchedData
currMin = ((prevMin + addMin) % 60);
let newTime = `${hour}:${zeroPad(currMin)}`;
// Filter based on current time
let currFilter = dayData.filter( d => d.StartTime.indexOf( ` ${newTime}` ) !== -1);
prevPeriodData = prevPeriodData.filter( d => d.EndTime.indexOf( ` ${newTime}` ) === -1 );
batchedData = [...batchedData, ...currFilter ];
} // End while
batchedData = [...batchedData, ...prevPeriodData];
prevPeriodData = batchedData;
min = currMin;
// Update the maps.LatLng data with new data, then set heatmap data
batchedData.forEach( (d,i) => {
// Check for last datum in current period
if (i + 1 === batchedData.length){
heatmap.setData(currPeriodData); // PROBLEM: about every 7th call to setData results in jank
} else {
currPeriodData.push(new google.maps.LatLng( d.Lat , d.Long ));
}
})
} // End transitionToNextPeriod()
TL;DR I want to display a long-running strip chart with Plotly.js. I don't know how to discard old points.
Details
The following updater from my CodePen at https://codepen.io/Michael-F-Ellis/pen/QvXPQr does almost what I want. It shows a set of 20 samples in 2 traces that update continuously at 500 msec intervals. At the end of the demo, it plots all the points to show they still exist.
var cnt = 0;
var interval = setInterval(function() {
// Add next point to each trace
Plotly.extendTraces('graph', {
y: [[rand()], [rand()]]
}, [0, 1])
// Display only 20 most recent points
Plotly.relayout('graph', { 'xaxis.range': [cnt-20, cnt]})
cnt = cnt+1;
if(cnt === 100) {
// Before ending the run, show all points
// to demonstrate they still exist in Plotly.
Plotly.relayout('graph', { 'xaxis.range': [0, cnt]});
clearInterval(interval);
}
}, 500);
The problem is that I do want to delete older points. The real application needs to run essentially forever on a system with limited memory. I'm looking for a Plotly call that will drop the oldest N trace points. It needs to be reasonably efficient as performance of the target system is also limited.
Thanks!
https://codepen.io/Michael-F-Ellis/pen/YxeEwm
The above seems workable from a behavioral standpoint. Here's the revised updating routine:
Plotly.plot('graph', data);
var cnt = 0;
var max = 20;
var interval = setInterval(function() {
// Add next point to each trace
Plotly.extendTraces('graph', {
y: [[rand()], [rand()]]
}, [0, 1])
// Keep only 'max' most recent points
if(cnt > max) {
data[0].y.shift();
data[1].y.shift();
}
cnt = cnt+1;
if(cnt === 100) {
// Before ending the run, show all points
// to demonstrate that only 'max' points
// still exist in Plotly.
Plotly.relayout('graph', { 'xaxis.range': [0, cnt]});
clearInterval(interval);
}
}, 500);
The solution is to keep the data object in a var outside of Plotly and use shift() to drop old points from the beginning of the array as new points are added.
I'm open to another solution, especially if there are known memory or performance problems with this approach.
I've written my own HTML5 canvas - javaScript based physics engine to simulate a number of points connected by springs. The current general structure of the program is
function init(){
// A bunch of event listeners
renderer();
physics();
}
var frameTime = 1;
function physics(){
// iterate the physics
parts.update();
setTimeout(physics, frameTime);
}
// render loop
function renderer(){
// draws a rectangle over the last frame
drawBackground();
// renders the objects themselves
parts.draw();
// update the timeout according to an onscreen slider
frameTime = Math.ceil(101 - speed_r.value) / 2;
setTimeout(renderer, 15);
}
The rationale behind the 2 different loops is that the human eye only needs to see 60fps, but doing more updates per second yields better physics.
I've since done more research, and found that the standard way to render animations with javaScript is to call requestAnimationFrame(), which as I understand it has the advantage of not rendering while the tab is deselected, improving battery life. However, due to the dual loop structure, the physics will continue to be calculated and will probably outweigh the renderer overhead.
The question is: What is the most performant and ideally most efficient way to achieve this?
To sync your physics simulation with the wall clock, and render the animation smoothly, you need to use a fixed time step and interpolation. Read this excellent article (see also: archive.org) about both subjects.
Using requestAnimationFrame is a good idea to save battery (it will lower the frame rate if the battery is low on most devices). You can use it for both the physics and rendering loop.
What you have to do is compute the time elapsed since the last frame and then use zero or many fixed steps to keep the physics loop in sync with the current (wall-clock) time. This is how all real-time physics engines work, including Box2D and Bullet Physics.
I made a complete JSFiddle using HTML5 Canvas and JavaScript that implements what you need, based on the article mentioned above. See the code below or open it on JSFiddle.
The integrate function is where you update your physics. In the code it is used to step a spring simulation forward.
var t = 0;
var dt = 0.01;
var currentTime;
var accumulator = 0;
var previousState = { x: 100, v: 0 };
var currentState = { x: 100, v: 0 };
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// start animation loop
requestAnimationFrame(animate);
function animate(newTime){
requestAnimationFrame(animate);
if (currentTime) {
var frameTime = newTime - currentTime;
if ( frameTime > 250 )
frameTime = 250;
accumulator += frameTime;
while ( accumulator >= dt )
{
previousState = currentState;
currentState = integrate( currentState, t, dt );
t += dt;
accumulator -= dt;
}
var alpha = accumulator / dt;
var interpolatedPosition = currentState.x * alpha + previousState.x * (1 - alpha);
render( interpolatedPosition );
}
currentTime = newTime;
}
// Move simulation forward
function integrate(state, time, fixedDeltaTime){
var fixedDeltaTimeSeconds = fixedDeltaTime / 1000;
var f = (200 - state.x) * 3;
var v = state.v + f * fixedDeltaTimeSeconds;
var x = state.x + v * fixedDeltaTimeSeconds;
return { x: x, v: v };
}
// Render the scene
function render(position){
// Clear
ctx.fillStyle = 'white';
ctx.fillRect(0,0,canvas.width,canvas.height);
// Draw circle
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(position,100,50,0,2*Math.PI);
ctx.closePath();
ctx.fill();
}
I think I'd look at putting the physics part in a web worker, and having it post updates to the main UI thread, which renders them on a requestAnimationFrame callback. That allows the physics code to run constantly (you don't even need setTimeout looping; although having it yield periodically so it can access messages from the front end — not least "stop"! — would be a good idea), while only updating the display as often as actually needed.
2018 update: As of ES2018, the worker and the main thread could share memory via a SharedArrayBuffer and the features of the Atomics object. Rather than having to break the work up so the worker could process messages, it could just check a location in the shared memory for flags (for instance, the flag saying it needs to stop). The worker could even be suspended right in the middle of the calculation (even in the middle of a standard loop, such as a for or while) and then resumed via Atomics.wait and Atomics.notify.
I'm develop my first phaser game for a client. The game is a car moving forward and it have two minutes to get the goal.
I want to increase progressive the car speed while the Up key is pressed until the speed limit has reached.
I'm moving the racetrack not the car by doing:
this.highway2 = game.add.tileSprite(game.world.centerX,game.world.height/2, game.cache.getImage('highway').width, game.cache.getImage('highway').height, 'highway');
this.highway2.anchor.setTo(0.5,0.5);
this.highway2.autoScroll(0,1000);
So, my questions are:
how can I control the speed of the autoScroll to simulate acceleration?
Is there a way to know how much time was a key pressed?
Is this the right approach to get this done?
Thanks in advanced.
Well, I don't know if this is the better way to do this, but it's work pretty well.
Just set speed limit and track it in the update function.
var playState = {
create: function(){
this.setInitialValues();
game.physics.startSystem(Phaser.Physics.ARCADE);
this.cursor = game.input.keyboard.createCursorKeys();
//highway
this.highway2 = game.add.tileSprite(game.world.centerX,game.world.height/2, game.cache.getImage('highway').width, game.cache.getImage('highway').height, 'highway');
this.highway2.anchor.setTo(0.5,0.5);
this.highway2.autoScroll(0,0);
//car
this.player = game.add.sprite(game.world.centerX+10, game.world.height-150, 'player');
this.player.anchor.setTo(0.5,0.5);
game.physics.arcade.enable(this.player);
//other things
},
update: function(){
this.movePlayer();
},
movePlayer: function(){
// move left and right
// If the left arrow key is pressed
if (this.cursor.left.isDown)
{
// Move the player to the left
this.player.body.velocity.x = -200;
}
// If the right arrow key is pressed
else if (this.cursor.right.isDown)
{ // Move the player to the right
this.player.body.velocity.x = 200;
}
// If neither the right or left arrow key is pressed
else
{
// Stop the player
this.player.body.velocity.x = 0;
}
//speed up and speed down
if (this.cursor.up.isDown)
{
if(this.currentSpeed < this.maxSpeed )
{
this.currentSpeed+=10;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
else{
if(this.currentSpeed > 0 )
{
this.currentSpeed-=10;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
if (this.cursor.down.isDown)
{
if(this.currentSpeed > 0 )
{
this.currentSpeed-=30;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
},
setInitialValues: function(){
this.maxSpeed=1500;
this.currentSpeed=0;
}
}
You should apply a strict separation of concerns to 1) simplify coding 2) ease the fine tuning and even make the game more interesting and 3) easily 'plug' you logic with another controller (touch event instead of keyboard).
So here you have two separate concerns :
* measure for how long the user has been accelerating.
* given current speed, min/max speed, thrust time, decide what is current acceleration (== speed change).
For 1) it's pretty straightForward : record the time when input start, now duration is current time - start time. You'd better use game time if you have one (rather than Date.now()), so that you avoid surprises after the game resumes from a long tab-out.
For 2) You should fine tune the acceleration/deceleration of your game, it will make it more interesting.
Most obvious is not to have a constant acceleration : it must be harder and harder to reach the last % of the max speed. This way you give and incentive/reward to the player not to touch obstacle.
What you should do if the player is not thrusting, i don't know : slowly decelerate or quickly return to normal speed ?
Also you'll have to decide wether the boost is infinite or not, and maybe of a cool-down time.
So the function that compute current acceleration depends on thrusting (bool), thrust time (double) regularSpeed, maxSpeed, minAcc, maxAcc.
There are a lot of options here, but the code to compute acceleration could look like :
if (thrusting) {
// if we're not even at regular speed, max acceleration
if (speed<regularSpeed) { acc = maxAcc; return; }
// if we are above maxSpeed, no acceleration (?? or friction ??)
if (speed>maxSpeed) { acc=0; return; }
// compute current speed ratio
// a figure in [0;1] representing where is the speed in [minSpeed; maxSpeed]
var speedRatio = (currSpeed-regularSpeed)/(maxSpeed-regularSpeed);
// ease this ratio as you like
speedRatio = Math.sqrt(speedRatio);
// compute acceleration : the more speed, the less acceleration
// you might want to put one/some threshold this formula.
acc= minAcc + (1-speedRatio)*(maxAcc-minAcc);
return;
} else {
// do nothing if <= regularSpeed.
if (speed<=regularSpeed) { acc=0 ; return;}
// reduce speed if above regular speed
acc = breakAcc ; // or with friction => acc = - k * currSpeed;
return;
}