Tutorial – Getting started with TypeScript and Cocos2d-x JS – Part 4 – Bootstrapping Cocos2d-x JS – Webpack – Chai – External Modules

Tutorial – Getting started with TypeScript and Cocos2d-x JS – Part 4 – Bootstrapping Cocos2d-x JS – Webpack – Chai – External Modules

Discussion and direction

I have decided that for the remainder of this series, we are going to build a simple tap to shoot 2d game. Our main focus will be on building the underlying TypeScript based architecture for Cocos2d-x JS,  but I thought that we might as well make it fun.

We are going use TypeScript’s External Modules and WebPack to aid us in increasing the power we have available to us for testing outside of the browser.

The code  architecture that we will build is one that I have found quite useful in developing my own games and is one that has a background in  games development using AS3 and RobotLegs.

Now to keep things simple, some of the libraries that we will introduce, such as the one we be using for IOC, will not be completely type safe.

The reason for this is that some of  alternative Typescript libraries available for expressing the patterns I want to demonstrate will only add an additional complexity to what we are trying to achieve.

I want this to be a more gentle approach to these patterns using Typescript and Cocos2d-x JS, without getting to heavy into the more advanced and newer features of Typescript.

Ill leave it as an exercise to the reader, to further explore the patterns  presented, and build an even more robust framework for developing games.

You can find the completed source for this article in the project named CocosTsGame_base on the GitHub repository. note that the frameworks, node_modules and typings folders have been added to the .gitignore to save space so you will have to first create a  cocos2dx project via the command line and then copy the provided  source over the top. be careful of files within the /src  folder and main.js in the  project root folder if you are using a version of Cocos2d-x other than 3.14.

The outline for the rest of this series is given below:

  • Introduce a bootstrapper to enable  switching between multiple applications under one project; allowing us to gradually increase complexity
  • Introduce TypeScript’s External Modules
  • Introduce WebPack
  • Introduce The Chai Assertion Library
  • Introduce an IOC framework to help us reduce hard dependencies between the layers of our application
  • Introduce an IOC driven MVCS pattern to build the framework for the wider application
  • Introduce an Entity Framework to manage our gameplay entities
  • Introduce a Factory pattern for our game assets and entities

Figure 1 illustrates a rough outline of the framework we will be implementing.

Focus for now on Bootstrap.ts, IApplication.ts, and Application1.ts to Application..n.ts.

 

Figure 1 – Outline of framework

The start() method defined in Bootstrap.ts  is the entry point.  It is here that we will choose which application we wish to launch upon startup.   The start up process is called from the  InitScene::onEnter() function defined in src/app.js.

InitScene is the default scene loaded by our  Cocos2d-x JS skeleton application after the resources defined in src/resource.js have been loaded by the resource loader. see: main.js.

We will ensure  our Application classes implement an IApplication interface. You can see an example defined in Application1.ts. We will use the Application1  class to  explore some basic Cocos2d-x  JS functionality.

As we progress throughout the series we will add more Application..n.ts files, and simply switch between applications in Bootstrap.ts, to run them at will.

Ok lets get started..

Create Cocos2dx JS Skeleton

Create a new Cocos2d-x JS  project by executing the following command line.

cocos new CocosTsGame -p com.yourcompany.CocosTsGame -l js

The project we set up here will be the base of all future posts in this series. It has been tested in both the browser and on native JSB builds. So you can be confident that this approach will work for your future games.

Add Node and Typings dependency configs

We need a package.json that will describe our npm dependencies. We will use this later to install the required node packages.

Under the project root, create a file named package.json, and add the following JSON

{
  "name": "CocosTsGame",
  "version": "1.0.0",
  "description": "base project for typescript based cocos2dx games ( external module )",
  "main": "./tsdist/main.js",
  "scripts": {
    "test": "mocha -w"
  },
  "keywords": [
    "typescript",
    "cocos2dxjs"
  ],
  "author": "Steven Daley - www.dalste.co.uk",
  "license": "ISC",
  "devDependencies": {
    "chai": "^3.5.0",
    "gulp": "^3.9.1",
    "gulp-mocha": "^4.3.1",
    "gulp-tslint": "^8.0.0",
    "gulp-typescript": "^3.1.6",
    "gulp-webpack": "^1.5.0",
    "source-map-loader": "^0.2.1",
    "ts-loader": "^2.1.0",
    "tslint": "^5.2.0",
    "typescript": "^2.3.2",
    "webpack": "^2.6.0",
    "webpack-stream": "^3.2.0"
  }
}

We need a typings.json to describe our typings dependencies. We will use this later to install the required typings packages.

Navigate to the project root, create a file named  typings.json, and add the following JSON

