mirror of
https://github.com/photonstorm/phaser
synced 2024-11-25 06:00:41 +00:00
Pixel Perfect click detection now works even if the Sprite is part of a texture atlas.
This commit is contained in:
parent
1294b3a2b9
commit
2921a6de2e
11 changed files with 135 additions and 66 deletions
|
@ -151,13 +151,13 @@ Version 1.1
|
||||||
* Added Rectangle.floorAll to floor all values in a Rectangle (x, y, width and height).
|
* 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).
|
* 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.
|
* 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
|
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: 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: more touch input examples (http://www.html5gamedevs.com/topic/1556-mobile-touch-event/)
|
||||||
* TODO: Sound.addMarker hh:mm:ss:ms
|
* TODO: Sound.addMarker hh:mm:ss:ms
|
||||||
|
|
|
@ -320,6 +320,10 @@
|
||||||
"file": "pixel+perfect+click+detection.js",
|
"file": "pixel+perfect+click+detection.js",
|
||||||
"title": "pixel perfect click detection"
|
"title": "pixel perfect click detection"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "pixelpick+-+atlas.js",
|
||||||
|
"title": "pixelpick - atlas"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "pixelpick+-+scrolling+effect.js",
|
"file": "pixelpick+-+scrolling+effect.js",
|
||||||
"title": "pixelpick - scrolling effect"
|
"title": "pixelpick - scrolling effect"
|
||||||
|
|
|
@ -43,6 +43,8 @@ function create() {
|
||||||
button3 = game.add.button(100, 300, 'button', changeSky, this, 2, 1, 0);
|
button3 = game.add.button(100, 300, 'button', changeSky, this, 2, 1, 0);
|
||||||
button3.name = 'sky3';
|
button3.name = 'sky3';
|
||||||
button3.width = 300;
|
button3.width = 300;
|
||||||
|
button3.anchor.setTo(0, 0.5);
|
||||||
|
// button3.angle = 0.1;
|
||||||
|
|
||||||
// Scaled button
|
// Scaled button
|
||||||
button4 = game.add.button(300, 450, 'button', changeSky, this, 2, 1, 0);
|
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.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.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.renderText('ox: ' + game.stage.offset.x + ' oy: ' + game.stage.offset.y, 32, 20);
|
||||||
// game.debug.renderPoint(button6.input._tempPoint);
|
game.debug.renderPoint(button3.input._tempPoint);
|
||||||
|
game.debug.renderPoint(button6.input._tempPoint);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,11 +37,13 @@ function outSprite() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
b.angle += 0.1;
|
b.angle += 0.05;
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
|
|
||||||
game.debug.renderSpriteInputInfo(b, 32, 32);
|
game.debug.renderSpriteInputInfo(b, 32, 32);
|
||||||
|
game.debug.renderSpriteCorners(b);
|
||||||
|
game.debug.renderPoint(b.input._tempPoint);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
52
examples/input/pixelpick - atlas.js
Normal file
52
examples/input/pixelpick - atlas.js
Normal file
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -11,7 +11,10 @@ var b;
|
||||||
|
|
||||||
function create() {
|
function create() {
|
||||||
|
|
||||||
|
Phaser.Canvas.setSmoothingEnabled(game.context, false);
|
||||||
|
|
||||||
b = game.add.sprite(game.world.centerX, game.world.centerY, 'mummy');
|
b = game.add.sprite(game.world.centerX, game.world.centerY, 'mummy');
|
||||||
|
|
||||||
b.anchor.setTo(0.5, 0.5);
|
b.anchor.setTo(0.5, 0.5);
|
||||||
b.scale.setTo(6, 6);
|
b.scale.setTo(6, 6);
|
||||||
b.animations.add('walk');
|
b.animations.add('walk');
|
||||||
|
@ -42,5 +45,7 @@ function outSprite() {
|
||||||
function render() {
|
function render() {
|
||||||
|
|
||||||
game.debug.renderSpriteInputInfo(b, 32, 32);
|
game.debug.renderSpriteInputInfo(b, 32, 32);
|
||||||
|
game.debug.renderSpriteCorners(b);
|
||||||
|
game.debug.renderPoint(b.input._tempPoint);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -400,6 +400,7 @@ Phaser.Game.prototype = {
|
||||||
this.plugins.preUpdate();
|
this.plugins.preUpdate();
|
||||||
this.physics.preUpdate();
|
this.physics.preUpdate();
|
||||||
|
|
||||||
|
this.stage.update();
|
||||||
this.input.update();
|
this.input.update();
|
||||||
this.tweens.update();
|
this.tweens.update();
|
||||||
this.sound.update();
|
this.sound.update();
|
||||||
|
|
|
@ -61,6 +61,18 @@ Phaser.Stage = function (game, width, height) {
|
||||||
*/
|
*/
|
||||||
this.aspectRatio = 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 = {
|
Phaser.Stage.prototype = {
|
||||||
|
@ -93,6 +105,24 @@ Phaser.Stage.prototype = {
|
||||||
window.onblur = this._onChange;
|
window.onblur = this._onChange;
|
||||||
window.onfocus = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -392,6 +392,7 @@ Phaser.Sprite.prototype.updateCache = function() {
|
||||||
|
|
||||||
if (this.worldTransform[1] != this._cache.i01 || this.worldTransform[3] != this._cache.i10)
|
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.a00 = this.worldTransform[0]; // scaleX a
|
||||||
this._cache.a01 = this.worldTransform[1]; // skewY c
|
this._cache.a01 = this.worldTransform[1]; // skewY c
|
||||||
this._cache.a10 = this.worldTransform[3]; // skewX b
|
this._cache.a10 = this.worldTransform[3]; // skewX b
|
||||||
|
@ -430,13 +431,8 @@ Phaser.Sprite.prototype.updateAnimation = function() {
|
||||||
|
|
||||||
if (this._cache.dirty && this.currentFrame)
|
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.width = this.currentFrame.width;
|
||||||
this._cache.height = this.currentFrame.height;
|
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.halfWidth = Math.floor(this._cache.width / 2);
|
||||||
this._cache.halfHeight = Math.floor(this._cache.height / 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.
|
* @param {number} y - Description.
|
||||||
* @return {Description} 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;
|
var a00 = this.worldTransform[0], a01 = this.worldTransform[1], a02 = this.worldTransform[2],
|
||||||
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;
|
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 = x + (this.anchor.x * this._cache.width);
|
||||||
p.x += (this.anchor.x * this._cache.width);
|
p.y = y + (this.anchor.y * this._cache.height);
|
||||||
p.y += (this.anchor.y * this._cache.height);
|
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
|
|
||||||
|
|
|
@ -484,47 +484,19 @@ Phaser.InputHandler.prototype = {
|
||||||
{
|
{
|
||||||
this.sprite.getLocalUnmodifiedPosition(this._tempPoint, pointer.x, pointer.y);
|
this.sprite.getLocalUnmodifiedPosition(this._tempPoint, pointer.x, pointer.y);
|
||||||
|
|
||||||
// The unmodified position is being offset by the anchor, i.e. into negative space
|
if (this._tempPoint.x >= 0 && this._tempPoint.x <= this.sprite.currentFrame.width && this._tempPoint.y >= 0 && this._tempPoint.y <= this.sprite.currentFrame.height)
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
// Un-rotated (but potentially scaled)
|
if (this.pixelPerfect)
|
||||||
if (this._tempPoint.x >= x && this._tempPoint.x <= this.sprite.width && this._tempPoint.y >= y && this._tempPoint.y <= this.sprite.height)
|
|
||||||
{
|
{
|
||||||
return true;
|
return this.checkPixel(this._tempPoint.x, this._tempPoint.y);
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (this.pixelPerfect)
|
|
||||||
// {
|
|
||||||
// return this.checkPixel(this._tempPoint.x, this._tempPoint.y);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@ -538,16 +510,16 @@ Phaser.InputHandler.prototype = {
|
||||||
*/
|
*/
|
||||||
checkPixel: function (x, y) {
|
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
|
// Grab a pixel from our image into the hitCanvas and then test it
|
||||||
|
|
||||||
if (this.sprite.texture.baseTexture.source)
|
if (this.sprite.texture.baseTexture.source)
|
||||||
{
|
{
|
||||||
this.game.input.hitContext.clearRect(0, 0, 1, 1);
|
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
|
// 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);
|
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);
|
var rgb = this.game.input.hitContext.getImageData(0, 0, 1, 1);
|
||||||
|
|
|
@ -206,25 +206,27 @@ Phaser.Utils.Debug.prototype = {
|
||||||
|
|
||||||
if (showBounds)
|
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.strokeRect(sprite.bounds.x, sprite.bounds.y, sprite.bounds.width, sprite.bounds.height);
|
||||||
|
this.context.closePath();
|
||||||
this.context.stroke();
|
this.context.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.context.beginPath();
|
this.context.beginPath();
|
||||||
// this.context.moveTo(sprite.topLeft.x, sprite.topLeft.y);
|
this.context.moveTo(sprite.topLeft.x, sprite.topLeft.y);
|
||||||
// this.context.lineTo(sprite.topRight.x, sprite.topRight.y);
|
this.context.lineTo(sprite.topRight.x, sprite.topRight.y);
|
||||||
// this.context.lineTo(sprite.bottomRight.x, sprite.bottomRight.y);
|
this.context.lineTo(sprite.bottomRight.x, sprite.bottomRight.y);
|
||||||
// this.context.lineTo(sprite.bottomLeft.x, sprite.bottomLeft.y);
|
this.context.lineTo(sprite.bottomLeft.x, sprite.bottomLeft.y);
|
||||||
// this.context.closePath();
|
this.context.closePath();
|
||||||
// this.context.strokeStyle = 'rgba(255,0,0,1)';
|
this.context.strokeStyle = 'rgba(255, 0, 255, 0.7)';
|
||||||
// this.context.stroke();
|
this.context.stroke();
|
||||||
|
|
||||||
this.renderPoint(sprite.center);
|
this.renderPoint(sprite.center);
|
||||||
this.renderPoint(sprite.topLeft, 'rgb(255,255,0)');
|
this.renderPoint(sprite.topLeft);
|
||||||
this.renderPoint(sprite.topRight, 'rgb(255,0,0)');
|
this.renderPoint(sprite.topRight);
|
||||||
this.renderPoint(sprite.bottomLeft, 'rgb(0,0,255)');
|
this.renderPoint(sprite.bottomLeft);
|
||||||
this.renderPoint(sprite.bottomRight, 'rgb(255,255,255)');
|
this.renderPoint(sprite.bottomRight);
|
||||||
|
|
||||||
if (showText)
|
if (showText)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue