Collision with sprite having another sprite as parent - cocos2d-iphone

Seems impossible but there has to be a solution.
I have the following classes:
#interface EnemiesEntities : CCSprite {
bool isFunctional;
CCSprite * laserBeam; // <----------- !!!!! That's where I want to check the collision.
CCSprite * leftRingEffect;
CCSprite * rightRingEffect;
}
#interface ShipEntity : CCSprite
{}
And I simply want to verify the collision between the ShipEntity and the laserBeam sprite (laserBeam is a member variable and child of EnemiesEntities class).
The method [laserBeam boundingBox] doesn't work as the boundingBox converts the coordinates relative to the parent node.
I tried thend adding to CCNode a method computing the boundingBox relative to the world but also this one did not work:
- (CGRect) worldBoundingBox
{
CGRect rect = CGRectMake(0, 0, contentSize_.width, contentSize_.height);
return CGRectApplyAffineTransform(rect, [self nodeToWorldTransform]);
}
I checked online and found only unuseful (to me) answers to the same question.
I then tried a different approach and tried to start from the boudningBox and change the position of the rectangle so obtained in respect to the parent position as following:
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
[laserBeam worldBoundingBox];
CGRect laserBoundingBox = [laserBeam boundingBox];
CGRect laserBox = CGRectMake(laserBeam.parent.position.x, laserBeam.parent.position.y, laserBoundingBox.size.width, laserBoundingBox.size.height);
CGRect hitBox = [self hitBox];
if(CGRectIntersectsRect([self boundingBox], laserBox))
{
laserBeam.showCollisionBox=TRUE;
return TRUE;
}
else {
return FALSE;
}
}
Unfortunately this does work only when the rotation of the parent sprite is set to 0.0 but when it actually changes then it doesn't work (is probably because the boundingBox is relative to the parent node and not world).
I am a bit lost and was wondering if any of you had better luck in solving this problem and which solution (code snippets please :)) you used.
EDIT in Response to #LearnCocos2D answer:
I followed the suggestion and added the following code which doesn't work properly (e.g. try with an EnemiesEntities object is rotated to -130.0f).
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CCLOG(#"rotation %f", laserBeam.rotation);
CGRect laserBoundingBox = [laserBeam boundingBox];
laserBoundingBox.origin = [self convertToWorldSpace:laserBeam.position];
CGRect shipBoundingBox = [self boundingBox]; //As we are in ShipEntity class
shipBoundingBox.origin = [self convertToWorldSpace:shipBoundingBox.origin];
//As this method is in the ShipEntity class there is no need to convert the origin to the world space. I added a breakpoint here and doing in this way the CGRect of both ShipEntity and gets misplaced.
if(CGRectIntersectsRect(shipBoundingBox, laserBoundingBox))
{
return TRUE;
}
else {
return FALSE;
}
}

The problem is in this line I think:
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
laserBeam isn't in laserBeam's space but laserBeams parent space. So the correct is:
CGPoint newPosition = [[laserBeam parent] convertToWorldSpace:laserBeam.position];
The whole code
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
CGRect laserBoundingBox = [laserBeam boundingBox];
laserBoundingBox.origin = newPosition;
CGRect hitBox = [self boundingBox];
hitbox.origin = [[self parent] convertToWorldSpace:hitbox.origin];
if(CGRectIntersectsRect(hitbox, laserBoundingBox))
{
laserBeam.showCollisionBox=TRUE;
return TRUE;
}
else {
return FALSE;
}
}

for both boundingboxes do:
bbox.origin = [self convertToWorldSpace:bbox.origin];
now you can compare the rects...
Update to update:
The boundingBox is an axis-aligned bounding box.
If the entity is rotated, the bounding box size increases to encompass all of the sprite's corners. Therefore collision (intersection) may be detected even relatively far away from the node when testing axis-aligned bounding boxes.
In ccConfig.h there's an option you can turn on to draw sprite bounding boxes, you should set this to 1 to see the bounding boxes: #define CC_SPRITE_DEBUG_DRAW 1
For oriented rectangles you need a different data structure and different intersection test, see for example this tutorial.