{
  "globalDependencies": {
    "mocha": "registry:dt/mocha#2.2.5+20170311011848"
  },
  "devDependencies": {
    "chai": "registry:npm/chai#3.5.0+20160723033700"
  }
}

Install Node and Typings dependencies

Open up a command line at the project root,  run the below commands:

node install

typings install

These commands should create folders named node_modules , and typings  each containing respective dependencies for the project.

Configure Gulp, TSLint, Mocha, Chai, and WebPack tasks

We need files that will provide configurations for our gulp driven test, and build tasks.

Under the root folder create a file named tsconfig.json and add the following JSON:

{
    "files": [
         "tssrc/Bootstrap.ts"
    ],
    "compilerOptions": {
        "noImplicitAny": true,
        "target": "es5",
        "module": "commonjs",
        
        "outDir":"tsdist/",

         "sourceMap": true,
        "jsx": "react",
        "allowJs": true
    }

}

Under the root folder create a file named tsconfig.test.json and add the following JSON:

{
    "files": [
        "tssrc/**/*.ts",
        "tstest/**/*.spec.ts"
    ],
    "compilerOptions": {
        "noImplicitAny": true,
        "target": "es5",
        "module": "commonjs"
    }

}

We need a file that will provide configuration for our gulp driven tslint task:

Under the root folder create a file named tslint.json and add the following JSON:

