Skip to content

Instantly share code, notes, and snippets.

@max-mapper
Last active January 28, 2024 18:11
Show Gist options
  • Save max-mapper/5147486 to your computer and use it in GitHub Desktop.
Save max-mapper/5147486 to your computer and use it in GitHub Desktop.
How-to: Write a node module with voxel.js

Writing node modules with voxel.js

This is a short guide that will teach you the workflows that have been figured out by the voxel.js community for writing node modules + sharing them on NPM and Github. It is assumed that you have a basic understanding of JavaScript, github and the command line (if not you can check out an introduction to git and the command line or learn JS basics from JavaScript for Cats)

The voxel-tower repository on github contains all the example code from this guide.

Table of contents

Installing Node

All of the components in the voxel.js ecosystem are open source + available on NPM and github. NPM is the Node Package Manager and is automatically installed when you install Node, which can be downloaded from nodejs.org.

You can use the command npm search voxel to see all of the modules in one big list. We use node modules because they are the simplest module format available in JS and we use NPM because everyone who shares node code uses NPM because it makes it super easy to share and publish modules, and generally gives you less headaches than other package managers.

We use browserify for packaging node modules together into game bundles that will run in your browser. Not every module on NPM will work with browserify (for example, browsers cannot yet make UDP connections), but a lot of them will!

Making a new project folder

voxel.js 'Add-ons' are actually just node modules. If this guide doesn't explain it well enough you can google for blog posts and tutorials on how to write node modules to find out more.

Let's pretend we are making a new module called voxel-tower, which will let you place a big stack of voxels. Starting from scratch here is how you can start hacking on this new module:

  • make a new folder called voxel-tower
  • inside that folder, you can either type npm init or manually make a file called package.json with the following contents:
{
  "name": "voxel-tower",
  "version": "0.0.1"
}
  • make another file called index.js with the following contents:
module.exports = function() {
  // TODO add some code :D
}

To recap: all you need to get started is a package.json with a name and version, a folder that shared the same name as your name in package.json, and a file called index.js that exports something (module.exports is the thing that gets loaded when you type require('voxel-tower')

Writing the module code

The best place to learn about how to use voxel.js is the voxel-engine readme as well as the source code for the examples and modules on voxeljs.com.

Let's add some code to our index.js:

// a convenience function, usage:
// var tower = require('voxel-tower')(game)
// if we did module.exports = Tower then the usage would
// have to be:
// var Tower = require('voxel-tower')
// var tower = new Tower()
module.exports = function(game, opts) {
  return new Tower(game, opts)
}

// expose the Tower constructor so that it is available
// in case someone wants to access the .prototype methods, etc
module.exports.Tower = Tower

function Tower(game, opts) {
  // protect against people who forget 'new'
  if (!(this instanceof Tower)) return new Tower(game, opts)
  
  // we need to store the passed in variables on 'this'
  // so that they are available to the .prototype methods
  this.game = game
  this.opts = opts || {}
  this.height = this.opts.height || 5
  this.material = this.opts.material || 'brick'
}

// creates a new stack of voxels
// usage:
// var tower = require('voxel-tower')(game, { height: 5 })
// tower.place([5, 30, 0])
Tower.prototype.place = function(position) {
  for (var i = 1; i < this.height + 1; i++) {
    var pos = [position.x, position.y + i, position.z]
    this.game.setBlock(pos, this.material)
  }
}

A common pattern is to pass in the game instance (from voxel-engine) so that you can modify the game state from within your module. If you need to access three.js methods you can use game.THREE. Don't require('three') in your modules as it will cause weird rendering bugs!

Make an example/demo

OK, lets get our module loaded into a game in the browser so we can test it out! The easiest way to do this is to add voxel-hello-world and the painterly-textures texture pack to your devDependencies section of package.json and then install them:

npm install --save-dev voxel-hello-world painterly-textures

Now make a file called demo.js with the following contents:

// __dirname is the current working directory, we pass it in to
// the textures module and receive back the path from here to where
// the textures are located
var textures = require('painterly-textures')(__dirname)
var game = require('voxel-hello-world')({texturePath: textures})
var tower = require('./')(game)

// make a tower appear after 5 seconds at the players position
setTimeout(function() {
  tower.place(game.controls.target().avatar.position)
}, 5000)

Then make a new file called index.html and put this line into it:

<!doctype html>
<html>
  <body>
    <script src="bundle.js"></script>
  </body>
</html>

To get your game running you have to build a browserify bundle. Here is how to do it manually:

# install browserify
npm install browserify -g
browserify demo.js -o bundle.js

# install and start a http server in the current directory
npm install http-server -g
http-server

Now you should be able to open http://localhost:8080 in your browser to play around with the game.

Development workflow

The above manual process of bundling the game from demo.js into bundle.js works okay but you have to go the command line every time. It also assumes that people who clone your repo will have already done npm install browserify -g ahead of time. Setup should be as easy as possible -- this way more people will send you pull requests!

Firstly, lets install a new devDependency called beefy which will replace the need for http-server by automatically hosting our game for us and -- this is the best part -- automatically bundling our game every time we refresh the page. We should also add browserify as well so that they both get installed automatically into the node_modules folder by npm install:

npm install --save-dev browserify beefy

Open up package.json and add a start script:

"scripts": {
  "start": "beefy demo.js:bundle.js 8080"
}

This essentially means every time a <script> tag requests test.js browserify bundle.js and send it back as test.js. Now you can just type npm start to start a local dev server. Go ahead and try it out!

The other huge benefit of this approach is that the steps to get your repository to run locally are just git clone, npm install and npm start.

There is more information available on the beefy website.

If your compile times are slow you can try this alternate version which is optimized for speed: beefy index.js:bundle.js --debug=false -- --fast, which

Making a good README

READMEs are important for users of your code as well as developers. The main outline of most of the voxel.js readmes is:

  • module title
  • short description
  • what is the module's name on npm
  • list of api methods and how to use them
  • optionally: license, changelog, misc notes

Most people use markdown formatting in their readmes. Make a file called README.md and start writing! You can also copy-paste an existing readme and change the relevant parts.

Your readme will show up on github as well as npmjs.org. A great example is the readme for voxel-physical, check it out on github and npmjs

Publishing to NPM

When your module works + is ready to share you just have to type npm publish to put it on the NPM registry. Whenever you change your projects version you should do npm publish again.

To decide when you should update your version number please read SEMVER FTW!.

A healthy package.json is also a sign of a good maintainer. Here is a great one to learn from: voxel-texture package.json. At a bare minimum you should have the name, description, author, and repository fields filled out.

Most authors choose a "do whatever you want, just don't sue me" license, such as BSD/MIT/Apache. The other kind of license is a "you can't use this commercially" or "you can't use this without putting my name on your website" but it's up to you if you want to be prescriptive about usage of your code: the more barriers you add the less you will receive contributions.

Finally, please send a pull request to https://github.com/maxogden/voxeljs.com and add your module to the list of add-ons so that it can be discovered and used by other developers.

If you have questions/comments you can view this article on github or you can also ask a question in the #voxel.js IRC room on freenode.

@marcusandre
Copy link

I really like this approach and workflow. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment