I'm building an app using spriteKit (objective-C), and I'm using intersectsNode to tell if a player-drawn line collides with letter tiles on the gameboard. I have another instance of intersectsNode in my program that is working fine, but for some reason this new scenario always returns false. I've checked the parameters, and the only thing spriteKit says is make sure they are both children of the same tree, which they definitely are (self). One is a SKSpriteNode, and the other is a SKShapeNode.
if (directionIsDecided == TRUE) {
for (letterTile in letterTileArray) {
if ([letterTile intersectsNode:selectorLine]) {
[lettersToBeCalculatedForPoints addObject:letterTile];
}
}
}
Hopefully I'm not overlooking something stupid... Any ideas what could cause this function to never return true?
*edit
Ok, I think it has something to do with how I'm drawing my line. I changed it up so it detects collision with the vowels that have been spawned on the board, each vowel being a child of letterTile, and now it tells me that ALL vowels are colliding with the selecorLine, even if it doesn't touch any of them.
This is how I created my selectorLine:
Implementation:
CGMutablePathRef pathToDraw;
SKShapeNode *selectorLine;
TouchesBegan:
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
selectorLine = [SKShapeNode node];
selectorLine.path = pathToDraw;
selectorLine.strokeColor = [SKColor greenColor];
selectorLine.lineWidth = 10;
[self addChild:selectorLine];
selectorLine.zPosition = 101;
TouchesMoved:
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
selectorLine.path = pathToDraw;
Any and all ideas are appreciated, thanks!
I have been working around this in my code by using CGRectIntersectsRect for SKShapeNode bounding rectangle intersections.
Using CGRectIntersectsRect your code would look like:
if (directionIsDecided == TRUE) {
for (letterTile in letterTileArray) {
if (CGRectIntersectsRect(letterTile.frame, selectorLine.frame)) {
[lettersToBeCalculatedForPoints addObject:letterTile];
}
}
}
intersectsNode uses the calculateAccumulatedFrame method.
It's stated a few places deep deep in the SpriteKit documentation that you should override this method with your own in many cases. After reading that I ended up doing it all the time. SKShapeNodes are a prime example of this. But if you subclass a SKNode and fill it with other content you'll probably also not want all of it to "intersect" as per usual.
So override the calculateAccumulatedFrame method for your shapenode subclass, and it should intersect nicely again.
I had the same problem as you. I had an array undeclared so adding objects to it did nothing and there were no nodes to check against. Double check that your letterTileArray is not empty.
Similar problem, in my case one of the nodes I was comparing was simple SKNode grouping together other node types. An SKNode has a position but no width nor height so it does not 'intersect' anything.
For me a combination of #JoePasq and #Theis Egeberg's answers was needed:
if node.calculateAccumulatedFrame().intersects(sprite.frame) {...
(note if both nodes are simple SKNodes then use caluclateAccuulatedFrame in both positions, in my case sprite was an SKSpriteNode so a simple .frame is fine.)
Related
i followed this wonderful guide about mario-style game:
http://www.raywenderlich.com/62053/sprite-kit-tutorial-make-platform-game-like-super-mario-brothers-part-2
however, i wanted to convert the movement controls to arrow keys, implemented by SKSpriteNodes with names that are detected by:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
// other left, up, down arrows with same code here
if ([node.name isEqualToString:#"rightArrow"]) {
self.player.moveRight = YES;
self.rightOriginalTouchLocation = location;
...
}
}
self.player.moveRight is a boolean value (much like moveForward in the guide), that tells the character at update to move.
it is terminated at:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
// other left, up, down arrows with same code here
if ([node.name isEqualToString:#"rightArrow"]) {
self.player.moveRight = NO;
}
...
}
however, i encounter the following problem - when i start the touch on the arrow, drag it outside the arrow, and then release the tap, it is not recognized as 'touch ended' for the arrow node (and it doesn't stop moving because of that).
i tried to solve it in many ways (even calculating touch move distance from original location and see if its too far, then cancel movement), but i always manage to reproduce the constant motion problem.
the issue lies with the fact that i can tap two arrows at the same time, so it is not enough to remember the last node tapped.
since i want to allow movement for different directions at the same time, i cant stop all movements in case one button is dismissed. i need to specifically know which button was released so i can stop that direction's movement only.
do you have any ideas for me? should i implement it in another way considering i want the arrow keys, or is there a method to detect which node is released even though it is not at its original location (of the tap)?
Thank you very much!
if anyone is interested about the issue - i had a problem where touching and moving from SKSpriteNode didnt call the touchesEnded for that SKSpriteNode (since the 'release' of the touch was not in the node).
i solved it by keeping the CGPoint of the touch at touchesBegan, and when touchesEnded called, i calculated distances to nearest key using simple distance function:
-(int)calculateDistanceWithPoints:(CGPoint)point1 andPoint:(CGPoint)point2 {
float d = sqrtf(pow((point1.x - point2.x), 2) + pow((point1.y - point2.y), 2));
return (int)d;
}
and then, in touchesEnded, i checked to which key the distance is minimal(i have 3 keys, so which key is most likely to be the key that was 'released') - and did the action required for that key release.
Issue: A SKShapeNode sometimes doesn't draw the entire path. If it's moving each frame some frames it doesn't draw. In my real case scenario I'm using a very complex 10+ sided shape but for this demo I'm just doing a triangle. See screenshots for example (http://imgur.com/a/50mHA).
Code: See this in action by adding this to the basic template, or any scene, and move the mouse (finger) around. Some shapes show this glitching more than others but if you move around enough you'll see it on every shape.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [[touches anyObject] locationInNode:self];
[self enumerateChildNodesWithName:#"path" usingBlock:^(SKNode *node, BOOL *stop) {
[node removeFromParent];
}];
CGMutablePathRef p = CGPathCreateMutable();
CGPathMoveToPoint(p, NULL, location.x-80, location.y-130);
CGPathAddLineToPoint(p, NULL, location.x+80, location.y);
CGPathAddLineToPoint(p, NULL, location.x-80, location.y+130);
CGPathCloseSubpath(p);
SKShapeNode *shape = [SKShapeNode node];
shape.name = #"path";
shape.path = p;
shape.fillColor = [UIColor redColor];
[self addChild:shape];
}
Any idea what's going on?
Edit: I wanted to add that the overall effect I'm trying to accomplish is dynamic lighting in a scene with physics bodies and a player (light source). I have used the information from http://ncase.me/sight-and-light/ to create a polygon. However as the player runs through the level, 99% of the time it works fine, except for that 1% where the shape glitches and doesn't draw completely. Which is what I'm showing in the example here.
OK, what you're doing here is very inefficient which is possibly why you're getting this effect.
What you should do is create a single shape node and store it in a property...
#property (nonatomic, strong) SKShapeNode *theShapeNode;
and when the scene loads you can create it once...
// in some method that runs once when the scene loads...
// path is relative to the shape node not the the scene it is in.
// by MOVING the shapeNode the path moves too.
CGMutablePathRef p = CGPathCreateMutable();
CGPathMoveToPoint(p, NULL, 0, -260);
CGPathAddLineToPoint(p, NULL, 160, 0);
CGPathAddLineToPoint(p, NULL, 0, 260);
CGPathCloseSubPath(p);
self.theShapeNode = [SKShapeNode node];
self.theShapeNode.path = p;
self.theShapeNode.fillColor = [UIColor redColor];
[self addChild:theShapeNode];
Then in the touches moved method...
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [[touches anyObject] locationInNode:self];
self.theShapeNode.position = location;
}
This means that each time the touch moves you're just repositioning the node that already exists. You're not having to create the path each time.
I'm using Sprite Kit to create a game involving balloons. The balloon image is a long rectangle (dotted line) but I've defined a physics body around the balloon itself using a polygon (solid line). I'd like players to be able to "pop" the balloon. I'm using the following block of code to achieve this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
for (SKNode *node in nodes) {
[node removeFromParent];
SKAction *pop = [SKAction playSoundFileNamed:#"pop.wav" waitForCompletion:NO];
[self runAction:pop];
}
}
Unfortunately, when there are several balloons in the bunch, this method can result in a single tap popping multiple balloons. This is because the sprite images overlap (even though the physics bodies do not). There is also the undesired effect that a tap on the string will have the same effect as a tap on the balloon.
I have access to the coordinates of the touch point, but is it even possible to detect whether a point lies within the area defined by the physics body (as opposed to the node)?
Use the physics world's bodyAtPoint: method:
SKPhysicsBody* body = [self.scene.physicsWorld bodyAtPoint:location];
bodyAtPoint is buggy but you have a working alternative.
You can create rays using baloon polygon path.
[self.physicsWorld enumerateBodiesAlongRayStart:rayStart end:rayEnd
usingBlock:^(SKPhysicsBody *body, CGPoint point,CGVector normal, BOOL *stop) {
if(body.categoryBitMask & baloonCategory){
SKPhysicsBody *contactBody=body;
CGPoint contactPoint=point;
*stop=YES;
}
}];
I looked but wasn't able to find a way of checking whether a touch is made within the physicsBody of a sprite. I would suggest separating the string and the ballon sprites.
You can try any the following:
Using separate sprite nodes for the balloon and the string and connecting them using a SKPhysicsJoint.
Subclass SKSpriteNode and add two sprites, i.e. the balloon and the string as children of the Sprite. Then u can check whether a touch made on the sprite is on the ballon or not.
Additionally for the multiple ballon pop problem, you can modify your code like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
//Instead of popping all nodes within the touch, pop only the first object.
if ([nodes count] > 0)
{
SKNode *node = [nodes firstObject];
[node removeFromParent];
SKAction *pop = [SKAction playSoundFileNamed:#"pop.wav" waitForCompletion:NO];
[self runAction:pop];
}
}
i am creating a project in which i have random Box2d bodies. Now i am drawing a line on basis of TouchesMoved by user in the DRAW method. i need to use the RayCasting method of Box2d to check for intersection between that line and the Box2D bodies.
i am using the following code for it in my Draw method
for(int i = 0; i < [pointTouches count]; i+=2)
{
CGPoint startPoint = CGPointFromString([pointTouches objectAtIndex:i]);
CGPoint endPoint = CGPointFromString([pointTouches objectAtIndex:i+1]);
ccDrawLine(startPoint, endPoint);
b2Vec2 start=[self toMeters:startPoint];
b2Vec2 end=[self toMeters:endPoint];
[self checkIntersectionbtw:start:end];
}
-(void)checkIntersectionbtw:(b2Vec2)point1:(b2Vec2)point2
{
RaysCastCallback callback;
world->RayCast(&callback, point1,point2);
if (callback.m_fixture)
{
NSLog(#"intersected");
checkPoint = true;
}
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *myTouch = [touches anyObject];
CGPoint currentTouchArea = [myTouch locationInView:[myTouch view]];
CGPoint lastTouchArea = [myTouch previousLocationInView:[myTouch view]];
currentTouchArea = [[CCDirector sharedDirector] convertToGL:currentTouchArea];
lastTouchArea = [[CCDirector sharedDirector] convertToGL:lastTouchArea];
[pointTouches addObject:NSStringFromCGPoint(currentTouchArea)];
[pointTouches addObject:NSStringFromCGPoint(lastTouchArea)];
}
but the callback only tells the intersection when the line drawn completely passes the bodies. when user starts from some point outside and leaves the point inside the box2d body the callback doesn't say the line intersected. what am i possibly doing wrong??
Are you drawing a line or a curve? For a line, I assume that you only use the first point and the last point detected. For a curve, you use all the points detected to form the curve that you are drawing.
If you are drawing a curve then think I understand the problem. You are calculating the intersection line using the consecutive points detected by the touch system. These consecutive points are very close to each other which makes very small ray casts, and what might be happening is that you might be casting the line with a starting and ending point inside the balls, which might issue a negative collision.
If the objective is to detect if you are touching the balls, I suggest using a sensor maintained under the touch, and then checking a collision with this code in your update method:
for (b2ContactEdge* ce = sensorbody->GetContactList(); ce; ce = ce->next)
{
b2Contact* c = ce->contact;
if(c->IsTouching())
{
const b2Body* bodyA = c->GetFixtureA()->GetBody();
const b2Body* bodyB = c->GetFixtureB()->GetBody();
const b2Body* ballBody = (bodyA == sensorbody)?bodyB:bodyA;
...
}
}
If you really want to use a raycast, then I suggest saving a few consecutive points and make a vector with them, to avoid the small ray cast.
edit: Sorry, The example I wrote is in C++, but you should be able to find an equivalent for Objective-C.
Box2D's raycasts ignore any 'back' facing edges they hit, so if the ray starts inside a fixture, that fixture will be ignored. Simplest thing to do is cast the same ray in both directions.
Basically, I've got a custom class that has a draw method that draws a line from point a to point b.
I'm subclassing CCSprite, so does the line then have a bounding box I can use to detect when someone touches the line?
As an example of what I'm trying to accomplish, I've cobbled together this code:
- (void)ccTouchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: [touch locationInView:touch.view ]];
for (Path *path in paths) {
CGRect pathRect = CGRectMake(path.position.x, path.position.y, path.contentSize.width, path.contentSize.height);
if(CGRectContainsPoint(pathRect, location)) {
CCLOG(#"Line Touched");
}
}
}
paths is a mutable array of Path objects. I've put logs after each statement in the method, and it gets through everything but the for loop. For some reason, it seems like it never gets into the loop.
The answer is yes, you can. You just have to make sure you set the rectangle the the right size, and place it at the correct origin point, which was where I was breaking it.