BLACK

A Free, Cross-Platform 2D Game Engine.

Introduction

Welcome to the Black Engine. Black Engine is a highly optimized 2D framework for desktop, mobile games and applications. This introduction covers the core concepts of the Black Engine and intended to give an overview to the scripts and the most important features of it. All of the descriptions are quite brief but you can find the links for more detail documentation below. Black Engine was designed on a clear philosophy of simplicity. In spite of fact that this is a turn-key production platform, Black Engine does not try to provide all-encompassing solutions for everything. Vice versa, we believe that the main task of Black is empowering you, as a game creator, with simple but strong tools which will allow you realize your personal unique vision. If you are an experienced developer Black Engine core concepts will be quite easy for understanding, but we strongly recommend you to take a minute and read this overview carefully because despite the simplicity of some of our conceptions, they can differ from what you might expect initially. Thank you for choosing our engine. Good luck! ☻ Massive Heights team.

Installing

Node.js runtime is required to run your future project. If it is not already installed on your computer, follow download link: Node.js. Even if you already have it, we recommend downloading the latest version.

Before you starting to write any code, create a folder for your projects or go to the folder where your projects are usually located, open command console in this folder and enter following commands:

git clone https://github.com/MassiveHeights/Black-Template.git
cd Black-Template
npm install

Running

This template provides ability to run development server on port 4245 to do so run npm start in the terminal then open a browser and navigate to http://127.0.0.1:4245. If you can see the anvil on the screen, then you've done everything right.

All files inside sheets, textures, fonts, html, spine and audio folders will be automatically copied into dist folder when changed. Settings in gulpfile.js allow you to build changes in a really short time. For better compatibility and faster loading time please use production build. Simply enter next command: npm run bundle.

Dig into

Open project folder in your favorite code editor. Navigate to /js/game.js file. This is your entry point, where you can start writing code.

So, now you can start working with Black!

Loading assets

Load image

Before you starting to write any code, create a folder for your projects or go to the folder where your projects are usually located. Open command console from this location and enter following commands:

Step one: Put your images in textures folder.

Step two: Declare AssetManager and path to your assets.

   let assets = AssetManager.default;
   assets.defaultPath = '/assets/';

Step three: Now we should specify the texture.

  assets.enqueueImage('anvil', 'popart_anvil.png');

Step four: Load all textures in AssetManager

  this.assets.on('complete', this.onAssetsLoaded, this);
  assets.loadQueue();

  onAssetsLoaded(m) {
    // All assets are ready to use
  }

Congratulations you’ve loaded the image in assets manager;

Load Atlas

We should specify the atlas.

assets.enqueueAtlas('atlas', 'atlas.png', 'atlas.json');

How to create an atlas using TexturePacker

First of all you need install TexturePacker. Then copy entire /TexturePacker/black folder into:
Windows: C:/Program Files/CodeAndWeb/TexturePacker/bin/exporters/
Mac: Applications/TexturePacker.app/Contents/Resources/exporters/
Now you can use Black Data Format.
Look at the TexturePacker documentation for more information.

Load Sound

We should specify the sound.

  assets.enqueueSound('mainTheme', '/mainTheme.ogg');

Congratulations you load sound in assets manager;

Load JSON

We must specify the JSON .

  assets.enqueueJson('TypeJson', '/type.json');

Congratulations you’ve loaded JSON in assets manager.

Creating and displaying a Sprite

this.sprite = new Sprite('anvil'); // Create new Sprite instance
this.addChild(this.sprite); // Add sprite to the scene

Sprite Transformations

Move sprite

    this.sprite.x = 100; // set x-coord
    this.sprite.y = 100; // set y-coord
    this.sprite.rotation = Math.PI; // set rootation in radians

Opacity sprite

If you want to change the transparency of sprite, you need to use property alpha, the opacity of sprite can be changed in the interval from 0 to 1

this.sprite.alpha = 0.4; // 40%

Game Objects

Game objects are simple objects. They usually equipped with visual or audible representation. Also they can be equipped with script components. Thus, game objects are differ from sprites or sounds because they are containers for these different types of components.

  let someGameObject = new GameObject(); // declared new Game Object

Look at the Game objects documentation for more information.

Components

Component based engine design was originally pioneered in order to avoid annoying class hierarchies which introduces inheritance. The idea is in packaging the functionality of game objects into separate objects. A single game object it's just a set of components. The components of a game object define it’s behavior, appearance and functionality.

this.view = new GameObject();
this.view.addChild(new Sprite('bp'));

this.sprite = new Sprite('rect');
this.sprite.x = 960 / 2;
this.sprite.y = 640 / 2;
this.view.addChild(this.sprite);

this.view.addComponent(new FPSComponent());

Message Passing

Components communicate with each-other and another systems through message passing. Also, components respond to a set of predefined messages which change them or trigger specific actions. You can send messages to hide graphics, play sounds or nudge physics objects. Moreover, the engine uses messages to notify components of events, for example when physics shapes collide. The message passing mechanism needs a recipient for every sent message. To send messages to each other you need any of two components in your app or game.

class MyGame extends GameObject {
  constructor() {
    super();

    this.name = 'root';
  }

  onAdded() {
    let someGameObject_1 = new GameObject();
    someGameObject_1.name = 'someGameObject_1';
    this.addChild(someGameObject_1);

    let someGameObject_2 = new GameObject();
    someGameObject_2.name = 'someGameObject_2';
    someGameObject_1.addChild(someGameObject_2);

    this.on('noop', this.onNoop, undefined);
    someGameObject_1.on('noop', this.onNoop, this);

    someGameObject_1.sendMessage('~noop', 'Hey!');
  }

