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.
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.
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.)
I'm trying to tap a falling object in my Cocos2d/Box2d app.
Here's my touchesBegan code:
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
for(b2Body *b = world->GetBodyList(); b; b=b->GetNext()) {
b2Vec2 bPos = b->GetPosition();
for (b2Fixture *f = b->GetFixtureList(); f; f=f->GetNext()) {
if (f->TestPoint(locationWorld)) {
NSLog(#"hit it!");
}
}
}
I have 2 objects in my world, the ground and the falling object. I have gravity really slow so touching it shoudn't be a problem. What is odd is that my positions seem off by a lot!
In the last run locationWorld (where I tapped) was:
Printing description of locationWorld:
(b2Vec2) locationWorld = {
x = 7.90625
y = 9.875
}
I went through the outer loop twice, once for each body, and got:
Printing description of bPos:
(b2Vec2) bPos = {
x = 3.03125
y = 19.6643
}
Printing description of bPos:
(b2Vec2) bPos = {
x = 8.59375
y = 17.4969
}
What is odd is that the 2nd body, the one reported at (8.59, 17.49) should be the falling object, but the Y coordinate is way off.
This is my first cocos2d/box2d app so I'm sure I'm missing something obvious. I've been googling for awhile and this seems like it should work.
Thanks for any help.
SEE MY ANSWER BELOW, MY CODE HERE IS CORRECT, I'M SETTING UP MY FIXTURES INCORRECTLY.
ok, this is sorta working now but I'm sure I'll have another question from it.
When I ran this above I had created a fairly small dynamic box so the items would pile up on the ground, overlapping each other.
dynamicBox.SetAsBox(.25f, .25f)
This was the cause. When I open this up to something much larger, say:
dynamicBox.SetAsBox((spriteSize.width/PTM_RATIO)/4, (spriteSize.height/PTM_RATIO)/4);
Then it becomes possible with the above code to touch a body. (So my question needs to be remformulated since I do want the objects piling up, overlapping each other.
I also have no idea why I needed to divide the size by 4 to get the items close to each other, that's just plain bothering me.
Don't know if I'm over-thinking this or not.. but I'm trying to be able to adjust the sprites showing when my character is moving in different directions.
For example, if the players finger is above the character I want it to go to the 'moveUp' frame. If the players' finger is below the character I want to go to the 'moveDown' frame, otherwise stay at the 'normalState' frame.
Can someone show me an example of this? Or direct me to a good general tutorial about implementing Sprite sheets/Sprites in this sort of way.
I have gone through and used sprite sheets in demo projects but am looking to release this and want to approach it the proper and most successful way.
Thanks!!
I assume you have already made your sprite sheet and/or packed it (I like TexturePacker). The code would be something like:
...init...
//Place all sprite frames from sprite sheet into cache
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"spriteSheet.png"];
CCSpriteBatchNode *gameBatchNode = [CCSpriteBatchNode batchNodeWithFile:#"spriteSheet.png"];
CCSprite *player = [CCSprite spriteWithSpriteFrameName:#"moveUp"];
[gameBatchNode addChild: player];
....
- (void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *aTouch = [touches anyObject];
CGPoint touchPosition = [player.parent convertTouchToNodeSpace:aTouch];
CGPoint touchPositionRelativeToPlayer = ccpSub(touchPosition, player.position);
if(touchPositionRelativeToPlayer.y > 0)
[player setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"moveUp"]];
else
[player setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"moveDown"]];
}
If you want other directions (W, E, NW, etc..) I would suggest you convert the touchPositionRelativeToPlayer to an angle using atan2 and determine the frame from that.
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.