53 Commits
Author SHA1 Message Date
Jeremiah Billmann 735f8e7253 Merge pull request #26 from OJFord/patch-1
fix typo preventing onEvent handler firing
2015-08-06 12:28:37 -05:00
Ollie Ford d05fd58480 fix typo preventing event handler firing
`options.OnEvent` is documented as `options.onEvent`. Assuming consistency with other events, and `_options.onEvent` was intended, fixing typo here instead of in [docs](https://github.com/jbillmann/GarageServer.IO/blob/master/documentation/ServerAPI.md#server-options).
2015-08-03 14:44:50 +01:00
Jeremiah Billmann f28dee8927 Furthered GarageServer.IO API calls to support client side prediction for entities - #20 - still have to change how client side prediction is invoked/processed. 2015-05-20 04:04:31 +00:00
Jeremiah Billmann fbdcfe3da5 Updated GarageServer.IO API calls to support client side prediction for entities - #20 2015-05-20 02:10:07 +00:00
Jeremiah Billmann 21d8fb2884 Creating new GarageServer.IO client API calls to support client side prediction for entities - #20 2015-05-20 01:20:14 +00:00
Jeremiah Billmann e96adda2c2 Refactoring client side prediction for players to make for consistent API calls with client side prediction for entities (#20) 2015-05-20 00:59:05 +00:00
Jeremiah Billmann bd341f5f0c Refactored getNewPlayerState to use new object literal 2015-05-20 00:41:32 +00:00
Jeremiah Billmann bb8d5a8b95 Updated .gitignore to exclude .settings (VS Code) 2015-05-16 21:04:33 -05:00
Jeremiah Billmann 50a6a1b094 Changed example package.json to use current compatible express & socket.io packages 2015-05-16 21:02:08 -05:00
Jeremiah Billmann 2c2a258718 Removed hardcode GarageServer.IO server path 2015-05-16 20:52:54 -05:00
Jeremiah Billmann 4b99023ce1 Corrected doctype 2015-05-16 15:17:35 -05:00
Jeremiah Billmann 4f8088871a Resolves #23 2014-11-15 04:02:36 +00:00
Jeremiah Billmann ac4bc7be48 Reduced size of internal JSON property names. 2014-11-15 02:52:24 +00:00
Jeremiah Billmann 06ecaacb91 Fixed socket.io client disconnect 2014-10-23 17:49:39 +00:00
Jeremiah Billmann 34c3d07852 Refixed interp issue related to previous snapping issue 2014-10-23 17:14:08 +00:00
Jeremiah Billmann 7bb18d3c3f Added Cloud9 folders to gitignore 2014-10-23 12:10:31 -05:00
Jeremiah Billmann ff67b5e945 Fixed a merging issue 2014-10-23 16:54:23 +00:00
Jeremiah Billmann 3e869e6d9b Merged changes 2014-10-23 16:40:07 +00:00
Jeremiah Billmann 5c01c26530 Upgraded to Socket.IO v1.1 2014-10-23 15:22:57 +00:00
Jeremiah Billmann 56ca345989 Updated node version in example 2013-09-20 12:08:10 -05:00
Jeremiah Billmann 191ebe0bef Fixed interp issue related to prior snapping issue 2013-09-20 11:50:04 -05:00
Jeremiah Billmann b5ae81e936 Added demo links 2013-09-20 10:53:13 -05:00
Jeremiah Billmann aa8eec72f2 Fixed issue with low latency and snapping. 2013-09-20 10:50:06 -05:00
Jeremiah Billmann 15d7670705 Updated version for example 2013-09-20 09:13:44 -05:00
Jeremiah Billmann 710f56555f Correction 2013-09-20 09:12:43 -05:00
Jeremiah Billmann f42847f0ac Updated example package.json for hosting 2013-09-20 09:12:02 -05:00
Jeremiah Billmann 7fa638c940 Version change 0.6.0 2013-09-20 09:13:07 -04:00
Jeremiah Billmann 790c69305a Resolved: #12 Regions - limit state bandwidth 2013-08-23 13:30:13 -04:00
Jeremiah Billmann e805c2d991 Updated docs 2013-08-18 16:00:00 -04:00
Jeremiah Billmann 80c5b28d85 Updated example docs 2013-08-18 15:28:21 -04:00
Jeremiah Billmann c670fdf909 Typo 2013-08-18 14:20:09 -04:00
Jeremiah Billmann 14a8efd70b Typo 2013-08-12 12:40:15 -04:00
Jeremiah Billmann f7c901ac4b Added logo 2013-08-12 12:39:20 -04:00
Jeremiah Billmann 878ce0458f Updated docs 2013-08-09 15:19:46 -04:00
Jeremiah Billmann d3cac4ced2 Updated docs 2013-08-09 15:18:48 -04:00
Jeremiah Billmann 2564f3492f Updated docs 2013-08-09 15:17:17 -04:00
Jeremiah Billmann 0eef87e5a9 Removed logo for now 2013-08-08 21:33:37 -04:00
Jeremiah Billmann 88b3c80d2b Added logo to readme 2013-08-08 21:32:49 -04:00
Jeremiah Billmann 32d6a15f19 Added logo to readme 2013-08-08 21:27:49 -04:00
Jeremiah Billmann b74ae6820a Added logo to readme 2013-08-08 20:41:19 -04:00
Jeremiah Billmann 3834200c5a Added logo 2013-08-08 20:39:27 -04:00
Jeremiah Billmann 779154d861 Documentation typos 2013-08-07 23:07:44 -04:00
Jeremiah Billmann 6ebc6ad84c Minor... 2013-08-06 22:42:21 -04:00
Jeremiah Billmann a622b12c7e Updated features 2013-08-05 23:01:37 -04:00
Jeremiah Billmann 6e5d29b058 Updated features 2013-08-05 22:53:49 -04:00
Jeremiah Billmann 347b399523 Updated features 2013-08-05 22:53:31 -04:00
Jeremiah Billmann cd6c7b9d83 Updated features 2013-08-05 22:53:16 -04:00
Jeremiah Billmann 55d26e51e7 Fixed typo 2013-08-05 22:41:53 -04:00
Jeremiah Billmann 486bf65403 Fixed typo 2013-08-05 22:41:25 -04:00
Jeremiah Billmann 6afb03a3a5 Added package file for example dependencies and readme 2013-08-05 22:40:08 -04:00
Jeremiah Billmann 621a4edf17 Corrected name 2013-08-05 22:33:03 -04:00
Jeremiah Billmann 0abca095c5 Added package file for example application 2013-08-05 22:26:54 -04:00
Jeremiah Billmann f31ee7e529 Fixed typo 2013-08-05 21:45:35 -04:00
22 changed files with 457 additions and 144 deletions
+2
View File
@@ -1,6 +1,8 @@
.idea
node_modules
.c9revisions
.c9
.settings
lib-cov
*.seed
*.log
+11 -4
View File
@@ -1,18 +1,25 @@
# GarageServer.IO
A simple, lightweight, HTML multiplayer game server (and client) for Node.js
![garagerserver.io](https://raw.github.com/jbillmann/GarageServer.IO/master/documentation/GarageServerIO_logo.png)
## Features
- Authoritative Game Server
- Client Side / Input Prediciton
- Authoritative Node.js game server and JavaScript client
- Client Side / Input Prediction
- Client Side Smoothing
- Entity Interpolation
- Server State History
- Server and Client Messaging
- Server Reconciliation
- Works with any Rendering and/or Physics Engine
- Region Broadcasting
- Works with any rendering and/or physics engine
## Install
### Demo
A playable [demo](http://garageserverio.jit.su/) of the [example](https://github.com/jbillmann/GarageServer.IO/tree/master/example) using GarageServer.IO.
### Server
`$ npm install garageserver.io`
@@ -33,4 +40,4 @@ After server install, copy the client from `/node_modules/garageserver.io/client
## License
[MIT License](https://github.com/jbillmann/GarageServer.IO/blob/master/LICENSE.md)
[MIT License](https://github.com/jbillmann/GarageServer.IO/blob/master/LICENSE.md)
+103 -41
View File
@@ -10,7 +10,7 @@ options = {
onEvent(callback(data)),
onWorldState(callback(state)),
onPing(callback(pingDelay)),
onUpdatePlayerPrediction(callback(currentState, inputs, deltaTime) : newState),
onUpdateClientPredictionReady(callback(playerId, playerCurrentState, entityCurrentStates, inputs, deltaTime)),
onInterpolation(callback(previousState, targetState, amount) : newState),
onReady(callback),
logging: true
@@ -20,6 +20,10 @@ api methods
addInput(input)
getPlayerStates : [, playerState]
getEntityStates : [, entityState]
updatePlayerState(id, state)
addEntity(id, state)
updateEntityState(id, state)
removeEntity(id)
getId() : playerid
sendServerEvent(data)
*/
@@ -84,10 +88,12 @@ var GarageServerIO = (function (socketio) {
this.time = time;
}
function Entity(id, maxUpdateBuffer) {
function Entity(id, referrerId, referrerSeq, maxUpdateBuffer) {
this.updates = [];
this.maxUpdateBuffer = maxUpdateBuffer;
this.id = id;
this.referrerId = referrerId;
this.referrerSeq = referrerSeq;
this.state = {};
this.inputController = new InputController();
}
@@ -138,7 +144,7 @@ var GarageServerIO = (function (socketio) {
};
function Player(id, maxUpdateBuffer) {
Entity.call(this, id, maxUpdateBuffer);
Entity.call(this, id, null, null, maxUpdateBuffer);
}
Player.prototype = Object.create(Entity.prototype);
@@ -147,8 +153,9 @@ var GarageServerIO = (function (socketio) {
this.maxUpdateBuffer = maxUpdateBuffer;
}
EntityController.prototype = {
add: function (id) {
var entity = new Entity(id, this.maxUpdateBuffer);
add: function (id, referrerId) {
var referrerSeq = this.entities.filter(function (value) { return value.referrerId === referrerId; }).length;
var entity = new Entity(id, referrerId, referrerSeq, this.maxUpdateBuffer);
this.entities.push(entity);
return entity;
},
@@ -181,13 +188,16 @@ var GarageServerIO = (function (socketio) {
initializeGarageServer = function (path, options) {
_options = options;
if(path == null || path.length <= 0) {
throw new Error('GarageServer.IO client is missing the server path - please verify the path argument passed to GarageServerIO.initializeGarageServer.');
}
_socket = _io.connect(path + '/garageserver.io');
registerSocketEvents();
},
registerSocketEvents = function () {
_socket.on('connect', function () {
_stateController.id = _socket.socket.sessionid;
_stateController.id = _socket.io.engine.id;
if (_options.logging) {
console.log('garageserver.io:: socket connect');
}
@@ -197,7 +207,7 @@ var GarageServerIO = (function (socketio) {
}
});
_socket.on('state', function (data) {
_socket.on('s', function (data) {
if (_options.onWorldState) {
_options.onWorldState(data.worldState);
}
@@ -219,7 +229,7 @@ var GarageServerIO = (function (socketio) {
}
setInterval(function (){
_socket.emit('ping', new Date().getTime());
_socket.emit('p', new Date().getTime());
}, _stateController.pingInterval);
});
@@ -241,11 +251,11 @@ var GarageServerIO = (function (socketio) {
}
});
_socket.on('update', function (data) {
_socket.on('u', function (data) {
update(data);
});
_socket.on('ping', function (data) {
_socket.on('p', function (data) {
_stateController.pingDelay = new Date().getTime() - data;
if (_options.logging) {
console.log('garageserver.io:: socket ping delay ' + _stateController.pingDelay);
@@ -255,7 +265,7 @@ var GarageServerIO = (function (socketio) {
}
});
_socket.on('removePlayer', function (id) {
_socket.on('rp', function (id) {
removePlayer(id);
if (_options.logging) {
console.log('garageserver.io:: socket removePlayer ' + id);
@@ -265,17 +275,27 @@ var GarageServerIO = (function (socketio) {
}
});
_socket.on('removeEntity', function (id) {
removeEntity(id);
_socket.on('re', function (data) {
if (_stateController.clientSidePrediction) {
_entityController.entities.forEach(function (entity) {
if (entity.referrerId === data.id && entity.referrerSeq === data.seq) {
removeEntity(entity.id);
}
});
}
else {
removeEntity(data.id);
}
if (_options.logging) {
console.log('garageserver.io:: socket removeEntity ' + id);
console.log('garageserver.io:: socket removeEntity ' + data.id);
}
if (_options.onEntityRemove) {
_options.onEntityRemove(id);
_options.onEntityRemove(data.id);
}
});
_socket.on('event', function(data) {
_socket.on('e', function(data) {
if (_options.logging) {
console.log('garageserver.io:: socket event ' + data);
}
@@ -290,17 +310,24 @@ var GarageServerIO = (function (socketio) {
},
sendServerEvent = function (data) {
_socket.emit('event', data);
_socket.emit('e', data);
},
addInput = function (clientInput) {
_playerController.entities.some(function (player) {
if (player.id === _stateController.id) {
if (_stateController.clientSidePrediction && _options.onUpdatePlayerPrediction) {
if (_stateController.clientSidePrediction && _options.onUpdateClientPredictionReady) {
var entityCurrentStates = [];
_entityController.entities.forEach(function(entity) {
if (entity.referrerId === player.id) {
entityCurrentStates.push({ id: entity.id, state: entity.state });
}
});
player.inputController.add(clientInput);
player.state = _options.onUpdatePlayerPrediction(player.state, [{ input: clientInput }], _stateController.physicsDelta);
_options.onUpdateClientPredictionReady(player.id, player.state, entityCurrentStates, [{ input: clientInput }], _stateController.physicsDelta);
}
_socket.emit('input', [ clientInput, player.inputController.sequenceNumber, _stateController.renderTime ]);
_socket.emit('i', [ clientInput, player.inputController.sequenceNumber, _stateController.renderTime ]);
}
});
},
@@ -336,25 +363,59 @@ var GarageServerIO = (function (socketio) {
return entityStates;
},
updatePlayerState = function (id, state) {
_playerController.entities.some(function(player) {
if (player.id === id) {
player.state = state;
return true;
}
});
},
removePlayer = function (id) {
_playerController.remove(id);
},
addEntity = function (id) {
_entityController.add(id, _stateController.id);
},
updateEntityState = function (id, state) {
_entityController.entities.some(function(entity) {
if (entity.id === id) {
entity.state = state;
return true;
}
});
},
removeEntity = function (id) {
_entityController.remove(id);
},
update = function (data) {
_stateController.setTime(data.time);
_stateController.setTime(data.t);
updatePlayers(data);
updateEntities(data);
},
updatePlayers = function (data) {
data.playerStates.forEach(function (playerState) {
updateEntity(_playerController, playerState, data.time);
data.ps.forEach(function (playerState) {
var playerFound = false;
_playerController.entities.some(function (player) {
if (player.id === playerState[0]) {
playerFound = true;
player.updateState(playerState[1], playerState[2], data.t);
return true;
}
});
if (!playerFound) {
var newPlayer = _playerController.add(playerState[0]);
newPlayer.addUpdate(playerState[1], playerState[2], data.t);
}
if (_options.onPlayerUpdate) {
_options.onPlayerUpdate(playerState[1]);
@@ -363,8 +424,20 @@ var GarageServerIO = (function (socketio) {
},
updateEntities = function (data) {
data.entityStates.forEach(function (entityState) {
updateEntity(_entityController, entityState, data.time);
data.es.forEach(function (entityState) {
var entityFound = false;
_entityController.entities.some(function (entity) {
if (entity.id === entityState[0] || (entity.referrerId === entityState[3] && entity.referrerSeq === entityState[4])) {
entityFound = true;
entity.updateState(entityState[1], entityState[2], data.t);
return true;
}
});
if (!entityFound) {
var newEntity = _entityController.add(entityState[0]);
newEntity.addUpdate(entityState[1], entityState[2], data.t);
}
if (_options.onEntityUpdate) {
_options.onEntityUpdate(entityState[1]);
@@ -372,21 +445,6 @@ var GarageServerIO = (function (socketio) {
});
},
updateEntity = function (entityController, entityState, time) {
var entityFound = false;
entityController.entities.some(function (entity) {
if (entity.id === entityState[0]) {
entityFound = true;
entity.updateState(entityState[1], entityState[2], time);
return true;
}
});
if (!entityFound) {
var newEntity = entityController.add(entityState[0]);
newEntity.addUpdate(entityState[1], entityState[2], time);
}
},
processEntityStatesCurrent = function (entityController) {
entityController.entities.forEach(function (entity) {
if (entity.anyUpdates() && !entity.inputController.any()) {
@@ -405,7 +463,7 @@ var GarageServerIO = (function (socketio) {
newState = _options.onInterpolation(positions.previous.state, positions.target.state, amount);
entity.state = newState = _options.onInterpolation(entity.state, newState, _stateController.smoothingFactor);
}
else {
else if (entity.inputController.sequenceNumber === 1) {
entity.state = entity.latestUpdate().state;
}
}
@@ -425,6 +483,10 @@ var GarageServerIO = (function (socketio) {
addInput: addInput,
getPlayerStates: getPlayerStates,
getEntityStates: getEntityStates,
updatePlayerState: updatePlayerState,
addEntity: addEntity,
updateEntityState: updateEntityState,
removeEntity: removeEntity,
getId: getId,
sendServerEvent: sendServerEvent
};
+56 -5
View File
@@ -113,18 +113,22 @@ The current ping in milliseconds.
---
```js
options.onUpdatePlayerPrediction(callback(currentState, inputs, deltaTime) : newState)
options.onUpdateClientPredictionReady(callback(playerId, playerCurrentState, entityCurrentStates: [, {id, state}], inputs, deltaTime))
```
**_Returns:_ object literal**
If using client side prediction, this callback should return the new state, `newState`, based on the current state, inputs to be processed, and the delta time.
If using client side prediction, this callback will fire when you should update player and entity states, based on the current states for player and entities, inputs to be processed, and the delta time.
`callback` **function**
Function to be invoked upon event firing.
`currentState` **object literal**
`playerId` **string**
The id of the player.
`playerCurrentState` **object literal**
The current state of the player.
`entityCurrentStates` **array**
The list of all entities and their current state that were invoked by the player.
`inputs` **array**
List of all the inputs to be processed.
@@ -200,6 +204,53 @@ Returns a list of current entity states from the most recent broadcast depending
The id of the entity.
`state` **object literal**
Object containing all properties specific to entity state in your game.
#### updatePlayerState
---
```js
GarageServerIO.updatePlayerState(id, state)
```
Notify the GarageServer.IO client of the new player state - this is intended to be used with client side prediction.
`id` **string**
The id of the player.
`state` **object literal**
Object containing all properties specific to the new player state in your game.
#### addEntity
---
```js
GarageServerIO.addEntity(id, state)
```
Notify the GarageServer.IO client of a new entity with an initial state - this is intended to be used with client side prediction.
`id` **string**
The id of the entity.
`state` **object literal**
Object containing all properties specific to new entity state in your game.
#### updateEntityState
---
```js
GarageServerIO.updateEntityState(id, state)
```
Notify the GarageServer.IO client of the new entity state - this is intended to be used with client side prediction.
`id` **string**
The id of the entity.
`state` **object literal**
Object containing all properties specific to the new entity state in your game.
#### removeEntity
---
```js
GarageServerIO.removeEntity(id)
```
Notify the GarageServer.IO client that an entity has been removed from the game - this is intended to be used with client side prediction.
`id` **string**
The id of the entity.
#### getId
---
```js
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+14 -13
View File
@@ -15,11 +15,11 @@ var server = garageServer.createGarageServer(sockets,
worldState: { width: '400px', height: '400px'; }
});
```
**2.** Start GarageServer.IO instance prior to starting physics loop. This starts the clock that is used for broadcasting state and storing state history.
**2.** Start GarageServer.IO instance prior to starting your physics loop. This starts the clock that is used for broadcasting state and storing state history.
```js
server.start();
```
**3.** Inside physics loop, process inputs for players, process entites and update their states. Note that state is an object literal effectively offering up any grab bag of properties that are specific to your game's state.
**3.** Inside your physics loop, process the inputs for players, process entites and update their states. Note that state is an object literal effectively offering up any grab bag of properties that are specific to your game's state.
```js
var players = server.getPlayers(),
entities = server.getEntities();
@@ -48,23 +48,24 @@ entities.forEach(function (entity) {
**1.** Initialize GarageServer.IO.
```js
GarageServerIO.initializeGarageServer('http://insertmygameurlhere.com', {
GarageServerIO.initializeGarageServer('http://insertmygameserverurlhere.com', {
onReady: function () {
// Call your game loop
},
onUpdatePlayerPrediction: function (state, inputs, deltaTime) {
onUpdateClientPredictionReady: function (playerId, playerCurrentState, entityCurrentStates, inputs, deltaTime) {
var newState = {};
if (!player.state.x) {
player.state.x = 0;
if (!playerCurrentState.x) {
playerCurrentState.x = 0;
}
for (i = 0; i < player.inputs.length; i ++) {
if (player.inputs[i].input === 'left') {
newState.x = player.state.x - (50 * deltaTime);
for (i = 0; i < inputs.length; i ++) {
if (inputs[i].input === 'left') {
newState.x = playerCurrentState.x - (50 * deltaTime);
} else if (inputs[i].input === 'right') {
newState.x = player.state.x + (50 * deltaTime);
newState.x = playerCurrentState.x + (50 * deltaTime);
}
}
return newState;
GarageServerIO.updatePlayerState(playerid, newState);
},
onInterpolation: function (previousState, targetState, amount) {
return { x: (previousState.x + amount * (targetState.x - previousState.x)) };
@@ -75,11 +76,11 @@ GarageServerIO.initializeGarageServer('http://insertmygameurlhere.com', {
}
};
```
**2.** Inside physics loop, capture and send input via GarageServer.IO. Similar to state, input offers up any grab bag of properties specific to your game's input.
**2.** Inside your physics loop, capture and send input via GarageServer.IO. Similar to state, input offers up any grab bag of properties specific to your game's input.
```js
GarageServerIO.addInput(myInput);
```
**3.** Inside render loop, extract player and entity states - this will retrieve the current states based on your interpolation, prediction and smoothing settings. Players are effectively clients connected to GarageServer.IO whereas entities are determined by players inputs and controlled by the server.
**3.** Inside your render loop, extract player and entity states - this will retrieve the current states based on your interpolation, prediction and smoothing settings. Players are effectively clients connected to GarageServer.IO whereas entities are determined by players inputs and controlled by the server.
```js
var playerStates = GarageServerIO.getPlayerStates(),
entityStates = GarageServerIO.getEntityStates();
+32 -4
View File
@@ -181,7 +181,7 @@ List of all previous states and their execution times up to `options.maxHistoryS
GarageServerIO.updatePlayerState(id, state)
```
Notify GargeServer.IO of a new state with an id of the player to be updated. The idea here is that this call is made during each pass of the physics loop on the server.
Notify GarageServer.IO of a new state with an id of the player to be updated. The idea here is that this call is made during each pass of the physics loop on the server.
`id` **string**
Id of the player whose state should be updated.
@@ -193,7 +193,7 @@ New state of the entity containing all of the properties specific to a player fo
GarageServerIO.updateEntityState(id, state)
```
Notify GargeServer.IO of a new state with an id of the entity to be updated. The idea here is that this call is made during each pass of the physics loop on the server.
Notify GarageServer.IO of a new state with an id of the entity to be updated. The idea here is that this call is made during each pass of the physics loop on the server.
`id` **string**
Id of the entity whose state should be updated.
@@ -202,13 +202,15 @@ New state of the entity containing all of the properties specific to an entity f
#### addEntity
---
```js
GarageServerIO.addEntity(id)
GarageServerIO.addEntity(id, referrerId)
```
Notify GarageServer.IO that a new entity has been added to the game.
`id` **string**
Id of the entity to be added.
`referrerId` **string**
Id of the player who invoked/create the entity - optional and used primarily for client side prediction.
#### removeEntity
---
```js
@@ -236,4 +238,30 @@ GarageServerIO.sendPlayersEvent(data)
```
Allows server to broadcast events to all players. Use this to make custom calls to GarageServer.IO clients for your game.
`data` **object literal**
Object containing all properties specific to the custom event.
Object containing all properties specific to the custom event.
#### setPlayerRegion
---
```js
GarageServerIO.setPlayerRegion(id, region)
```
Sets the player region. GarageServer.IO will broadcast the state of players and entities who share the same region. NOTE: This will implicitly enable GarageServer.IO region broadcasting - only those players and entities with regions with be notified of state. Use `clearRegions` to revert region broadcasting.
`id` **string**
Id of the player to receive event.
`region` **string**
Name of the region.
#### setEntityRegion
---
```js
GarageServerIO.setEntityRegion(id, region)
```
Sets the entity region. GarageServer.IO will broadcast the state of players and entities who share the same region. NOTE: This will implicitly enable GarageServer.IO region broadcasting - only those players and entities with regions with be notified of state. Use `clearRegions` to revert region broadcasting.
`id` **string**
Id of the entity to receive event.
`region` **string**
Name of the region.
#### clearRegions
---
```js
GarageServerIO.clearRegions()
```
Clears all regions associated with players and entities. GarageServer.IO will default back to broadcasting state to all players.
+15
View File
@@ -0,0 +1,15 @@
# GarageServer.IO Example
A sample application using GarageServer.IO
### Demo
A playable [demo](http://garageserverio.jit.su/) of this example using GarageServer.IO.
## Install
Change the `path` parameter of `GarageServerIO.initializeGarageServer` that is found in file, `/public/javascripts/game.js`, to point to the URL where your Node app runs. I.e. `http://127.0.0.1:3000`. Note that the example app uses a default port of `process.env.PORT || 3000`. This can be changed in `app.js`.
Execute `$ npm install` in the `example/` root directory and `$ node app.js` to run the application.
NOTE: The example has `options.clientSidePrediction` and `options.interpolation` enabled on the server. If you're running it locally (no ping), you may want to disable these (`false`).
+1 -1
View File
@@ -7,7 +7,7 @@ var express = require('express'),
app = express();
app.configure(function () {
app.set('port', process.env.PORT || 2121);
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
+1 -1
View File
@@ -29,7 +29,7 @@ Game.prototype.update = function () {
self = this;
players.forEach(function (player) {
var newState = gamePhysics.getNewPlayerState(player.state, player.inputs, self.physicsDelta, self.server);
var newState = gamePhysics.getNewPlayerState(player.id, player.state, player.inputs, self.physicsDelta, self.server);
self.server.updatePlayerState(player.id, newState);
});
+22
View File
@@ -0,0 +1,22 @@
{
"name": "garageserver.io.example",
"subdomain": "garageserverio",
"description": "A sample application using garageserver.io",
"author": {
"name": "Jeremiah Billmann",
"email": "jcbillmann@yahoo.com"
},
"version": "0.6.0",
"license": "MIT",
"dependencies": {
"express": "^3.1.0",
"jade": "*",
"socket.io": "^1.1.0"
},
"engines": {
"node": ">=0.10"
},
"scripts": {
"start": "node app.js"
}
}
+7 -2
View File
@@ -7,10 +7,15 @@ $(function () {
window.addEventListener('resize', resizeCanvas, false);
GarageServerIO.initializeGarageServer('http://garageserver_io.jbillmann.c9.io', {
GarageServerIO.initializeGarageServer('', {
logging: true,
onReady: startGame,
onUpdatePlayerPrediction: GamePhysics.getNewPlayerState,
onUpdateClientPredictionReady: function (playerId, playerCurrentState, entityCurrentStates, inputs, deltaTime) {
entityCurrentStates.forEach(function (entity) {
GarageServerIO.updateEntityState(entity.id, GamePhysics.getNewEntityState(entity.state, deltaTime));
});
GarageServerIO.updatePlayerState(playerId, GamePhysics.getNewPlayerState(playerId, playerCurrentState, inputs, deltaTime, GarageServerIO));
},
onInterpolation: GamePhysics.getInterpolatedState
});
File diff suppressed because one or more lines are too long
+31 -21
View File
@@ -1,37 +1,44 @@
(function(exports){
exports.getNewPlayerState = function (state, inputs, deltaTime, garageServer) {
var i = 0, distance = 0;
exports.getNewPlayerState = function (id, state, inputs, deltaTime, garageServer) {
var i = 0, distance = 0, newState = {};
if (!state.ang && state.ang !== 0) {
state.ang = 0;
state.x = 0;
state.y = 0;
state.ship = Math.floor(Math.random() * 9) + 1;
state.lastFire = new Date().getTime();
newState.ang = 0;
newState.x = 0;
newState.y = 0;
newState.ship = Math.floor(Math.random() * 9) + 1;
newState.lastFire = new Date().getTime();
}
else {
newState.ang = state.ang;
newState.x = state.x;
newState.y = state.y;
newState.ship = state.ship;
newState.lastFire = state.lastFire;
}
for (i = 0; i < inputs.length; i ++) {
if (inputs[i].input === 'left') {
state.ang -= (125 * deltaTime);
newState.ang -= (125 * deltaTime);
} else if (inputs[i].input === 'right') {
state.ang += (125 * deltaTime);
newState.ang += (125 * deltaTime);
} else if (inputs[i].input === 'up') {
distance += (125 * deltaTime);
} else if (inputs[i].input === 'space') {
if (garageServer && (new Date().getTime() - state.lastFire) > 1000) {
if ((new Date().getTime() - newState.lastFire) > 1000) {
var newId = guid();
garageServer.addEntity(newId);
garageServer.updateEntityState(newId, { x: state.x, y: state.y, ang: state.ang } );
state.lastFire = new Date().getTime();
garageServer.addEntity(newId, id);
garageServer.updateEntityState(newId, { x: newState.x, y: newState.y, ang: newState.ang } );
newState.lastFire = new Date().getTime();
}
}
}
var newPoint = getPoint(state.ang, distance, state.x, state.y);
state.x = newPoint.x;
state.y = newPoint.y;
var newPoint = getPoint(newState.ang, distance, newState.x, newState.y);
newState.x = newPoint.x;
newState.y = newPoint.y;
return state;
return newState;
};
function getPoint(angle, distance, oldX, oldY) {
@@ -43,12 +50,15 @@
}
exports.getNewEntityState = function (state, deltaTime) {
var newState = {};
var distance = 300 * deltaTime;
var newPoint = getPoint(state.ang, distance, state.x, state.y);
state.x = newPoint.x;
state.y = newPoint.y;
newState.ang = state.ang;
newState.x = newPoint.x;
newState.y = newPoint.y;
return state;
return newState;
};
exports.getInterpolatedState = function (previousState, targetState, amount) {
+1 -1
View File
@@ -1,4 +1,4 @@
doctype 5
doctype html
html
head
title= title
+8 -2
View File
@@ -8,7 +8,7 @@ function EntityController (maxHistorySecondBuffer) {
}
EntityController.prototype = {
add: function (id) {
add: function (id, referrerId) {
var newEntity, entityFound = false;
this.entities.some(function (entity) {
@@ -20,7 +20,8 @@ EntityController.prototype = {
});
if (!entityFound) {
newEntity = new entity(id, this.maxHistorySecondBuffer);
var referrerSeq = this.entities.filter(function (value) { return value.referrerId === referrerId; }).length;
newEntity = new entity(id, referrerId, referrerSeq, this.maxHistorySecondBuffer);
this.entities.push(newEntity);
}
return newEntity;
@@ -32,5 +33,10 @@ EntityController.prototype = {
return;
}
}
},
clearRegions: function () {
for (var i = 0; i < this.entities.length; i ++) {
this.entities[i].setRegion('');
}
}
};
+4 -4
View File
@@ -9,11 +9,11 @@ function PlayerController (maxHistorySecondBuffer) {
PlayerController.prototype = Object.create(entityController.prototype);
PlayerController.prototype.add = function (client) {
PlayerController.prototype.add = function (socket) {
var newPlayer, playerFound = false;
this.entities.some(function (player) {
if (player.client.id === client.id) {
if (player.id === socket.id) {
newPlayer = player;
playerFound = true;
return true;
@@ -21,7 +21,7 @@ PlayerController.prototype.add = function (client) {
});
if (!playerFound) {
newPlayer = new player(client, this.maxHistorySecondBuffer);
newPlayer = new player(socket, this.maxHistorySecondBuffer);
this.entities.push(newPlayer);
}
return newPlayer;
@@ -29,7 +29,7 @@ PlayerController.prototype.add = function (client) {
PlayerController.prototype.addInput = function (id, input, sequence, time) {
this.entities.some(function (player) {
if (player.client.id === id) {
if (player.id === id) {
player.inputs.push({ input: input, seq: sequence, time: time });
return true;
}
+7 -1
View File
@@ -2,12 +2,15 @@ var history = require('./history');
exports = module.exports = Entity;
function Entity (id, maxHistorySecondBuffer) {
function Entity (id, referrerId, referrerSeq, maxHistorySecondBuffer) {
this.state = {};
this.sequence = 1;
this.id = id;
this.referrerId = referrerId;
this.referrerSeq = referrerSeq;
this.maxHistorySecondBuffer = maxHistorySecondBuffer;
this.stateHistory = [];
this.region = '';
}
Entity.prototype = {
@@ -31,5 +34,8 @@ Entity.prototype = {
if (spliceTo > 0) {
this.stateHistory.splice(0, spliceTo);
}
},
setRegion: function (region) {
this.region = region;
}
};
+10 -3
View File
@@ -2,16 +2,23 @@ var entity = require('./entity');
exports = module.exports = Player;
function Player (client, maxHistorySecondBuffer) {
entity.call(this, client.id, maxHistorySecondBuffer);
this.client = client;
function Player (socket, maxHistorySecondBuffer) {
entity.call(this, socket.id, null, null, maxHistorySecondBuffer);
this.socket = socket;
this.inputs = [];
}
Player.prototype = Object.create(entity.prototype);
Player.prototype.setRegion = function (region) {
this.socket.join(region);
this.socket.leave(this.region);
this.region = region;
};
Player.prototype.addState = function (state, executionTime) {
this.addHistory(state, executionTime);
this.state = state;
this.sequence += this.inputs.length;
this.inputs = [];
};
+38 -15
View File
@@ -36,9 +36,14 @@ function GarageServer(socketio, options) {
this.socketPath = namespace;
this.io = socketio;
this.clientSidePrediction = options.clientSidePrediction;
this.registerSocketEvents(options);
this.game = new garageServerGame(options, function (state) {
socketio.of(namespace).emit('update' ,state);
this.game = new garageServerGame(options, function (state, region) {
if (!region) {
socketio.of(namespace).emit('u' ,state);
} else {
socketio.of(namespace).in(region).emit('u' ,state);
}
});
}
@@ -49,7 +54,7 @@ GarageServer.prototype.registerSocketEvents = function (options) {
if (options.logging) {
console.log('garageserver.io:: socket ' + socket.id + ' connection');
}
socket.emit('state', {
socket.emit('s', {
physicsDelta: (options.physicsInterval ? options.physicsInterval : 15) / 1000,
smoothingFactor: options.smoothingFactor ? options.smoothingFactor : 0.3,
interpolation: options.interpolation ? options.interpolation : false,
@@ -68,26 +73,26 @@ GarageServer.prototype.registerSocketEvents = function (options) {
self.onPlayerDisconnect(socket, options);
});
socket.on('input', function (data) {
socket.on('i', function (data) {
if (options.logging) {
console.log('garageserver.io:: socket input ' + socket.id + ' ' + data[0] + ' ' + data[1]);
}
self.onPlayerInput(socket, data, options);
});
socket.on('ping', function (data) {
socket.on('p', function (data) {
if (options.logging) {
console.log('garageserver.io:: socket ping ' + data);
}
self.onPing(socket, data, options);
});
socket.on('event', function (data) {
socket.on('e', function (data) {
if (options.logging) {
console.log('garageserver.io:: event ' + data);
}
if (options.OnEvent) {
options.OnEvent(data);
if (options.onEvent) {
options.onEvent(data);
}
});
});
@@ -102,7 +107,7 @@ GarageServer.prototype.onPlayerConnect = function (socket, options) {
GarageServer.prototype.onPlayerDisconnect = function (socket, options) {
this.game.removePlayer(socket.id);
socket.broadcast.emit('removePlayer', socket.id);
socket.broadcast.emit('rp', socket.id);
if (options.onPlayerDisconnect) {
options.onPlayerDisconnect(socket);
}
@@ -116,7 +121,7 @@ GarageServer.prototype.onPlayerInput = function (socket, input, options) {
};
GarageServer.prototype.onPing = function (socket, data, options) {
socket.emit('ping', data);
socket.emit('p', data);
if (options.onPing) {
options.onPing(socket, data);
}
@@ -146,12 +151,18 @@ GarageServer.prototype.updateEntityState = function (id, state) {
this.game.updateEntityState(id, state);
};
GarageServer.prototype.addEntity = function (id) {
this.game.addEntity(id);
GarageServer.prototype.addEntity = function (id, referrerId) {
this.game.addEntity(id, referrerId);
};
GarageServer.prototype.removeEntity = function (id) {
this.io.of(this.socketPath).emit('removeEntity', id);
if (this.clientSidePrediction) {
var entity = this.game.getEntity(id);
this.io.of(this.socketPath).emit('re', { id: entity.referrerId, seq: entity.referrerSeq });
}
else {
this.io.of(this.socketPath).emit('re', { id: id });
}
this.game.removeEntity(id);
};
@@ -160,9 +171,21 @@ GarageServer.prototype.sendPlayerEvent = function (id, data) {
};
GarageServer.prototype.sendPlayersEvent = function (data) {
this.io.of(this.socketPath).emit('event', data);
this.io.of(this.socketPath).emit('e', data);
};
GarageServer.prototype.setPlayerRegion = function (id, region) {
this.game.setPlayerRegion(id, region);
};
GarageServer.prototype.setEntityRegion = function (id, region) {
this.game.setEntityRegion(id, region);
};
GarageServer.prototype.clearRegions = function () {
this.game.clearRegions();
};
exports.createGarageServer = function (io, options) {
return new GarageServer(io, options);
};
};
+92 -25
View File
@@ -11,6 +11,7 @@ function GarageServerGame(options, broadcastCallback) {
this.playerController = new playerController(this.options.maxHistorySecondBuffer ? this.options.maxHistorySecondBuffer : 1000);
this.entityController = new entityController(this.options.maxHistorySecondBuffer ? this.options.maxHistorySecondBuffer : 1000);
this.broadcastCallback = broadcastCallback;
this.regions = [];
}
GarageServerGame.prototype.start = function () {
@@ -25,19 +26,50 @@ GarageServerGame.prototype.stop = function () {
};
GarageServerGame.prototype.broadcastState = function () {
var currentTime = new Date().getTime() - this.startTime,
state = { time: currentTime, playerStates: [], entityStates: [] };
var i = 0, currentTime = new Date().getTime() - this.startTime,
state = { t: currentTime, ps: [], es: [] };
state.playerStates = this.getState(this.playerController);
state.entityStates = this.getState(this.entityController);
this.broadcastCallback(state);
if (this.regions.length > 0) {
for (i = 0; i < this.regions.length; i ++) {
state.ps = this.getStateByRegion(this.playerController, this.regions[i]);
state.es = this.getStateByRegion(this.entityController, this.regions[i]);
this.broadcastCallback(state, this.regions[i]);
}
} else {
state.ps = this.getState(this.playerController);
state.es = this.getState(this.entityController);
this.broadcastCallback(state);
}
};
GarageServerGame.prototype.getState = function (controller) {
var states = [];
var states = [],
clientSidePrediction = this.options.clientSidePrediction;
controller.entities.forEach(function (entity) {
states.push([ entity.id, entity.state, entity.sequence ]);
if (clientSidePrediction && entity.referrerId != null) {
states.push([ entity.id, entity.state, entity.sequence, entity.referrerId, entity.referrerSeq ]);
}
else {
states.push([ entity.id, entity.state, entity.sequence ]);
}
});
return states;
};
GarageServerGame.prototype.getStateByRegion = function (controller, region) {
var states = [],
clientSidePrediction = this.options.clientSidePrediction;
controller.entities.forEach(function (entity) {
if (entity.region === region) {
if (clientSidePrediction && entity.referrerId != null) {
states.push([ entity.id, entity.state, entity.sequence, entity.referrerId, entity.referrerSeq ]);
}
else {
states.push([ entity.id, entity.state, entity.sequence ]);
}
}
});
return states;
};
@@ -58,18 +90,6 @@ GarageServerGame.prototype.getEntities = function () {
return list;
};
GarageServerGame.prototype.getPlayer = function (id) {
var playerFound;
this.playerController.entities.some(function (player) {
if (player.id === id) {
playerFound = player;
return true;
}
});
return playerFound;
};
GarageServerGame.prototype.updatePlayerState = function (id, state) {
this.updateState(this.playerController, id, state);
};
@@ -89,18 +109,27 @@ GarageServerGame.prototype.updateState = function (controller, id, state) {
});
};
GarageServerGame.prototype.addPlayer = function (client) {
this.playerController.add(client);
GarageServerGame.prototype.addPlayer = function (socket) {
this.playerController.add(socket);
};
GarageServerGame.prototype.removePlayer = function (id) {
this.playerController.remove(id);
};
GarageServerGame.prototype.addEntity = function (id) {
this.entityController.add(id);
GarageServerGame.prototype.addEntity = function (id, referrerId) {
this.entityController.add(id, referrerId);
};
GarageServerGame.prototype.getEntity = function (id) {
for (var idx = 0; idx < this.entityController.entities.length; idx ++) {
if (this.entityController.entities[idx].id === id) {
return this.entityController.entities[idx];
}
}
};
GarageServerGame.prototype.removeEntity = function (id) {
this.entityController.remove(id);
};
@@ -112,8 +141,46 @@ GarageServerGame.prototype.addPlayerInput = function (id, input, sequence, time)
GarageServerGame.prototype.sendPlayerEvent = function (id, data) {
this.playerController.entities.some(function (player) {
if (player.id === id) {
player.client.emit('event', data);
player.socket.emit('e', data);
return true;
}
});
};
GarageServerGame.prototype.setPlayerRegion = function (id, region) {
this.setRegion(this.playerController, id, region);
};
GarageServerGame.prototype.setEntityRegion = function (id, region) {
this.setRegion(this.entityController, id, region);
};
GarageServerGame.prototype.setRegion = function (controller, id, region) {
var self = this;
controller.entities.some(function (entity) {
if (entity.id === id) {
entity.setRegion(region);
self.updateRegions(region);
return true;
}
});
};
GarageServerGame.prototype.updateRegions = function (region) {
var regionFound = false;
this.regions.forEach(function (item) {
if (item === region) {
regionFound = true;
}
});
if (!regionFound) {
this.regions.push(region);
}
};
GarageServerGame.prototype.clearRegions = function () {
this.regions = [];
this.entityController.clearRegions();
this.playerController.clearRegions();
};
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "Jeremiah Billmann",
"email": "jcbillmann@yahoo.com"
},
"version": "0.5.2",
"version": "0.6.0",
"homepage": "https://github.com/jbillmann/GarageServer.IO",
"license" : "MIT",
"keywords": [