  onNoop(msg, text) {
    console.log('message received:', text, 'sender: ', msg.sender.name, 'receiver: ', msg.target.name);
  }
}

// Create and start engine
let black = new Black('container', MyGame, 'canvas');
black.start();

Look at the Message passing documentation for more detail instruction of how message passing works.

Texture Atlases

The atlas is a set of separate images which are compiled into a larger sheet to increase the productivity and memory. You can keep fixed images or flip-book animated series of them. Atlases are used by Sprite and emitter components for sharing graphics resources.

Atlas Using

  this.assets = AssetManager.default;
   this.assets.defaultPath = '/examples/assets/';
   this.assets.enqueueAtlas('atlas', 'atlas.png', 'atlas.json');   // Preload some images
   this.assets.on('complete', this.onAssetsLoaded, this);   // Pass on load complete handler and this for correct context
   this.assets.loadQueue();

   onAssetsLoaded()
   {
     // Your Code
   }

Input

Input binding files define how the game should interpret a hardware input (mouse, keyboard and touchscreen). The file binds the hardware input with high level input actions like "fire" and "move". In script components that listen to input you can script the actions, which the game or app should take in case of a certain input. You can look at the Input documentation for details.

createSprite(){
  let rect = new Sprite('rect');
  rect.addComponent(new InputComponent());
  rect.on('pointerUp', this.onUp, this);
}
onUp() {
 console.log('up');
}

Math Helpers

Black Engine implements all of the basic mathematical methods such as: random between, clamp, lerp, PI, DEG2RAD and etc.

  let a = 5, b = 1, t = 20;
  let resultLerp = MathEx.lerp(a, b, t);
  let sprite = new Sprite('rect');
  sprite.rotation = MathEx.PI2;
let min = 1, max = 10;
let randomNumber = MathEx.randomBetween(min,max);

See the Math documentation for details.

Particle System

Emitter is very useful for creating pleasant visual effects, particularly in games. You can use them to create snow, rain or falling leaves. Black Engine contains a powerful particle effect that allows you to build and tweak the effects while you run them in real time in your game. Emitter documentation can give you the details on how it works. Also you can look at some cool effects in the examples

class MyGame extends GameObject {
  constructor() {
    super();
    this.assets = AssetManager.default;    // Create own asset manager
    this.assets.defaultPath = '/examples/assets/';
    this.assets.enqueueAtlas('img-atlas', 'atlas.png', 'atlas.json');  // Preload some images
    this.assets.on('complete', this.onAssetsLoaded, this);// Pass on load complete handler and this for correct context
    this.assets.loadQueue();
  }

  onAssetsLoaded() {
    this.view = new GameObject();
    this.addChild(this.view);
    let bg = new Sprite('blueprint-landscape');    // Add background sprite
    this.view.addChild(bg);

    let tHeart = AssetManager.default.getTexture('heart');
    let e = new Emitter();
    e.emitCount = new FloatScatter(3);
    e.emitDelay = new FloatScatter(0);
    e.emitInterval = new FloatScatter(1/200);
    e.emitNumRepeats = new FloatScatter(Infinity);
    e.textures = [tHeart];

    e.x = 960 / 2;
    e.y = 640 / 2;
    e.space = this.view;
    e.addInitializer(new Life(new FloatScatter(1.15)));

    e.addAction(new ScaleOverLife(new FloatScatter(1, 0, Ease.backIn)));
    e.addAction(new Acceleration(new VectorScatter(-800, -800, 800, 800)));
    e.addAction(new RotationOverLife(new FloatScatter(0, -Math.PI * 2, Ease.backInOut)));

    this.view.addChild(e);
    this.emitter = e;
    this.emitter = e;
  }

  onPostUpdate(dt) {
    if (!this.emitter)
      return;

    let p = this.view.globalToLocal(Input.pointerPosition);
    let dx = p.x - this.emitter.x;
    let dy = p.y - this.emitter.y;

    let t = Black.instance.uptime * 5;
		let x = 13 * Math.sin(t) + Math.random();
		let y = 13 * Math.cos(t) + Math.random();

    this.emitter.x = (x * 12) + 480;
    this.emitter.y = (y * 12) + 320;
    this.emitter.rotation = -Math.PI / 2;
  }
}

Tweening

A tween is a concept which allows you to change the values of the properties of an object in a smooth way. You should just tell it which properties you want to change and which final values they should have when the tween will finish running, and how long should this take, and the tweening engine will take care of finding the intermediate values from starting to the ending of a point.

let view = new GameObject();

addChild(view);

let bg = new Sprite('bp');
view.addChild(bg);

sprite1 = new Sprite('rect');
sprite1.x = 880;
sprite1.y = 320;

sprite1.scaleX = 1.25;
sprite1.scaleY = 1.25;

view.addChild(sprite1);

let t1 = sprite1.addComponent(new Tween({ x: 480 }, 2));
let t2 = sprite1.addComponent(new Tween({ rotation: 2 * Math.PI }, 4, { delay: 2 }));

Tween documentation can give you more details on how it works.

Custom Components

Black Engine does not attempt to provide a comprehensive solution for everything and to solve some problems you have to write your own custom components.

class FPSComponent extends Component  {
  constructor() {
    super();
    this.txtFPS = null;
  }

  onAdded(){
    this.txtFPS = new TextField('FPS: 0');
    this.txtFPS.x = 0;
    this.txtFPS.y = 0;
    this.gameObject.addChild(this.txtFPS);
  }

  onRemoved(){
  }

  onUpdate(){
    this.txtFPS.text = 'FPS: ' + Black.instance.FPS;
  }
}

Components documentation will give you details on how it works.