RaananW

Celebrating 2.5 - Babylon.js challenge is back!

43 posts in this topic

9 hours ago, BitOfGold said:

@ozRocker:
I recommend this, about spatial partitioning (grid based message filtering) :
https://davidwalsh.name/3d-websockets

 

ok, neat!  Now I know I was on the right track with the grid.

It got really complicated though cos I was trying to deal with objects that were spanning 2 grid cells and also caching grid cells so players wouldn't have to keep checking what's in the grid cell when they move around unless the cell has been marked as "dirty".  But if grids are the way to go, I'll investigate it further. 

Share this post


Link to post
Share on other sites

@endel that server seems pretty so far cool. I am not sure if I am using it the right way, but it's definable fun to play around with. :D I have to admit that I never really used typescript before. Locally everything runs fine, though. Now I wanted to deploy it to heroku to see how that works out... well, somehow it didn't... here comes the error log. Problem seems to be that "tsc: not found" when compiling the server with "npm install --prefix server && tsc -p server" ... any hint what I am doing wrong?

-----> Node.js app detected
-----> Creating runtime environment
       
       NPM_CONFIG_LOGLEVEL=error
       NPM_CONFIG_PRODUCTION=true
       NODE_ENV=production
       NODE_MODULES_CACHE=true
-----> Installing binaries
       engines.node (package.json):  6.9.1
       engines.npm (package.json):   unspecified (use default)
       
       Downloading and installing node 6.9.1...
       Using default npm version: 3.10.8
-----> Restoring cache
       Skipping cache restore (new runtime signature)
