Hello friend. Nengi.js is a networking library/engine for node.js and HTML5.
Nengi straddles two types of performance: volume and responsiveness.
With respect to volume, it is possible to make a nengi game that can support 100+ concurrent players or 50,000+ entities on a 20 tick server.
Regarding responsiveness, the advanced api consists of tools that can eliminate input delay, setup lag compensated collisions, and smooth away lag. This boils down to a game with controls so responsive that it feels like single player.
Have your players saying:
"HOW IS THIS A BROWSER GAME?!"
Join us on nengi's Discord server or help support development on timetocode's Patreon
Currently compatible with node 14; will not work with node 15+
This is a stable branch of nengi, the very same that has a several millions of gameplays on it. It however relies on a websocket library that has no support past node 14 (cWS.js).
Node 16 support (NEW, also may not work with node 17)
There is an experimental branch called 'sixteen' which uses uWS.js for its websocket layer. The previously used library (cWS.js) was itself a fork of an earlier version of uWS.js. The nengi api remains unchanged EXCEPT: bots don't work (yet) and the SSL syntax has changed (see uWS.js docs for new ssl syntax). There's no particular reason to think that this branch of nengi is less stable, but it is being kept separate until more users have tested it and bot support has been added (which will have to come through an addtional websocket library, likely the one simply named "ws"). If you are using this branch please share your experiences on the nengi discord server. It has been noted by the creator of uWS.js that they will only support major node versions (14, 16, 18, etc) so the same can be said for this branch of nengi.
Features
- Authoritative Server Model (anti-cheat)
- Binary compression
- Optimized game state snapshots
- Networking culling
- An easy to use api consisting of
- Entities - persistent objects that stay synced in real time
- Messages - events and other instantaneous occurrences
- Commands - input from game clients
- Channels - compose the above into more advanced features
- Interpolation
- Compatible with PIXI.js, Babylon.js, Three.js, and much more
- Clientside prediction api
- --> move instantly on the client
- --> nengi will provide reconciliation data if needed
- Lag compensated shots / collisions
- --> a player fires a shot
- --> nengi can rewind the game state to the point in time that the shot occured given the player's latency
Templates
Please note that these templates have not been updated recently, and while they should all work you may want to bump the nengi version to latest after checking them out.
There is a tutorial series for those learning nengi available at https://timetocode.com/nengi/intro-1. The code contained in these tutorials is pretty nice -- if you're making a 2D game in nengi without csp then forking this code makes for a great template.
Other templates:
- nice-proto - A prototype of a simpler API, this is nengi-2d-csp under the hood. The most mature version of this api is in the tutorial mentioned above.
- nengi-barebone - A barebone nengi template
- nengi-2d-basic - A simple 2d shooter
- nengi-2d-csp - A simple 2d shooter with prediction/compensation
- nengi-babylon-3d-shooter - A template for 3D predicted games with Babylon.js
- 3d-top-down - A top down game, in a 3d engine (Babylon.js)
Usage
The API documentation is the place to go for implementation details. But as an appetizer here is a tour of the the functionality associated with one of nengi's core features, the nengi.Entity
the nengi.Entity stack
// this is your own game object, it can have any properties or methods
class PlayerCharacter {
constructor(x, y) {
this.x = x
this.y = y
this.likesKittens = true
}
someLogic() {
//etc
}
}
// and this tells nengi what exactly to network about it
PlayerCharacter.protocol = {
x: { type: nengi.Float32, interp: true },
y: { type: nengi.Float32, interp: true }
}
The entity can then go on to be used in a nengi.Instance (the node.js game server)
const entity = new PlayerCharacter(50, 50)
instance.addEntity(entity)
Any changes that occur to the entity's networked properties (in this case x & y) will automatically be detected and synchronized. The nengi.Client (the HTML5 game client) will receive the following data which encompasses any state change on any entity:
const network = client.readNetwork()
network.entities.forEach(snapshot => {
snapshot.createEntities.forEach(entity => {
// entity { nid: 65534, ntype: 0, x: 50, y: 50, protocol: { name: 'PlayerCharacter', ... }}
})
snapshot.updateEntities.forEach(update => {
// update { nid: 65534, prop: 'x', value: 63, path: ['x'] }
})
snapshot.deleteEntities.forEach(nid => {
// nid 65534
})
})
This is everything the game client needs to keep any number of entities in sync: create, update, and delete. The data that traveled over the network was already optimized and highly compressed. The game state that comes out of client.readNetwork() is already smoothly interpolated and compatible with different frame rates (30, 60, 144, 250 etc).
This automatic synchornization of state on entities is the soul of nengi. It strikes a balance between configurability, performance, and ease of development. It also stays out of your game code. For things that are not easily represented as an entity, we have the nengi.Message, which can be used to manually network just about anything else.
The above is sufficient to network a vast variety of games where the game client sends commands to the game server and then waits on a reply from the server before the seeing the results of its commands. I refer to these as "non-predicted" games and every aspiring multiplayer programmer should make at least one before moving deeper with nengi.
That's the end of the basic path for a nengi entity, but the documentation covers the more advanced life of an entity. Summarized briefly, entities are culled, can be used for predictive movement on the game client, and have their state rewinded for advanced lag compensation.