CCTMXTiledMap touch detection is a bit off - objective-c

I used the tile tutorial from www.raywenderlich.com. I set up a tile map in a landscape view and used the code from the tutorial to detect touch, like this:
CGPoint touchLocation = [touch locationInView: [touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
And use this bit to figure which tile it is on:
CGPoint touchTilePos = [self tileCoordForPosition:touchLocation];
int tileIndex = [self tileIndexForTileCoord:(touchTilePos)];
CCSprite *touchTile = [self.background tileAt:touchTilePos];
self.player.position = [touchTile.parent convertToWorldSpace:touchTile.position];
Problem is that it's a bit off. Touch close to the left side is fairly close, but touch close to the right side is detected way off to the left. Seems like some sort of scaling issue, but the only scaling in my code is the player sprite.
Any ideas? Suggestions?
Any halp is greatly appreciated!
EDIT: here is the two methods referenced in the code:
- (CGPoint) tileCoordForPosition:(CGPoint)position
{
int x = position.x / _tileMap.tileSize.width;
int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - position.y) / _tileMap.tileSize.height;
return ccp(x, y);
}
- (int) tileIndexForTileCoord:(CGPoint)aTileCoord
{
return aTileCoord.y*(self.tileMap.mapSize.width) + aTileCoord.x;
}
UPDATE:
I simplified the code:
CGPoint touchLocation = [touch locationInView: [touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self.background convertToNodeSpace:touchLocation];
int x = touchLocation.x / _tileMap.tileSize.width;
int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - touchLocation.y) / _tileMap.tileSize.height;
CGPoint touchTilePos = ccp(x,y);
Also, i noticed that right off the start, touchLocation is off to the left, i don't think this code works correctly for my case (iPad/landscape):
CGPoint touchLocation = [touch locationInView: [touch view]];

I made a fatal mistake! I am using HEX tiles, and they overlap a bit (about 1/4). I have to account for that! And that's why the effect gets worse as i tap further to the right,
Here is the code to fix it:
int x = (touchLocation.x - tileSize.width/8) / (_tileMap.tileSize.width * (3.0/4.0)) ;
int y = 0;
if(x % 2 == 0)
{
// even
y = ((_tileMap.mapSize.height * tileSize.height) - touchLocation.y) / tileSize.height;
}
else
{
// odd
y = ((_tileMap.mapSize.height * tileSize.height) - (touchLocation.y + tileSize.height/2)) / tileSize.height;
}
CGPoint touchTilePos = ccp(x,y);
Cheers!

The tutorial code looks like it should work fine. I use code that's mathematically the same with consistent/correct results.
My only suggestion is to make doubly sure that the self object in this line:
touchLocation = [self convertToNodeSpace:touchLocation]; is the actual, immediate parent node of your TMXMap.
In my code, the CCLayer is not the immediate parent, so I end up explicitly tagging my map's parent node so I can grab it like this:
CCNode *parentNode = [self getChildByTag:kTagParentNode];
CGPoint worldPt = [parentNode convertToNodeSpace:touchLocation];

Related

Touch node selection doesn't work cocos2d objective c

i'm working on a node selection procedure.
i have an array of CCDrawNode objects and i need to do something with the selected node
I do something like this :
CGPoint TOUCHLOC = [touch locationInView: [touch view]];
for (int j=0; j<=[theArray count]-1; j++)
{
node = [theArray objectAtIndex:j];
if (CGRectContainsPoint(node.boundingBox, TOUCHLOC))
{
return node;
}
}
Also tried something like this:
float left = node.boundingBox.origin.x - node.boundingBox.size.width;
float right = node.boundingBox.origin.x + node.boundingBox.size.width;
float top = node.boundingBox.origin.y + node.boundingBox.size.height;
float bottom = node.boundingBox.origin.y - node.boundingBox.size.height;
CGRect r = CGRectMake(left, bottom, right - left, top - bottom);
OR
CGRect r = CGRectMake(left, bottom, top - bottom , right - left );
if (CGRectContainsPoint(r, TOUCHLOC))
{
return node;
}
This procedure was working perfect, but i updated the framework and the method
[touch locationInWorld]; is deprecated
Now is
[touch locationInView: [touch view]];
This does't work correctly sometimes selects an incorrect node or none.
Does someone knows the correct way to select the node
Thanks in advance!

Check when SKShapeNode is clicked

So I generated an SKShapeNode, and need to know when that node is clicked. I do so by calling:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:positionInScene];
if ([node.name isEqualToString:TARGET_NAME]) {
// do whatever
}
}
}
So the result I'm getting is pretty weird. Clicking the dot itself does in fact work. However, pressing anywhere on the screen that is southwest of the SKShapeNode's position will also render the above code as true.
With the SKShapeNode represented by the red dot, any UITouch in the shaded region would render my code above as true.
Here is how I am building the SKShapeNode. It may also be important to note that my application runs in landscape mode.
#define RANDOM_NUMBER(min, max) (arc4random() % (max - min) + min)
- (SKShapeNode *)makeNodeWithName:(NSString *)name color:(UIColor *)color
{
SKShapeNode *circle = [SKShapeNode new];
int maxXCoord = self.frame.size.width;
int maxYCoord = self.frame.size.height;
CGFloat x = RANDOM_NUMBER((int)TARGET_RADIUS, (int)(maxXCoord - TARGET_RADIUS));
CGFloat y = RANDOM_NUMBER((int)TARGET_RADIUS, (int)(maxYCoord - TARGET_RADIUS - 15));
circle.fillColor = color;
circle.strokeColor = color;
circle.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(x, y, TARGET_RADIUS, TARGET_RADIUS)].CGPath;
circle.name = name;
return circle;
}
Thanks for any help!
This happens because the circle node's position is at origin, and it draws the path in a rect starting at (x,y). So the node's frame is stretched to encompass everything between (0,0) to (x+TARGET_RADIUS, y+TARGET_RADIUS).
You can check this out for yourself, by visualizing the circle's frame:
SKSpriteNode *debugFrame = [SKSpriteNode spriteNodeWithColor:[NSColor yellowColor] size:circle.frame.size];
debugFrame.anchorPoint = CGPointMake(0, 0);
debugFrame.position = circle.frame.origin;
debugFrame.alpha = 0.5f;
[self addChild:test];
This reveals the actual clickable region (on OSX):
To fix your issue, try this:
circle.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-TARGET_RADIUS/2.0f, -TARGET_RADIUS/2.0f, TARGET_RADIUS, TARGET_RADIUS)].CGPath;
and add
circle.position = CGPointMake(x, y);

How to apply impulse on chipmunk body on a specific direction?

i am using:
cpVect vector1=cpv(angle, 400);
[body applyForce:vector1 offset:vector1];
but this not working well as needed to put a correct angle using touch screen
below line of code used for getting angle
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for( UITouch *touch in touches )
{
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
float angleRadians = atanf((float)location.y/ (float)location.x-1);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle = -1 * angleDegrees;
angle = angleDegrees + 30;
}
}
Thanks to you all for your precious time to give on that.
Finally i found the way to achieve impulse on a chipmunk body on a specific direction.
Now i am using below code.
**angle = angle-90; //angle from cctouchesMoved function
float magnitude=shootPower; // magnitude range between 200 to 1000
cpVect impulse = cpv((cos(angle) * magnitude) , -(sin(angle) * magnitude));
[body applyImpulse:impulse offset:cpvzero];**
// applyImpulse gives the right way for putting impulse as needed using above sin and cos theta values.

Need CCRenderTexture to render faster ios

I'm making a drawing app, and I'm having the users draw with CCRenderTexture. It basically keeps rendering a picture of a black circle to simulate drawing. When I move my finger slowly, it works really well since the circles come together to form a line. However, when I move my finger quickly, it ends up just being a bunch of circles that aren't connected (http://postimage.org/image/wvj3w632n/). My question is how I get the render texture to render the image faster or have it fill in the blanks for me.
Also, I'm not completely sold on this method, but it's what I've found while looking around. Feel free to suggest whatever you think would be better. I was originally using ccdrawline but it really killed my performance. Thanks!
The gaps between start point and the end points need to be sorted out. I am pasting code that might help you to resolve the situation you showed in the link.
in .h file
CCRenderTexture *target;
CCSprite* brush;
in the init method of .m file
target = [[CCRenderTexture renderTextureWithWidth:size.width height:size.height] retain];
[target setPosition:ccp(size.width/2, size.height/2)];
[self addChild:target z:1];
brush = [[CCSprite spriteWithFile:#"brush_i3.png"] retain];
add the touches method I am showing the touchesMoved code.
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint start = [touch locationInView: [touch view]];
start = [[CCDirector sharedDirector] convertToGL: start];
CGPoint end = [touch previousLocationInView:[touch view]];
end = [[CCDirector sharedDirector] convertToGL:end];
printf("\n x= %f \t y= %f",start.x,start.y);
float distance = ccpDistance(start, end);
if (distance > 1)
{
int d = (int)distance;
for (int i = 0; i < d; i++)
{
float difx = end.x - start.x;
float dify = end.y - start.y;
float delta = (float)i / distance;
[brush setPosition:ccp(start.x + (difx * delta), start.y + (dify * delta))];
[target begin];
[brush setColor:ccc3(0, 255, 0)];
brush.opacity = 5;
[brush visit];
[target end];
}
}
}
Hopefully it would work for you.
Its not that CCRenderTexture draws too slow its that the event only fires so often. You do need to fill in the gaps between the touch points you receive.
There is a great tutorial here about it which you may have already seen, http://www.learn-cocos2d.com/2011/12/how-to-use-ccrendertexture-motion-blur-screenshots-drawing-sketches/#sketching

Trying to rotate my sprite smoothly at a constant speed. But it decreases the speed

(I'm using cocos2d for iphone)
First I'll explain you my code: (the important parts)
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
float dstAngle = CC_RADIANS_TO_DEGREES(-ccpToAngle(ccpSub(location, plane.position)));
plane.dstAngle = dstAngle;
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
float dstAngle = CC_RADIANS_TO_DEGREES(-ccpToAngle(ccpSub(location, plane.position)));
plane.dstAngle = dstAngle; }
If I touch the screen I calculate the angle between my plane and the touch location.
Now I set the "destination" angle of the plane.
My "plane" is a subclass.
In my header file I have these float values:
//this is in my plane subclass
float diffAngle_;
float startAngle_;
float dstAngle_;
float smoothRotationSpeed_;
EDIT: In my init of the layer I set the "smoothRotationSpeed" to a value. The "rotationTick" method IS scheduled.
I have a tick method to rotate the plane (also in the plane subclass)
-(void)rotateTick:(ccTime)dt {
startAngle_ = [self rotation];
if (startAngle_ > 0)
startAngle_ = fmodf(startAngle_, 360.0f);
else
startAngle_ = fmodf(startAngle_, -360.0f);
diffAngle_ =dstAngle_ - startAngle_;
if (diffAngle_ > 180)
diffAngle_ -= 360;
if (diffAngle_ < -180)
diffAngle_ += 360;
[self setRotation: startAngle_ + diffAngle_ * dt]; //smooth rotate
}
-(void)setSmoothRotationSpeed:(float)smoothRotationSpeed {
[self schedule:#selector(rotateTick:)]; //this will be called if the "smoothRotationSpeed" value is changed
}
The problem is now if I touch a location the sprites rotates to it correctly but at first it rotates a little bit fast and than it looses it's speed..
The nearer it is to the destination angle the slower it moves...
But I want it to move at a constant speed. This "constant" speed is defined in the "smoothRotationSpeed" value but how can I implement it in my code?
If I set the interval to the "smoothRotationSpeed" value it wouldn't be smooth. It would lag.
Would anyone like to help me please?
You're incrementing the rotation of your sprite based on the difference between the target and current angles, so the closer the current angle is to the target angle, the smaller is the increment you're applying to the current rotation. But from what you're saying, that's not what you want. So you could simply do something like this:
-(void)rotateTick:(ccTime)dt {
startAngle_ = [self rotation];
if (startAngle_ > 0)
startAngle_ = fmodf(startAngle_, 360.0f);
else
startAngle_ = fmodf(startAngle_, -360.0f);
diffAngle_ =dstAngle_ - startAngle_;
if (diffAngle_ > 180)
diffAngle_ -= 360;
if (diffAngle_ < -180)
diffAngle_ += 360;
if ( fabs(diffAngle_) < smoothRotationSpeed_*dt ) // finished rotation
{
[self setRotation: dstAngle_]; // go to the end position
}
else if (diffAngle_ > 0)
{
[self setRotation: startAngle_ + smoothRotationSpeed_* dt]; //smooth rotate
}
else
{
[self setRotation: startAngle_ - smoothRotationSpeed_* dt]; //smooth rotate
}
}
-(void)setSmoothRotationSpeed:(float)smoothRotationSpeed
{
smoothRotationSpeed_ = smoothRotationSpeed;
[self schedule:#selector(rotateTick:)]; //this will be called if the "smoothRotationSpeed" value is changed
}