Related

Fixed sprite on the tile map cocos2d?

I can't figure out how to handle with tile map layer and other node. I want to make another one layer(CCNode) for such things as menu button, score, joystick that must always stay fixed(not scrolled), when the whole map is scrolled.
self.theMap=[CCTiledMap tiledMapWithFile:#"map.tmx"];
self.bgLayer=[theMap layerNamed:#"bg"];
[self addChild:theMap z:-1];
CCNode *joystickNode;
joystickNode=[CCNode node];
[bgLayer.parent addChild:joystickNode z:2];
upArrowFrame=[CCSpriteFrame frameWithImageNamed:#"uparrow.png"];
upArrow=[CCButton buttonWithTitle:#"" spriteFrame:upArrowFrame];
[upArrow setTarget:self selector:#selector(upArrowPressed)];
upArrow.position= ccp(190,190);
[joystickNode addChild:upArrow z:2];
Now upArrow is not visible on the screen at all. If I add to self instead of joystickNode, it will appear.
I can't even understand, what parent should new CCNode have. Can anyone explain it to me? I also tried to add new CCNode as a child of self and theMap.
EDIT: Oops, it's actually moving the camera. How to implement it in this case?
-(void)setCenterOfScreen :(CGPoint) position {
CGSize screenSize=[[CCDirector sharedDirector]viewSize];
int x=MAX(position.x, screenSize.width/2);
int y=MAX(position.y, screenSize.height/2);
x= MIN(x, theMap.mapSize.width * theMap.tileSize.width - screenSize.width/2);
y= MIN(y, theMap.mapSize.height * theMap.tileSize.height - screenSize.height/2);
CGPoint goodPoint =ccp(x,y);
CGPoint centerOfScreen = ccp(screenSize.width/2, screenSize.height/2);
CGPoint difference = ccpSub(centerOfScreen, goodPoint);
self.position=difference;
}
- (void)update:(CCTime)dt{
[self setCenterOfScreen:hero.position];
}

Texture position of a sprite shifted [cocos2d v3]

I've been struggling for days with that problem: I have CCNode >> StateComponent and in the StateComponent I have a CCSprite as an attribute and add it as a child in StateComponent. When I set the position of an StateComponent object and NOT of the sprite, the bounding box of the StateComponent object appears at the right place. The default values for a sprite position are set on (0,0). The bounding box of the sprite appears at (0,0) but the sprite texture is shifted from (0,0).
I add the StateComponent object afterwards to a CCScene.
Could maybe someone help me with advice: how can I set the sprite position so that the texture and bounding box appears at the same position as the StateComponent object? Later I'd like to detect if there is a touch on the node(sprite) and then rotate the node with the sprite.
Any help would be really appreciated!!!
#interface StateComponent : CCNode {
}
#end
#implementation StateComponent
-(instancetype) initWithGestureStatewithSprite:(CCSprite*) sprite andPosition: (CGPoint) spritePosition RelativeAngle:(float) angle {
self = [super init];
if (!self) {
return nil;
}
self.sprite = sprite;
self.relativeAngle = angle;
self.position = spritePosition;
[self addChild:sprite];
return self;
}
#end
#interface StateViewScene : CCScene {
}
#end
#implementation StateViewScene
-(id) init {
self = [super init];
if (!self) {
return nil;
}
StateComponent * body = [[StateComponent alloc] initWithGestureStatewithSprite [CCSprite spriteWithImageNamed:#"body.png"] andPosition: CGPointMake(512,384) RelativeAngle:0];
[self addChild:body];
return self;
}
Have you tried to set the content Size of the Node to the Sprite content Size?
-(instancetype) initWithGestureStatewithSprite:(CCSprite*) sprite andPosition: (CGPoint) spritePosition RelativeAngle:(float) angle {
...
self.contentSize = sprite.contentSize;
...
I managed to solve the problem by converting to node space the StateComponent position couple of times as I actually have a tree like structure of StateComponents with sprites.
Thanks for the help! :)
---Edit---
This article helped me and might be interesting: http://www.koboldtouch.com/display/IDCAR/Converting+Between+Coordinate+Spaces

cocos2d flappy bird demo

I am working with the flappy bird demo trying different things just to get to "know each other".
Going through the demo, I've managed to change the direction of the game to vertical scroll moving upwards.
Having reversed the CGFloat to negative values makes my obstacles move upward but once they are out of bounds they do not re-spawn.
If I change the values for a downward scroll they re-spawn as per the update method.
Can someone explain to me what I'm doing wrong with the x to y conversion? Why is the bottom recognized and the top of my screen not?
Thanks in advance
#import "MainScene.h"
static const CGFloat scrollSpeed = -280.f; //upwards
static const CGFloat firstObstaclePosition = -568.f;
static const CGFloat distanceBetweenObstacles = 80;
#implementation MainScene {
CCSprite *_hero;
CCPhysicsNode *_physicsNode;
NSMutableArray *_obstacles;
}
- (void)spawnNewObstacle {
CCNode *previousObstacle = [_obstacles lastObject];
CGFloat previousObstacleYPosition = previousObstacle.position.y;
if (!previousObstacle) {
// this is the first obstacle
previousObstacleYPosition = firstObstaclePosition;
}
CCNode *obstacle = [CCBReader load:#"Obstacle"];
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles);
[_physicsNode addChild:obstacle];
[_obstacles addObject:obstacle];
}
- (void)update:(CCTime)delta {
_hero.position = ccp(_hero.position.x, _hero.position.y + delta * scrollSpeed);//move on Y axis
_physicsNode.position = ccp(_physicsNode.position.x, _physicsNode.position.y - (scrollSpeed *delta));//scroll in Y axis
//spawn more
NSMutableArray *offScreenObstacles = nil;
for (CCNode *obstacle in _obstacles) {
CGPoint obstacleWorldPosition = [_physicsNode convertToWorldSpace:obstacle.position];
CGPoint obstacleScreenPosition = [self convertToNodeSpace:obstacleWorldPosition];
if (obstacleScreenPosition.y < -obstacle.contentSize.height) {
if (!offScreenObstacles) {
offScreenObstacles = [NSMutableArray array];
}
[offScreenObstacles addObject:obstacle];
}
}
for (CCNode *obstacleToRemove in offScreenObstacles) {
[obstacleToRemove removeFromParent];
[_obstacles removeObject:obstacleToRemove];
// for each removed obstacle, add a new one
[self spawnNewObstacle];
}
}
- (void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
_obstacles = [NSMutableArray array];
[self spawnNewObstacle];
[self spawnNewObstacle];
[self spawnNewObstacle];
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
}
#end
I've attached the _physicsNode screenshot from SB.
It looks like your obstacles will be spawning fine if they are a short, constant height, and the distance between them value is large enough. It may be better to incorporate the height of the obstacles to get a more meaningful value of the distance variable. Just a thought.
The line -
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles);
Could be -
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles + previousObstacle.contentSize.height);
As for the problem of the vertical scrolling working downwards and not upwards I believe it is due to this line:
if (obstacleScreenPosition.y < -obstacle.contentSize.height) {
Since this line is responsible for determining when an obstacle is off the screen it has an effect on the spawning of the next obstacle. It makes sense why this line works for downwards scrolling but needs to be changed for upwards scrolling.
Try:
if (obstacleScreenPosition.y > (_physicsNode.contentSize.height + obstacle.contentSize.height)) {
You may or may not need the size of the obstacle depending on where it is anchored.
I hope this works, Good luck.

How to build Physics Body using Different sprites in spritebuilder

I just started using SpriteBuilder with cocos2d - v3.
It looks promising and a good tool to have.
But my issue is that I want to create a man like character with all body parts as different sprites and put them into a Physics body using joints. I am struggling to find any example of it using spritebuilder or chipmunk.
Will greatly appreciate any help here. Thx in advance.
EDIT : Here's code for body. I used peevedpenquin example from link
Here is my code for character body. I just added a block as body part to penguin's tail.
#import "PenguinWithBlock.h"
#implementation PenguinWithBlock{
CCNode *_pBlock;
CCNode *_penguinB;
CCPhysicsJoint *_penguinJoint;
CCPhysicsNode *_penguinNode;
}
- (void)didLoadFromCCB {
_penguinNode.debugDraw = TRUE;
[_pBlock.physicsBody setCollisionGroup:_penguinB];
[_penguinB.physicsBody setCollisionGroup:_penguinB];
// create a joint to connect the catapult arm with the catapult
_penguinJoint = [CCPhysicsJoint connectedPivotJointWithBodyA:_penguinB.physicsBody bodyB:_pBlock.physicsBody anchorA:_penguinB.anchorPointInPoints];
}
This is Gameplay.m file
#implementation Gameplay {
CCPhysicsNode *_physicsNode;
CCNode *_catapultArm;
CCNode *_catapult;
CCNode *_levelNode;
CCNode *_contentNode;
CCPhysicsJoint *_catapultJoint;
CCNode *_pullbackNode;
CCPhysicsJoint *_pullbackJoint;
CCNode *_mouseJointNode;
CCPhysicsJoint *_mouseJoint;
PenguinWithBlock *_currentPenguin;
CCPhysicsJoint *_penguinCatapultJoint;
CCAction *_followPenguin;
}
// is called when CCB file has completed loading
- (void)didLoadFromCCB {
// tell this scene to accept touches
self.userInteractionEnabled = TRUE;
_physicsNode.collisionDelegate = self;
CCScene *level = [CCBReader loadAsScene:#"Levels/Level1"];
[_levelNode addChild:level];
// visualize physics bodies & joints
//_physicsNode.debugDraw = TRUE;
// catapultArm and catapult shall not collide
[_catapultArm.physicsBody setCollisionGroup:_catapult];
[_catapult.physicsBody setCollisionGroup:_catapult];
// create a joint to connect the catapult arm with the catapult
_catapultJoint = [CCPhysicsJoint connectedPivotJointWithBodyA:_catapultArm.physicsBody bodyB:_catapult.physicsBody anchorA:_catapultArm.anchorPointInPoints];
// nothing shall collide with our invisible nodes
_pullbackNode.physicsBody.collisionMask = #[];
// create a spring joint for bringing arm in upright position and snapping back when player shoots
_pullbackJoint = [CCPhysicsJoint connectedSpringJointWithBodyA:_pullbackNode.physicsBody bodyB:_catapultArm.physicsBody anchorA:ccp(0, 0) anchorB:ccp(34, 138) restLength:60.f stiffness:500.f damping:40.f];
_mouseJointNode.physicsBody.collisionMask = #[];
}
- (void)update:(CCTime)delta
{
if (_currentPenguin.launched) {
// if speed is below minimum speed, assume this attempt is over
if (ccpLength(_currentPenguin.physicsBody.velocity) < MIN_SPEED){
[self nextAttempt];
return;
}
int xMin = _currentPenguin.boundingBox.origin.x;
if (xMin < self.boundingBox.origin.x) {
[self nextAttempt];
return;
}
int xMax = xMin + _currentPenguin.boundingBox.size.width;
if (xMax > (self.boundingBox.origin.x + self.boundingBox.size.width)) {
[self nextAttempt];
return;
}
}
}
- (void)nextAttempt {
_currentPenguin = nil;
[_contentNode stopAction:_followPenguin];
CCActionMoveTo *actionMoveTo = [CCActionMoveTo actionWithDuration:1.f position:ccp(0, 0)];
[_contentNode runAction:actionMoveTo];
}
// called on every touch in this scene
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLocation = [touch locationInNode:_contentNode];
// start catapult dragging when a touch inside of the catapult arm occurs
if (CGRectContainsPoint([_catapultArm boundingBox], touchLocation))
{
// move the mouseJointNode to the touch position
_mouseJointNode.position = touchLocation;
// setup a spring joint between the mouseJointNode and the catapultArm
_mouseJoint = [CCPhysicsJoint connectedSpringJointWithBodyA:_mouseJointNode.physicsBody bodyB:_catapultArm.physicsBody anchorA:ccp(0, 0) anchorB:ccp(34, 138) restLength:0.f stiffness:3000.f damping:150.f];
// create a penguin from the ccb-file
_currentPenguin = (PenguinWithBlock*)[CCBReader load:#"PenguinWithBlock"];
// initially position it on the scoop. 34,138 is the position in the node space of the _catapultArm
CGPoint penguinPosition = [_catapultArm convertToWorldSpace:ccp(34, 138)];
// transform the world position to the node space to which the penguin will be added (_physicsNode)
_currentPenguin.position = [_physicsNode convertToNodeSpace:penguinPosition];
// add it to the physics world
[_physicsNode addChild:_currentPenguin];
// we don't want the penguin to rotate in the scoop
_currentPenguin.physicsBody.allowsRotation = FALSE;
// create a joint to keep the penguin fixed to the scoop until the catapult is released
_penguinCatapultJoint = [CCPhysicsJoint connectedPivotJointWithBodyA:_currentPenguin.physicsBody bodyB:_catapultArm.physicsBody anchorA:_currentPenguin.anchorPointInPoints];
}
}
- (void)touchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
// whenever touches move, update the position of the mouseJointNode to the touch position
CGPoint touchLocation = [touch locationInNode:_contentNode];
_mouseJointNode.position = touchLocation;
}
- (void)launchPenguin {
// loads the Penguin.ccb we have set up in Spritebuilder
CCNode* penguin = [CCBReader load:#"PenguinWithBlock"];
// position the penguin at the bowl of the catapult
penguin.position = ccpAdd(_catapultArm.position, ccp(16, 50));
// add the penguin to the physicsNode of this scene (because it has physics enabled)
[_physicsNode addChild:penguin];
// manually create & apply a force to launch the penguin
CGPoint launchDirection = ccp(1, 0);
CGPoint force = ccpMult(launchDirection, 8000);
[penguin.physicsBody applyForce:force];
// ensure followed object is in visible are when starting
self.position = ccp(0, 0);
CCActionFollow *follow = [CCActionFollow actionWithTarget:penguin worldBoundary:self.boundingBox];
[_contentNode runAction:follow];
}
-(void) touchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
// when touches end, meaning the user releases their finger, release the catapult
[self releaseCatapult];
}
-(void) touchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
// when touches are cancelled, meaning the user drags their finger off the screen or onto something else, release the catapult
[self releaseCatapult];
}
- (void)releaseCatapult {
if (_mouseJoint != nil)
{
// releases the joint and lets the catapult snap back
[_mouseJoint invalidate];
_mouseJoint = nil;
// releases the joint and lets the penguin fly
[_penguinCatapultJoint invalidate];
_penguinCatapultJoint = nil;
// after snapping rotation is fine
_currentPenguin.physicsBody.allowsRotation = TRUE;
_currentPenguin.launched = TRUE;
// follow the flying penguin
_followPenguin = [CCActionFollow actionWithTarget:_currentPenguin worldBoundary:self.boundingBox];
[_contentNode runAction:_followPenguin];
}
}
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair seal:(CCNode *)nodeA wildcard:(CCNode *)nodeB
{
float energy = [pair totalKineticEnergy];
// if energy is large enough, remove the seal
if (energy > 5000.f)
{
[self sealRemoved:nodeA];
}
}
- (void)sealRemoved:(CCNode *)seal {
// load particle effect
CCParticleSystem *explosion = (CCParticleSystem *)[CCBReader load:#"SealExplosion"];
// make the particle effect clean itself up, once it is completed
explosion.autoRemoveOnFinish = TRUE;
// place the particle effect on the seals position
explosion.position = seal.position;
// add the particle effect to the same node the seal is on
[seal.parent addChild:explosion];
// finally, remove the destroyed seal
[seal removeFromParent];
}
- (void)retry {
// reload this level
[[CCDirector sharedDirector] replaceScene: [CCBReader loadAsScene:#"Gameplay"]];
}
PenguinWithBlock settings in SpriteBuilder:
You can use a "blank" CCNode with all others parts of the body being children of it, maybe even have children of another body parts. The tree of nodes will heavily depend on the body design.
Give a look at this tutorial, as it teaches the basics to do so. Also, SpriteBuilder still doesn't have the feature to create the joints inside it, but you can easily do this programmatically as the tutorial does.
EDIT: This can help you

Cocos2d - Orbiting/spinning one sprite around another

I've got to sprites, essentially a nucleus (parent) and an electron (child). I'm trying to find a way to have the electron orbit the nucleus. I've found a few posts here and there on moving anchor points, but that is apparently related to the texture of the sprite, not the sprite itself.
This is my current init method for my parent sprite:
self = [super init];
if (self) {
CCSprite *orbitAnchor = [CCSprite new];
[self addChild:orbitAnchor];
orbitAnchor.position = ccp(32,32);
CCSprite *orbiter = [CCSprite spriteWithFile:#"line.png" rect:CGRectMake(0, 0, 8, 8)];
[orbitAnchor addChild:orbiter];
orbiter.position = ccp(40,40);
CCAction *orbitAction = [CCRepeatForever actionWithAction:[CCRotateTo actionWithDuration:1 angle:720]];
[orbitAnchor runAction:orbitAction];
[self initAnimations];
}
return self;
Those numbers are all totally arbitrary though - I just stuck them in there and got what looked best. I'm sure there's a more programatic way to do what I want.
Basically, I'm looking for a way to set the child's axis point at the center of the parent, and then have it rotate around that point. It seems like a relatively simple thing to do using CCRotate and such, but I think I'm missing what to search for in order to move the anchor point of the child. Any suggestions/other posts?
Thanks
You have [CCSprite new] which is an unusual use, probably not supported. Unless the orbit anchor node should display a texture, you can just use a CCNode as anchor.
Technically you're doing everything correct from what I can see. You might want to try without the rotate actions and instead change the direction manually in a scheduled update method.
CCNode *orbitAnchor = [CCNode node];
[self addChild:orbitAnchor z:0 tag:1];
orbitAnchor.position = ccp(32,32);
CCSprite *orbiter = [CCSprite spriteWithFile:#"line.png" rect:CGRectMake(0, 0, 8, 8)];
[orbitAnchor addChild:orbiter z:0 tag:2];
orbiter.position = ccp(40,40);
[self scheduleUpdate];
Update method:
-(void) update:(ccTime)delta
{
CCNode* orbitAnchor = [self getChildByTag:1];
orbitAnchor.direction += 5;
}
From the image filename it looks like you're trying to draw a line from the orbitAnchor to the orbiter. You can do that with ccDrawLine:
-(void) draw
{
CCNode* orbitAnchor = [self getChildByTag:1];
CCNode* orbiter = [self getChildByTag:2];
ccDrawLine(orbitAnchor.position, orbiter.position);
}
Anchor points. Automatically, objects are placed based on the center of an object right? with anchor points you can move that around, so if you rotate the object it will rotate around the anchorpoint. http://www.qcmat.com/understanding-anchorpoint-in-cocos2d/