-----> Building dependencies
       Installing node modules (package.json)
       
       > babylonjs-colyseus@1.0.0 install /tmp/build_1f4a07a7597ab37694579564c70a41a7
       > npm run compile-server && npm run compile-client
       
       
       > babylonjs-colyseus@1.0.0 compile-server /tmp/build_1f4a07a7597ab37694579564c70a41a7
       > npm install --prefix server && tsc -p server
       
       babylonjs-colyseus-server@1.0.0 /tmp/build_1f4a07a7597ab37694579564c70a41a7/server
       ├── @types/cors@0.0.33
       ├─┬ @types/express@4.0.34
       │ ├── @types/express-serve-static-core@4.0.39
       │ └─┬ @types/serve-static@1.7.31
       │   └── @types/mime@0.0.29
       ├── @types/node@6.0.53
       ├── @types/shortid@0.0.28
       ├─┬ colyseus@0.4.4
       │ ├── @types/fossil-delta@0.2.2
       │ ├── @types/mocha@2.2.34
       │ ├── @types/msgpack-lite@0.1.1
       │ ├── @types/ws@0.0.35
       │ ├─┬ clock-timer.js@1.1.4
       │ │ └── clock.js@1.1.5
       │ ├── fossil-delta@0.2.5
       │ ├─┬ msgpack-lite@0.1.26
       │ │ ├── event-lite@0.1.1
       │ │ ├── ieee754@1.1.8
       │ │ ├── int64-buffer@0.1.9
       │ │ └── isarray@1.0.0
       │ ├─┬ timeframe@0.3.5
       │ │ └── harmony-proxy@1.0.1
       │ └─┬ ws@1.1.1
       │   ├── options@0.0.6
       │   └── ultron@1.0.2
       ├─┬ cors@2.8.1
       │ └── vary@1.1.0
       ├─┬ express@4.14.0
       │ ├─┬ accepts@1.3.3
       │ │ ├─┬ mime-types@2.1.13
       │ │ │ └── mime-db@1.25.0
       │ │ └── negotiator@0.6.1
       │ ├── array-flatten@1.1.1
       │ ├── content-disposition@0.5.1
       │ ├── content-type@1.0.2
       │ ├── cookie@0.3.1
       │ ├── cookie-signature@1.0.6
       │ ├─┬ debug@2.2.0
       │ │ └── ms@0.7.1
       │ ├── depd@1.1.0
       │ ├── encodeurl@1.0.1
       │ ├── escape-html@1.0.3
       │ ├── etag@1.7.0
       │ ├─┬ finalhandler@0.5.0
       │ │ ├── statuses@1.3.1
       │ │ └── unpipe@1.0.0
       │ ├── fresh@0.3.0
       │ ├── merge-descriptors@1.0.1
       │ ├── methods@1.1.2
       │ ├─┬ on-finished@2.3.0
       │ │ └── ee-first@1.1.1
       │ ├── parseurl@1.3.1
       │ ├── path-to-regexp@0.1.7
       │ ├─┬ proxy-addr@1.1.2
       │ │ ├── forwarded@0.1.0
       │ │ └── ipaddr.js@1.1.1
       │ ├── qs@6.2.0
       │ ├── range-parser@1.2.0
       │ ├─┬ send@0.14.1
       │ │ ├── destroy@1.0.4
       │ │ ├─┬ http-errors@1.5.1
       │ │ │ ├── inherits@2.0.3
       │ │ │ └── setprototypeof@1.0.2
       │ │ └── mime@1.3.4
       │ ├── serve-static@1.11.1
       │ ├─┬ type-is@1.6.14
       │ │ └── media-typer@0.3.0
       │ └── utils-merge@1.0.0
       └── shortid@2.2.6
       
       sh: 1: tsc: not found
       
       npm ERR! Linux 3.13.0-105-generic
       npm ERR! argv "/tmp/build_1f4a07a7597ab37694579564c70a41a7/.heroku/node/bin/node" "/tmp/build_1f4a07a7597ab37694579564c70a41a7/.heroku/node/bin/npm" "run" "compile-server"
       npm ERR! node v6.9.1
       npm ERR! npm  v3.10.8
       npm ERR! file sh
       npm ERR! code ELIFECYCLE
       npm ERR! errno ENOENT
       npm ERR! syscall spawn
       npm ERR! babylonjs-colyseus@1.0.0 compile-server: `npm install --prefix server && tsc -p server`
       npm ERR! spawn ENOENT
       npm ERR!
       npm ERR! Failed at the babylonjs-colyseus@1.0.0 compile-server script 'npm install --prefix server && tsc -p server'.
       npm ERR! Make sure you have the latest version of node.js and npm installed.
       npm ERR! If you do, this is most likely a problem with the babylonjs-colyseus package,
       npm ERR! not with npm itself.
       npm ERR! Tell the author that this fails on your system:
       npm ERR!     npm install --prefix server && tsc -p server
       npm ERR! You can get information on how to open an issue for this project with:
       npm ERR!     npm bugs babylonjs-colyseus
       npm ERR! Or if that isn't available, you can get their info via:
       npm ERR!     npm owner ls babylonjs-colyseus
       npm ERR! There is likely additional logging output above.
       
       npm ERR! Please include the following file with any support request:
       npm ERR!     /tmp/build_1f4a07a7597ab37694579564c70a41a7/npm-debug.log
       
       npm ERR! Linux 3.13.0-105-generic
       npm ERR! argv "/tmp/build_1f4a07a7597ab37694579564c70a41a7/.heroku/node/bin/node" "/tmp/build_1f4a07a7597ab37694579564c70a41a7/.heroku/node/bin/npm" "install" "--unsafe-perm" "--userconfig" "/tmp/build_1f4a07a7597ab37694579564c70a41a7/.npmrc"
       npm ERR! node v6.9.1
       npm ERR! npm  v3.10.8
       npm ERR! code ELIFECYCLE
       npm ERR! babylonjs-colyseus@1.0.0 install: `npm run compile-server && npm run compile-client`
       npm ERR! Exit status 1
       npm ERR!
       npm ERR! Failed at the babylonjs-colyseus@1.0.0 install script 'npm run compile-server && npm run compile-client'.
       npm ERR! Make sure you have the latest version of node.js and npm installed.
       npm ERR! If you do, this is most likely a problem with the babylonjs-colyseus package,
       npm ERR! not with npm itself.
       npm ERR! Tell the author that this fails on your system:
       npm ERR!     npm run compile-server && npm run compile-client
       npm ERR! You can get information on how to open an issue for this project with:
       npm ERR!     npm bugs babylonjs-colyseus
       npm ERR! Or if that isn't available, you can get their info via:
       npm ERR!     npm owner ls babylonjs-colyseus
       npm ERR! There is likely additional logging output above.
       
       npm ERR! Please include the following file with any support request:
       npm ERR!     /tmp/build_1f4a07a7597ab37694579564c70a41a7/npm-debug.log
