How can I ensure I still get correct touch inputs when my scene is offset?

How can I accept touch input beyond the scene's bounds, so that no matter what I set self.position to, touches can still be detected?

I'm creating a tile based game from Ray Winderlich on Cocos2d version 3.0. I am at the point of setting the view of the screen to a zoomed in state on my tile map. I have successfully been able to do that although now my touches are not responding since I'm out of the coordinate space the touches used to work on.

This method is called to set the zoomed view to the player's position:

-(void)setViewPointCenter:(CGPoint)position{
CGSize winSize = [CCDirector sharedDirector].viewSizeInPixels;
int x = MAX(position.x, winSize.width/2);
int y = MAX(position.y, winSize.height/2);
x = MIN(x, (_tileMap.mapSize.width * _tileMap.tileSize.width) - winSize.width / 2);
y = MIN(y, (_tileMap.mapSize.height * _tileMap.tileSize.height) - winSize.height / 2);
CGPoint actualPosition = ccp(x, y);

CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
NSLog(@"centerOfView%@", NSStringFromCGPoint(centerOfView));
CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
NSLog(@"viewPoint%@", NSStringFromCGPoint(viewPoint));

//This changes the position of the helloworld layer/scene so that
//we can see the portion of the tilemap we're interested in.
//That however makes my touchbegan method stop firing
self.position = viewPoint;
}


This is what the NSLog prints from the method:

2014-01-30 07:05:08.725 TestingTouch[593:60b] centerOfView{512, 384}
2014-01-30 07:05:08.727 TestingTouch[593:60b] viewPoint{0, -832}


As you can see the y coordinate is -800. If i comment out the line self.position = viewPoint then the self.position reads {0, 0} and touches are detectable again but then we don't have a zoomed view on the character. Instead it shows the view on the bottom left of the map.

Here's a video demonstration.

How can I fix this?

Update 1

Here is the github page to my repository.

Update 2 Mark has been able to come up with a temporary solution so far by setting the hitAreaExpansion to a large number like so:

self.hitAreaExpansion = 10000000.0f;


This will cause touches to respond again all over! However, if there is a solution that would not require me to set the property with an absolute number then that would be great!

It appears that setting self.hitAreaExpansion might be the answer, however it only needs to be set to the difference between the edge of the view field and the edge of the map, for the largest difference on either axis.

In this case the map is square, 1600 x 1600, and the view port is 320 x 480, so the greatest distance is 1600 - 320 = 1280.

self.hitAreaExpansion = 1280;


However as this is dependent on the screen size we should probably do something like :

CGSize winSize = [CCDirector sharedDirector].viewSize;
int widthExpansion = (tileMap.tileSize.width * tileMap.mapSize.width) - winSize.width;
int heightExpansion = (tileMap.tileSize.height * tileMap.mapSize.height)  - winSize.height;
self.hitAreaExpansion = (widthExpansion < heightExpansion)
? heightExpansion
: widthExpansion


Although setting a massive expansion also works just fine ;).

You may have already noted this but the root cause of the issue comes from CCNode.m, in hitTestWithWorldPos which uses hitAreaExpansion to expand the view port, which by default returns false for anything outside the physical limitations :

- (BOOL)hitTestWithWorldPos:(CGPoint)pos
{
pos = [self convertToNodeSpace:pos];
CGPoint offset = ccp(-_hitAreaExpansion, -_hitAreaExpansion);
CGSize size = CGSizeMake(self.contentSizeInPoints.width - offset.x, self.contentSizeInPoints.height - offset.y);
if ((pos.y < offset.y) || (pos.y > size.height) || (pos.x < offset.x) || (pos.x > size.width)) return(NO);

return(YES);
}


Perhaps it would make more sense if self.position had a setter which does the math automatically assuming self is able to access the dimension of the background layer and the dimensions of the view port.

First: are you actually zooming? this looks like an offset not a zoom. A zoom changes the scale whereas an offset just changes the origin (or center if you prefer).

Second: in either case you need to adjust the touch coordinates by the same adjustment as the scene was adjusted in order to map the coordinates. (Either just a pure x/y offset or an x/y offset and a scale if you are zooming as well as changing the center).

• Hey Dad, I sure am glad to see you! Sorry yes, it is an offset so that the view adjusts appropriately. That is my question, how to be able to get touches to respond since when I offset the view, the touchBegan method ceases being called – Pavan Jan 29 '14 at 19:49
• I have posted a video demonstration, might help you with something. – Pavan Jan 29 '14 at 20:23
• Hey so the weekend is coming up now, I thought I'd add a repository for everyone so that rather than following the tutorial you can just pick up from the repository and test out the problem right away! – Pavan Jan 30 '14 at 19:20

Just worked through the tutorial by myself and came up with another solution (inspired by http://forum.cocos2d-swift.org/t/cocos2d-v3-replacement-for-cclayer/11705/3):

First I am adding all my content to a new node named ContentNode. After that, instead of moving the whole scene (which is not the right way in cocos2d 3.X because scenes are not equal to layers), I am only manipulating (i.e. translating) my ContentNode in case of touch events.

I've created a GitHub-Repository (https://github.com/danurna/TileGame) where you can clone the working tutorial code using cocos2d 3.X. Some minor changes where necessary but basically it is still the steps shown in Ray Wenderlich's tutorial.