diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d7d852f..1eee6cac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- `SideCamera` replaced with `BaseCamera.strategy.lockToActorAxis`
- `Body.wasTouching` replaced with event type `CollisionEnd`
## Added
+- Option bag constructors have been added for commonly-used classes (see /src/engine/Docs/Constructors.md) ([#410])(https://github.com/excaliburjs/Excalibur/issues/410)
## Changed
diff --git a/karma.conf.js b/karma.conf.js
index b30abc7e..117d2abd 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -1,120 +1,119 @@
// Karma configuration
// Generated on Sat Sep 09 2017 14:56:04 GMT-0500 (Central Daylight Time)
-module.exports = function(config) {
+module.exports = function (config) {
config.set({
-
- // base path that will be used to resolve all patterns (eg. files, exclude)
- basePath: '',
-
-
- // frameworks to use
- // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
- frameworks: ['jasmine', 'karma-typescript'],
-
-
- // list of files / patterns to load in the browser
- files: [
- './build/dist/excalibur.js',
- './build/dist/excalibur.d.ts',
- './src/engine/**/*.ts',
- './src/spec/images/**/*.png',
- './src/spec/support/js-imagediff.js',
- './src/spec/support/js-imagediff.d.ts',
- './src/spec/Mocks.ts',
- './src/spec/TestUtils.ts',
- './src/spec/*.ts',
- ],
-
-
- // list of files to exclude
- exclude: [
- // "node_modules",
- // "typedoc-default-themes"
- ],
-
-
- // preprocess matching files before serving them to the browser
- // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
- preprocessors: {
- 'src/spec/Mocks.ts': ['karma-typescript'],
- 'src/spec/TestUtils.ts': ['karma-typescript'],
- 'src/spec/*.ts': ['karma-typescript'],
- 'build/dist/excalibur.js': ['coverage']
- },
-
- karmaTypescriptConfig: {
- compilerOptions: {
- noImplicitAny: false,
- target: "ES5",
- module: "none",
- sourceMap: true,
- removeComments: false,
- declaration: true,
- experimentalDecorators: true
- },
- include: [
- './build/dist/excalibur.d.ts',
- 'src/spec/Mocks.ts',
- 'src/spec/TestUtils.ts',
- 'src/spec/*.ts'
- ]
- },
-
-
-
- // test results reporter to use
- // possible values: 'dots', 'progress'
- // available reporters: https://npmjs.org/browse/keyword/karma-reporter
- reporters: ['progress', 'karma-typescript', 'coverage'],
-
- coverageReporter : {
- reporters:[
- {type: 'html', dir:'coverage/'},
- {type: 'lcovonly', dir: 'coverage/', file: 'lcov.info'},
- {type: 'text-summary'}
- ],
- },
-
- // web server port
- port: 9876,
-
-
- // enable / disable colors in the output (reporters and logs)
- colors: true,
-
-
- // level of logging
- // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
- logLevel: config.LOG_INFO,
-
-
- // enable / disable watching file and executing tests whenever any file changes
- autoWatch: false,
-
-
- // start these browsers
- // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
- browsers: ['ChromeHeadlessDebug'],
-
- customLaunchers: {
- ChromeHeadlessDebug: {
- base: 'ChromeHeadless',
- flags: [
- '--remote-debugging-port=9333',
- '--no-sandbox'
- ]
- }
- },
-
-
- // Continuous Integration mode
- // if true, Karma captures browsers, runs the tests and exits
- singleRun: true,
-
- // Concurrency level
- // how many browser should be started simultaneous
- concurrency: Infinity
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine', 'karma-typescript'],
+
+
+ // list of files / patterns to load in the browser
+ files: [
+ './build/dist/excalibur.js',
+ './build/dist/excalibur.d.ts',
+ './src/engine/**/*.ts',
+ './src/spec/images/**/*.png',
+ './src/spec/support/js-imagediff.js',
+ './src/spec/support/js-imagediff.d.ts',
+ './src/spec/Mocks.ts',
+ './src/spec/TestUtils.ts',
+ './src/spec/*.ts',
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ // "node_modules",
+ // "typedoc-default-themes"
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ 'src/spec/Mocks.ts': ['karma-typescript'],
+ 'src/spec/TestUtils.ts': ['karma-typescript'],
+ 'src/spec/*.ts': ['karma-typescript'],
+ 'build/dist/excalibur.js': ['coverage']
+ },
+
+ karmaTypescriptConfig: {
+ compilerOptions: {
+ noImplicitAny: false,
+ target: "ES5",
+ module: "none",
+ sourceMap: true,
+ removeComments: false,
+ declaration: true,
+ experimentalDecorators: true
+ },
+ include: [
+ './build/dist/excalibur.d.ts',
+ 'src/spec/Mocks.ts',
+ 'src/spec/TestUtils.ts',
+ 'src/spec/*.ts'
+ ]
+ },
+
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress', 'karma-typescript', 'coverage'],
+
+ coverageReporter: {
+ reporters: [
+ { type: 'html', dir: 'coverage/' },
+ { type: 'lcovonly', dir: 'coverage/', file: 'lcov.info' },
+ { type: 'text-summary' }
+ ],
+ },
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['ChromeHeadlessDebug'],
+
+ customLaunchers: {
+ ChromeHeadlessDebug: {
+ base: 'ChromeHeadless',
+ flags: [
+ '--remote-debugging-port=9333',
+ '--no-sandbox'
+ ]
+ }
+ },
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: true,
+
+ // Concurrency level
+ // how many browser should be started simultaneous
+ concurrency: Infinity
})
- }
-
\ No newline at end of file
+}
diff --git a/package-lock.json b/package-lock.json
index daa3a59c..7046ed0f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3,6 +3,39 @@
"requires": true,
"lockfileVersion": 1,
"dependencies": {
+ "@types/fs-extra": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-4.0.0.tgz",
+ "integrity": "sha512-PlKJw6ujJXLJjbvB3T0UCbY3jibKM6/Ya5cc9j1q+mYDeK3aR4Dp+20ZwxSuvJr9mIoPxp7+IL4aMOEvsscRTA==",
+ "dev": true,
+ "requires": {
+ "@types/node": "9.4.5"
+ }
+ },
+ "@types/handlebars": {
+ "version": "4.0.31",
+ "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.31.tgz",
+ "integrity": "sha1-p/umb6/kJxOu6I7sqNuRGS7+bnI=",
+ "dev": true
+ },
+ "@types/highlight.js": {
+ "version": "9.1.8",
+ "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.1.8.tgz",
+ "integrity": "sha1-0ifxi8uPPxh+FpZfJESFmgRol1g=",
+ "dev": true
+ },
+ "@types/lodash": {
+ "version": "4.14.74",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.74.tgz",
+ "integrity": "sha512-BZknw3E/z3JmCLqQVANcR17okqVTPZdlxvcIz0fJiJVLUCbSH1hK3zs9r634PVSmrzAxN+n/fxlVRiYoArdOIQ==",
+ "dev": true
+ },
+ "@types/marked": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz",
+ "integrity": "sha512-CSf9YWJdX1DkTNu9zcNtdCcn6hkRtB5ILjbhRId4ZOQqx30fXmdecuaXhugQL6eyrhuXtaHJ7PHI+Vm7k9ZJjg==",
+ "dev": true
+ },
"@types/minimatch": {
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-2.0.29.tgz",
@@ -10,11 +43,20 @@
"dev": true
},
"@types/node": {
- "version": "7.0.12",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.12.tgz",
- "integrity": "sha1-rl9noZwV91IUgATbB8u7Ny5p78k=",
+ "version": "9.4.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.5.tgz",
+ "integrity": "sha512-DvC7bzO5797bkApgukxouHmkOdYN2D0yL5olw0RncDpXUa6n39qTVsUi/5g2QJjPgl8qn4zh+4h0sofNoWGLRg==",
"dev": true
},
+ "@types/shelljs": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.0.tgz",
+ "integrity": "sha1-IpwVfGvB5n1rmQ5sXhjb0v9Yz/A=",
+ "dev": true,
+ "requires": {
+ "@types/node": "9.4.5"
+ }
+ },
"abbrev": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz",
@@ -576,6 +618,7 @@
"requires": {
"anymatch": "1.3.0",
"async-each": "1.0.1",
+ "fsevents": "1.1.3",
"glob-parent": "2.0.0",
"inherits": "2.0.3",
"is-binary-path": "1.0.1",
@@ -1658,6 +1701,910 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
+ "fsevents": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz",
+ "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "nan": "2.8.0",
+ "node-pre-gyp": "0.6.39"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ajv": {
+ "version": "4.11.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "co": "4.6.0",
+ "json-stable-stringify": "1.0.1"
+ }
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true
+ },
+ "aproba": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "delegates": "1.0.0",
+ "readable-stream": "2.2.9"
+ }
+ },
+ "asn1": {
+ "version": "0.2.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "assert-plus": {
+ "version": "0.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "aws-sign2": {
+ "version": "0.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "aws4": {
+ "version": "1.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "balanced-match": {
+ "version": "0.4.2",
+ "bundled": true,
+ "dev": true
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "tweetnacl": "0.14.5"
+ }
+ },
+ "block-stream": {
+ "version": "0.0.9",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ },
+ "boom": {
+ "version": "2.10.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "hoek": "2.16.3"
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.7",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "balanced-match": "0.4.2",
+ "concat-map": "0.0.1"
+ }
+ },
+ "buffer-shims": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "co": {
+ "version": "4.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true
+ },
+ "combined-stream": {
+ "version": "1.0.5",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "delayed-stream": "1.0.0"
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true,
+ "dev": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true
+ },
+ "cryptiles": {
+ "version": "2.0.5",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "boom": "2.10.1"
+ }
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "assert-plus": "1.0.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "debug": {
+ "version": "2.6.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "deep-extend": {
+ "version": "0.4.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ecc-jsbn": {
+ "version": "0.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "jsbn": "0.1.1"
+ }
+ },
+ "extend": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "extsprintf": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "form-data": {
+ "version": "2.1.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "asynckit": "0.4.0",
+ "combined-stream": "1.0.5",
+ "mime-types": "2.1.15"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true
+ },
+ "fstream": {
+ "version": "1.0.11",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "inherits": "2.0.3",
+ "mkdirp": "0.5.1",
+ "rimraf": "2.6.1"
+ }
+ },
+ "fstream-ignore": {
+ "version": "1.0.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fstream": "1.0.11",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4"
+ }
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aproba": "1.1.1",
+ "console-control-strings": "1.1.0",
+ "has-unicode": "2.0.1",
+ "object-assign": "4.1.1",
+ "signal-exit": "3.0.2",
+ "string-width": "1.0.2",
+ "strip-ansi": "3.0.1",
+ "wide-align": "1.1.2"
+ }
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "assert-plus": "1.0.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "glob": {
+ "version": "7.1.2",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.11",
+ "bundled": true,
+ "dev": true
+ },
+ "har-schema": {
+ "version": "1.0.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "har-validator": {
+ "version": "4.2.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ajv": "4.11.8",
+ "har-schema": "1.0.5"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "hawk": {
+ "version": "3.1.3",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "boom": "2.10.1",
+ "cryptiles": "2.0.5",
+ "hoek": "2.16.3",
+ "sntp": "1.0.9"
+ }
+ },
+ "hoek": {
+ "version": "2.16.3",
+ "bundled": true,
+ "dev": true
+ },
+ "http-signature": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "assert-plus": "0.2.0",
+ "jsprim": "1.4.0",
+ "sshpk": "1.13.0"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true,
+ "dev": true
+ },
+ "ini": {
+ "version": "1.3.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "number-is-nan": "1.0.1"
+ }
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "jodid25519": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "jsbn": "0.1.1"
+ }
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "json-stable-stringify": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "jsonify": "0.0.0"
+ }
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "jsonify": {
+ "version": "0.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "jsprim": {
+ "version": "1.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.0.2",
+ "json-schema": "0.2.3",
+ "verror": "1.3.6"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "mime-db": {
+ "version": "1.27.0",
+ "bundled": true,
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.15",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "mime-db": "1.27.0"
+ }
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "brace-expansion": "1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true,
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "node-pre-gyp": {
+ "version": "0.6.39",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "1.0.2",
+ "hawk": "3.1.3",
+ "mkdirp": "0.5.1",
+ "nopt": "4.0.1",
+ "npmlog": "4.1.0",
+ "rc": "1.2.1",
+ "request": "2.81.0",
+ "rimraf": "2.6.1",
+ "semver": "5.3.0",
+ "tar": "2.2.1",
+ "tar-pack": "3.4.0"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1.1.0",
+ "osenv": "0.1.4"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "1.1.4",
+ "console-control-strings": "1.1.0",
+ "gauge": "2.7.4",
+ "set-blocking": "2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true
+ },
+ "oauth-sign": {
+ "version": "0.8.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "1.0.2",
+ "os-tmpdir": "1.0.2"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true
+ },
+ "performance-now": {
+ "version": "0.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "1.0.7",
+ "bundled": true,
+ "dev": true
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "qs": {
+ "version": "6.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "0.4.2",
+ "ini": "1.3.4",
+ "minimist": "1.2.0",
+ "strip-json-comments": "2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.2.9",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "buffer-shims": "1.0.0",
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "1.0.7",
+ "string_decoder": "1.0.1",
+ "util-deprecate": "1.0.2"
+ }
+ },
+ "request": {
+ "version": "2.81.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aws-sign2": "0.6.0",
+ "aws4": "1.6.0",
+ "caseless": "0.12.0",
+ "combined-stream": "1.0.5",
+ "extend": "3.0.1",
+ "forever-agent": "0.6.1",
+ "form-data": "2.1.4",
+ "har-validator": "4.2.1",
+ "hawk": "3.1.3",
+ "http-signature": "1.1.1",
+ "is-typedarray": "1.0.0",
+ "isstream": "0.1.2",
+ "json-stringify-safe": "5.0.1",
+ "mime-types": "2.1.15",
+ "oauth-sign": "0.8.2",
+ "performance-now": "0.2.0",
+ "qs": "6.4.0",
+ "safe-buffer": "5.0.1",
+ "stringstream": "0.0.5",
+ "tough-cookie": "2.3.2",
+ "tunnel-agent": "0.6.0",
+ "uuid": "3.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "glob": "7.1.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.0.1",
+ "bundled": true,
+ "dev": true
+ },
+ "semver": {
+ "version": "5.3.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "sntp": {
+ "version": "1.0.9",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "hoek": "2.16.3"
+ }
+ },
+ "sshpk": {
+ "version": "1.13.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "asn1": "0.2.3",
+ "assert-plus": "1.0.0",
+ "bcrypt-pbkdf": "1.0.1",
+ "dashdash": "1.14.1",
+ "ecc-jsbn": "0.1.1",
+ "getpass": "0.1.7",
+ "jodid25519": "1.0.2",
+ "jsbn": "0.1.1",
+ "tweetnacl": "0.14.5"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "code-point-at": "1.1.0",
+ "is-fullwidth-code-point": "1.0.0",
+ "strip-ansi": "3.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.0.1"
+ }
+ },
+ "stringstream": {
+ "version": "0.0.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "2.2.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "block-stream": "0.0.9",
+ "fstream": "1.0.11",
+ "inherits": "2.0.3"
+ }
+ },
+ "tar-pack": {
+ "version": "3.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "debug": "2.6.8",
+ "fstream": "1.0.11",
+ "fstream-ignore": "1.0.5",
+ "once": "1.4.0",
+ "readable-stream": "2.2.9",
+ "rimraf": "2.6.1",
+ "tar": "2.2.1",
+ "uid-number": "0.0.6"
+ }
+ },
+ "tough-cookie": {
+ "version": "2.3.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "punycode": "1.4.1"
+ }
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "5.0.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "uid-number": {
+ "version": "0.0.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true
+ },
+ "uuid": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "verror": {
+ "version": "1.3.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "extsprintf": "1.0.2"
+ }
+ },
+ "wide-align": {
+ "version": "1.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "1.0.2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true
+ }
+ }
+ },
"gaze": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz",
@@ -2372,9 +3319,9 @@
}
},
"highlight.js": {
- "version": "9.10.0",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.10.0.tgz",
- "integrity": "sha1-+fCxTAvgDw5PseV3t0n+2eb1L1U=",
+ "version": "9.12.0",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz",
+ "integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=",
"dev": true
},
"hmac-drbg": {
@@ -2493,9 +3440,9 @@
"dev": true
},
"interpret": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.2.tgz",
- "integrity": "sha1-9PYj8LtxIvFfVxfI4lS4FhtcWy0=",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
+ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=",
"dev": true
},
"is-arrayish": {
@@ -3495,9 +4442,9 @@
"dev": true
},
"marked": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz",
- "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz",
+ "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==",
"dev": true
},
"maxmin": {
@@ -3705,6 +4652,13 @@
"duplexer2": "0.0.2"
}
},
+ "nan": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
+ "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=",
+ "dev": true,
+ "optional": true
+ },
"ncp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz",
@@ -5291,9 +6245,9 @@
"@types/shelljs": "0.7.0",
"fs-extra": "4.0.3",
"handlebars": "4.0.6",
- "highlight.js": "9.10.0",
- "lodash": "4.17.4",
- "marked": "0.3.6",
+ "highlight.js": "9.12.0",
+ "lodash": "4.17.5",
+ "marked": "0.3.12",
"minimatch": "3.0.3",
"progress": "2.0.0",
"shelljs": "0.7.8",
@@ -5301,48 +6255,6 @@
"typescript": "2.4.1"
},
"dependencies": {
- "@types/fs-extra": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-4.0.0.tgz",
- "integrity": "sha512-PlKJw6ujJXLJjbvB3T0UCbY3jibKM6/Ya5cc9j1q+mYDeK3aR4Dp+20ZwxSuvJr9mIoPxp7+IL4aMOEvsscRTA==",
- "dev": true,
- "requires": {
- "@types/node": "7.0.12"
- }
- },
- "@types/handlebars": {
- "version": "4.0.31",
- "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.31.tgz",
- "integrity": "sha1-p/umb6/kJxOu6I7sqNuRGS7+bnI=",
- "dev": true
- },
- "@types/highlight.js": {
- "version": "9.1.8",
- "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.1.8.tgz",
- "integrity": "sha1-0ifxi8uPPxh+FpZfJESFmgRol1g=",
- "dev": true
- },
- "@types/lodash": {
- "version": "4.14.74",
- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.74.tgz",
- "integrity": "sha512-BZknw3E/z3JmCLqQVANcR17okqVTPZdlxvcIz0fJiJVLUCbSH1hK3zs9r634PVSmrzAxN+n/fxlVRiYoArdOIQ==",
- "dev": true
- },
- "@types/marked": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz",
- "integrity": "sha512-CSf9YWJdX1DkTNu9zcNtdCcn6hkRtB5ILjbhRId4ZOQqx30fXmdecuaXhugQL6eyrhuXtaHJ7PHI+Vm7k9ZJjg==",
- "dev": true
- },
- "@types/shelljs": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.0.tgz",
- "integrity": "sha1-IpwVfGvB5n1rmQ5sXhjb0v9Yz/A=",
- "dev": true,
- "requires": {
- "@types/node": "7.0.12"
- }
- },
"fs-extra": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
@@ -5364,9 +6276,9 @@
}
},
"lodash": {
- "version": "4.17.4",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
- "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
+ "version": "4.17.5",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
+ "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==",
"dev": true
},
"progress": {
@@ -5382,16 +6294,10 @@
"dev": true,
"requires": {
"glob": "7.0.6",
- "interpret": "1.0.2",
+ "interpret": "1.1.0",
"rechoir": "0.6.2"
}
},
- "typedoc-default-themes": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz",
- "integrity": "sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic=",
- "dev": true
- },
"typescript": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.1.tgz",
@@ -5400,10 +6306,16 @@
}
}
},
+ "typedoc-default-themes": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz",
+ "integrity": "sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic=",
+ "dev": true
+ },
"typescript": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.1.tgz",
- "integrity": "sha1-7znN6ierrAtQAkLWcmq5DgyEZjE=",
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.2.2.tgz",
+ "integrity": "sha1-YGAiUIR5tV/6NotY/uljoD39eww=",
"dev": true
},
"uglify-js": {
diff --git a/package.json b/package.json
index 4849df04..dbc11791 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,7 @@
"travis-ci": "^2.1.0",
"tslint": "^3.13.0",
"typedoc": "0.9.0",
- "typescript": "2.6.1"
+ "typescript": "2.2.2"
},
"dependencies": {}
}
diff --git a/sandbox/src/game.ts b/sandbox/src/game.ts
index d498c35d..4701b0c6 100644
--- a/sandbox/src/game.ts
+++ b/sandbox/src/game.ts
@@ -1,35 +1,35 @@
///
/*********************
- * uuuuuuuuuuuuuuuuuuuu
- * u" uuuuuuuuuuuuuuuuuu "u
- * u" u$$$$$$$$$$$$$$$$$$$$u "u
- * u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u
- * u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
- * u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
- * u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
- * $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
- * $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
- * $ $$$" ... "$... ...$" ... "$$$ ... "$$$ $
- * $ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $
- * $ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $
- * $ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $
- * $ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $
- * $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
- * "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
- * "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
- * "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
- * "u "$$$$$$$$$$$$$$$$$$$$$$$$" u"
- * "u "$$$$$$$$$$$$$$$$$$$$" u"
- * "u """""""""""""""""" u"
+ * uuuuuuuuuuuuuuuuuuuu
+ * u" uuuuuuuuuuuuuuuuuu "u
+ * u" u$$$$$$$$$$$$$$$$$$$$u "u
+ * u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u
+ * u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
+ * u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
+ * u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
+ * $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
+ * $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
+ * $ $$$" ... "$... ...$" ... "$$$ ... "$$$ $
+ * $ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $
+ * $ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $
+ * $ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $
+ * $ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $
+ * $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
+ * "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
+ * "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
+ * "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
+ * "u "$$$$$$$$$$$$$$$$$$$$$$$$" u"
+ * "u "$$$$$$$$$$$$$$$$$$$$" u"
+ * "u """""""""""""""""" u"
* """"""""""""""""""""
- *
+ *
* WARNING: Do not use this sandbox as a "sample" of how to use Excalibur *properly.*
* This is a messy POS that we use to do crazy integration testing and is really
* a terrible example.
- *
+ *
* Please don't reference this. Reference the official sample games!
- *
+ *
* Thank you,
* Excalibur.js team
*/
@@ -67,23 +67,38 @@ ex.Physics.acc = new ex.Vector(0, 800); // global accel
// Add some UI
-var heart = new ex.UIActor(0, 0, 20, 20);
+//var heart = new ex.UIActor(0, 0, 20, 20);
+var heart = new ex.UIActor({x: 0, y: 0, width: 20, height: 20});
heart.scale.setTo(2, 2);
heart.addDrawing(heartTex.asSprite());
game.add(heart);
// Turn on debug diagnostics
game.isDebug = false;
-var blockSprite = new ex.Sprite(imageBlocks, 0, 0, 65, 49);
+//var blockSprite = new ex.Sprite(imageBlocks, 0, 0, 65, 49);
+var blockSprite = new ex.Sprite({image: imageBlocks, sx:0, sy: 0, swidth: 65, sheight: 49});
// Create spritesheet
-var spriteSheetRun = new ex.SpriteSheet(imageRun, 21, 1, 96, 96);
-var spriteSheetJump = new ex.SpriteSheet(imageJump, 21, 1, 96, 96);
+//var spriteSheetRun = new ex.SpriteSheet(imageRun, 21, 1, 96, 96);
+var spriteSheetRun = new ex.SpriteSheet({ image: imageRun,
+ columns: 21,
+ rows: 1,
+ spWidth: 96,
+ spHeight: 96
+});
+//var spriteSheetJump = new ex.SpriteSheet(imageJump, 21, 1, 96, 96);
+var spriteSheetJump = new ex.SpriteSheet({ image: imageJump,
+ columns: 21,
+ rows: 1,
+ spWidth: 96,
+ spHeight: 96
+});
var tileBlockWidth = 64,
tileBlockHeight = 48,
spriteTiles = new ex.SpriteSheet(imageBlocks, 1, 1, tileBlockWidth, tileBlockHeight);
// create a collision map
-var tileMap = new ex.TileMap(100, 300, tileBlockWidth, tileBlockHeight, 4, 500);
+//var tileMap = new ex.TileMap(100, 300, tileBlockWidth, tileBlockHeight, 4, 500);
+var tileMap = new ex.TileMap({ x: 100, y: 300, cellWidth: tileBlockWidth, cellHeight: tileBlockHeight, rows: 4, cols: 500 });
tileMap.registerSpriteSheet("default", spriteTiles);
tileMap.data.forEach(function (cell: ex.Cell) {
cell.solid = true;
@@ -92,8 +107,21 @@ tileMap.data.forEach(function (cell: ex.Cell) {
game.add(tileMap);
// Create spriteFont
-var spriteFont = new ex.SpriteFont(spriteFontImage, '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ', true, 16, 3, 16, 16);
-var label = new ex.Label('Hello World', 100, 100, null, spriteFont);
+//var spriteFont = new ex.SpriteFont(spriteFontImage, '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ', true, 16, 3, 16, 16);
+var spriteFont = new ex.SpriteFont({
+ image: spriteFontImage,
+ alphabet: '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ',
+ caseInsensitive: true,
+ columns: 16,
+ rows: 3,
+ spWidth: 16,
+ spHeight: 16
+});
+//var label = new ex.Label('Hello World', 100, 100, null, spriteFont);
+var label = new ex.Label({text: 'Hello World',
+ x: 100,
+ y: 100,
+ spriteFont: spriteFont});
label.actions.scaleTo(2, 2, .5, .5).scaleTo(1, 1, .5, .5).repeatForever();
game.add(label);
@@ -115,8 +143,14 @@ var currentX = 0;
for (var i = 0; i < 36; i++) {
currentX = tileBlockWidth * i + 10;
var color = new ex.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255);
- var block = new ex.Actor(currentX, 350 + Math.random() * 100, tileBlockWidth, tileBlockHeight, color);
- block.collisionType = ex.CollisionType.Fixed;
+ var block = new ex.Actor({ x: currentX,
+ y: 350 + Math.random() * 100,
+ width: tileBlockWidth,
+ height: tileBlockHeight,
+ color: color,
+ collisionType: ex.CollisionType.Fixed });
+ //var block = new ex.Actor(currentX, 350 + Math.random() * 100, tileBlockWidth, tileBlockHeight, color);
+ //block.collisionType = ex.CollisionType.Fixed;
block.addCollisionGroup('ground');
block.addDrawing(Animations.Block, blockAnimation);
@@ -150,9 +184,15 @@ game.add(follower);
// Create the player
-var player = new ex.Actor(100, -200, 32, 96);
-player.enableCapturePointer = true;
-player.collisionType = ex.CollisionType.Active;
+// var player = new ex.Actor(100, -200, 32, 96);
+// player.enableCapturePointer = true;
+// player.collisionType = ex.CollisionType.Active;
+var player = new ex.Actor({x: 100,
+ y: -200,
+ width: 32,
+ height: 96,
+ enableCapturePointer: true,
+ collisionType: ex.CollisionType.Active});
follower.actions.meet(player, 60).asPromise().then(() => {
console.log("Player met!!");
});
@@ -169,12 +209,23 @@ var healthbar = new ex.Actor(0, -70, 140, 5, new ex.Color(0, 255, 0));
player.add(healthbar);
// Add Title above player
-var playerLabel = new ex.Label('My Player', -70, -69, null, spriteFont);
+var playerLabel = new ex.Label({
+ text: 'My Player',
+ pos: new ex.Vector(-70, -69),
+ fontFamily: 'Times New Roman'
+ // spriteFont: spriteFont
+});
player.add(playerLabel);
// Retrieve animations for player from sprite sheet
-var left = spriteSheetRun.getAnimationBetween(game, 1, 11, 50);
+var left_sprites = spriteSheetRun.getAnimationBetween(game, 1, 11, 50).sprites;
+var left = new ex.Animation({
+ engine: game,
+ speed: 50,
+ sprites: left_sprites
+});
+// var left = new ex.Animation(game, left_sprites, 50);
var right = spriteSheetRun.getAnimationBetween(game, 1, 11, 50);
right.flipHorizontal = true;
var idle = spriteSheetRun.getAnimationByIndices(game, [0], 200);
@@ -316,9 +367,9 @@ player.on('precollision', (data?: ex.PreCollisionEvent) => {
player.setDrawing(Animations.Idle);
}
inAir = false;
- if (data.other && !(game.input.keyboard.isHeld(ex.Input.Keys.Left) ||
- game.input.keyboard.isHeld(ex.Input.Keys.Right) ||
- game.input.keyboard.isHeld(ex.Input.Keys.Up) ||
+ if (data.other && !(game.input.keyboard.isHeld(ex.Input.Keys.Left) ||
+ game.input.keyboard.isHeld(ex.Input.Keys.Right) ||
+ game.input.keyboard.isHeld(ex.Input.Keys.Up) ||
game.input.keyboard.isHeld(ex.Input.Keys.Down))) {
player.vel.x = data.other.vel.x;
player.vel.y = data.other.vel.y;
@@ -387,25 +438,50 @@ var camera = game.currentScene.camera;
game.add(player);
// Add particle emitter
-var emitter = new ex.ParticleEmitter(100, 300, 2, 2);
-emitter.minVel = 417;
-emitter.maxVel = 589;
-emitter.minAngle = Math.PI;
-emitter.maxAngle = Math.PI * 2;
-emitter.isEmitting = false;
-emitter.emitRate = 494;
-emitter.opacity = 0.84;
-emitter.fadeFlag = true;
-emitter.particleLife = 2465;
-emitter.maxSize = 1.5;
-emitter.minSize = .1;
-emitter.acceleration = new ex.Vector(0, 460);
-emitter.beginColor = ex.Color.Red;
-emitter.endColor = ex.Color.Yellow;
-emitter.particleSprite = blockSprite.clone();
-emitter.particleSprite.anchor = new ex.Vector(.5, .5);
-emitter.particleRotationalVelocity = Math.PI / 10;
-emitter.randomRotation = true;
+var sprite = blockSprite.clone();
+sprite.anchor = new ex.Vector(.5, .5);
+var emitter = new ex.ParticleEmitter({
+ x: 100,
+ y: 300,
+ width: 2,
+ height: 2,
+ minVel: 417,
+ maxVel: 589,
+ minAngle: Math.PI,
+ maxAngle: Math.PI * 2,
+ isEmitting: false,
+ emitRate: 494,
+ opacity: 0.84,
+ fadeFlag: true,
+ particleLife: 2465,
+ maxSize: 1.5,
+ minSize: .1,
+ acceleration: new ex.Vector(0, 460),
+ beginColor: ex.Color.Red,
+ endColor: ex.Color.Yellow,
+ particleSprite: sprite,
+ particleRotationalVelocity: Math.PI / 10,
+ randomRotation: true
+});
+// var emitter = new ex.ParticleEmitter(100, 300, 2, 2);
+// emitter.minVel = 417;
+// emitter.maxVel = 589;
+// emitter.minAngle = Math.PI;
+// emitter.maxAngle = Math.PI * 2;
+// emitter.isEmitting = false;
+// emitter.emitRate = 494;
+// emitter.opacity = 0.84;
+// emitter.fadeFlag = true;
+// emitter.particleLife = 2465;
+// emitter.maxSize = 1.5;
+// emitter.minSize = .1;
+// emitter.acceleration = new ex.Vector(0, 460);
+// emitter.beginColor = ex.Color.Red;
+// emitter.endColor = ex.Color.Yellow;
+// emitter.particleSprite = blockSprite.clone();
+// emitter.particleSprite.anchor = new ex.Vector(.5, .5);
+// emitter.particleRotationalVelocity = Math.PI / 10;
+// emitter.randomRotation = true;
emitter.particleSprite.addEffect(new ex.Effects.Grayscale());
game.add(emitter);
diff --git a/src/engine/Actor.ts b/src/engine/Actor.ts
index 07f4ca80..3ef07e21 100644
--- a/src/engine/Actor.ts
+++ b/src/engine/Actor.ts
@@ -22,21 +22,35 @@ import { Body } from './Collision/Body';
import { Side } from './Collision/Side';
import { IEvented } from './Interfaces/IEvented';
import { IActionable } from './Actions/IActionable';
+import { Configurable } from './Configurable';
import * as Traits from './Traits/Index';
import * as Effects from './Drawing/SpriteEffects';
import * as Util from './Util/Util';
import * as Events from './Events';
+
/**
- * The most important primitive in Excalibur is an `Actor`. Anything that
- * can move on the screen, collide with another `Actor`, respond to events,
- * or interact with the current scene, must be an actor. An `Actor` **must**
- * be part of a [[Scene]] for it to be drawn to the screen.
- *
- * [[include:Actors.md]]
- *
+ * [[include:Constructors.md]]
*/
-export class Actor extends Class implements IActionable, IEvented {
+export interface IActorArgs extends Partial {
+ width?: number;
+ height?: number;
+ pos?: Vector;
+ vel?: Vector;
+ acc?: Vector;
+ rotation?: number;
+ rx?: number;
+ z?: number;
+ color?: Color;
+ visible?: boolean;
+ body?: Body;
+ collisionType?: CollisionType;
+}
+
+/**
+ * @hidden
+ */
+export class ActorImpl extends Class implements IActionable, IEvented {
/**
* Indicates the next id to be set
*/
@@ -44,7 +58,7 @@ export class Actor extends Class implements IActionable, IEvented {
/**
* The unique identifier for the actor
*/
- public id: number = Actor.maxId++;
+ public id: number = ActorImpl.maxId++;
/**
* The physics body the is associated with this actor. The body is the container for all physical properties, like position, velocity,
@@ -299,7 +313,7 @@ export class Actor extends Class implements IActionable, IEvented {
/**
* Indicates whether the actor is physically in the viewport
*/
- public isOffScreen = false;
+ public isOffScreen: boolean = false;
/**
* The visibility of an actor
*/
@@ -386,7 +400,7 @@ export class Actor extends Class implements IActionable, IEvented {
* Configuration for [[CapturePointer]] trait
*/
public capturePointer: Traits.ICapturePointerConfig = {
- captureMoveEvents: false
+ captureMoveEvents: false
};
private _zIndex: number = 0;
@@ -401,9 +415,18 @@ export class Actor extends Class implements IActionable, IEvented {
* @param color The starting color of the actor. Leave null to draw a transparent actor. The opacity of the color will be used as the
* initial [[opacity]].
*/
- constructor(x?: number, y?: number, width?: number, height?: number, color?: Color) {
+ constructor(xOrConfig?: number | IActorArgs, y?: number, width?: number, height?: number, color?: Color) {
super();
- this.pos.x = x || 0;
+
+ if (xOrConfig && typeof xOrConfig === 'object') {
+ var config = xOrConfig;
+ xOrConfig = config.pos ? config.pos.x : config.x;
+ y = config.pos ? config.pos.y : config.y;
+ width = config.width;
+ height = config.height;
+ }
+
+ this.pos.x = xOrConfig || 0;
this.pos.y = y || 0;
this._width = width || 0;
this._height = height || 0;
@@ -412,6 +435,7 @@ export class Actor extends Class implements IActionable, IEvented {
// set default opacity of an actor to the color
this.opacity = color.a;
}
+
// Build default pipeline
//this.traits.push(new ex.Traits.EulerMovement());
// TODO: TileMaps should be converted to a collision area
@@ -1120,6 +1144,29 @@ export class Actor extends Class implements IActionable, IEvented {
}
}
+
+/**
+ * The most important primitive in Excalibur is an `Actor`. Anything that
+ * can move on the screen, collide with another `Actor`, respond to events,
+ * or interact with the current scene, must be an actor. An `Actor` **must**
+ * be part of a [[Scene]] for it to be drawn to the screen.
+ *
+ * [[include:Actors.md]]
+ *
+ *
+ * [[include:Constructors.md]]
+ *
+ */
+export class Actor extends Configurable(ActorImpl) {
+ constructor();
+ constructor(config?: IActorArgs);
+ constructor(x?: number, y?: number, width?: number, height?: number, color?: Color);
+ constructor(xOrConfig?: number | IActorArgs, y?: number, width?: number, height?: number, color?: Color) {
+ super(xOrConfig, y, width, height, color);
+ }
+}
+
+
/**
* An enum that describes the types of collisions actors can participate in
*/
@@ -1149,4 +1196,4 @@ export enum CollisionType {
* collision events.
*/
Fixed
-}
+}
\ No newline at end of file
diff --git a/src/engine/Algebra.ts b/src/engine/Algebra.ts
index 4b34e9b9..9541b7f0 100644
--- a/src/engine/Algebra.ts
+++ b/src/engine/Algebra.ts
@@ -1,5 +1,3 @@
-
-
/**
* A 2D vector on a plane.
*/
@@ -498,4 +496,4 @@ export class Projection {
}
return 0;
}
-}
+}
\ No newline at end of file
diff --git a/src/engine/Configurable.ts b/src/engine/Configurable.ts
new file mode 100644
index 00000000..2febe451
--- /dev/null
+++ b/src/engine/Configurable.ts
@@ -0,0 +1,28 @@
+export type Constructor = {
+ new(...args: any[]): T;
+}
+export function Configurable>(base: T): T {
+ return class extends base {
+ public assign(props: Partial) {
+
+ //set the value of every property that was passed in,
+ //if the constructor previously set this value, it will be overridden here
+ for (var k in props) {
+ if (typeof (this)[k] !== 'function') {
+ (this)[k] = (props)[k];
+ }
+ }
+ }
+
+ constructor(...args: any[]) {
+ super(...args);
+ //get the number of arguments that aren't undefined. TS passes a value to all parameters
+ //of whatever ctor is the implementation, so args.length doesn't work here.
+ var size = args.filter(function (value) { return value !== undefined; }).length;
+ if (size === 1 && args[0] && typeof args[0] === 'object' && !(args[0] instanceof Array)) {
+ this.assign(args[0]);
+ }
+ }
+
+ };
+}
\ No newline at end of file
diff --git a/src/engine/Docs/Constructors.md b/src/engine/Docs/Constructors.md
new file mode 100644
index 00000000..157ebe64
--- /dev/null
+++ b/src/engine/Docs/Constructors.md
@@ -0,0 +1,61 @@
+## Constructor Arguments
+
+In Excalibur there are option bag constructors available on most types. These support any public property or member, methods are not supported. The API documentation does not provide an exhaustive list of possible properties but a list of commonly used properties.
+
+
+For example instead of doing this:
+
+```typescript
+var actor = new ex.Actor(1, 2, 100, 100, ex.Color.Red);
+actor.collisionType = ex.CollisionType.Active;
+
+```
+
+This is possible:
+```typescript
+var options: IActorArgs = {
+ pos: new ex.Vector(1,2);
+ width: 100,
+ height: 100,
+ color: ex.Color.Red,
+ collisionType: ex.CollisionType.Active
+}
+
+var actor = new ex.Actor(options);
+```
+
+In fact you can create a duplicate this way
+
+```typescript
+var actor = new ex.Actor({
+ pos: new ex.Vector(1,2)
+})
+var actorClone = new ex.Actor(actor);
+
+expect(actor.pos).toBe(actorClone.pos); // true;
+```
+
+Types that support option bags can have their properties mass assigned using the assign method.
+
+```typescript
+var actor = new ex.Actor(options);
+
+actor.assign({
+ pos: new ex.Vector(100, 100),
+ width: 1000,
+ color: ex.Color.Red,
+});
+```
+
+See:
+ - [[Actor]]
+ - [[Animation]]
+ - [[Label]]
+ - [[Sprite]]
+ - [[SpriteSheet]]
+ - [[SpriteFont]]
+ - [[UIActor]]
+ - [[Particle]]
+ - [[ParticleEmitter]]
+ - [[TileMap]]
+ - [[Cell]]
diff --git a/src/engine/Drawing/Animation.ts b/src/engine/Drawing/Animation.ts
index 3ecc51e2..575cda2c 100644
--- a/src/engine/Drawing/Animation.ts
+++ b/src/engine/Drawing/Animation.ts
@@ -1,4 +1,5 @@
import { Sprite } from './Sprite';
+import { IAnimationArgs } from '../Drawing/Animation';
import * as Effects from './SpriteEffects';
import { Color } from './Color';
@@ -6,20 +7,18 @@ import { IDrawable } from '../Interfaces/IDrawable';
import { Vector } from '../Algebra';
import { Engine } from '../Engine';
import * as Util from '../Util/Util';
+import { Configurable } from '../Configurable';
/**
- * Animations allow you to display a series of images one after another,
- * creating the illusion of change. Generally these images will come from a [[SpriteSheet]] source.
- *
- * [[include:Animations.md]]
+ * @hidden
*/
-export class Animation implements IDrawable {
+export class AnimationImpl implements IDrawable {
/**
* The sprite frames to play, in order. See [[SpriteSheet.getAnimationForAll]] to quickly
* generate an [[Animation]].
*/
- public sprites: Sprite[];
+ public sprites: Sprite[] = [];
/**
* Duration to show each frame (in milliseconds)
@@ -33,9 +32,9 @@ export class Animation implements IDrawable {
private _oldTime: number = Date.now();
- public anchor = new Vector(0.0, 0.0);
+ public anchor: Vector = new Vector(0.0, 0.0);
public rotation: number = 0.0;
- public scale = new Vector(1, 1);
+ public scale: Vector = new Vector(1, 1);
/**
* Indicates whether the animation should loop after it is completed
@@ -61,7 +60,7 @@ export class Animation implements IDrawable {
public flipHorizontal: boolean = false;
public width: number = 0;
- public height: number = 0;
+ public height: number= 0;
public naturalWidth: number = 0;
public naturalHeight: number = 0;
@@ -73,23 +72,33 @@ export class Animation implements IDrawable {
* @param speed The number in milliseconds to display each frame in the animation
* @param loop Indicates whether the animation should loop after it is completed
*/
- constructor(engine: Engine, images: Sprite[], speed: number, loop?: boolean) {
- this.sprites = images;
+ constructor(engineOrConfig: Engine | IAnimationArgs, sprites: Sprite[], speed: number, loop?: boolean) {
+
+ var engine = engineOrConfig;
+ if (engineOrConfig && !(engineOrConfig instanceof Engine)) {
+ var config = engineOrConfig;
+ engine = config.engine;
+ sprites = config.sprites;
+ speed = config.speed;
+ loop = config.loop;
+ }
+
+ this.sprites = sprites;
this.speed = speed;
- this._engine = engine;
+ this._engine = engine;
if (loop != null) {
this.loop = loop;
}
- if (images && images[0]) {
- this.height = images[0] ? images[0].height : 0;
- this.width = images[0] ? images[0].width : 0;
+ if (sprites && sprites[0]) {
+ this.height = sprites[0] ? sprites[0].height : 0;
+ this.width = sprites[0] ? sprites[0].width : 0;
- this.naturalWidth = images[0] ? images[0].naturalWidth : 0;
- this.naturalHeight = images[0] ? images[0].naturalHeight : 0;
+ this.naturalWidth = sprites[0] ? sprites[0].naturalWidth : 0;
+ this.naturalHeight = sprites[0] ? sprites[0].naturalHeight : 0;
- this.freezeFrame = images.length - 1;
+ this.freezeFrame = sprites.length - 1;
}
}
@@ -294,4 +303,35 @@ export class Animation implements IDrawable {
this.reset();
this._engine.playAnimation(this, x, y);
}
- }
\ No newline at end of file
+}
+
+/**
+ * [[include:Constructors.md]]
+ */
+export interface IAnimationArgs extends Partial {
+ engine: Engine;
+ sprites: Sprite[];
+ speed: number;
+ loop?: boolean;
+ anchor?: Vector;
+ rotation?: number;
+ scale?: Vector;
+ flipVertical?: boolean;
+ flipHorizontal?: boolean;
+ width?: number;
+ height?: number;
+}
+
+/**
+ * Animations allow you to display a series of images one after another,
+ * creating the illusion of change. Generally these images will come from a [[SpriteSheet]] source.
+ *
+ * [[include:Animations.md]]
+ */
+export class Animation extends Configurable(AnimationImpl) {
+ constructor(config: IAnimationArgs);
+ constructor(engine: Engine, images: Sprite[], speed: number, loop?: boolean);
+ constructor(engineOrConfig: Engine | IAnimationArgs, images?: Sprite[], speed?: number, loop?: boolean) {
+ super(engineOrConfig, images, speed, loop);
+ }
+}
\ No newline at end of file
diff --git a/src/engine/Drawing/Index.ts b/src/engine/Drawing/Index.ts
index a339ff1b..8dc275ef 100644
--- a/src/engine/Drawing/Index.ts
+++ b/src/engine/Drawing/Index.ts
@@ -1,8 +1,8 @@
-export * from './Animation';
+export { Animation, IAnimationArgs } from './Animation';
export * from './Color';
export * from './Polygon';
-export * from './Sprite';
-export * from './SpriteSheet';
+export { Sprite, ISpriteArgs }from './Sprite';
+export {SpriteSheet, SpriteFont, ISpriteSheetArgs, ISpriteFontInitArgs, ISpriteFontOptions } from './SpriteSheet';
import * as effects from './SpriteEffects';
export { effects as Effects }
\ No newline at end of file
diff --git a/src/engine/Drawing/Sprite.ts b/src/engine/Drawing/Sprite.ts
index c8e5732c..c2708809 100644
--- a/src/engine/Drawing/Sprite.ts
+++ b/src/engine/Drawing/Sprite.ts
@@ -6,14 +6,12 @@ import { Texture } from '../Resources/Texture';
import { Vector } from '../Algebra';
import { Logger } from '../Util/Log';
import { clamp } from '../Util/Util';
+import { Configurable } from '../Configurable';
/**
- * A [[Sprite]] is one of the main drawing primitives. It is responsible for drawing
- * images or parts of images from a [[Texture]] resource to the screen.
- *
- * [[include:Sprites.md]]
+ * @hidden
*/
-export class Sprite implements IDrawable {
+export class SpriteImpl implements IDrawable {
private _texture: Texture;
public rotation: number = 0.0;
@@ -36,6 +34,11 @@ export class Sprite implements IDrawable {
public height: number = 0;
public effects: Effects.ISpriteEffect[] = [];
+ public sx: number = 0;
+ public sy: number = 0;
+ public swidth: number = 0;
+ public sheight: number = 0;
+
public naturalWidth: number = 0;
public naturalHeight: number = 0;
@@ -52,13 +55,27 @@ export class Sprite implements IDrawable {
* @param swidth The width of the sprite in pixels
* @param sheight The height of the sprite in pixels
*/
- constructor(image: Texture, public sx: number, public sy: number, public swidth: number, public sheight: number) {
+ constructor(imageOrConfig: Texture | ISpriteArgs, sx: number, sy: number, swidth: number, sheight: number) {
if (sx < 0 || sy < 0 || swidth < 0 || sheight < 0) {
this.logger.error('Sprite cannot have any negative dimensions x:',
sx, 'y:', sy, 'width:', swidth, 'height:', sheight);
}
- this._texture = image;
+ var image = imageOrConfig;
+ if (imageOrConfig && !(imageOrConfig instanceof Texture)) {
+ sx = imageOrConfig.sx;
+ sy = imageOrConfig.sy;
+ swidth = imageOrConfig.swidth;
+ sheight = imageOrConfig.sheight;
+ image = imageOrConfig.image;
+ }
+
+ this.sx = sx || 0;
+ this.sy = sy || 0;
+ this.swidth = swidth || 0;
+ this.sheight = sheight || 0;
+
+ this._texture = image;
this._spriteCanvas = document.createElement('canvas');
this._spriteCanvas.width = swidth;
this._spriteCanvas.height = sheight;
@@ -320,7 +337,7 @@ export class Sprite implements IDrawable {
/**
* Produces a copy of the current sprite
*/
- public clone(): Sprite {
+ public clone(): SpriteImpl {
var result = new Sprite(this._texture, this.sx, this.sy, this.swidth, this.sheight);
result.scale = this.scale.clone();
result.rotation = this.rotation;
@@ -334,4 +351,35 @@ export class Sprite implements IDrawable {
return result;
}
-}
\ No newline at end of file
+}
+
+/**
+ * [[include:Constructors.md]]
+ */
+export interface ISpriteArgs extends Partial {
+ image: Texture;
+ sx: number;
+ sy: number;
+ swidth: number;
+ sheight: number;
+ rotation?: number;
+ anchor?: Vector;
+ scale?: Vector;
+ flipVertical?: boolean;
+ flipHorizontal?: boolean;
+}
+
+/**
+ * A [[Sprite]] is one of the main drawing primitives. It is responsible for drawing
+ * images or parts of images from a [[Texture]] resource to the screen.
+ *
+ * [[include:Sprites.md]]
+ */
+export class Sprite extends Configurable(SpriteImpl) {
+ constructor(config: ISpriteArgs);
+ constructor(image: Texture, sx: number, sy: number, swidth: number, sheight: number)
+ constructor(imageOrConfig: Texture | ISpriteArgs, sx?: number, sy?: number, swidth?: number, sheight?: number) {
+ super(imageOrConfig, sx, sy, swidth, sheight);
+ }
+}
+
diff --git a/src/engine/Drawing/SpriteSheet.ts b/src/engine/Drawing/SpriteSheet.ts
index adbc2a47..2ca89686 100644
--- a/src/engine/Drawing/SpriteSheet.ts
+++ b/src/engine/Drawing/SpriteSheet.ts
@@ -1,5 +1,5 @@
import { Sprite } from './Sprite';
-import { Animation } from './Animation';
+import { Animation } from '../Drawing/Animation';
import { Color } from './Color';
import * as Effects from './SpriteEffects';
@@ -7,16 +7,18 @@ import { Texture } from '../Resources/Texture';
import { Engine } from '../Engine';
import { Logger } from '../Util/Log';
import { TextAlign, BaseAlign } from '../Label';
+import { Configurable } from '../Configurable';
/**
- * Sprite sheets are a useful mechanism for slicing up image resources into
- * separate sprites or for generating in game animations. [[Sprite|Sprites]] are organized
- * in row major order in the [[SpriteSheet]].
- *
- * [[include:SpriteSheets.md]]
+ * @hidden
*/
-export class SpriteSheet {
+export class SpriteSheetImpl {
public sprites: Sprite[] = [];
+ public image: Texture = null;
+ public columns: number = 0;
+ public rows: number = 0;
+ public spWidth: number;
+ public spHeight: number;
/**
* @param image The backing image texture to build the SpriteSheet
@@ -25,8 +27,29 @@ export class SpriteSheet {
* @param spWidth The width of each individual sprite in pixels
* @param spHeight The height of each individual sprite in pixels
*/
- constructor(public image: Texture, public columns: number, public rows: number, spWidth: number, spHeight: number) {
- this.sprites = new Array(columns * rows);
+ constructor(imageOrConfigOrSprites: Texture | ISpriteSheetArgs | Sprite[],
+ columns?: number, rows?: number, spWidth?: number, spHeight?: number) {
+
+ var loadFromImage: boolean = false;
+ if (imageOrConfigOrSprites instanceof Array) {
+ this.sprites = imageOrConfigOrSprites;
+ } else {
+ if (imageOrConfigOrSprites && !(imageOrConfigOrSprites instanceof Texture)) {
+ this.columns = imageOrConfigOrSprites.columns;
+ this.rows = imageOrConfigOrSprites.rows;
+ this.spWidth = imageOrConfigOrSprites.spWidth;
+ this.spHeight = imageOrConfigOrSprites.spHeight;
+ this.image = imageOrConfigOrSprites.image;
+ } else {
+ this.image = imageOrConfigOrSprites;
+ this.columns = columns;
+ this.rows = rows;
+ this.spWidth = spWidth;
+ this.spHeight = spHeight;
+ }
+ this.sprites = new Array(this.columns * this.rows);
+ loadFromImage = true;
+ }
// TODO: Inspect actual image dimensions with preloading
/*if(spWidth * columns > this.internalImage.naturalWidth){
@@ -37,15 +60,19 @@ export class SpriteSheet {
throw new Error("SpriteSheet specified is higher than image height");
}*/
- var i = 0;
- var j = 0;
- for (i = 0; i < rows; i++) {
- for (j = 0; j < columns; j++) {
- this.sprites[j + i * columns] = new Sprite(this.image, j * spWidth, i * spHeight, spWidth, spHeight);
+ if (loadFromImage) {
+ var i = 0;
+ var j = 0;
+ for (i = 0; i < this.rows; i++) {
+ for (j = 0; j < this.columns; j++) {
+ this.sprites[j + i * this.columns] = new Sprite(this.image,
+ j * this.spWidth, i * this.spHeight, this.spWidth, this.spHeight);
+ }
}
}
}
+
/**
* Create an animation from the this SpriteSheet by listing out the
* sprite indices. Sprites are organized in row major order in the SpriteSheet.
@@ -66,10 +93,11 @@ export class SpriteSheet {
/**
* Create an animation from the this SpriteSheet by specifing the range of
- * images with the beginning and ending index
+ * images with the beginning (inclusive) and ending (exclusive) index
+ * For example `getAnimationBetween(engine, 0, 5, 200)` returns an animation with 5 frames.
* @param engine Reference to the current game Engine
- * @param beginIndex The index to start taking frames
- * @param endIndex The index to stop taking frames
+ * @param beginIndex The index to start taking frames (inclusive)
+ * @param endIndex The index to stop taking frames (exclusive)
* @param speed The number in milliseconds to display each frame in the animation
*/
public getAnimationBetween(engine: Engine, beginIndex: number, endIndex: number, speed: number) {
@@ -108,13 +136,35 @@ export class SpriteSheet {
}
/**
- * Sprite fonts are a used in conjunction with a [[Label]] to specify
- * a particular bitmap as a font. Note that some font features are not
- * supported by Sprite fonts.
+ * [[include:Constructors.md]]
+ */
+export interface ISpriteSheetArgs extends Partial {
+ image: Texture;
+ sprites?: Sprite[];
+ spWidth: number;
+ spHeight: number;
+ rows: number;
+ columns: number;
+}
+
+/**
+ * Sprite sheets are a useful mechanism for slicing up image resources into
+ * separate sprites or for generating in game animations. [[Sprite|Sprites]] are organized
+ * in row major order in the [[SpriteSheet]].
*
- * [[include:SpriteFonts.md]]
+ * [[include:SpriteSheets.md]]
*/
-export class SpriteFont extends SpriteSheet {
+export class SpriteSheet extends Configurable(SpriteSheetImpl) {
+ constructor(config: ISpriteSheetArgs);
+ constructor(sprites: Sprite[]);
+ constructor(image: Texture, columns: number, rows: number, spWidth: number, spHeight: number);
+ constructor(imageOrConfigOrSprites: Texture | ISpriteSheetArgs | Sprite[],
+ columns?: number, rows?: number, spWidth?: number, spHeight?: number) {
+ super(imageOrConfigOrSprites, columns, rows, spWidth, spHeight);
+ }
+}
+
+export class SpriteFontImpl extends SpriteSheet {
private _currentColor: Color = Color.Black.clone();
private _currentOpacity: Number = 1.0;
private _sprites: { [key: string]: Sprite; } = {};
@@ -126,8 +176,8 @@ export class SpriteFont extends SpriteSheet {
private _textShadowSprites: { [key: string]: Sprite; } = {};
private _shadowOffsetX: number = 5;
private _shadowOffsetY: number = 5;
-
-
+ private _alphabet: string;
+ private _caseInsensitive: boolean;
/**
* @param image The backing image texture to build the SpriteFont
@@ -138,14 +188,23 @@ export class SpriteFont extends SpriteSheet {
* @param spWidth The width of each character in pixels
* @param spHeight The height of each character in pixels
*/
- constructor(public image: Texture,
- private alphabet: string,
- private caseInsensitive: boolean,
+ constructor(imageOrConfig: Texture | ISpriteFontInitArgs,
+ alphabet: string,
+ caseInsensitive: boolean,
columns: number,
rows: number,
- public spWidth: number,
- public spHeight: number) {
- super(image, columns, rows, spWidth, spHeight);
+ spWidth: number,
+ spHeight: number) {
+ super(imageOrConfig instanceof Texture ? {image: imageOrConfig, spWidth: spWidth,
+ spHeight: spHeight, rows: rows, columns: columns } : imageOrConfig);
+
+ if (imageOrConfig && !(imageOrConfig instanceof Texture)) {
+ alphabet = imageOrConfig.alphabet;
+ caseInsensitive = imageOrConfig.caseInsensitive;
+ }
+
+ this._alphabet = alphabet;
+ this._caseInsensitive = caseInsensitive;
this._sprites = this.getTextSprites();
}
@@ -154,9 +213,9 @@ export class SpriteFont extends SpriteSheet {
*/
public getTextSprites(): { [key: string]: Sprite; } {
var lookup: { [key: string]: Sprite; } = {};
- for (var i = 0; i < this.alphabet.length; i++) {
- var char = this.alphabet[i];
- if (this.caseInsensitive) {
+ for (var i = 0; i < this._alphabet.length; i++) {
+ var char = this._alphabet[i];
+ if (this._caseInsensitive) {
char = char.toLowerCase();
}
lookup[char] = this.sprites[i].clone();
@@ -251,7 +310,7 @@ export class SpriteFont extends SpriteSheet {
for (var i = 0; i < text.length; i++) {
var character = text[i];
- if (this.caseInsensitive) {
+ if (this._caseInsensitive) {
character = character.toLowerCase();
}
try {
@@ -298,4 +357,44 @@ export interface ISpriteFontOptions {
textAlign?: TextAlign;
baseAlign?: BaseAlign;
maxWidth?: number;
+}
+
+/**
+ * [[include:Constructors.md]]
+ */
+export interface ISpriteFontInitArgs extends ISpriteSheetArgs {
+ image: Texture;
+ columns: number;
+ rows: number;
+ spWidth: number;
+ spHeight: number;
+ alphabet: string;
+ caseInsensitive: boolean;
+}
+
+/**
+ * Sprite fonts are a used in conjunction with a [[Label]] to specify
+ * a particular bitmap as a font. Note that some font features are not
+ * supported by Sprite fonts.
+ *
+ * [[include:SpriteFonts.md]]
+ */
+export class SpriteFont extends Configurable(SpriteFontImpl) {
+ constructor(config: ISpriteFontInitArgs);
+ constructor(image: Texture,
+ alphabet: string,
+ caseInsensitive: boolean,
+ columns: number,
+ rows: number,
+ spWidth: number,
+ spHeight: number)
+ constructor(imageOrConfig: Texture | ISpriteFontInitArgs,
+ alphabet?: string,
+ caseInsensitive?: boolean,
+ columns?: number,
+ rows?: number,
+ spWidth?: number,
+ spHeight?: number) {
+ super(imageOrConfig, alphabet, caseInsensitive, columns, rows, spWidth, spHeight);
+ }
}
\ No newline at end of file
diff --git a/src/engine/Index.ts b/src/engine/Index.ts
index 9b592140..6d60af0f 100644
--- a/src/engine/Index.ts
+++ b/src/engine/Index.ts
@@ -6,22 +6,23 @@ export var EX_VERSION = '__EX_VERSION';
// This file is used as the bundle entrypoint and exports everything
// that will be exposed as the `ex` global variable.
-export * from './Actor';
+export { Actor, IActorArgs, CollisionType } from './Actor';
export * from './Algebra';
export * from './Camera';
export * from './Class';
+export * from './Configurable'
export * from './Debug';
export * from './Engine';
export * from './EventDispatcher';
export * from './Events';
export * from './Group';
-export * from './Label';
+export { Label, FontStyle, FontUnit, TextAlign, BaseAlign } from './Label';
export * from './Loader';
-export * from './Particles';
+export { Particle, ParticleEmitter, IParticleArgs, IParticleEmitterArgs, EmitterType } from './Particles';
export * from './Physics';
export * from './Promises';
export * from './Scene';
-export * from './TileMap';
+export { TileMap, Cell, ITileMapArgs, ICellArgs, TileSprite } from './TileMap';
export * from './Timer';
export * from './Trigger';
export * from './UIActor';
diff --git a/src/engine/Interfaces/IAudioImplementation.ts b/src/engine/Interfaces/IAudioImplementation.ts
index ff93fc7a..387a38c6 100644
--- a/src/engine/Interfaces/IAudioImplementation.ts
+++ b/src/engine/Interfaces/IAudioImplementation.ts
@@ -1,6 +1,9 @@
import { Promise } from './../Promises';
import { IAudio } from './IAudio';
+
+export type ExResponseType = '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text';
+
/**
* Represents an audio implementation like [[AudioTag]] or [[WebAudio]]
*/
@@ -9,7 +12,7 @@ export interface IAudioImplementation {
/**
* XHR response type
*/
- responseType: XMLHttpRequestResponseType;
+ responseType: ExResponseType;
/**
* Processes raw data and transforms into sound data
diff --git a/src/engine/Label.ts b/src/engine/Label.ts
index 20eb74c6..74562137 100644
--- a/src/engine/Label.ts
+++ b/src/engine/Label.ts
@@ -2,8 +2,8 @@ import { Engine } from './Engine';
import { Color } from './Drawing/Color';
import { SpriteFont } from './Drawing/SpriteSheet';
import { Actor, CollisionType } from './Actor';
-
-
+import { Configurable } from './Configurable';
+import { Vector } from './Algebra';
/**
* Enum representing the different font size units
* https://developer.mozilla.org/en-US/docs/Web/CSS/font-size
@@ -95,22 +95,35 @@ export enum BaseAlign {
Bottom
}
- /**
- * Enum representing the different possible font styles
- */
- export enum FontStyle {
- Normal,
- Italic,
- Oblique
- }
+/**
+ * Enum representing the different possible font styles
+ */
+export enum FontStyle {
+ Normal,
+ Italic,
+ Oblique
+}
- /**
- * Labels are the way to draw small amounts of text to the screen. They are
- * actors and inherit all of the benefits and capabilities.
- *
- * [[include:Labels.md]]
- */
- export class Label extends Actor {
+/**
+ * [[include:Constructors.md]]
+ */
+export interface ILabelArgs extends Partial {
+ text?: string;
+ bold?: boolean;
+ pos?: Vector;
+ spriteFont?: SpriteFont;
+ fontFamily?: string;
+ fontSize?: number;
+ fontStyle?: FontStyle;
+ fontUnit?: FontUnit;
+ textAlign?: TextAlign;
+ maxWidth?: number;
+}
+
+/**
+ * @hidden
+ */
+export class LabelImpl extends Actor {
/**
* The text to draw.
@@ -145,7 +158,7 @@ export enum BaseAlign {
/**
* The css units for a font size such as px, pt, em (SpriteFont only support px), by default is 'px';
- */
+ */
public fontUnit: FontUnit = FontUnit.Px;
/**
@@ -173,9 +186,9 @@ export enum BaseAlign {
*/
public caseInsensitive: boolean = true;
- private _textShadowOn: boolean = false;
- private _shadowOffsetX: number = 0;
- private _shadowOffsetY: number = 0;
+ private _textShadowOn: boolean;
+ private _shadowOffsetX: number;
+ private _shadowOffsetY: number;
/**
* @param text The text of the label
@@ -185,19 +198,26 @@ export enum BaseAlign {
* @param spriteFont Use an Excalibur sprite font for the label's font, if a SpriteFont is provided it will take precedence
* over a css font.
*/
- constructor(text?: string, x?: number, y?: number, fontFamily?: string, spriteFont?: SpriteFont) {
- super(x, y);
- this.text = text || '';
+ constructor(textOrConfig?: string | Partial, x?: number, y?: number, fontFamily?: string, spriteFont?: SpriteFont) {
+ super(typeof textOrConfig === 'string' ? {
+ text: textOrConfig, x: x, y: y,
+ fontFamily: fontFamily, spriteFont: spriteFont
+ } : textOrConfig);
+
+ this.text = textOrConfig || '';
this.color = Color.Black.clone();
this.spriteFont = spriteFont;
this.collisionType = CollisionType.PreventCollision;
this.fontFamily = fontFamily || 'sans-serif'; // coalesce to default canvas font
+
+ this._textShadowOn = false;
+ this._shadowOffsetX = 0;
+ this._shadowOffsetY = 0;
if (spriteFont) {
//this._textSprites = spriteFont.getTextSprites();
}
}
-
/**
* Returns the width of the text in the label (in pixels);
* @param ctx Rendering context to measure the string with
@@ -329,31 +349,31 @@ export enum BaseAlign {
private _fontDraw(ctx: CanvasRenderingContext2D) {
- if (this.spriteFont) {
- this.spriteFont.draw(ctx, this.text, 0, 0, {
- color: this.color.clone(),
- baseAlign: this.baseAlign,
- textAlign: this.textAlign,
- fontSize: this.fontSize,
- letterSpacing: this.letterSpacing,
- opacity: this.opacity
- });
+ if (this.spriteFont) {
+ this.spriteFont.draw(ctx, this.text, 0, 0, {
+ color: this.color.clone(),
+ baseAlign: this.baseAlign,
+ textAlign: this.textAlign,
+ fontSize: this.fontSize,
+ letterSpacing: this.letterSpacing,
+ opacity: this.opacity
+ });
+ } else {
+ var oldAlign = ctx.textAlign;
+ var oldTextBaseline = ctx.textBaseline;
+
+ ctx.textAlign = this._lookupTextAlign(this.textAlign);
+ ctx.textBaseline = this._lookupBaseAlign(this.baseAlign);
+ if (this.color) {
+ this.color.a = this.opacity;
+ }
+ ctx.fillStyle = this.color.toString();
+ ctx.font = this._fontString;
+ if (this.maxWidth) {
+ ctx.fillText(this.text, 0, 0, this.maxWidth);
} else {
- var oldAlign = ctx.textAlign;
- var oldTextBaseline = ctx.textBaseline;
-
- ctx.textAlign = this._lookupTextAlign(this.textAlign);
- ctx.textBaseline = this._lookupBaseAlign(this.baseAlign);
- if (this.color) {
- this.color.a = this.opacity;
- }
- ctx.fillStyle = this.color.toString();
- ctx.font = this._fontString;
- if (this.maxWidth) {
- ctx.fillText(this.text, 0, 0, this.maxWidth);
- } else {
- ctx.fillText(this.text, 0, 0);
- }
+ ctx.fillText(this.text, 0, 0);
+ }
ctx.textAlign = oldAlign;
ctx.textBaseline = oldTextBaseline;
@@ -368,4 +388,19 @@ export enum BaseAlign {
super.debugDraw(ctx);
}
+}
+
+/**
+ * Labels are the way to draw small amounts of text to the screen. They are
+ * actors and inherit all of the benefits and capabilities.
+ *
+ * [[include:Labels.md]]
+ */
+export class Label extends Configurable(LabelImpl) {
+ constructor();
+ constructor(config?: ILabelArgs);
+ constructor(text?: string, x?: number, y?: number, fontFamily?: string, spriteFont?: SpriteFont);
+ constructor(textOrConfig?: string | ILabelArgs, x?: number, y?: number, fontFamily?: string, spriteFont?: SpriteFont) {
+ super(textOrConfig, x, y, fontFamily, spriteFont);
+ }
}
\ No newline at end of file
diff --git a/src/engine/Particles.ts b/src/engine/Particles.ts
index a154c75e..8fd824c7 100644
--- a/src/engine/Particles.ts
+++ b/src/engine/Particles.ts
@@ -6,6 +6,8 @@ import { Vector } from './Algebra';
import * as Util from './Util/Util';
import * as DrawUtil from './Util/DrawUtil';
import * as Traits from './Traits/Index';
+import { Configurable } from './Configurable';
+import { Random } from './Math/Random';
/**
* An enum that represents the types of emitter nozzles
@@ -22,9 +24,9 @@ export enum EmitterType {
}
/**
- * Particle is used in a [[ParticleEmitter]]
+ * @hidden
*/
-export class Particle {
+export class ParticleImpl {
public position: Vector = new Vector(0, 0);
public velocity: Vector = new Vector(0, 0);
public acceleration: Vector = new Vector(0, 0);
@@ -58,7 +60,7 @@ export class Particle {
public sizeRate: number = 0;
public elapsedMultiplier: number = 0;
- constructor(emitter: ParticleEmitter,
+ constructor(emitterOrConfig: ParticleEmitter | IParticleArgs,
life?: number,
opacity?: number,
beginColor?: Color,
@@ -68,7 +70,22 @@ export class Particle {
acceleration?: Vector,
startSize?: number,
endSize?: number) {
- this.emitter = emitter;
+ var emitter = emitterOrConfig;
+ if (emitter && ! (emitterOrConfig instanceof ParticleEmitter)) {
+ var config = emitterOrConfig;
+ emitter = config.emitter;
+ life = config.life;
+ opacity = config.opacity;
+ endColor = config.endColor;
+ beginColor = config.beginColor;
+ position = config.position;
+ velocity = config.velocity;
+ acceleration = config.acceleration;
+ startSize = config.startSize;
+ endSize = config.endSize;
+
+ }
+ this.emitter = emitter;
this.life = life || this.life;
this.opacity = opacity || this.opacity;
this.endColor = endColor || this.endColor.clone();
@@ -150,18 +167,62 @@ export class Particle {
}
/**
- * Using a particle emitter is a great way to create interesting effects
- * in your game, like smoke, fire, water, explosions, etc. `ParticleEmitter`
- * extend [[Actor]] allowing you to use all of the features that come with.
- *
- * [[include:Particles.md]]
+ * [[include:Constructors.md]]
*/
-export class ParticleEmitter extends Actor {
+export interface IParticleArgs extends Partial {
+ emitter: ParticleEmitter;
+ position?: Vector;
+ velocity?: Vector;
+ acceleration?: Vector;
+ particleRotationalVelocity?: number;
+ currentRotation?: number;
+ particleSize?: number;
+ particleSprite?: Sprite;
+}
- private _particlesToEmit: number = 0;
+/**
+ * Particle is used in a [[ParticleEmitter]]
+ */
+export class Particle extends Configurable(ParticleImpl) {
+ constructor(config: IParticleArgs);
+ constructor(emitter: ParticleEmitter,
+ life?: number,
+ opacity?: number,
+ beginColor?: Color,
+ endColor?: Color,
+ position?: Vector,
+ velocity?: Vector,
+ acceleration?: Vector,
+ startSize?: number,
+ endSize?: number);
+ constructor(emitterOrConfig: ParticleEmitter | IParticleArgs,
+ life?: number,
+ opacity?: number,
+ beginColor?: Color,
+ endColor?: Color,
+ position?: Vector,
+ velocity?: Vector,
+ acceleration?: Vector,
+ startSize?: number,
+ endSize?: number) {
+ super(emitterOrConfig, life, opacity, beginColor, endColor, position, velocity, acceleration, startSize, endSize);
+ }
+}
+
+/**
+ * @hidden
+ */
+export class ParticleEmitterImpl extends Actor {
+
+ private _particlesToEmit: number;
public numParticles: number = 0;
+ /**
+ * Random number generator
+ */
+ public random: Random;
+
/**
* Gets or sets the isEmitting flag
*/
@@ -245,11 +306,11 @@ export class ParticleEmitter extends Actor {
/**
* Gets or sets the beginning color of all particles
*/
- public beginColor: Color = Color.White;
+ public beginColor: Color = Color.White.clone();
/**
* Gets or sets the ending color of all particles
*/
- public endColor: Color = Color.White;
+ public endColor: Color = Color.White.clone();
/**
* Gets or sets the sprite that a particle should use
@@ -283,11 +344,13 @@ export class ParticleEmitter extends Actor {
* @param width The width of the emitter
* @param height The height of the emitter
*/
- constructor(x?: number, y?: number, width?: number, height?: number) {
- super(x, y, width, height, Color.White);
+ constructor(xOrConfig?: number | IParticleEmitterArgs, y?: number, width?: number, height?: number) {
+ super(typeof xOrConfig === 'number' ? { x: xOrConfig, y: y, width: width, height: height } : xOrConfig);
+ this._particlesToEmit = 0;
this.collisionType = CollisionType.PreventCollision;
this.particles = new Util.Collection();
this.deadParticles = new Util.Collection();
+ this.random = new Random();
// Remove offscreen culling from particle emitters
for (let i = 0; i < this.traits.length; i++) {
@@ -322,17 +385,17 @@ export class ParticleEmitter extends Actor {
var ranX = 0;
var ranY = 0;
- var angle = Util.randomInRange(this.minAngle, this.maxAngle);
- var vel = Util.randomInRange(this.minVel, this.maxVel);
- var size = this.startSize || Util.randomInRange(this.minSize, this.maxSize);
+ var angle = Util.randomInRange(this.minAngle, this.maxAngle, this.random);
+ var vel = Util.randomInRange(this.minVel, this.maxVel, this.random);
+ var size = this.startSize || Util.randomInRange(this.minSize, this.maxSize, this.random);
var dx = vel * Math.cos(angle);
var dy = vel * Math.sin(angle);
if (this.emitterType === EmitterType.Rectangle) {
- ranX = Util.randomInRange(this.pos.x, this.pos.x + this.getWidth());
- ranY = Util.randomInRange(this.pos.y, this.pos.y + this.getHeight());
+ ranX = Util.randomInRange(this.pos.x, this.pos.x + this.getWidth(), this.random);
+ ranY = Util.randomInRange(this.pos.y, this.pos.y + this.getHeight(), this.random);
} else if (this.emitterType === EmitterType.Circle) {
- var radius = Util.randomInRange(0, this.radius);
+ var radius = Util.randomInRange(0, this.radius, this.random);
ranX = radius * Math.cos(angle) + this.pos.x;
ranY = radius * Math.sin(angle) + this.pos.y;
}
@@ -354,7 +417,7 @@ export class ParticleEmitter extends Actor {
}
p.particleRotationalVelocity = this.particleRotationalVelocity;
if (this.randomRotation) {
- p.currentRotation = Util.randomInRange(0, Math.PI * 2);
+ p.currentRotation = Util.randomInRange(0, Math.PI * 2, this.random);
}
if (this.focus) {
p.focus = this.focus.add(new Vector(this.pos.x, this.pos.y));
@@ -403,4 +466,51 @@ export class ParticleEmitter extends Actor {
}
}
+}
+
+/**
+ * [[include:Constructors.md]]
+ */
+export interface IParticleEmitterArgs extends Partial {
+ width?: number;
+ height?: number;
+ isEmitting?: boolean;
+ minVel?: number;
+ maxVel?: number;
+ acceleration?: Vector;
+ minAngle?: number;
+ maxAngle?: number;
+ emitRate?: number;
+ particleLife?: number;
+ opacity?: number;
+ fadeFlag?: boolean;
+ focus?: Vector;
+ focusAccel?: number;
+ startSize?: number;
+ endSize?: number;
+ minSize?: number;
+ maxSize?: number;
+ beginColor?: Color;
+ endColor?: Color;
+ particleSprite?: Sprite;
+ emitterType?: EmitterType;
+ radius?: number;
+ particleRotationalVelocity?: number;
+ randomRotation?: boolean;
+ random?: Random;
+}
+
+/**
+ * Using a particle emitter is a great way to create interesting effects
+ * in your game, like smoke, fire, water, explosions, etc. `ParticleEmitter`
+ * extend [[Actor]] allowing you to use all of the features that come with.
+ *
+ * [[include:Particles.md]]
+ */
+export class ParticleEmitter extends Configurable(ParticleEmitterImpl) {
+ constructor(config?: IParticleEmitterArgs);
+ constructor(x?: number | IParticleEmitterArgs, y?: number, width?: number, height?: number);
+ constructor(xOrConfig?: number | IParticleEmitterArgs, y?: number, width?: number, height?: number) {
+ super(xOrConfig, y, width, height);
+ }
}
\ No newline at end of file
diff --git a/src/engine/Resources/Sound.ts b/src/engine/Resources/Sound.ts
index a8131578..725ddf5c 100644
--- a/src/engine/Resources/Sound.ts
+++ b/src/engine/Resources/Sound.ts
@@ -1,5 +1,5 @@
import { ILoadable } from '../Interfaces/ILoadable';
-import { IAudioImplementation } from '../Interfaces/IAudioImplementation';
+import { IAudioImplementation, ExResponseType } from '../Interfaces/IAudioImplementation';
import { IAudio } from '../Interfaces/IAudio';
import { Logger } from '../Util/Log';
import * as Util from '../Util/Util';
@@ -17,7 +17,7 @@ if ((window).AudioContext) {
*/
export class AudioTag implements IAudioImplementation {
- public responseType: XMLHttpRequestResponseType = 'blob';
+ public responseType: ExResponseType = 'blob';
/**
* Transforms raw Blob data into a object URL for use in audio tag
@@ -42,7 +42,7 @@ export class AudioTag implements IAudioImplementation {
export class WebAudio implements IAudioImplementation {
private _logger = Logger.getInstance();
- public responseType: XMLHttpRequestResponseType = 'arraybuffer';
+ public responseType: ExResponseType = 'arraybuffer';
/**
* Processes raw arraybuffer data and decodes into WebAudio buffer (async).
diff --git a/src/engine/TileMap.ts b/src/engine/TileMap.ts
index 6909ea33..89625da3 100644
--- a/src/engine/TileMap.ts
+++ b/src/engine/TileMap.ts
@@ -7,14 +7,12 @@ import { Actor } from './Actor';
import { Logger } from './Util/Log';
import { SpriteSheet } from './Drawing/SpriteSheet';
import * as Events from './Events';
+import { Configurable } from './Configurable';
/**
- * The [[TileMap]] class provides a lightweight way to do large complex scenes with collision
- * without the overhead of actors.
- *
- * [[include:TileMaps.md]]
+ * @hidden
*/
-export class TileMap extends Class {
+export class TileMapImpl extends Class {
private _collidingX: number = -1;
private _collidingY: number = -1;
private _onScreenXStart: number = 0;
@@ -24,6 +22,12 @@ export class TileMap extends Class {
private _spriteSheets: { [key: string]: SpriteSheet } = {};
public logger: Logger = Logger.getInstance();
public data: Cell[] = [];
+ public x: number;
+ public y: number;
+ public cellWidth: number;
+ public cellHeight: number;
+ public rows: number;
+ public cols: number;
public on(eventName: Events.preupdate, handler: (event?: Events.PreUpdateEvent) => void): void;
public on(eventName: Events.postupdate, handler: (event?: Events.PostUpdateEvent) => void): void;
@@ -43,19 +47,28 @@ export class TileMap extends Class {
* @param cols The number of cols in the TileMap (should not be changed once set)
*/
constructor(
- public x: number,
- public y: number,
- public cellWidth: number,
- public cellHeight: number,
- public rows: number,
- public cols: number) {
+ xOrConfig: number | ITileMapArgs,
+ y: number,
+ cellWidth: number,
+ cellHeight: number,
+ rows: number,
+ cols: number) {
super();
+ if (xOrConfig && typeof xOrConfig === 'object') {
+ var config = xOrConfig;
+ xOrConfig = config.x;
+ y = config.y;
+ cellWidth = config.cellWidth;
+ cellHeight = config.cellHeight;
+ rows = config.rows;
+ cols = config.cols;
+ }
this.data = new Array(rows * cols);
for (var i = 0; i < cols; i++) {
for (var j = 0; j < rows; j++) {
(() => {
var cd = new Cell(
- i * cellWidth + x,
+ i * cellWidth + xOrConfig,
j * cellHeight + y,
cellWidth,
cellHeight,
@@ -242,6 +255,44 @@ export class TileMap extends Class {
}
}
+/**
+ * [[include:Constructors.md]]
+ */
+export interface ITileMapArgs extends Partial {
+ x: number;
+ y: number;
+ cellWidth: number;
+ cellHeight: number;
+ rows: number;
+ cols: number;
+}
+
+/**
+ * The [[TileMap]] class provides a lightweight way to do large complex scenes with collision
+ * without the overhead of actors.
+ *
+ * [[include:TileMaps.md]]
+ */
+export class TileMap extends Configurable(TileMapImpl) {
+ constructor(config: ITileMapArgs);
+ constructor(
+ x: number,
+ y: number,
+ cellWidth: number,
+ cellHeight: number,
+ rows: number,
+ cols: number);
+ constructor(
+ xOrConfig: number | ITileMapArgs,
+ y?: number,
+ cellWidth?: number,
+ cellHeight?: number,
+ rows?: number,
+ cols?: number) {
+ super(xOrConfig, y, cellWidth, cellHeight, rows, cols);
+ }
+}
+
/**
* Tile sprites are used to render a specific sprite from a [[TileMap]]'s spritesheet(s)
*/
@@ -254,17 +305,17 @@ export class TileSprite {
}
/**
- * TileMap Cell
- *
- * A light-weight object that occupies a space in a collision map. Generally
- * created by a [[TileMap]].
- *
- * Cells can draw multiple sprites. Note that the order of drawing is the order
- * of the sprites in the array so the last one will be drawn on top. You can
- * use transparency to create layers this way.
+ * @hidden
*/
-export class Cell {
+export class CellImpl {
private _bounds: BoundingBox;
+ public x: number;
+ public y: number;
+ public width: number;
+ public height: number;
+ public index: number;
+ public solid: boolean = false;
+ public sprites: TileSprite[] = [];
/**
* @param x Gets or sets x coordinate of the cell in world coordinates
@@ -276,13 +327,30 @@ export class Cell {
* @param sprites The list of tile sprites to use to draw in this cell (in order)
*/
constructor(
- public x: number,
- public y: number,
- public width: number,
- public height: number,
- public index: number,
- public solid: boolean = false,
- public sprites: TileSprite[] = []) {
+ xOrConfig: number | ICellArgs,
+ y: number,
+ width: number,
+ height: number,
+ index: number,
+ solid: boolean = false,
+ sprites: TileSprite[] = []) {
+ if (xOrConfig && typeof xOrConfig === 'object') {
+ var config = xOrConfig;
+ xOrConfig = config.x;
+ y = config.y;
+ width = config.width;
+ height = config.height;
+ index = config.index;
+ solid = config.solid;
+ sprites = config.sprites;
+ }
+ this.x = xOrConfig;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.index = index;
+ this.solid = solid;
+ this.sprites = sprites;
this._bounds = new BoundingBox(this.x, this.y, this.x + this.width, this.y + this.height);
}
@@ -319,4 +387,49 @@ export class Cell {
public clearSprites() {
this.sprites.length = 0;
}
+}
+
+/**
+ * [[include:Constructors.md]]
+ */
+export interface ICellArgs extends Partial {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ index: number;
+ solid?: boolean;
+ sprites?: TileSprite[];
+}
+
+/**
+ * TileMap Cell
+ *
+ * A light-weight object that occupies a space in a collision map. Generally
+ * created by a [[TileMap]].
+ *
+ * Cells can draw multiple sprites. Note that the order of drawing is the order
+ * of the sprites in the array so the last one will be drawn on top. You can
+ * use transparency to create layers this way.
+ */
+export class Cell extends Configurable(CellImpl) {
+ constructor(config: ICellArgs);
+ constructor(
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ index: number,
+ solid?: boolean,
+ sprites?: TileSprite[]);
+ constructor(
+ xOrConfig: number | ICellArgs,
+ y?: number,
+ width?: number,
+ height?: number,
+ index?: number,
+ solid?: boolean,
+ sprites?: TileSprite[]) {
+ super(xOrConfig, y, width, height, index, solid, sprites);
+ }
}
\ No newline at end of file
diff --git a/src/engine/UIActor.ts b/src/engine/UIActor.ts
index e0f6e9a9..41b92c02 100644
--- a/src/engine/UIActor.ts
+++ b/src/engine/UIActor.ts
@@ -1,8 +1,9 @@
import { Vector } from './Algebra';
import { Engine } from './Engine';
-import { Actor, CollisionType } from './Actor';
+import { Actor, IActorArgs, CollisionType } from './Actor';
import * as Traits from './Traits/Index';
+
/**
* Helper [[Actor]] primitive for drawing UI's, optimized for UI drawing. Does
* not participate in collisions. Drawn on top of all other actors.
@@ -10,14 +11,21 @@ import * as Traits from './Traits/Index';
export class UIActor extends Actor {
protected _engine: Engine;
+ constructor();
+ constructor(xOrConfig?: number, y?: number, width?: number, height?: number)
+ constructor(config?: IActorArgs);
/**
* @param x The starting x coordinate of the actor
* @param y The starting y coordinate of the actor
* @param width The starting width of the actor
* @param height The starting height of the actor
*/
- constructor(x?: number, y?: number, width?: number, height?: number) {
- super(x, y, width, height);
+ constructor(xOrConfig?: number | IActorArgs, y?: number, width?: number, height?: number) {
+ if (typeof(xOrConfig !== 'object')) {
+ super(xOrConfig, y, width, height);
+ } else {
+ super(xOrConfig);
+ }
this.traits = [];
this.traits.push(new Traits.CapturePointer());
this.anchor.setTo(0, 0);
diff --git a/src/engine/Util/Util.ts b/src/engine/Util/Util.ts
index dddcf927..843ea290 100644
--- a/src/engine/Util/Util.ts
+++ b/src/engine/Util/Util.ts
@@ -1,4 +1,5 @@
import { Vector } from '../Algebra';
+import { Random } from '../Math/Random';
import { Side } from '../Collision/Side';
/**
@@ -135,12 +136,12 @@ export function clamp(val: number, min: number, max: number) {
return Math.min(Math.max(min, val), max);
}
-export function randomInRange(min: number, max: number): number {
- return min + Math.random() * (max - min);
+export function randomInRange(min: number, max: number, random: Random = new Random()): number {
+ return random ? random.floating(min, max) : min + Math.random() * (max - min);
}
-export function randomIntInRange(min: number, max: number): number {
- return Math.round(randomInRange(min, max));
+export function randomIntInRange(min: number, max: number, random: Random = new Random()): number {
+ return random ? random.integer(min, max) : Math.round(randomInRange(min, max));
}
export function canonicalizeAngle(angle: number): number {
diff --git a/src/spec/ActorSpec.ts b/src/spec/ActorSpec.ts
index e69ead63..ccd244b9 100644
--- a/src/spec/ActorSpec.ts
+++ b/src/spec/ActorSpec.ts
@@ -38,6 +38,47 @@ describe('A game actor', () => {
expect(actor.pos.y).toBe(10);
});
+ it('should have props set by constructor', () => {
+ let actor = new ex.Actor({
+ pos: new ex.Vector(2, 3),
+ scene: scene,
+ width: 100,
+ height: 200,
+ vel: new ex.Vector(30, 40),
+ acc: new ex.Vector(50, 60),
+ rotation: 2,
+ rx: .1,
+ z: 10,
+ restitution: 2,
+ color: ex.Color.Red,
+ visible: false,
+ collisionType: ex.CollisionType.Fixed
+ });
+
+ let actor2 = new ex.Actor({
+ x: 4,
+ y: 5
+ });
+
+ expect(actor.x).toBe(2);
+ expect(actor.y).toBe(3);
+ expect(actor.getWidth()).toBe(100);
+ expect(actor.getHeight()).toBe(200);
+ expect(actor.vel.x).toBe(30);
+ expect(actor.vel.y).toBe(40);
+ expect(actor.acc.x).toBe(50);
+ expect(actor.acc.y).toBe(60);
+ expect(actor.rotation).toBe(2);
+ expect(actor.rx).toBe(.1);
+ expect(actor.z).toBe(10);
+ expect(actor.color.toString()).toBe(ex.Color.Red.toString());
+ expect(actor.visible).toBe(false);
+ expect(actor.restitution).toBe(2);
+ expect(actor.collisionType).toBe(ex.CollisionType.Fixed);
+ expect(actor2.x).toBe(4);
+ expect(actor2.y).toBe(5);
+ });
+
it('should have an old position after an update', () => {
actor.pos.setTo(10, 10);
@@ -76,7 +117,6 @@ describe('A game actor', () => {
actor.vel.y = 10;
actor.vel.x = -10;
-
expect(actor.pos.x).toBe(0);
expect(actor.pos.y).toBe(0);
diff --git a/src/spec/AlgebraSpec.ts b/src/spec/AlgebraSpec.ts
index cdc4367b..bd57d0c8 100644
--- a/src/spec/AlgebraSpec.ts
+++ b/src/spec/AlgebraSpec.ts
@@ -415,4 +415,4 @@ describe('Projections', () => {
expect(proj.getOverlap(proj2)).toBe(0);
});
-});
+});
\ No newline at end of file
diff --git a/src/spec/AnimationSpec.ts b/src/spec/AnimationSpec.ts
index 8295b7ce..801c9d2d 100644
--- a/src/spec/AnimationSpec.ts
+++ b/src/spec/AnimationSpec.ts
@@ -3,11 +3,49 @@
describe('An animation', () => {
var animation;
+ var engine: ex.Engine;
beforeEach(() => {
animation = new ex.Animation(null, null, 0);
+ engine = TestUtils.engine({
+ width: 500,
+ height: 500
+ });
+ });
+ afterEach(() => {
+ engine.stop();
+ engine = null;
});
it('should have loop default to true', () => {
expect(animation.loop).toBe(true);
});
+
+ it('should have props set by constructor', () => {
+ let animation = new ex.Animation({
+ engine: engine,
+ sprites: [],
+ speed: 20,
+ loop: false,
+ anchor: new ex.Vector(1, 1),
+ rotation: Math.PI,
+ scale: new ex.Vector(2, 2),
+ flipVertical: true,
+ flipHorizontal: true,
+ width: 100,
+ height: 200
+ });
+
+ expect(animation.width).toBe(100);
+ expect(animation.height).toBe(200);
+ expect(animation.flipHorizontal).toBe(true);
+ expect(animation.flipVertical).toBe(true);
+ expect(animation.anchor.x).toBe(1);
+ expect(animation.anchor.y).toBe(1);
+ expect(animation.scale.x).toBe(2);
+ expect(animation.scale.y).toBe(2);
+ expect(animation.rotation).toBe(Math.PI);
+ expect(animation.loop).toBe(false);
+ expect(animation.speed).toBe(20);
+ expect(animation.sprites.length).toBe(0);
+ });
});
diff --git a/src/spec/LabelSpec.ts b/src/spec/LabelSpec.ts
index c5591227..8bd6d535 100644
--- a/src/spec/LabelSpec.ts
+++ b/src/spec/LabelSpec.ts
@@ -32,6 +32,33 @@ describe('A label', () => {
//console.log('============================\n OS:' + platform.os.family);
});
+ it('should have props set by constructor', () => {
+ let label = new ex.Label({
+ text: 'test text',
+ bold: true,
+ pos: new ex.Vector(1, 2),
+ spriteFont: null,
+ fontFamily: 'Verdana',
+ fontSize: 12,
+ fontStyle: ex.FontStyle.Normal,
+ fontUnit: ex.FontUnit.Px,
+ textAlign: ex.TextAlign.Left,
+ maxWidth: 200
+ });
+
+ expect(label.text).toBe('test text');
+ expect(label.bold).toBe(true);
+ expect(label.pos.x).toBe(1);
+ expect(label.pos.y).toBe(2);
+ expect(label.spriteFont).toBe(null);
+ expect(label.fontFamily).toBe('Verdana');
+ expect(label.fontSize).toBe(12);
+ expect(label.fontStyle).toBe(ex.FontStyle.Normal);
+ expect(label.fontUnit).toBe(ex.FontUnit.Px);
+ expect(label.textAlign).toBe(ex.TextAlign.Left);
+ expect(label.maxWidth).toBe(200);
+ });
+
it('should be loaded', () => {
expect(ex.Label).toBeTruthy();
});
diff --git a/src/spec/ParticleSpec.ts b/src/spec/ParticleSpec.ts
new file mode 100644
index 00000000..153c1b79
--- /dev/null
+++ b/src/spec/ParticleSpec.ts
@@ -0,0 +1,128 @@
+///
+///
+///
+
+describe('A particle', () => {
+ let engine: ex.Engine;
+ let texture: ex.Texture;
+ beforeEach(() => {
+
+ jasmine.addMatchers(imagediff.jasmine);
+ engine = TestUtils.engine({
+ width: 800,
+ height: 200
+ });
+
+ texture = new ex.Texture('base/src/spec/images/SpriteFontSpec/SpriteFont.png', true);
+
+ });
+ afterEach(() => {
+ engine.stop();
+ engine = null;
+ });
+
+ it('should have props set by the constructor', () => {
+ let emitter = new ex.ParticleEmitter({
+ x: 400,
+ y: 100,
+ width: 20,
+ height: 30,
+ isEmitting: true,
+ minVel: 40,
+ maxVel: 50,
+ acceleration: ex.Vector.Zero.clone(),
+ minAngle: 0,
+ maxAngle: Math.PI / 2,
+ emitRate: 3,
+ particleLife: 4,
+ opacity: .5,
+ fadeFlag: false,
+ focus: null,
+ focusAccel: null,
+ startSize: 1,
+ endSize: 10,
+ minSize: 1,
+ maxSize: 3,
+ beginColor: ex.Color.Red.clone(),
+ endColor: ex.Color.Blue.clone(),
+ particleSprite: null,
+ emitterType: ex.EmitterType.Circle,
+ radius: 20,
+ particleRotationalVelocity: 3,
+ randomRotation: false,
+ random: new ex.Random(1337)
+ });
+
+ expect(emitter.x).toBe(400);
+ expect(emitter.y).toBe(100);
+ expect(emitter.getWidth()).toBe(20);
+ expect(emitter.getHeight()).toBe(30);
+ expect(emitter.isEmitting).toBe(true);
+ expect(emitter.minVel).toBe(40);
+ expect(emitter.maxVel).toBe(50);
+ expect(emitter.acceleration.toString()).toBe(ex.Vector.Zero.clone().toString());
+ expect(emitter.minAngle).toBe(0);
+ expect(emitter.maxAngle).toBe(Math.PI / 2);
+ expect(emitter.emitRate).toBe(3);
+ expect(emitter.particleLife).toBe(4);
+ expect(emitter.opacity).toBe(.5);
+ expect(emitter.fadeFlag).toBe(false);
+ expect(emitter.focus).toBe(null);
+ expect(emitter.focusAccel).toBe(null);
+ expect(emitter.startSize).toBe(1);
+ expect(emitter.endSize).toBe(10);
+ expect(emitter.minSize).toBe(1);
+ expect(emitter.maxSize).toBe(3);
+ expect(emitter.beginColor.toString()).toBe(ex.Color.Red.clone().toString());
+ expect(emitter.endColor.toString()).toBe(ex.Color.Blue.clone().toString());
+ expect(emitter.particleSprite).toBe(null);
+ expect(emitter.emitterType).toBe(ex.EmitterType.Circle);
+ expect(emitter.radius).toBe(20);
+ expect(emitter.particleRotationalVelocity).toBe(3);
+ expect(emitter.randomRotation).toBe(false);
+ expect(emitter.random.seed).toBe(1337);
+
+ });
+
+ it('should emit particles', (done) => {
+ let emitter = new ex.ParticleEmitter({
+ x: 400,
+ y: 100,
+ width: 20,
+ height: 30,
+ isEmitting: true,
+ minVel: 100,
+ maxVel: 200,
+ acceleration: ex.Vector.Zero.clone(),
+ minAngle: 0,
+ maxAngle: Math.PI / 2,
+ emitRate: 5,
+ particleLife: 4000,
+ opacity: .5,
+ fadeFlag: false,
+ focus: null,
+ focusAccel: null,
+ startSize: 30,
+ endSize: 40,
+ beginColor: ex.Color.Red.clone(),
+ endColor: ex.Color.Blue.clone(),
+ particleSprite: null,
+ emitterType: ex.EmitterType.Circle,
+ radius: 20,
+ particleRotationalVelocity: 3,
+ randomRotation: false,
+ random: new ex.Random(1337)
+ });
+
+ emitter.emitParticles(10);
+ emitter.update(engine, 100);
+ emitter.update(engine, 100);
+ emitter.update(engine, 100);
+
+ emitter.draw(engine.ctx);
+
+ imagediff.expectCanvasImageMatches('ParticleSpec/Particles.png', engine.canvas, done);
+
+ });
+
+});
\ No newline at end of file
diff --git a/src/spec/SoundSpec.ts b/src/spec/SoundSpec.ts
index 0d5e12dc..26914a12 100644
--- a/src/spec/SoundSpec.ts
+++ b/src/spec/SoundSpec.ts
@@ -297,7 +297,7 @@ describe('Sound resource', () => {
});
class MockAudioImplementation implements ex.IAudioImplementation {
- public responseType: XMLHttpRequestResponseType = 'arraybuffer';
+ public responseType: ex.ExResponseType = 'arraybuffer';
public processData(data: any): ex.Promise {
return ex.Promise.resolve(data);
}
diff --git a/src/spec/SpriteFontSpec.ts b/src/spec/SpriteFontSpec.ts
new file mode 100644
index 00000000..5042315b
--- /dev/null
+++ b/src/spec/SpriteFontSpec.ts
@@ -0,0 +1,131 @@
+///
+///
+///
+
+describe('A spritefont', () => {
+ let engine: ex.Engine;
+ let texture: ex.Texture;
+ beforeEach(() => {
+
+ jasmine.addMatchers(imagediff.jasmine);
+ engine = TestUtils.engine({
+ width: 800,
+ height: 200
+ });
+
+ texture = new ex.Texture('base/src/spec/images/SpriteFontSpec/SpriteFont.png', true);
+
+ });
+ afterEach(() => {
+ engine.stop();
+ engine = null;
+ });
+
+ it('should have props set by the constructor', () => {
+ texture.load().then(() => {
+ let sf = new ex.SpriteFont({
+ image: texture,
+ alphabet: '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ',
+ caseInsensitive: true,
+ columns: 16,
+ rows: 3,
+ spWidth: 16,
+ spHeight: 16
+ });
+
+ expect(sf.image.isLoaded());
+ expect(sf.columns).toBe(16);
+ expect(sf.rows).toBe(3);
+ expect(sf.spWidth).toBe(16);
+ expect(sf.spHeight).toBe(16);
+ });
+ });
+
+ it('should draw a string', (done) => {
+ texture.load().then(() => {
+ let sf = new ex.SpriteFont({
+ image: texture,
+ alphabet: '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ',
+ caseInsensitive: true,
+ columns: 16,
+ rows: 3,
+ spWidth: 16,
+ spHeight: 16
+ });
+
+ sf.draw(engine.ctx, 'This is a test string!?-&.\'', 20, 20, {
+ color: ex.Color.Green,
+ opacity: 1,
+ fontSize: 20,
+ letterSpacing: 0,
+ textAlign: ex.TextAlign.Left,
+ baseAlign: ex.BaseAlign.Bottom,
+ maxWidth: 500
+ });
+
+ imagediff.expectCanvasImageMatches('SpriteFontSpec/ExpectedFont.png', engine.canvas, done);
+ });
+ });
+
+ it('should get text sprites', (done) => {
+ texture.load().then(() => {
+ let sf = new ex.SpriteFont({
+ image: texture,
+ alphabet: '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ',
+ caseInsensitive: true,
+ columns: 16,
+ rows: 3,
+ spWidth: 16,
+ spHeight: 16
+ });
+
+ let sprites = sf.getTextSprites();
+
+ sprites['0'].draw(engine.ctx, 0, 0);
+ sprites['4'].draw(engine.ctx, 16, 0);
+ sprites['9'].draw(engine.ctx, 2 * 16, 0);
+ // tslint:disable-next-line:no-string-literal
+ sprites['a'].draw(engine.ctx, 3 * 16, 0);
+ // tslint:disable-next-line:no-string-literal
+ sprites['q'].draw(engine.ctx, 4 * 16, 0);
+ // tslint:disable-next-line:no-string-literal
+ sprites['z'].draw(engine.ctx, 5 * 16, 0);
+ sprites['&'].draw(engine.ctx, 1 * 16, 16);
+ sprites['.'].draw(engine.ctx, 2 * 16, 16);
+ sprites['"'].draw(engine.ctx, 3 * 16, 16);
+ sprites['?'].draw(engine.ctx, 4 * 16, 16);
+ sprites['-'].draw(engine.ctx, 5 * 16, 16);
+
+ imagediff.expectCanvasImageMatches('SpriteFontSpec/IndividualSprites.png', engine.canvas, done);
+ });
+ });
+
+ it('should have text shadow', (done) => {
+ texture.load().then(() => {
+ let sf = new ex.SpriteFont({
+ image: texture,
+ alphabet: '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ',
+ caseInsensitive: true,
+ columns: 16,
+ rows: 3,
+ spWidth: 16,
+ spHeight: 16
+ });
+
+ sf.setTextShadow(5, 5, ex.Color.Yellow);
+
+ sf.draw(engine.ctx, 'This should have text shadow', 20, 20, {
+ color: ex.Color.Blue,
+ opacity: 1,
+ fontSize: 20,
+ letterSpacing: 0,
+ textAlign: ex.TextAlign.Left,
+ baseAlign: ex.BaseAlign.Bottom,
+ maxWidth: 500
+ });
+
+ imagediff.expectCanvasImageMatches('SpriteFontSpec/TextShadow.png', engine.canvas, done);
+ });
+ });
+
+});
\ No newline at end of file
diff --git a/src/spec/SpriteSheetSpec.ts b/src/spec/SpriteSheetSpec.ts
new file mode 100644
index 00000000..ebd9650b
--- /dev/null
+++ b/src/spec/SpriteSheetSpec.ts
@@ -0,0 +1,130 @@
+///
+///
+///
+
+describe('A spritesheet', () => {
+ let engine: ex.Engine;
+ let texture: ex.Texture;
+ beforeEach(() => {
+
+ jasmine.addMatchers(imagediff.jasmine);
+ engine = TestUtils.engine({
+ width: 96,
+ height: 96
+ });
+
+ texture = new ex.Texture('base/src/spec/images/SpriteSheetSpec/PlayerRun.png', true);
+
+ });
+ afterEach(() => {
+ engine.stop();
+ engine = null;
+ });
+
+ it('should have props set by the constructor', (done) => {
+ texture.load().then(() => {
+ let ss = new ex.SpriteSheet({
+ image: texture,
+ columns: 21,
+ rows: 1,
+ spWidth: 96,
+ spHeight: 96
+ });
+
+ expect(ss.image.isLoaded());
+ expect(ss.columns).toBe(21);
+ expect(ss.rows).toBe(1);
+ expect(ss.spWidth).toBe(96);
+ expect(ss.spHeight).toBe(96);
+
+ expect(ss.sprites.length).toBe(21);
+
+ ss.getSprite(0).draw(engine.ctx, 0, 0);
+
+ imagediff.expectCanvasImageMatches('SpriteSheetSpec/PlayerRun0.png', engine.canvas, done);
+ });
+ });
+
+ it('should getAnimationByIndices', () => {
+ texture.load().then(() => {
+ let ss = new ex.SpriteSheet({
+ image: texture,
+ columns: 21,
+ rows: 1,
+ spWidth: 96,
+ spHeight: 96
+ });
+
+ let frames = [0, 1, 2, 3, 4, 5, 5, 5, 5];
+ let anim = ss.getAnimationByIndices(engine, frames, 50);
+
+ expect(anim).not.toBeNull();
+ expect(anim.sprites.length).toBe(frames.length);
+ expect(anim.speed).toBe(50);
+
+ });
+ });
+
+ it('should getAnimationBetween', () => {
+ texture.load().then(() => {
+ let ss = new ex.SpriteSheet({
+ image: texture,
+ columns: 21,
+ rows: 1,
+ spWidth: 96,
+ spHeight: 96
+ });
+
+ let anim = ss.getAnimationBetween(engine, 0, 5, 50);
+
+ expect(anim).not.toBeNull();
+ expect(anim.sprites.length).toBe(5);
+ expect(anim.speed).toBe(50);
+
+ });
+ });
+
+ it('should getAnimationForAll', () => {
+ texture.load().then(() => {
+ let ss = new ex.SpriteSheet({
+ image: texture,
+ columns: 21,
+ rows: 1,
+ spWidth: 96,
+ spHeight: 96
+ });
+
+ let anim = ss.getAnimationForAll(engine, 50);
+
+ expect(anim).not.toBeNull();
+ expect(anim.sprites.length).toBe(21);
+ expect(anim.speed).toBe(50);
+
+ });
+ });
+
+ it('should getSprite at an index', (done) => {
+ texture.load().then(() => {
+ let ss = new ex.SpriteSheet({
+ image: texture,
+ columns: 21,
+ rows: 1,
+ spWidth: 96,
+ spHeight: 96
+ });
+
+ expect(ss.image.isLoaded());
+ expect(ss.columns).toBe(21);
+ expect(ss.rows).toBe(1);
+ expect(ss.spWidth).toBe(96);
+ expect(ss.spHeight).toBe(96);
+
+ expect(ss.sprites.length).toBe(21);
+
+ ss.getSprite(20).draw(engine.ctx, 0, 0);
+
+ imagediff.expectCanvasImageMatches('SpriteSheetSpec/PlayerRun20.png', engine.canvas, done);
+ });
+ });
+
+});
\ No newline at end of file
diff --git a/src/spec/SpriteSpec.ts b/src/spec/SpriteSpec.ts
new file mode 100644
index 00000000..69abd276
--- /dev/null
+++ b/src/spec/SpriteSpec.ts
@@ -0,0 +1,61 @@
+///
+///
+///
+
+describe('A sprite', () => {
+ let engine: ex.Engine;
+ let texture: ex.Texture;
+ beforeEach(() => {
+
+ jasmine.addMatchers(imagediff.jasmine);
+ engine = TestUtils.engine({
+ width: 62,
+ height: 64
+ });
+
+ texture = new ex.Texture('base/src/spec/images/SpriteSpec/icon.png', true);
+
+ });
+ afterEach(() => {
+ engine.stop();
+ engine = null;
+ });
+
+ it('should have props set by the constructor', (done) => {
+ texture.load().then(() => {
+ let sprite = new ex.Sprite({
+ image: texture,
+ sx: 0,
+ sy: 0,
+ swidth: 62,
+ sheight: 64,
+ rotation: 0,
+ anchor: new ex.Vector(0.0, 0.0),
+ scale: new ex.Vector(1, 1),
+ flipVertical: false,
+ flipHorizontal: false
+ });
+
+ expect(texture.isLoaded()).toBe(true);
+ expect(sprite.sx).toBe(0);
+ expect(sprite.sy).toBe(0);
+ expect(sprite.swidth).toBe(62);
+ expect(sprite.sheight).toBe(64);
+ expect(sprite.rotation).toBe(0);
+ expect(sprite.anchor.x).toBe(0.0);
+ expect(sprite.anchor.y).toBe(0.0);
+ expect(sprite.scale.x).toBe(1);
+ expect(sprite.scale.y).toBe(1);
+ expect(sprite.flipHorizontal).toBe(false);
+ expect(sprite.flipVertical).toBe(false);
+
+ sprite.draw(engine.ctx, 0, 0);
+
+ imagediff.expectCanvasImageMatches('SpriteSpec/icon.png', engine.canvas, done);
+ });
+
+
+ });
+
+
+});
\ No newline at end of file
diff --git a/src/spec/TileMapSpec.ts b/src/spec/TileMapSpec.ts
index 816db1ef..3c16f311 100644
--- a/src/spec/TileMapSpec.ts
+++ b/src/spec/TileMapSpec.ts
@@ -1,18 +1,74 @@
+///
///
-
///
-
describe('A TileMap', () => {
+ let engine: ex.Engine;
+ let texture: ex.Texture;
+ beforeEach(() => {
+
+ jasmine.addMatchers(imagediff.jasmine);
+ engine = TestUtils.engine({
+ width: 800,
+ height: 200
+ });
+
+ texture = new ex.Texture('base/src/spec/images/TileMapSpec/Blocks.png', true);
+
+ });
+ afterEach(() => {
+ engine.stop();
+ engine = null;
+ });
+
it('exists', () => {
expect(ex.TileMap).toBeDefined();
});
- it('collides with actors in the Box system', () => {
- //fail('todo');
+ it('should have props set by the constructor', () => {
+ let tm = new ex.TileMap({
+ x: 0,
+ y: 0,
+ cellWidth: 64,
+ cellHeight: 48,
+ rows: 4,
+ cols: 20
+ });
+
+
+ expect(tm.x).toBe(0);
+ expect(tm.y).toBe(0);
+ expect(tm.cellWidth).toBe(64);
+ expect(tm.cellHeight).toBe(48);
+ expect(tm.rows).toBe(4);
+ expect(tm.cols).toBe(20);
});
- it('collides with actors in the Rigid system', () => {
- //fail('todo');
+ it('should draw the correct proportions', (done) => {
+ texture.load().then(() => {
+
+
+ let tm = new ex.TileMap({
+ x: 30,
+ y: 30,
+ cellWidth: 64,
+ cellHeight: 48,
+ rows: 3,
+ cols: 7
+ });
+ let spriteTiles = new ex.SpriteSheet(texture, 1, 1, 64, 48);
+ tm.registerSpriteSheet('default', spriteTiles);
+ tm.data.forEach(function (cell: ex.Cell) {
+ cell.solid = true;
+ cell.pushSprite(new ex.TileSprite('default', 0));
+ });
+
+ tm.draw(engine.ctx, 100);
+
+ imagediff.expectCanvasImageMatches('TileMapSpec/TileMap.png', engine.canvas, done);
+
+ });
});
+
+
});
diff --git a/src/spec/UIActorSpec.ts b/src/spec/UIActorSpec.ts
index e991d113..3aca22a9 100644
--- a/src/spec/UIActorSpec.ts
+++ b/src/spec/UIActorSpec.ts
@@ -13,9 +13,14 @@ describe('A UIActor', () => {
beforeEach(() => {
jasmine.addMatchers(imagediff.jasmine);
- uiActor = new ex.UIActor(50, 50, 100, 50);
- uiActor.color = ex.Color.Blue;
- uiActor.collisionType = ex.CollisionType.Active;
+ uiActor = new ex.UIActor({
+ x: 50,
+ y: 50,
+ width: 100,
+ height: 50,
+ color: ex.Color.Blue,
+ collisionType: ex.CollisionType.Active
+ });
engine = TestUtils.engine();
diff --git a/src/spec/icon.png b/src/spec/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..3c303d20a18ce42a1d8064a68af037c0fa47e2f3
GIT binary patch
literal 370
zcmeAS@N?(olHy`uVBq!ia0y~yV6bCgU~u4IV_;x#dU@n40|NtRfk$L90|U1Z2s2)~
zTla^7fl)i#uNuZeuq
zB+-`HuORW}+RXQda?cbzzxzG=c;CU}@9qAX&seca>x1Fk1D6^2EFR=7f6e4FNu}2<
zc;54-W3d?)eaUbCEz&5EDUc~Y@lvC!V9sK`9Y_2Azbc8GcFF3rk>lfr$2+Y3mS<{Z
z$P^qnYngW_Q;J>Y_}#Ku#~6=)e0Fm!`=rgcA4lJKXVT03F4JXleL~1`@yipx{3)vU
zdi}5_f9_=yK8xZ#wIR>t#jJcEHy9tkegA6c@vAF}qQ+YD?b9!utu#GTcz|*H>5o>+Gi3@6-2ElDyzl_y
d|JgN+-z>|bmN_~8XJBAp@O1TaS?83{1OQ2st=a$p
literal 0
HcmV?d00001
diff --git a/src/spec/images/ParticleSpec/Particles.png b/src/spec/images/ParticleSpec/Particles.png
new file mode 100644
index 0000000000000000000000000000000000000000..0db043482aa39cd94ebf5a54dfa6c746e48c89de
GIT binary patch
literal 10956
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
B!W94j
literal 0
HcmV?d00001
diff --git a/src/spec/images/SpriteFontSpec/TextShadow.png b/src/spec/images/SpriteFontSpec/TextShadow.png
new file mode 100644
index 0000000000000000000000000000000000000000..f15bea4518b6e7b6034acb150e7989274ab739f7
GIT binary patch
literal 7186
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
DWSf
literal 0
HcmV?d00001
diff --git a/src/spec/images/SpriteSheetSpec/PlayerRun.png b/src/spec/images/SpriteSheetSpec/PlayerRun.png
new file mode 100644
index 0000000000000000000000000000000000000000..ba1394c1defffb5c7f4393bff9c0d3bb24875ccf
GIT binary patch
literal 147657
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 |