-----> Build failed
       
       We're sorry this build is failing! You can troubleshoot common issues here:
       https://devcenter.heroku.com/articles/troubleshooting-node-deploys
       
       If you're stuck, please submit a ticket so we can help:
       https://help.heroku.com/
       
       Love,
       Heroku
       
 !     Push rejected, failed to compile Node.js app.
 !     Push failed

 

Share this post


Link to post
Share on other sites
On 22/12/2016 at 8:54 PM, RaananW said:

Yeah! teams! just what I like to hear.

@Raggar - you are not the first one facing this issue. There are a lot of resources about it (http://www.gamedonia.com/blog/lag-compensation-techniques-for-multiplayer-games-in-realtime for example). Timeframe, the project you linked to, is integrated in @endel's server implementation, so I would recommend giving it a try.

My 2 cents - don't overcrowd the network channel. Send only what's needed, do whatever you can on the server. Limit the number of players in a single game, or have very strong servers :) one of the two.

I remember reading an article about slither.io , which have around 600 players in each world. This do nothing new - compensate for lag using calculations on the server and estimations to your next point. If you play the game with network throttling you can actually see how they correct your position

Yeah, but so far I haven't found an article mentioning how it is implemented. As I wrote, I'm unsure as how to 'rewind' the state server-side. But I guess it comes down to some testing, as it would anyways.

I have had a look at his server, as well as GarageServer.io. Problem is, I'll have to use my own socket implementation, as I have WebRTC running and handled in Node.js on my server. Player creation, settings etc. are still done using sockets.

I tried a few ways of creating an authoritative server using Cannon.js both client- and server-side.

1) Using Gabriel Gambetta's example:  http://www.gabrielgambetta.com/fpm_live.html

2) On every input, I save the position of my physics body, then when I receive the position of the physics body according to the server, as well as the input sequence number associated to this position, I calculate the difference, and move the client body to the new position, there by making sure the client is in the right position. This one brought my some stange issues, and doesn't work the way I thought it would.

3) Interpolating the client position(slowly) towards the server position, so no matter your latency, you will be slightly off. This works decently, but there are some issues with the exchange between the client and the server, so if I qucikly press left, right, left, it gets crazy out of sync.

So far the best option seems to be 1. This way I can use deltas for rotational data, and it automatically takes care of packet loss too, although it send quite a bit more info, and there fore bigger packets.

The big issue with this solution is, that it applies units to the position, instead of using Cannon.js' own velocity. This might cause some issues, but still works a lot better than the other methods I tried out.

It uses delta time to calculate the distance, so it is consistant across different frame rates. Speed Can be cheated this way, but it's just a matter of applying some speed check server-side, as you would if you sent positions instead of inputs.

Another thing is the rays. In Cannon.js it takes an ariginating point, and a target point, so I'll either have to calculate these using Babylon.js, and send them to the server. Or somehow calculate the target point on the server, using Cannon.js and the rotation/direction of the player. The last one can only be cheated using traditional aimbots, but I have no idea how to implement it. :P

Share this post


Link to post
Share on other sites