{
  "rules": {
    "class-name": true,
    "comment-format": [true, "check-space"],
    "curly": true,
    "eofline": true,
    "forin": true,
    "indent": [true, "spaces"],
    "label-position": true,
    "max-line-length": [true, 140],
    "member-access": true,
    "member-ordering": [true,
      "public-before-private",
      "static-before-instance",
      "variables-before-functions"
    ],
    "no-arg": true,
    "no-bitwise": true,
    "no-console": [true,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-debugger": true,
    "no-duplicate-variable": true,
    "no-empty": true,
    "no-eval": true,
    "no-inferrable-types": true,
    "no-shadowed-variable": false,
    "no-string-literal": true,
    "no-switch-case-fall-through": true,
    "no-trailing-whitespace": true,
    "no-unused-expression": true,
    "no-use-before-declare": true,
    "no-var-keyword": true,
    "object-literal-sort-keys": true,
    "no-for-in-array": true,
    "one-line": [true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-finally",
      "check-whitespace"
    ],
    "quotemark": [true, "double", "avoid-escape"],
    "radix": true,
    "semicolon": true,
    "trailing-comma": [false, {
      "singleline": "never",
      "multiline": "always"
    }],
    "triple-equals": [true, "allow-null-check"],
    "typedef-whitespace": [true, {
      "call-signature": "nospace",
      "index-signature": "nospace",
      "parameter": "nospace",
      "property-declaration": "nospace",
      "variable-declaration": "nospace"
    }],
    "variable-name": false,
    "whitespace": [true,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator",
      "check-type"
    ]
  }
}

We will be making use of WebPack to bundle the output of our typescript transpilation into  a single js file named “root/src/applicationbundle.js”

The great thing about WebPack is that it’s output is compatible with both the browser and native JSB.

Under the root folder create a file named webpack.config.js and add the following source:

var webpack = require('webpack'),
    path = require('path');
module.exports = {
    context: __dirname + '/tssrc',
    entry: {
      index: './Bootstrap.ts'
    },
    output: {
        path: __dirname + '/src',
        publicPath: '/',
        library:"CocosTSGame",

        filename: 'applicationbundle.js'
    },
    resolve: {
      modules: [__dirname ,"node_modules"],
      extensions: [ '.ts', '.js']
    },
    module: {
      loaders: [
        { test: /\.ts$/, loaders: ['ts-loader'], exclude: /node_modules/ }
      ]
    }
}

Key features of above config include:

  • We define a single WebPack entry point as Bootstrap.ts
  • We define the output folder for applicationbundle.js as /src
  • We define a variable CocosTSGame that will essentially be an in memory library (or namespace) from which we can access our transpiled classes and functions.  It will be placed at global scope within JSB or the browser
  • We define the output file applicationbundle.js of which will  contain the entire transpiled source

Now we need our gulp task runner config, which will provide the commands we need to do all of our testing. linting, packing and building.

Navigate to the project root and create a file named gulpfile.js

Insert the following source:

'use strict'
var gulp = require("gulp");
var ts = require("gulp-typescript");
var tsProject = ts.createProject("tsconfig.json");

var tsTestProject = ts.createProject("tsconfig.test.json");
const mocha = require('gulp-mocha');

var gulpTslint = require("gulp-tslint");
var tslint = require("tslint");

gulp.task('lint', function () {
    var program = tslint.Linter.createProgram("./tsconfig.json");

    gulp.src('tssrc/**/*.ts', { base: '.' })
        .pipe(gulpTslint({ program })).pipe(gulpTslint.report());
});


gulp.task('test', function () {

    return gulp.src(['./tssrc/**/*.ts','./tstest/**/*.spec.ts'],
        {
            base: '.'
        })
        /*transpile*/
        .pipe(tsTestProject())
        /*flush to disk*/
        .pipe(gulp.dest('tsdist'))
        /*execute tests*/
        .pipe(mocha())
        .on("error", function (err) {
            console.log(err)
        });
});


var tsconfig = require(process.cwd() + '/tsconfig.json');

gulp.task('typescript', function() {
  var tsResult = tsProject.src() // instead of gulp.src(...) 
    .pipe(tsTestProject());
  
  return tsResult.js.pipe(gulp.dest(tsconfig.compilerOptions.outDir));
});

var gutil = require('gulp-util');
var webpack = require('webpack');
var webpackConfig = require(process.cwd() + '/webpack.config.js');
gulp.task('webpack', ['typescript'], function(callback) {
    // run webpack
    webpack(webpackConfig, function(err, stats) {
        if(err) throw new gutil.PluginError('webpack', err);
        gutil.log('[webpack]', stats.toString({
            // output options
        }));
        callback();
    });
});

 

The above gulp file implements  the following commands:

  • gulp lint – provides static analysis checks and feedback for our TypeScript source code

  • gulp test – executes out mocha tests

  • gulp typescript – compiles our typescript

  • gulp webpack – uses typescript command to transpile our typescript and  bundle the resulting js into applicationbundle.js

 

Adding our Application Bootstrap

Navigate to the root folder and create a folder named tssrc:

Navigate to tssrc,

Add a file named Bootstrap.ts and add the following code:

import {IApplication}  from "./IApplication";
import Application1  from "./Application1";
import Application2  from "./Application2";

export const App: IApplication = new Application1();

export function start(): void {
  App.startUp();
}

Add a file named IApplication.ts and add the following code:

export interface IApplication {
    startUp(): void;
}

export default IApplication;

Add a file named Application1.ts and add the following code:

import {IApplication}  from "./IApplication";
export default class Application1 implements IApplication { 

    _config: {
        isdebug:boolean
    }
    constructor(){

    }
    startUp() {
        console.log("Hello Application1");
    }
}

Add a file named Application2.ts and add the following code:

import {IApplication}  from "./IApplication";
export default class Application2 implements IApplication { 

    _config: {
        isdebug:boolean
    }
    
    startUp() {
        console.log("Hello Application2");  
    }
}

We are going to create a mocha test utilising the chai assertion library, (already added to project via project dependencies) see package.json :

Navigate to your project root and create a folder named tstest

Create a file named Application1.spec.ts and insert the following code:

 /// <reference path="../typings/globals/mocha/index.d.ts" />
/// <reference path="../typings/modules/chai/index.d.ts" />

import { expect } from 'chai';
import Application1  from "../tssrc/Application1";


describe('Application', () => {

    beforeEach(() => {
            console.log("before");
    });

    describe('Application1', () => {

        it('should be non null', () => {
            var app = new Application1();
            
            expect(app).to.not.be.null;
        });
    });
});

Verify that everything is working by the executing following commands

gulp lint

gulp test

gulp webpack

Configuring Cocos2dx-JS

Add the path ./src/applicationbundle.js to project.json “jsList” array, it should read as below:

{
    "project_type": "javascript",

    "debugMode" : 1,
    "showFPS" : true,
    "frameRate" : 60,
    "noCache" : false,
    "id" : "gameCanvas",
    "renderMode" : 0,
    "engineDir":"frameworks/cocos2d-html5",

    "modules" : ["cocos2d"],

    "jsList" : [
        "src/resource.js",
        "src/applicationbundle.js",
        "src/app.js"
    ]
}

Navigate to src folder

Modify the app.js file so that it reads:

var InitScene = cc.Scene.extend({
    _firstEntry:true,
    onEnter:function () {
        this._super();
        if(this._firstEntry)
        {
            this._firstEntry = false;
            CocosTSGame.start();
        }

    }
});

Note the reference to the CocosTSGame global variable that we defined within out WebPack config. The bundled start() method is defined within Bootstrap.ts

Navigate to the projects root folder.

Modify main.js so that it reads (changes have been highlighted)

/**
 * A brief explanation for "project.json":
 * Here is the content of project.json file, this is the global configuration for your game, you can modify it to customize some behavior.
 * The detail of each field is under it.
 {
    "project_type": "javascript",
    // "project_type" indicate the program language of your project, you can ignore this field

    "debugMode"     : 1,
    // "debugMode" possible values :
    //      0 - No message will be printed.
    //      1 - cc.error, cc.assert, cc.warn, cc.log will print in console.
    //      2 - cc.error, cc.assert, cc.warn will print in console.
    //      3 - cc.error, cc.assert will print in console.
    //      4 - cc.error, cc.assert, cc.warn, cc.log will print on canvas, available only on web.
    //      5 - cc.error, cc.assert, cc.warn will print on canvas, available only on web.
    //      6 - cc.error, cc.assert will print on canvas, available only on web.

    "showFPS"       : true,
    // Left bottom corner fps information will show when "showFPS" equals true, otherwise it will be hide.

    "frameRate"     : 60,
    // "frameRate" set the wanted frame rate for your game, but the real fps depends on your game implementation and the running environment.

    "noCache"       : false,
    // "noCache" set whether your resources will be loaded with a timestamp suffix in the url.
    // In this way, your resources will be force updated even if the browser holds a cache of it.
    // It's very useful for mobile browser debugging.

    "id"            : "gameCanvas",
    // "gameCanvas" sets the id of your canvas element on the web page, it's useful only on web.

    "renderMode"    : 0,
    // "renderMode" sets the renderer type, only useful on web :
    //      0 - Automatically chosen by engine
    //      1 - Forced to use canvas renderer
    //      2 - Forced to use WebGL renderer, but this will be ignored on mobile browsers

    "engineDir"     : "frameworks/cocos2d-html5/",
    // In debug mode, if you use the whole engine to develop your game, you should specify its relative path with "engineDir",
    // but if you are using a single engine file, you can ignore it.

    "modules"       : ["cocos2d"],
    // "modules" defines which modules you will need in your game, it's useful only on web,
    // using this can greatly reduce your game's resource size, and the cocos console tool can package your game with only the modules you set.
    // For details about modules definitions, you can refer to "../../frameworks/cocos2d-html5/modulesConfig.json".

    "jsList"        : [
    ]
    // "jsList" sets the list of js files in your game.
 }
 *
 */

cc.game.onStart = function(){
    if(!cc.sys.isNative && document.getElementById("cocosLoading")) //If referenced loading.js, please remove it
        document.body.removeChild(document.getElementById("cocosLoading"));

    // Pass true to enable retina display, on Android disabled by default to improve performance
    cc.view.enableRetina(cc.sys.os === cc.sys.OS_IOS ? true : false);

    // Adjust viewport meta
    cc.view.adjustViewPort(true);

    // Uncomment the following line to set a fixed orientation for your game
    // cc.view.setOrientation(cc.ORIENTATION_PORTRAIT);

    // Setup the resolution policy and design resolution size
    cc.view.setDesignResolutionSize(960, 640, cc.ResolutionPolicy.SHOW_ALL);

    // The game will be resized when browser size change
    cc.view.resizeWithBrowserSize(true);

    //load resources
    cc.LoaderScene.preload(g_resources, function () {
        cc.director.runScene(new InitScene());
    }, this);
};
cc.game.run();

 

That is it for now, I encourage you to read the TypeScript documentation to familiarise yourself with namespaces, external modules, type definition files and other aspects of  TypeScript goodness.

I will see you in part 5 of this tutorial  where we will explore these topics further, introduce some useful typescript definition files , explore the Cocosdx2d scene graph, introduce karma for browser testing, and introduce some factories for our game assets.

You will find the completed CocosTsGame_base project on the Git Repository.

 

Command line quick reference

#exit the current command line
ctrl +c

# install web pack and chai dependencies
npm install gulp-webpack
npm install --save-dev webpack
npm install --save-dev webpack-stream
npm install --save-dev chai

typings install chai --save-dev

 

references

https://stackoverflow.com/questions/37977444/inconsistent-cannot-find-name-x-typescript-errors-in-vs-code

http://www.thinkingincrowd.me/2016/01/02/Modulization-and-Bundling-with-TypeScript-and-Webpack-for-JS-Full-Stack-Project/

https://stackoverflow.com/questions/40573196/using-webpack-2-from-gulp-webpack-stream-for-webpack-2

https://www.npmjs.com/package/gulp-webpack

https://www.typescriptlang.org/docs/handbook/integrating-with-build-tools.html#webpack

https://www.typescriptlang.org/docs/handbook/modules.html

https://www.typescriptlang.org/docs/handbook/integrating-with-build-tools.html#webpack

https://www.npmjs.com/package/gulp-webpack

One Reply to “Tutorial – Getting started with TypeScript and Cocos2d-x JS – Part 4 – Bootstrapping Cocos2d-x JS – Webpack – Chai – External Modules”

Leave a Reply

Your email address will not be published. Required fields are marked *