Detect touch in sprite

No way to detect touch on a sprite? I refer to detect touch and not touch if transparent part of the sprite as use png

use cocos2d 2.2.6

Do you want to know how to do it or if there is a built-in function?

I want to know how to do or suggestions, I was using the “menuitem” but me when you touch me also take the transparent part of the image

The simplest way is within onTouchBegan or onTouchesBegan:

// get the location of the touch on screen
var location = touch.getLocation();
// get the location of the touch relative to your button
var nodeSpaceLocation = node.getParent().convertToNodeSpace(location);
// check if touch is inside node's bounding box
if (cc.rectContainsPoint(node.getBoundingBox(), nodeSpaceLocation)) {
  // node has been touched; add code here
}

Change node to the variable for your sprite.

Cocos2d-x does not support pixel perfect touch as far as I know.

There are a few ways that spring to mind, you can render your scene using a unique shader to an render buffer as solid colours per object, you then sample the render buffer with the touch coord and map that back to the object.

Another is similar but more brute force: you could simply sample the texture of your sprite after transforming the touch position into the texel space.

And thirdly you might try and clip away the transparent pixels by creating a mesh based on the texture and test against that. cocos2d-x has recently added such a feature.

I have a v2.2.6 app that uses a RenderTexture and glReadPixels to test if the touched location is transparent or not. Works perfectly for me, and it’s relatively simple. Not sure if this one of @almax27’s methods or not.

How I do it:

  1. Check that the touch is within the bounding box of the sprite.
  2. If it is, move the sprite so that it is the same distance from the bottom left of the screen as it was from the touch.
  3. Call begin with your RenderTexture.
  4. Visit your sprite.
  5. Use glReadPixels (probably gl.readPixels in JavaScript) to get the bottom-left pixel of the screen.
  6. Call end with your RenderTexture.
  7. Place your sprite back where it was.
  8. glReadPixels should have given you an array with the colours of the pixel. It is RGBA, so the alpha value is color[3], if you called your array color.

I can provide source code, but maybe one of the other 3 methods might be easier or better, I’m not sure.

Yeah it’s similar to the second solution I suggested, quick to implement and simple but will get very expensive if you start testing lots of different images. glReadPixels can be pretty slow so you want to minimise how much you call it. First solution is probably the most performant but will take longer to implement.

Continuing the discussion from Detect touch in sprite:

Try the code you passed me but I pulled this error on the console:
Uncaught TypeError: cc.rectContainsPoint is not a function

you got an example? I would greatly appreciate it

Sorry, I have been busy for the last few days.

I am not sure why you are getting this error. It should be a built-in function of Cocos2d-x.

Here is some code that works for me. Not sure if it is perfect, but I have tested it and it seems to work.

// the onTouchesBegan function for your layer
onTouchesBegan:function(touches) {
    // to check position, it needs to be in the node space of the node's parent
    var pos = this.node.getParent().convertToNodeSpace(touches[0].getLocation());
    // checking for transparency can be costly, so avoid it if you can. if the touch is outside of the node's
    // bounding box, then there is no chance you have touched the node, so no need to check for transparency.
    if (cc.rectContainsPoint(this.node.getBoundingBox(), pos)) {
        // if the bounding box was touched, then you need to check for transparency
        if (!isTransparent(this.node, pos)) {
            // if the touched pixel was not transparent, you have touched the node
            cc.log("Touched!");
            return;
        }
    }
    
    // either the touch was outside the bounding box or was a transparent pixel
    cc.log("Not touched!");
}

// check if a position in the node space of a node's parent is a transparent pixel
function isTransparent(node, pos) {
    // used to store the RGBA value for the pixel
    var pixel = new Uint8Array(4);
    // a RenderTexture is used to read the colour of the pixel
    var renderTexture = cc.RenderTexture.create(5, 5);
    // to be efficient, we create a small RenderTexture and move the node so that the touched point is at
    // at position (0, 0), which is the bottom-left of the RenderTexture. because of this, we need to remember
    // the location of the node so we can move it back afterwards.
    var originalPosition = node.getPosition();
    
    // move the node so that the touched point is at (0, 0)
    node.setPosition(originalPosition.x - pos.x, originalPosition.y - pos.y);
    
    // because we get get the colour of the pixel of the RenderTexture, we need to clear the RenderTexture to
    // make sure we are only getting the colour of the node. 
    renderTexture.beginWithClear(0, 0, 0, 0);
    // draw the node onto the RenderTexture
    node.visit();
    // get the colour of the pixel at (0, 0)
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
    // finish drawing on the RenderTexture
    renderTexture.end();
    
    // put the node back in its original position
    node.setPosition(originalPosition);
    
    // pixel[3] is the alpha value of the pixel. if it has a value of 0, the pixel is completely transparent. sometimes
    // the edges of nodes are partially transparent, so you should determine at which value you decide a pixel
    // is not transparent.
    return (pixel[3] < 50);
}

This seems very expensive doing all this checking just create a customs sprite object which inherits from ccSrpite then if you create one global touch eventlistener and inside this check the bounds of the touch against its parents bounds (Tells you if inside the sprite) then just add this touch listener to the custom sprite whenever you create one.

Global Listener

var onTouchBegan = function(touch,event)
{
	var target = event.getCurrentTarget();
	var location = target.convertToNodeSpace(touch.getLocation());
	var targetSize = target.getContentSize();
	var targetRectangle = cc.rect(0, 0, targetSize.width, targetSize.height);
	//Check if pressed inside the object
	if (cc.rectContainsPoint(targetRectangle, location)) 
	{
		target.setPressed(true);
	}
	return true;
};

this gets if the touch was inside the sprite then you just need to add it to your object here is a simple example

var ObjectTile = cc.Sprite.extend({	
	checkHit:false,
	lightListener:null,
	
	ctor:function() 
	{
		this._super(res.Outline_png);
		
		//Add touch listener for object
		this.lightListener = cc.EventListener.create({
			event: cc.EventListener.TOUCH_ONE_BY_ONE,
			swallowTouches: false,
			onTouchBegan: onTouchBegan,
			onTouchEnded: onTouchEnded
		});
		cc.eventManager.addListener(this.lightListener, this);
	},
	
	removeListener:function()
	{
		cc.eventManager.removeListener(this.lightListener);
	},
	
	setPressed:function(on)
	{
		this.checkHit = on;
	},
});
2 Likes

kelor wants to know how to determine if a touch is on a transparent or on an opaque pixel. Testing if the touch location is within the bounding box will tell you if you are touching the sprite, but not if the pixel you are touching is transparent or not.

Hey, you can now fix it, looking at google found a feature that detects if a sprite transparent or opaque and opaque if I put the function.
and it works on 2.2.6 :slight_smile:
Thank you very much to all

@keior please link what you found on google so others who have the same issue in the future can benefit,

Thank you :smiley:

@almax27 yes :blush:
http://pastebin.com/y77ZftdH
work with cocos 2D 2.2

1 Like