Home

Awesome

<p align="center"> <img src="./logo.png" width="180px"> </p> <p align="center"> <img src="https://github.com/3mcd/javelin/workflows/CD/badge.svg?branch=release/next" alt="CD"> <a href="https://codecov.io/gh/3mcd/javelin"><img src="https://codecov.io/gh/3mcd/javelin/branch/master/graph/badge.svg?token=8UMA33S9UL" alt="codecov"></a> <a href="https://discord.gg/AbEWH3taWU"><img src="https://img.shields.io/discord/844566064281026600?logo=discord" alt="discord"></a> <img src="https://img.shields.io/lgtm/grade/javascript/github/3mcd/javelin"> </p>

Javelin

Join us on Discord!

Javelin is a collection of JavaScript packages that provide the means to create multiplayer games for Node and web browsers. It's comprised of an Entity-Component System (ECS), an efficient networking protocol, and other helpful utilities like a high-precision game loop for Node.

Javelin's lack of opinions makes it a good candidate for building many kinds of multiplayer games, but you'll need to bring your own network transport (WebSockets, WebRTC, etc.), database, auth strategy, controller support, matchmaking, etc.

API Sample

Below is an example game server that broadcasts entities with both Position and Velocity components to connected clients.

import { createWorld, createQuery, component, useMonitor } from "@javelin/ecs"
import { createMessageProducer, encode, float64 } from "@javelin/net"
import { createHrtimeLoop } from "@javelin/hrtime-loop"
import { broadcast } from "./net"

const Position = {
  x: float64,
  y: float64,
}
const Velocity = {
  x: float64,
  y: float64,
}
const world = createWorld()
const points = createQuery(Position, Velocity)
const messages = createMessageProducer()

// create an entity
world.create(component(Position), component(Velocity, { x: 0, y: -9.81 }))
// create/delete points on client
world.addSystem(() => useMonitor(points, messages.attach, messages.destroy))
// update point positions
world.addSystem(() =>
  points((e, [p, v]) => {
    p.x += v.x
    p.y += v.y
  }),
)
// broadcast messages to clients
world.addSystem(() => {
  if (useInterval((1 / 20) * 1000)) {
    points(messages.update)
    broadcast(encode(messages.take()))
  }
})
// start a high-precision game loop
createHrtimeLoop(world.step, (1 / 60) * 1000).start()

There's a lot more that goes into building a game, but hopefully this example demonstrates the unobtrusive and consise nature of Javelin's APIs.

Docs

Visit https://javelin.games

Packages

NameDescription
@javelin/coreCore types, utilities, and data model
@javelin/ecsBuild games using the ECS pattern
@javelin/netSynchronize ECS worlds
@javelin/packConvert objects to and from binary arrays
@javelin/hrtime-loopCreate smooth, high-precision game loops in NodeJS
@javelin/isomorphic-utilsUniversal utils requiring separate browser/node builds

Examples

NameDescriptionNPM Script
spinEntity/component synchronization over WebRTC datachannelsexample:spin
interopInterop with Cannon and Three.js objectsexample:interop

Development

This project is a monorepository (monorepo for short), which is a collection of independent NPM packages that are located in the same repository for improved development, publishing, and visibility.

You can "bootstrap" all monorepo and package dependencies using the following command:

pnpm i

Build times are quick thanks to TypeScript project references. You can build (or re-build) all packages like so:

pnpm build --recursive

The structure of the monorepo looks something like this:

packages/
├── core/
│   ├── dist/
│   │   ├── esm/
│   │   │   ├── index.js
│   │   │   ├── index.d.ts
│   │   │   └── ...
│   │   └── cjs/index.js
│   ├── src/
│   │   ├── index.js
│   │   └── ...
│   ├── README.md
│   ├── package.json
│   └── tsconfig.json
├── ecs/
└── ...

Where each package has its own README file, TypeScript configuration file, source and dist directories, and scripts which describe how the package is cleaned, build, and prepared for publishing.

Each package has the following specified in its package.json:

"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",

Where "main" is a CommonJS entrypoint, "module" is an ESM entrypoint, and "types" is the root .d.ts file generated by the TypeScript compiler.

Packages reference eachother using pnpm workspace aliases and TypeScript project references. For example, the ecs package depends on core, so in the package.json file of @javelin/ecs:

{
  "dependencies": {
    "@javelin/core": "workspace:*"
  }
}

And in the same package's TypeScript config file, we make sure to include a project reference for faster builds and other benefits:

{
  "references": [{ "path": "../../core" }]
}