diff --git a/README.md b/README.md index 5c5cdd1d..49af09ee 100644 --- a/README.md +++ b/README.md @@ -151,13 +151,13 @@ Version 1.1 * Added Rectangle.floorAll to floor all values in a Rectangle (x, y, width and height). * Fixed Sound.resume so it now correctly resumes playback from the point it was paused (fixes issue 51, thanks Yora). * Sprite.loadTexture now works correctly with static images, RenderTextures and Animations. - +* Lots of fixes within Sprite.bounds. The bounds is now correct regardless of rotation, anchor or scale of the Sprite or any of its parent objects. +* On a busy page it's possible for the game to boot with an incorrect stage offset x/y which can cause input events to be calculated wrong. A new property has been added to Stage to combat this issue: Stage.checkOffsetInterval. By default it will check the canvas offset every 2500ms and adjust it accordingly. You can set the value to 'false' to disable the check entirely, or set a higher or lower value. We recommend that you get the value quite low during your games preloader, but once the game has fully loaded hopefully the containing page will have settled down, so it's probably safe to disable the check entirely. +* Pixel Perfect click detection now works even if the Sprite is part of a texture atlas. Outstanding Tasks ----------------- -* BUG: The pixel perfect click check doesn't work if the sprite is part of a texture atlas yet. -* TODO: look at Sprite.crop (http://www.html5gamedevs.com/topic/1617-error-in-spritecrop/) * TODO: d-pad example (http://www.html5gamedevs.com/topic/1574-gameinputondown-question/) * TODO: more touch input examples (http://www.html5gamedevs.com/topic/1556-mobile-touch-event/) * TODO: Sound.addMarker hh:mm:ss:ms diff --git a/examples/_site/examples.json b/examples/_site/examples.json index 29e2a3bb..88388bb0 100644 --- a/examples/_site/examples.json +++ b/examples/_site/examples.json @@ -320,6 +320,10 @@ "file": "pixel+perfect+click+detection.js", "title": "pixel perfect click detection" }, + { + "file": "pixelpick+-+atlas.js", + "title": "pixelpick - atlas" + }, { "file": "pixelpick+-+scrolling+effect.js", "title": "pixelpick - scrolling effect" diff --git a/examples/buttons/button scale.js b/examples/buttons/button scale.js index 8ab6715b..aa639e50 100644 --- a/examples/buttons/button scale.js +++ b/examples/buttons/button scale.js @@ -43,6 +43,8 @@ function create() { button3 = game.add.button(100, 300, 'button', changeSky, this, 2, 1, 0); button3.name = 'sky3'; button3.width = 300; + button3.anchor.setTo(0, 0.5); + // button3.angle = 0.1; // Scaled button button4 = game.add.button(300, 450, 'button', changeSky, this, 2, 1, 0); @@ -85,7 +87,8 @@ function render () { // game.debug.renderWorldTransformInfo(button1, 32, 132); // game.debug.renderText('sx: ' + button3.scale.x + ' sy: ' + button3.scale.y + ' w: ' + button3.width + ' cw: ' + button3._cache.width, 32, 20); - // game.debug.renderPoint(button2.input._tempPoint); - // game.debug.renderPoint(button6.input._tempPoint); + game.debug.renderText('ox: ' + game.stage.offset.x + ' oy: ' + game.stage.offset.y, 32, 20); + game.debug.renderPoint(button3.input._tempPoint); + game.debug.renderPoint(button6.input._tempPoint); } diff --git a/examples/input/pixel perfect click detection.js b/examples/input/pixel perfect click detection.js index 4246de80..70fcada7 100644 --- a/examples/input/pixel perfect click detection.js +++ b/examples/input/pixel perfect click detection.js @@ -37,11 +37,13 @@ function outSprite() { } function update() { - b.angle += 0.1; + b.angle += 0.05; } function render() { game.debug.renderSpriteInputInfo(b, 32, 32); + game.debug.renderSpriteCorners(b); + game.debug.renderPoint(b.input._tempPoint); } diff --git a/examples/input/pixelpick - atlas.js b/examples/input/pixelpick - atlas.js new file mode 100644 index 00000000..eef6172a --- /dev/null +++ b/examples/input/pixelpick - atlas.js @@ -0,0 +1,52 @@ + +var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create }); + +function preload() { + + game.load.atlas('atlas', 'assets/pics/texturepacker_test.png', 'assets/pics/texturepacker_test.json'); + +} + +var chick; +var car; +var mech; +var robot; +var cop; + +function create() { + + game.stage.backgroundColor = '#404040'; + + // This demonstrates pixel perfect click detection even if using sprites in a texture atlas. + + chick = game.add.sprite(64, 64, 'atlas'); + chick.frameName = 'budbrain_chick.png'; + chick.inputEnabled = true; + chick.input.pixelPerfect = true; + chick.input.useHandCursor = true; + + cop = game.add.sprite(600, 64, 'atlas'); + cop.frameName = 'ladycop.png'; + cop.inputEnabled = true; + cop.input.pixelPerfect = true; + cop.input.useHandCursor = true; + + robot = game.add.sprite(50, 300, 'atlas'); + robot.frameName = 'robot.png'; + robot.inputEnabled = true; + robot.input.pixelPerfect = true; + robot.input.useHandCursor = true; + + car = game.add.sprite(100, 400, 'atlas'); + car.frameName = 'supercars_parsec.png'; + car.inputEnabled = true; + car.input.pixelPerfect = true; + car.input.useHandCursor = true; + + mech = game.add.sprite(250, 100, 'atlas'); + mech.frameName = 'titan_mech.png'; + mech.inputEnabled = true; + mech.input.pixelPerfect = true; + mech.input.useHandCursor = true; + +} diff --git a/examples/input/pixelpick - spritesheet.js b/examples/input/pixelpick - spritesheet.js index f0692822..d798775a 100644 --- a/examples/input/pixelpick - spritesheet.js +++ b/examples/input/pixelpick - spritesheet.js @@ -11,7 +11,10 @@ var b; function create() { + Phaser.Canvas.setSmoothingEnabled(game.context, false); + b = game.add.sprite(game.world.centerX, game.world.centerY, 'mummy'); + b.anchor.setTo(0.5, 0.5); b.scale.setTo(6, 6); b.animations.add('walk'); @@ -42,5 +45,7 @@ function outSprite() { function render() { game.debug.renderSpriteInputInfo(b, 32, 32); + game.debug.renderSpriteCorners(b); + game.debug.renderPoint(b.input._tempPoint); } diff --git a/src/core/Game.js b/src/core/Game.js index 02b40049..73fd060a 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -400,6 +400,7 @@ Phaser.Game.prototype = { this.plugins.preUpdate(); this.physics.preUpdate(); + this.stage.update(); this.input.update(); this.tweens.update(); this.sound.update(); diff --git a/src/core/Stage.js b/src/core/Stage.js index 76133cbb..50499321 100644 --- a/src/core/Stage.js +++ b/src/core/Stage.js @@ -61,6 +61,18 @@ Phaser.Stage = function (game, width, height) { */ this.aspectRatio = width / height; + /** + * @property {number} _nextOffsetCheck - The time to run the next offset check. + * @private + */ + this._nextOffsetCheck = 0; + + /** + * @property {number|false} checkOffsetInterval - The time (in ms) between which the stage should check to see if it has moved. + * @default + */ + this.checkOffsetInterval = 2500; + }; Phaser.Stage.prototype = { @@ -93,6 +105,24 @@ Phaser.Stage.prototype = { window.onblur = this._onChange; window.onfocus = this._onChange; + }, + + /** + * Runs Stage processes that need periodic updates, such as the offset checks. + * @method Phaser.Stage#update + */ + update: function () { + + if (this.checkOffsetInterval !== false) + { + if (this.game.time.now > this._nextOffsetCheck) + { + Phaser.Canvas.getOffset(this.canvas, this.offset); + this._nextOffsetCheck = this.game.time.now + this.checkOffsetInterval; + } + + } + }, /** diff --git a/src/gameobjects/Sprite.js b/src/gameobjects/Sprite.js index 3b261715..0a45b074 100644 --- a/src/gameobjects/Sprite.js +++ b/src/gameobjects/Sprite.js @@ -392,6 +392,7 @@ Phaser.Sprite.prototype.updateCache = function() { if (this.worldTransform[1] != this._cache.i01 || this.worldTransform[3] != this._cache.i10) { + console.log('updateCache wt', this.name); this._cache.a00 = this.worldTransform[0]; // scaleX a this._cache.a01 = this.worldTransform[1]; // skewY c this._cache.a10 = this.worldTransform[3]; // skewX b @@ -430,13 +431,8 @@ Phaser.Sprite.prototype.updateAnimation = function() { if (this._cache.dirty && this.currentFrame) { - console.log('ua frame 2 change', this.name); - // this._cache.width = Math.floor(this.currentFrame.sourceSizeW * this._cache.scaleX); - // this._cache.height = Math.floor(this.currentFrame.sourceSizeH * this._cache.scaleY); this._cache.width = this.currentFrame.width; this._cache.height = this.currentFrame.height; - // this._cache.width = Math.floor(this.currentFrame.sourceSizeW); - // this._cache.height = Math.floor(this.currentFrame.sourceSizeH); this._cache.halfWidth = Math.floor(this._cache.width / 2); this._cache.halfHeight = Math.floor(this._cache.height / 2); @@ -540,14 +536,16 @@ Phaser.Sprite.prototype.getLocalPosition = function(p, x, y, sx, sy) { * @param {number} y - Description. * @return {Description} Description. */ -Phaser.Sprite.prototype.getLocalUnmodifiedPosition = function(p, x, y) { +Phaser.Sprite.prototype.getLocalUnmodifiedPosition = function(p, gx, gy) { - p.x = this._cache.a11 * this._cache.idi * x + -this._cache.i01 * this._cache.idi * y + (this._cache.a12 * this._cache.i01 - this._cache.a02 * this._cache.a11) * this._cache.idi; - p.y = this._cache.a00 * this._cache.idi * y + -this._cache.i10 * this._cache.idi * x + (-this._cache.a12 * this._cache.a00 + this._cache.a02 * this._cache.i10) * this._cache.idi; + var a00 = this.worldTransform[0], a01 = this.worldTransform[1], a02 = this.worldTransform[2], + a10 = this.worldTransform[3], a11 = this.worldTransform[4], a12 = this.worldTransform[5], + id = 1 / (a00 * a11 + a01 * -a10), + x = a11 * id * gx + -a01 * id * gy + (a12 * a01 - a02 * a11) * id, + y = a00 * id * gy + -a10 * id * gx + (-a12 * a00 + a02 * a10) * id; - // apply anchor - p.x += (this.anchor.x * this._cache.width); - p.y += (this.anchor.y * this._cache.height); + p.x = x + (this.anchor.x * this._cache.width); + p.y = y + (this.anchor.y * this._cache.height); return p; diff --git a/src/input/InputHandler.js b/src/input/InputHandler.js index fe90ba7c..38a3d865 100644 --- a/src/input/InputHandler.js +++ b/src/input/InputHandler.js @@ -484,47 +484,19 @@ Phaser.InputHandler.prototype = { { this.sprite.getLocalUnmodifiedPosition(this._tempPoint, pointer.x, pointer.y); - // The unmodified position is being offset by the anchor, i.e. into negative space - - // var x = this.sprite.anchor.x * this.sprite.width; - // var y = this.sprite.anchor.y * this.sprite.height; - var x = 0; - var y = 0; - - // check world transform - if (this.sprite.worldTransform[3] == 0 && this.sprite.worldTransform[1] == 0) + if (this._tempPoint.x >= 0 && this._tempPoint.x <= this.sprite.currentFrame.width && this._tempPoint.y >= 0 && this._tempPoint.y <= this.sprite.currentFrame.height) { - // Un-rotated (but potentially scaled) - if (this._tempPoint.x >= x && this._tempPoint.x <= this.sprite.width && this._tempPoint.y >= y && this._tempPoint.y <= this.sprite.height) + if (this.pixelPerfect) { - return true; + return this.checkPixel(this._tempPoint.x, this._tempPoint.y); } - } - else - { - // Rotated (and could be scaled too) - if (this._tempPoint.x >= x && this._tempPoint.x <= this.sprite.currentFrame.width && this._tempPoint.y >= y && this._tempPoint.y <= this.sprite.currentFrame.height) + else { return true; } } } - // if (this.pixelPerfect) - // { - // return this.checkPixel(this._tempPoint.x, this._tempPoint.y); - // } - // else - // { - // return true; - // } - // } - // } - - // } - - // } - return false; }, @@ -538,16 +510,16 @@ Phaser.InputHandler.prototype = { */ checkPixel: function (x, y) { - x += (this.sprite.texture.frame.width * this.sprite.anchor.x); - y += (this.sprite.texture.frame.height * this.sprite.anchor.y); - // Grab a pixel from our image into the hitCanvas and then test it - if (this.sprite.texture.baseTexture.source) { this.game.input.hitContext.clearRect(0, 0, 1, 1); // This will fail if the image is part of a texture atlas - need to modify the x/y values here + + x += this.sprite.texture.frame.x; + y += this.sprite.texture.frame.y; + this.game.input.hitContext.drawImage(this.sprite.texture.baseTexture.source, x, y, 1, 1, 0, 0, 1, 1); var rgb = this.game.input.hitContext.getImageData(0, 0, 1, 1); diff --git a/src/utils/Debug.js b/src/utils/Debug.js index 30122ee2..f959530b 100644 --- a/src/utils/Debug.js +++ b/src/utils/Debug.js @@ -206,25 +206,27 @@ Phaser.Utils.Debug.prototype = { if (showBounds) { - this.context.strokeStyle = 'rgba(255,0,0,1)'; + this.context.beginPath(); + this.context.strokeStyle = 'rgba(0, 255, 0, 0.7)'; this.context.strokeRect(sprite.bounds.x, sprite.bounds.y, sprite.bounds.width, sprite.bounds.height); + this.context.closePath(); this.context.stroke(); } - // this.context.beginPath(); - // this.context.moveTo(sprite.topLeft.x, sprite.topLeft.y); - // this.context.lineTo(sprite.topRight.x, sprite.topRight.y); - // this.context.lineTo(sprite.bottomRight.x, sprite.bottomRight.y); - // this.context.lineTo(sprite.bottomLeft.x, sprite.bottomLeft.y); - // this.context.closePath(); - // this.context.strokeStyle = 'rgba(255,0,0,1)'; - // this.context.stroke(); + this.context.beginPath(); + this.context.moveTo(sprite.topLeft.x, sprite.topLeft.y); + this.context.lineTo(sprite.topRight.x, sprite.topRight.y); + this.context.lineTo(sprite.bottomRight.x, sprite.bottomRight.y); + this.context.lineTo(sprite.bottomLeft.x, sprite.bottomLeft.y); + this.context.closePath(); + this.context.strokeStyle = 'rgba(255, 0, 255, 0.7)'; + this.context.stroke(); this.renderPoint(sprite.center); - this.renderPoint(sprite.topLeft, 'rgb(255,255,0)'); - this.renderPoint(sprite.topRight, 'rgb(255,0,0)'); - this.renderPoint(sprite.bottomLeft, 'rgb(0,0,255)'); - this.renderPoint(sprite.bottomRight, 'rgb(255,255,255)'); + this.renderPoint(sprite.topLeft); + this.renderPoint(sprite.topRight); + this.renderPoint(sprite.bottomLeft); + this.renderPoint(sprite.bottomRight); if (showText) {