diff --git a/.eslintrc.json b/.eslintrc.json index 866b253dd..3df3a43cf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -46,7 +46,7 @@ "indent": [ "error", 4, { "SwitchCase": 1 } ], "key-spacing": [ "error", { "beforeColon": false, "afterColon": true }], "linebreak-style": [ "off" ], - "lines-around-comment": [ "error", { "beforeBlockComment": true, "afterBlockComment": false, "beforeLineComment": true, "afterLineComment": false, "allowBlockStart": true, "allowBlockEnd": false, "allowObjectStart": true, "allowArrayStart": true, "allowBlockEnd": true }], + "lines-around-comment": [ "error", { "beforeBlockComment": true, "afterBlockComment": false, "beforeLineComment": true, "afterLineComment": false, "allowBlockStart": true, "allowBlockEnd": false, "allowObjectStart": true, "allowArrayStart": true }], "new-parens": "error", "no-array-constructor": "error", "no-lonely-if": "error", @@ -66,7 +66,7 @@ "space-in-parens": [ "error", "never" ], "space-infix-ops": [ "error", { "int32Hint": true } ], "wrap-regex": "error", - "spaced-comment": [ "error", "always", { "block": { "balanced": true }} ] + "spaced-comment": [ "error", "always", { "block": { "balanced": true, "exceptions": ["*", "!"] }} ] } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 7dff26d3c..939dcefcc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "//": "npm publish --tag beta", "name": "phaser", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "release": "Shadow Coast", "description": "A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.", "author": "Richard Davey (http://www.photonstorm.com)", diff --git a/src/gameobjects/text/static/Text.js b/src/gameobjects/text/static/Text.js index bf94ed152..e8cbece09 100644 --- a/src/gameobjects/text/static/Text.js +++ b/src/gameobjects/text/static/Text.js @@ -576,6 +576,9 @@ var Text = new Class({ w *= this.resolution; h *= this.resolution; + w = Math.max(w, 1); + h = Math.max(h, 1); + if (canvas.width !== w || canvas.height !== h) { canvas.width = w; diff --git a/src/gameobjects/tilemap/Tile.js b/src/gameobjects/tilemap/Tile.js index c8572302f..4078e2f15 100644 --- a/src/gameobjects/tilemap/Tile.js +++ b/src/gameobjects/tilemap/Tile.js @@ -182,6 +182,12 @@ var Tile = new Class({ * @default */ this.tint = 0xffffff; + + /** + * An empty object where physics-engine specific information (e.g. bodies) may be stored. + * @property {object} physics + */ + this.physics = {}; }, /** diff --git a/src/input/InputManager.js b/src/input/InputManager.js index a03eef659..c92f92fc2 100644 --- a/src/input/InputManager.js +++ b/src/input/InputManager.js @@ -1,14 +1,15 @@ -// Phaser.Input.InputManager - var Class = require('../utils/Class'); var EventEmitter = require('eventemitter3'); var Gamepad = require('./gamepad/GamepadManager'); var Keyboard = require('./keyboard/KeyboardManager'); var Mouse = require('./mouse/MouseManager'); var Pointer = require('./Pointer'); +var Rectangle = require('../geom/rectangle/Rectangle'); var Touch = require('./touch/TouchManager'); var TransformXY = require('../math/TransformXY'); +// Phaser.Input.InputManager + var InputManager = new Class({ initialize: @@ -46,7 +47,7 @@ var InputManager = new Class({ this.ignoreEvents = false; - this.bounds; + this.bounds = new Rectangle(); this._tempPoint = { x: 0, y: 0 }; this._tempHitTest = []; @@ -72,22 +73,16 @@ var InputManager = new Class({ updateBounds: function () { - var bounds = this.canvas.getBoundingClientRect(); + var clientRect = this.canvas.getBoundingClientRect(); + var bounds = this.bounds; - if (window.scrollX) - { - bounds.left += window.scrollX; - } - - if (window.scrollY) - { - bounds.top += window.scrollY; - } - - this.bounds = bounds; + bounds.left = clientRect.left + window.pageXOffset; + bounds.top = clientRect.top + window.pageYOffset; + bounds.width = clientRect.width; + bounds.height = clientRect.height; }, - update: function (time, delta) + update: function (time) { this.keyboard.update(); this.gamepad.update(); diff --git a/src/loader/filetypes/HTML5AudioFile.js b/src/loader/filetypes/HTML5AudioFile.js index 12c45cea4..dbee29f7b 100644 --- a/src/loader/filetypes/HTML5AudioFile.js +++ b/src/loader/filetypes/HTML5AudioFile.js @@ -15,6 +15,8 @@ var HTML5AudioFile = new Class({ { this.locked = locked; + this.loaded = false; + var fileConfig = { type: 'audio', extension: GetFastValue(url, 'type', ''), @@ -29,7 +31,14 @@ var HTML5AudioFile = new Class({ onLoad: function () { - this.callback(this, true); + if(this.loaded) + { + return; + } + + this.loaded = true; + + this.loader.nextFile(this, true); }, onError: function (event) @@ -41,7 +50,7 @@ var HTML5AudioFile = new Class({ audio.onerror = null; } - this.callback(this, false); + this.loader.nextFile(this, false); }, onProgress: function (event) @@ -50,18 +59,22 @@ var HTML5AudioFile = new Class({ audio.oncanplaythrough = null; audio.onerror = null; - if(++this.filesLoaded === this.filesTotal) + this.filesLoaded++; + + this.percentComplete = Math.min((this.filesLoaded / this.filesTotal), 1); + + this.loader.emit('fileprogress', this, this.percentComplete); + + if(this.filesLoaded === this.filesTotal) { this.onLoad(); } - - this.percentComplete = Math.min((this.filesLoaded / this.filesTotal), 1); }, // Called by the Loader, starts the actual file downloading - load: function (callback, baseURL) + load: function (loader) { - this.callback = callback; + this.loader = loader; this.data = []; @@ -90,7 +103,7 @@ var HTML5AudioFile = new Class({ for (i = 0; i < this.data.length; i++) { audio = this.data[i]; - audio.src = GetURL(this, baseURL || ''); + audio.src = GetURL(this, loader.baseURL); if (!this.locked) { @@ -100,13 +113,7 @@ var HTML5AudioFile = new Class({ if (this.locked) { - setTimeout(function () - { - this.filesLoaded = this.filesTotal; - this.percentComplete = 1; - this.onLoad(); - - }.bind(this)); + setTimeout(this.onLoad.bind(this)); } } diff --git a/src/loader/filetypes/TilemapJSONFile.js b/src/loader/filetypes/TilemapJSONFile.js index 90d7d2392..1e23b3ec7 100644 --- a/src/loader/filetypes/TilemapJSONFile.js +++ b/src/loader/filetypes/TilemapJSONFile.js @@ -17,9 +17,9 @@ var TilemapJSONFile = function (key, url, path, format, xhrSettings) }; // When registering a factory function 'this' refers to the Loader context. -// +// // There are several properties available to use: -// +// // this.scene - a reference to the Scene that owns the GameObjectFactory FileTypesManager.register('tilemapTiledJSON', function (key, url, xhrSettings) @@ -48,12 +48,12 @@ FileTypesManager.register('tilemapWeltmeister', function (key, url, xhrSettings) for (var i = 0; i < key.length; i++) { // If it's an array it has to be an array of Objects, so we get everything out of the 'key' object - this.addFile(TilemapJSONFile(key[i], url, this.path, TILEMAP_FORMATS.WELTMEISTER.TILED_JSON, xhrSettings)); + this.addFile(TilemapJSONFile(key[i], url, this.path, TILEMAP_FORMATS.WELTMEISTER, xhrSettings)); } } else { - this.addFile(TilemapJSONFile(key, url, this.path, TILEMAP_FORMATS.WELTMEISTER.TILED_JSON, xhrSettings)); + this.addFile(TilemapJSONFile(key, url, this.path, TILEMAP_FORMATS.WELTMEISTER, xhrSettings)); } // For method chaining diff --git a/src/physics/matter-js/Factory.js b/src/physics/matter-js/Factory.js index 8965ac131..fa72e04ca 100644 --- a/src/physics/matter-js/Factory.js +++ b/src/physics/matter-js/Factory.js @@ -5,11 +5,12 @@ var Constraint = require('./lib/constraint/Constraint'); var MatterImage = require('./MatterImage'); var MatterSprite = require('./MatterSprite'); var PointerConstraint = require('./PointerConstraint'); +var MatterTileBody = require('./MatterTileBody'); // When registering a factory function 'this' refers to the GameObjectFactory context. -// +// // There are several properties available to use: -// +// // this.scene - a reference to the Scene that owns the GameObjectFactory // this.displayList - a reference to the Display List the Scene owns // this.updateList - a reference to the Update List the Scene owns @@ -219,6 +220,13 @@ var Factory = new Class({ return image; }, + tileBody: function (tile, options) + { + var tileBody = new MatterTileBody(this.world, tile, options); + + return tileBody; + }, + sprite: function (x, y, key, frame, options) { var sprite = new MatterSprite(this.world, x, y, key, frame, options); diff --git a/src/physics/matter-js/MatterTileBody.js b/src/physics/matter-js/MatterTileBody.js new file mode 100644 index 000000000..ae9360edb --- /dev/null +++ b/src/physics/matter-js/MatterTileBody.js @@ -0,0 +1,188 @@ +var AnimationComponent = require('../../gameobjects/components/Animation'); +var Bodies = require('./lib/factory/Bodies'); +var Class = require('../../utils/Class'); +var Components = require('./components'); +var GetFastValue = require('../../utils/object/GetFastValue'); +var HasValue = require('../../utils/object/HasValue'); +var Extend = require('../../utils/object/Extend'); +var Body = require('./lib/body/Body'); +var Vertices = require('./lib/geometry/Vertices'); + +var MatterTileBody = new Class({ + + Mixins: [ + Components.Bounce, + Components.Collision, + Components.Friction, + Components.Gravity, + Components.Mass, + Components.Sensor, + Components.Sleep, + Components.Static + ], + + initialize: + + function MatterTileBody (world, tile, options) + { + this.tile = tile; + this.world = world; + + // A tile is only allowed one matter body + if (tile.physics.matterBody) + { + tile.physics.matterBody.destroy(); + } + tile.physics.matterBody = this; + + // Set the body either from an existing body (if provided), the shapes in the tileset + // collision layer (if it exists) or a rectangle matching the tile. + var body = GetFastValue(options, 'body', null); + var addToWorld = GetFastValue(options, 'addToWorld', true); + if (!body) + { + var tileset = tile.layer.tilemapLayer.tileset; + var collisionGroup = tileset.getTileCollisionGroup(tile.index); + var collisionObjects = GetFastValue(collisionGroup, 'objects', []); + if (collisionObjects.length > 0) + { + this.setFromTileCollision(options); + } + else + { + this.setFromTileRectangle(options); + } + } + else + { + this.setBody(body, addToWorld); + } + }, + + setFromTileRectangle: function (options) + { + if (options === undefined) { options = {}; } + if (!HasValue(options, "isStatic")) { options.isStatic = true; } + if (!HasValue(options, "addToWorld")) { options.addToWorld = true; } + + var tile = this.tile; + var tilemapLayer = tile.layer.tilemapLayer; + var tileset = tilemapLayer.tileset; + var w = tile.width * tilemapLayer.scaleX; + var h = tile.height * tilemapLayer.scaleY; + var x = tilemapLayer.tileToWorldX(tile.x); + var y = tilemapLayer.tileToWorldY(tile.y); + var body = Bodies.rectangle(x + (w / 2), y + (h / 2), w, h, options); + this.setBody(body, options.addToWorld); + + return this; + }, + + setFromTileCollision: function (options) + { + if (options === undefined) { options = {}; } + if (!HasValue(options, "isStatic")) { options.isStatic = true; } + if (!HasValue(options, "addToWorld")) { options.addToWorld = true; } + + var tile = this.tile; + var tilemapLayer = tile.layer.tilemapLayer; + var tileset = tilemapLayer.tileset; + var w = tile.width * tilemapLayer.scaleX; + var h = tile.height * tilemapLayer.scaleY; + var x = tilemapLayer.tileToWorldX(tile.x); + var y = tilemapLayer.tileToWorldY(tile.y); + + var collisionGroup = tileset.getTileCollisionGroup(tile.index); + var collisionObjects = GetFastValue(collisionGroup, 'objects', []); + + var parts = []; + for (var i = 0; i < collisionObjects.length; i++) + { + var object = collisionObjects[i]; + var ox = x + object.x; + var oy = y + object.y; + var ow = object.width; + var oh = object.height; + var body = null; + + if (object.rectangle) + { + body = Bodies.rectangle(ox + ow / 2, oy + oh / 2, ow, oh, options); + } + else if (object.ellipse) + { + body = Bodies.circle(ox + ow / 2, oy + oh / 2, ow / 2, options); + } + else if (object.polygon || object.polyline) + { + // Polygons and polylines are both treated as closed polygons. Concave shapes are + // supported if poly-decomp library is included, but it's best to manually create + // convex polygons. + var originalPoints = object.polygon ? object.polygon : object.polyline; + var points = originalPoints.map(function (p) { + return { x: p[0], y: p[1] }; + }); + var vertices = Vertices.create(points); + + // Translate from object position to center of mass + var center = Vertices.centre(vertices); + body = Bodies.fromVertices(ox + center.x, oy + center.y, vertices, options); + } + + if (body) + { + parts.push(body); + } + } + + if (parts.length === 1) + { + this.setBody(parts[0], options.addToWorld); + } + else if (parts.length > 1) + { + options.parts = parts; + this.setBody(Body.create(options), options.addToWorld); + } + + return this; + }, + + setBody: function (body, addToWorld) + { + if (addToWorld === undefined) { addToWorld = true; } + + if (this.body) + { + this.removeBody(); + } + + this.body = body; + this.body.gameObject = this; + + if (addToWorld) + { + this.world.add(this.body); + } + + return this; + }, + + removeBody: function () + { + if (this.body) + { + this.world.remove(this.body); + this.body.gameObject = undefined; + this.body = undefined; + } + }, + + destroy: function () + { + this.removeBody(); + tile.physics.matterBody = undefined; + } +}); + +module.exports = MatterTileBody; diff --git a/src/physics/matter-js/World.js b/src/physics/matter-js/World.js index 09d241bfb..ab9a3271a 100644 --- a/src/physics/matter-js/World.js +++ b/src/physics/matter-js/World.js @@ -10,6 +10,7 @@ var GetValue = require('../../utils/object/GetValue'); var MatterBody = require('./lib/body/Body'); var MatterEvents = require('./lib/core/Events'); var MatterWorld = require('./lib/body/World'); +var MatterTileBody = require('./MatterTileBody'); var World = new Class({ @@ -38,7 +39,7 @@ var World = new Class({ * @property {object} walls - An object containing the 4 wall bodies that bound the physics world. */ this.walls = { left: null, right: null, top: null, bottom: null }; - + if (GetFastValue(config, 'setBounds', false)) { var boundsConfig = config['setBounds']; @@ -283,6 +284,39 @@ var World = new Class({ return this; }, + /** + * All colliding tiles will be set + */ + convertTilemapLayer: function (tilemapLayer, options) + { + var layerData = tilemapLayer.layer; + var tiles = tilemapLayer.getTilesWithin(0, 0, layerData.width, layerData.height, { + isColliding: true + }); + + this.convertTiles(tiles, options); + + return this; + }, + + /** + * Array of tiles + */ + convertTiles: function (tiles, options) + { + if (tiles.length === 0) + { + return this; + } + + for (var i = 0; i < tiles.length; i++) + { + new MatterTileBody(this, tiles[i], options); + } + + return this; + }, + nextGroup: function (isNonColliding) { return MatterBody.nextGroup(isNonColliding); diff --git a/src/sound/BaseSoundManager.js b/src/sound/BaseSoundManager.js index ab36167fd..c2ea7c459 100644 --- a/src/sound/BaseSoundManager.js +++ b/src/sound/BaseSoundManager.js @@ -167,6 +167,7 @@ var BaseSoundManager = new Class({ * @method Phaser.Sound.BaseSoundManager#play * @param {string} key - Asset key for the sound. * @param {ISoundConfig | ISoundMarker} [extra] - An optional additional object containing settings to be applied to the sound. It could be either config or marker object. + * @returns {boolean} Whether the sound started playing successfully. */ play: function (key, extra) { var sound = this.add(key); @@ -175,14 +176,14 @@ var BaseSoundManager = new Class({ if (extra) { if (extra.name) { sound.addMarker(extra); - sound.play(extra.name); + return sound.play(extra.name); } else { - sound.play(extra); + return sound.play(extra); } } else { - sound.play(); + return sound.play(); } }, /** @@ -193,11 +194,12 @@ var BaseSoundManager = new Class({ * @param {string} key - Asset key for the sound. * @param {string} spriteName - The name of the sound sprite to play. * @param {ISoundConfig} [config] - An optional config object containing default sound settings. + * @returns {boolean} Whether the audio sprite sound started playing successfully. */ playAudioSprite: function (key, spriteName, config) { var sound = this.addAudioSprite(key); sound.once('ended', sound.destroy, sound); - sound.play(spriteName, config); + return sound.play(spriteName, config); }, /** * Removes a sound from the sound manager. diff --git a/src/sound/SoundManagerCreator.js b/src/sound/SoundManagerCreator.js index 7e055d5df..44d55bd14 100644 --- a/src/sound/SoundManagerCreator.js +++ b/src/sound/SoundManagerCreator.js @@ -1,6 +1,6 @@ -var BaseSoundManager = require('./BaseSoundManager'); var WebAudioSoundManager = require('./webaudio/WebAudioSoundManager'); var HTML5AudioSoundManager = require('./html5/HTML5AudioSoundManager'); +var NoAudioSoundManager = require('./noaudio/NoAudioSoundManager'); var SoundManagerCreator = { @@ -11,8 +11,7 @@ var SoundManagerCreator = { if ((audioConfig && audioConfig.noAudio) || (!deviceAudio.webAudio && !deviceAudio.audioData)) { - // TODO add no audio implementation of BaseSoundManager - return new BaseSoundManager(game); + return new NoAudioSoundManager(game); } if(deviceAudio.webAudio && !(audioConfig && audioConfig.disableWebAudio)) diff --git a/src/sound/index.js b/src/sound/index.js index 1bd8f2d1a..f15f79577 100644 --- a/src/sound/index.js +++ b/src/sound/index.js @@ -11,6 +11,9 @@ module.exports = { WebAudioSoundManager: require('./webaudio/WebAudioSoundManager'), HTML5AudioSound: require('./html5/HTML5AudioSound'), - HTML5AudioSoundManager: require('./html5/HTML5AudioSoundManager') + HTML5AudioSoundManager: require('./html5/HTML5AudioSoundManager'), + + NoAudioSound: require('./noaudio/NoAudioSound'), + NoAudioSoundManager: require('./noaudio/NoAudioSoundManager') }; diff --git a/src/sound/noaudio/NoAudioSound.js b/src/sound/noaudio/NoAudioSound.js new file mode 100644 index 000000000..9b20f0a60 --- /dev/null +++ b/src/sound/noaudio/NoAudioSound.js @@ -0,0 +1,62 @@ +var Class = require('../../utils/Class'); +var EventEmitter = require('eventemitter3'); +var Extend = require('../../utils/object/Extend'); +var BaseSound = require('../BaseSound'); +var NoAudioSound = new Class({ + Extends: EventEmitter, + initialize: function NoAudioSound(manager, key, config) { + EventEmitter.call(this); + this.manager = manager; + this.key = key; + this.isPlaying = false; + this.isPaused = false; + this.totalRate = 1; + this.duration = 0; + this.totalDuration = 0; + this.config = Extend({ + mute: false, + volume: 1, + rate: 1, + detune: 0, + seek: 0, + loop: false, + delay: 0 + }, config); + this.currentConfig = this.config; + this.mute = false; + this.volume = 1; + this.rate = 1; + this.detune = 0; + this.seek = 0; + this.loop = false; + this.markers = {}; + this.currentMarker = null; + this.pendingRemove = false; + }, + addMarker: function (marker) { + return false; + }, + updateMarker: function (marker) { + return false; + }, + removeMarker: function (markerName) { + return null; + }, + play: function (markerName, config) { + return false; + }, + pause: function () { + return false; + }, + resume: function () { + return false; + }, + stop: function () { + return false; + }, + destroy: function () { + this.manager.remove(this); + BaseSound.prototype.destroy.call(this); + } +}); +module.exports = NoAudioSound; diff --git a/src/sound/noaudio/NoAudioSoundManager.js b/src/sound/noaudio/NoAudioSoundManager.js new file mode 100644 index 000000000..0532b2a09 --- /dev/null +++ b/src/sound/noaudio/NoAudioSoundManager.js @@ -0,0 +1,52 @@ +var Class = require('../../utils/Class'); +var EventEmitter = require('eventemitter3'); +var NoAudioSound = require('./NoAudioSound'); +var BaseSoundManager = require('../BaseSoundManager'); +var NOOP = require('../../utils/NOOP'); +var NoAudioSoundManager = new Class({ + Extends: EventEmitter, + initialize: function NoAudioSoundManager(game) { + EventEmitter.call(this); + this.game = game; + this.sounds = []; + this.mute = false; + this.volume = 1; + this.rate = 1; + this.detune = 0; + this.pauseOnBlur = true; + this.locked = false; + }, + add: function (key, config) { + var sound = new NoAudioSound(this, key, config); + this.sounds.push(sound); + return sound; + }, + addAudioSprite: function (key, config) { + var sound = this.add(key, config); + sound.spritemap = {}; + return sound; + }, + play: function (key, extra) { + return false; + }, + playAudioSprite: function (key, spriteName, config) { + return false; + }, + remove: function (sound) { + return BaseSoundManager.prototype.remove.call(this, sound); + }, + removeByKey: function (key) { + return BaseSoundManager.prototype.removeByKey.call(this, key); + }, + pauseAll: NOOP, + resumeAll: NOOP, + stopAll: NOOP, + update: NOOP, + destroy: function () { + BaseSoundManager.prototype.destroy.call(this); + }, + forEachActiveSound: function (callbackfn, thisArg) { + BaseSoundManager.prototype.forEachActiveSound.call(this, callbackfn, thisArg); + } +}); +module.exports = NoAudioSoundManager;