@iiceman hey! this issue has been reported already, you just need to set NPM_CONFIG_PRODUCTION environment variable to false on Heroku.
EDIT: If you want to try out the client v0.6.0 alpha, I've recently added a new feature that simplifies listening to changes coming from the server: https://github.com/gamestdio/colyseus.js#listening-to-room-state-change The boilerplate is using v0.5.x that doesn't have it.

@Raggar about client prediction, it very much depends on the experience you want your players to have. Sometimes, simply interpolating values in the client is enough. wilds.io just interpolate player's positions, for example. Getting slightly out of sync is not really a big deal when it doesn't influence the gameplay directly. If every player is kinda delayed, nobody will ever be ahead in time. :) 

For really fast objects then it might make sense to apply some sort of client-side prediction. So I'd recommend evaluating the kind of objects you have, and which ones really makes sense to apply prediction or not. Cheers!

iiceman likes this

Share this post


Link to post
Share on other sites

@endel I have updated the client... but haven't really tried the new state change listener since I still have the feeling that I don't use it the right way. If I want to tell the server and all the clients that one client did something..like moving, what method do I use and how are the other clients supposed to listen? Right now I am doing that for sending data:

setInterval(function () {
	if(
		prevCamPositon instanceof BABYLON.Vector3 &&
		(prevCamPositon.x !== camera.position.x ||
		prevCamPositon.y !== camera.position.y ||
		prevCamPositon.z !== camera.position.z)
	) {
		prevCamPositon = camera.position.clone();
		console.log(room.state);
		room.send({
			type: 'position',
			position: camera.position
		});
	}
}, 100);

And that for receiving data (updating target position of other clients but not my own - and then doing a lerp in the registerBeforeRender to smoothly transition to that target position):

room.onData.add(function (data) {
	// check if the server told me my client id
	if(data.hasOwnProperty('type') && data.type === 'myClientId'){
		myClientId = data.clientId
	}

	// check if server said something about a position update
	if(data.hasOwnProperty('type') && data.type === 'position' && data.clientId !== myClientId) {
		if (players.hasOwnProperty(data.clientId) === false) {
			players[data.clientId] = BABYLON.MeshBuilder.CreateSphere(data.clientId, {diameter: 1}, scene);
		}
		players[data.clientId].targetPosition = new BABYLON.Vector3(data.position.x, data.position.y, data.position.z);
	}
});

 

Share this post


Link to post
Share on other sites

@iiceman hey! alright. I see you're using your client's data and trying to replicate it to all clients. Depending on the kind of game you're making, this could be fine - it's very likely, though, that all clients will be seeing a completely different state of the game. Ideally, you should only send the action the clients are trying to perform to the server, validate and then process them on the server.

For position update, the clients should send the command "move forward", for example. And then the server calculates it based on the data it already has, and updates the room state. When the room state is changed, the changes will be broadcasted to all clients - and only then the entity will actually move.

In authoritative server implementations like this, it's important to never trust the client. Most of the game loop will be implemented on the server (movements, collisions, actions, etc), even though some of them can be present in the client as well as some sort of prediction when it's necessary.

The clients are usually just a dumb representation of the data coming from the server.

Hope this helps, and please let me know if you have any questions regarding the usage of Colyseus :) Cheers and happy holidays! :) 

iiceman likes this

Share this post


Link to post
Share on other sites

Okay... got it, thanks for the explaination... makes sense indeed, but makes things a lot more complicated at the same time.

This means for example that I should not actually use the Babylon controls for the camera. I would have to re-implement the camera movement with its inertia and everything on the server side. Same goes for collisions detection with my level architecture. I don't even know how to implement a simple ray casting on the server side...

This leads to the questions: is there a way that I can use those function from BabylonJs on the node server? I mean I don't have a canvas... can I still somehow create scene and simulate everything on the server side?

About Colyseus itself... probably still a lot of questions... I think I have to look at more examples and try things out before I can actually put my thoughts into understandable questions, but I'll let you know when I am there ;)

Share this post


