- Looking for ideas? Check out "good first PR" label.
- Or start a conversation in Issues to get help and advice from community on PR ideas.
- Read the coding standards below.
- Keep PR simple and focused - one PR per feature.
- Make a Pull Request.
- Complete the Contributor License Agreement.
- Happy Days! 🎉
Feel free to contribute bug fixes or documentation fixes as pull request.
If you are looking for ideas what to work on, head to Issues and checkout out open tickets or start a conversation. It is best to start conversation if you are going to make major changes to the engine or add significant features to get advice on how to approach it. Forum is good place to have a chat with community as well.
Try to keep PR focused on a single feature, small PR's are easier to review and will get merged faster. Too large PR's are better be broken into smaller ones so they can be merged and tested on its own.
Our coding standards are derived from the Google JavaScript Coding Standards which are based on ES5 (ECMA2009). Also we have a whitelist of modern JavaScript features (ES6+), explicitly listed below.
Simple code is always better. Modular (horizontal dependencies) code is easier to extend and work with, than with vertical dependencies.
For example, use "Initialize" instead of "Initialise", and "color" instead of "colour".
You may use the following JavaScript language features in the engine codebase:
let
const
for of
loops- Arrow function expressions
- Classes
- Default parameters
- Modules
- Optional chaining
- Static keyword
- Template literals
- Set - only functionality supported by all browsers, including Safari 8 - see the table at the end of the page.
for..of
type of loop should not be used to iterate a set as it is not supported on Safari 8. - Map - only functionality supported by all browsers, including Safari 8 - see the table at the end of the page.
for..of
type of loop should not be used to iterate a map as it is not supported on Safari 8.
For example:
// Notice there is no new line before the opening brace
function inc() {
x++;
}
Also use the following style for 'if' statements:
if (test) {
// do something
} else {
// do something else
}
If condition with body is small and is two-liner, can avoid using braces:
if (test === 0)
then();
Ensure that your IDE of choice is set up to insert '4 spaces' for every press of the Tab key and replaces tabs with spaces on save. Different browsers have different tab lengths and a mixture of tabs and spaces for indentation can create funky results.
On save, set your text editor to remove trailing spaces and ensure there is an empty line at the end of the file.
let foo = 16 + 32 / 4;
for (let i = 0; i < list.length; i++) {
// ...
}
let fn = function () {
};
foo();
bar(1, 2);
let a = {};
let b = { key: 'value' };
let c = [];
let d = [ 32, 64 ];
for (let i = 0; i < items.length; i++) {
const item = items[i];
}
var a = 10; // not good
// ok
for (let i = 0; i < items.length; i++) {
const item = items[i];
}
// more readable but generally slower
for (const item of items) {
}
In functions exit early to simplify logic flow and avoid building indention-hell:
let foo = function (bar) {
if (! bar)
return;
return bar + 32;
};
Same for iterators:
for (let i = 0; i < items.length; i++) {
if (! items[i].test)
continue;
items[i].bar();
}
// Namespace should have short lowercase names
let namespace = { };
// Classes should be CamelCase
class MyClass { }
// Variables should be mixedCase
let mixedCase = 1;
// Function are usually variables so should be mixedCase
// (unless they are class constructors)
let myFunction = function () { };
let myFunction = () => { };
// Constants should be ALL_CAPITALS separated by underscores.
// Note, ES5 doesn't support constants,
// so this is just convention.
const THIS_IS_CONSTANT = "well, kind of";
// Enum constants follow similar rules as normal constants. In general,
// the enum consists of the type, and its values.
// In other languages, this is implemented as
// enum CubeFace {
// PosX: 0,
// PosY: 1
// }
// Due to the lack of native enum support by JavaScript, the enums are
// represented by constants. The constant name contains the enum name without
// the underscores, followed by the values with optional underscores as
// needed to improve the readibility. This is one possible implementation:
const CUBEFACE_POSX = 0;
const CUBEFACE_POSY = 1;
// and this is also acceptable
const CUBEFACE_POS_X = 0;
const CUBEFACE_POS_Y = 1;
// Private variables should start with a leading underscore.
// Note, you should attempt to make private variables actually private using
// a closure.
let _private = "private";
let _privateFn = function () { };
Treat acronyms like a normal word. e.g.
let json = ""; // not "JSON";
let id = 1; // not "ID";
function getId() { }; // not "getID"
function loadJson() { }; // not "loadJSON"
new HttpObject(); // not "HTTPObject";
function asyncFunction(success, error) {
// do something
}
function asyncFunction(success) {
// do something
}
function asyncFunction(callback) {
// do something
}
It is often useful to be able to cache the 'this' object to get around the scoping behavior of JavaScript. If you need to do this, cache it in a variable called 'self'.
let self = this;
setTimeout(function() {
this.foo();
}.bind(this)); // don't do this
Instead use self
reference in upper scope:
let self = this;
setTimeout(function() {
self.foo();
});
Use this notation for function default parameters:
// good
function foo(a, b = 10) {
return a + b;
}
// not good
function foo(a, b) {
if (b === undefined)
b = 10;
return a + b;
}
Variables that should be accessible only within class should start with _
:
class Item {
constructor() {
this._a = "private";
}
bar() {
this._a += "!";
}
}
let foo = new Item();
foo._a += "?"; // not good
The hasOwnProperty() function should be used when iterating over an object's members. This is to avoid accidentally picking up unintended members that may have been added to the object's prototype. For example:
for (let key in values) {
if (! values.hasOwnProperty(key))
continue;
doStuff(values[key]);
}
Filenames should be all lower case with words separated by dashes. The usual format should be {{{file-name.js}}}
e.g.
asset-registry.js
graph-node.js
The entire PlayCanvas API must be declared under the pc
namespace. This is handled by build script, so ES6 notation of import
/export
should be used. The vast majority of the PlayCanvas codebase is made up of class
definitions. These have the following structure (which should be adhered to):
class Class {
someFunc(x) { }
}
export { Class };
You can also extend existing classes:
import { Class } from './class.js';
class SubClass extends Class {
constructor() {
// call parent class constructor
super();
}
someFunc(x) {
// if method is overriden
// this is the way to call parent class method
super.someFunc(x);
}
}
export { SubClass };
Use class
instead of prototype
for defining Classes:
// good
class Class {
someFunc() { }
}
// not good
function Class() { }
Class.prototype.someFunc = function() { };