Link to post
Share on other sites
On 24/12/2016 at 6:35 PM, endel said:

 

@Raggar about client prediction, it very much depends on the experience you want your players to have. Sometimes, simply interpolating values in the client is enough. wilds.io just interpolate player's positions, for example. Getting slightly out of sync is not really a big deal when it doesn't influence the gameplay directly. If every player is kinda delayed, nobody will ever be ahead in time. :) 

For really fast objects then it might make sense to apply some sort of client-side prediction. So I'd recommend evaluating the kind of objects you have, and which ones really makes sense to apply prediction or not. Cheers!

As what I'm doing is nothing more than a generic FPS(so far), it is crucial that the positions are as accurate as possible at any times.

If I were to make, as an example, a racing game, I think interpolating the client position to the server's would be perfectly fine.

Share this post


Link to post
Share on other sites

Hey people!

Happy new year!!!

I am happy this thread is up and running.

Was wondering how your progress is. Anyone care to share what was already done and what is still not implemented? I think it will motivate all of us to know that the others are also still stuck on designing the login menu.

I will start:

I am building a turn-by-turn game, based on angular.js and a real-time database (so far I am using firebase but I am considering moving to a self-hosted server). So far I have implemented the ability to create a game room, join a game, and see the list of games available, as well as a simple login mechanism and the ability to upload your own avatar. As it is a turn-by-turn game, I have implemented the progress-updater, that receives events from other players and (for now) notifies the user that something was done. This works rather well. The entire thing is written in TypeScript.

Apart from canvas initialization and general tests I have not yet implemented anything with babylon.js . And my design abilities are far from perfect, so the entire thing still looks like a demo hacked in 5 minutes.

So, this is me. So little time, so much to do.

How about you?

 

Share this post


Link to post
Share on other sites

Well. So far, I've chosen to go with interpolation to correct the positions of clients, and if I speed up the velocity according to the latency of the client, I seem to get a decent result. Very decent actually.

My problem as of now is, that:

On 24/12/2016 at 3:55 PM, Raggar said:

3) Interpolating the client position(slowly) towards the server position, so no matter your latency, you will be slightly off. This works decently, but there are some issues with the exchange between the client and the server, so if I qucikly press left, right, left, it gets crazy out of sync.

This only happens in the multiplayer version of my code. In the test version, it only happens rarely, and not as severly.

The two pieces of code are almost identical, but this somehow happens without me knowing why :P

And since they are so similar, it's hard to debug, so I might just have to redo the multiplayer part bit by bit, while constantly testing for the issue.

Share this post


Link to post
Share on other sites
On 1/2/2017 at 4:23 PM, RaananW said:

Hey people!

[...]

Was wondering how your progress is. Anyone care to share what was already done and what is still not implemented? I think it will motivate all of us to know that the others are also still stuck on designing the login menu.

[...]

How about you?

 

I am happy to say I am still on it, and here is my latest update:

 

jerome likes this

Share this post


Link to post
Share on other sites

I've the multiplayer part working, prediction and correction of server state, some of it based on latency so it looks better and more smooth.

Next step is interpolation of the game states from the server. I'm not quite sure how to implement this, but I'm thinking about using delta time to calculate the lerp percentage to make it frame independent.

Then when a new state is received from the server, I snap to the previous state, and uses the delta time to calculate the lerp. If I send the state to the clients 20 times each second, and have ~60 fps, I'll use the ~17ms to get a value like .03 or point .05 depending on what looks the best.

So far I only send the client's ping once every second, and I'm planning to use this update to send a full state, and then the remaining 19 to only send deltas. Whenever a client sends input, they should send the last acknowledged full state, and deltas are then then based on this state, so packet losses won't mess too much with the accuracy of the states. Right now, I send all clients' states 20 times a second, but I'll have to only send changed states, too. But that shouldn't take more than a few minutes to implement. Same thing goes with input. If neither rotation nor input has changed, no need to send any.

The reason I mention packet loss is, that I am planning to use WebRTC. I have a little test with it successfully running on Node.js as well as in the browser. I'll just have to test whether I can, in a stable way, habe 2 dataChannels for each client, one reliable and ordered and one unreliable and unordered. Otherwise I'll have to implement reliability myself, with retransmissions of packets and acknowledgments. I would prefer the first option, though.

2 other issues I have are:

First, right now I send 1 state for every client, 20 times a second. With, let's say 10 clients, that's 10 states * 20 frames * 10 players * every second. That sounds like a lot, so I'll have to send updates in arrays, and maybe split these array into smaller arrays, depending on the sizes of the states, as well as the player count, as to not enforce MTU-fragmentation.

The other one is, that I'm sending inputs every frame, that's, when everything runs smooth, 60 times a second. I think this sounds like a lot, too. But I'm afraid there is no way around it, as the input applies velocities, and because of that, can't just be applied at any time. It has to be as quickly as possible, to get the most accurate results. I guess I could just send the inputs, at most, 20 or 30 times per second, and suffer a bit from inaccuracy. But as this is the only data that i so far send from the client to the server, I'm not too worried about it, yet. Same goes with deltas. I'm not planning on sending deltas for rotations at this point, but I might have to at some point.

Share this post


Link to post
Share on other sites

Interpolation of the other players now look smooth and right.

I'm not too happy with the correction of the clients own player, after applying some 'only send when state has changed' on the server.

For now, I'll revert back to the old way of doing it, as I guess this is more important to test, as it will show how many players can actually be active and playing at once.

Then, at some point, I can go back and give it a try again. I'm not quite sure where the issue happens, as the functions are pretty much the same, but somehow creates very different results. The first way is very accurate, the second one seems to make it randomly inaccurate some times.

As there are some differences between the API of WebRTC and Socket.io, the next step will be to implement the DataChannels, then test and find a suitable solution to the reliable/unreliable problem with either my own implementation or multiple channels.

Then comes delta compression, which I have an idea how to implement, based on every player's own acknowledged state, as I'll have to send every state to every single connected player.

 

Share this post


Link to post
Share on other sites

Ehh.

I got server-side hitboxes properly working, using 3 boxes to simplify intersection checking.

The problem I'm facing right now is, that after changing from sockets to WebRTC, I have some weird issues I can't really figure out how to fix.

When one player joins, everything runs smooth and perfectly as it did with sockets, but as soon as another player joins, the accuracy slowly gets worse. Then, with any additional player, it only gets worse. I tried with big and small packets, but it doesn't make any difference. The functions handling the interpolation of both the local player, as well as the remote players run at about 0-2ms, so this isn't the bottleneck either.

I have checked and double-checked my code many times, but everything should simply work as it did before the switch.
This is starting to give me quite a headache :P

Share this post


Link to post
Share on other sites

Stupid me.

For every frame in my network loop (20/sec), I create an array of the positions of every single player connected to the server, and sends it to, again, every player connected to the server.

What I did was create said array OUTSIDE of the 'for loop' handling every single player, this resulted in the array containing duplicates of every players state.

So instead of an array looking like this (Simplified and made realable):

[{playerID: 1, position: 1}, {playerID: 2, position: 0}], the array would be:

[{playerID: 1, position: 1}, {playerID: 2, position: 0}, {playerID: 1, position: 1}, {playerID: 2, position: 0}], and of course increasing with the number of connections.

This was fixed by initiating the array inside the loop.

 

Now I can have 5 players connected before the speed of the player object starts to get affected. Something else to investigate I guess :P

Right now, I'm sending player inputs every frame,  at 60/sec, which I would like to cut down to ~30/sec to save bandwidth, but still keep it as accurate as possible. I will have to change the implementation, so that only inputs that have changed will be sent, but this early I like to assume that every player will be doing everything he can at any time. I tested the server's received packets, and 4 players summed up to just about the right amount of packets(4 * 60/sec).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.