I am creating a game using sprite kit but I seem to have trouble with the bodyWithTexture when using it with collisions. bodyWithRectangle and circleOfRadius work fine, but when i use bodyWithTexture it looks like the didBeginContact method is being called more than once.
here is an example of the code i'm using
-(SKNode *) createPlayer
{
level3Player = [SKNode node];
player3Sprite = [SKSpriteNode spriteNodeWithImageNamed:#"character.png"];
level3Player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player3Sprite.size.width/2];
level3Player.physicsBody.categoryBitMask = playerCategory3;
level3Player.physicsBody.contactTestBitMask = platformCategory3 | rockCategory;
[level3Player setPosition:CGPointMake(self.size.height/2, screenHeightL3 *11)];
level3Player.physicsBody.affectedByGravity = NO;
player3Sprite.physicsBody.dynamic = YES;
level3Player.zPosition = 2;
[level3Player setScale:0.6];
[level3Player addChild:player3Sprite];
return level3Player;
}
-(void) addRocksL3
{
int randomNumber = arc4random_uniform(300);
rock1 = [SKSpriteNode spriteNodeWithImageNamed:#"AsteroidFire.png"];
rock1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:rock1.size.width/2];
rock1.position = CGPointMake(self.size.width * 3, randomNumber);
rock1.physicsBody.categoryBitMask = rockCategory;
rock1.physicsBody.contactTestBitMask = playerCategory3;
rock1.physicsBody.dynamic = NO;
rock1.physicsBody.affectedByGravity = NO;
rock1.zPosition = 2;
[rock1 setScale:0.3];
[foregroundLayerL3 addChild:rock1];
[self addChild:rock1];
}
-(void) didBeginContact:(SKPhysicsContact*) contact
{
SKPhysicsBody *firstBody, *secondBody;
if(contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if((secondBody.categoryBitMask == platformCategory3) | redPlatformCategory)
{
level3Player.physicsBody.velocity = CGVectorMake(0, 100);
level3Player.physicsBody.affectedByGravity = YES;
player3Sprite.texture = [SKTexture textureWithImageNamed:#"goo5.png"];
SKAction *sound1 = [SKAction playSoundFileNamed:#"squish.wav" waitForCompletion:NO];
[self runAction:sound1];
gestureRec3.enabled = YES;
}
if(secondBody.categoryBitMask == rockCategory)
{
gestureRec3.enabled = YES;
playerL3.physicsBody.velocity = CGVectorMake(0, 200);
SKAction *playSound = [SKAction playSoundFileNamed:#"Hurt.wav" waitForCompletion:NO];
[self runAction:playSound];
hitCountL3++;
}
switch (hitCountL3)
{
case 1:
[health1Level3 removeFromParent];
[self healthNodelevel31];
break;
case 2:
[hit1L3 removeFromParent];
[self healthNodeLevel32];
break;
case 3:
[hit2L3 removeFromParent];
player3Sprite.texture = [SKTexture textureWithImageNamed:#"splat.png"];
[self gameOverSplatLevel3];
didDie3 = true;
SKAction *playSplat = [SKAction playSoundFileNamed:#"splat.wav" waitForCompletion:NO];
[self runAction:playSplat];
break;
}
when i use this code my character will sometimes take 1 hit and sometimes take all 3 hits when i collide with the rock. I could use circleOfRadius which works fine, but it's not what I am really looking for. Is there anyway i could use bodyWithTexture so my character only takes 1 hit each time?
If you are experiencing multiple collisions eg. didBeginContact is called multiple times, you have few options...
Without looking at your code, lets say you have a player and a rock. Each time when player collides with rock you want to remove the rock. So, you remove the rock from its parent. But before that, you make this change in your code (pseudo code):
if([rockNode parent]){
[rockNode removeFromParent];
}
The other way would be to subclass SKSpriteNode and make a Rock class and to make a custom boolean property which will change its value once when first collision happens. But this is just unnecessary complication:
if(rockNode.canCollide){
[rockNode removeFromParent];
rockNode.canCollide = NO;
}
I had many problems with correct amount of collisions. Sometimes it would get one, sometimes none. So I tried this and it works. The only thing to change is in didBeginContact method.
I will presume that you declared categories like this:
//define collision categories
static const uint32_t category1 = 0x1 << 0;
static const uint32_t category2 = 0x1 << 1;
static const uint32_t category3 = 0x1 << 2;
Try to replace your code in didBeginContact with this one. I remember that correct collisions finally got to work after I did this.
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKNode *newFirstBody = contact.bodyA.node;
SKNode *newSecondBody = contact.bodyB.node;
uint32_t collision = newFirstBody.physicsBody.categoryBitMask | newSecondBody.physicsBody.categoryBitMask;
if (collision == (category1 | category2))
{
NSLog(#"hit");
}
}
Hope it helps
Related
I'm trying to move both a rocket and word attached on the sprite to move up at the same time. However, I've set it such that only the words fly up and not the rocket. I was thinking of renaming rocket (used for words) to something else, but I feel like that would be a terribly inefficient way. Here's what I have so far
- (id)init
{
// Apple recommend assigning self with supers return value
self = [super init];
if (!self) return(nil);
// Enable touch handling on scene node
self.userInteractionEnabled = YES;
//Add a sprite
rocket[0] = [CCSprite spriteWithImageNamed:#"rocket_base_blue.png"];
rocket[0].scale = (0.15f);
rocket[0].positionType = CCPositionTypeNormalized;
rocket[0].position = ccp(0.5f,0.23f);
rocket[1] = [CCSprite spriteWithImageNamed:#"rocket_base_red.png"];
rocket[1].scale = (0.15f);
rocket[1].positionType = CCPositionTypeNormalized;
rocket[1].position = ccp(0.7f,0.23f);
rocket[2] = [CCSprite spriteWithImageNamed:#"rocket_base_green.png"];
rocket[2].scale = (0.15f);
rocket[2].positionType = CCPositionTypeNormalized;
rocket[2].position = ccp(0.9f,0.23f);
[self addChild:rocket[0]];
[self addChild:rocket[1]];
[self addChild:rocket[2]];
[self setupWordRockets:2];
[self intro];
// done
return self;
}
// -----------------------------------------------------------------------
#pragma mark - Game Events
// -----------------------------------------------------------------------
- (void)setupWordRockets:(int) wordLength
{
//randomizes the words
NSInteger rando = arc4random() % 2;
if (rando == 1) {
// Add a sprite
rocket[0] = [CCSprite spriteWithImageNamed:#"b.png"];
rocket[0].scale = (0.16f);
rocket[0].positionType = CCPositionTypeNormalized;
rocket[0].position = ccp(0.5f,0.2f);
rocket[1] = [CCSprite spriteWithImageNamed:#"a.png"];
rocket[1].scale = (.16f);
rocket[1].positionType = CCPositionTypeNormalized;
rocket[1].position = ccp(0.7f,0.2f);
rocket[2] = [CCSprite spriteWithImageNamed:#"g.png"];
rocket[2].scale = (.16f);
rocket[2].positionType = CCPositionTypeNormalized;
rocket[2].position = ccp(0.9f,0.2f);
} else {
rocket[0] = [CCSprite spriteWithImageNamed:#"g.png"];
rocket[0].scale = (0.16f);
rocket[0].positionType = CCPositionTypeNormalized;
rocket[0].position = ccp(0.5f,0.2f);
rocket[1] = [CCSprite spriteWithImageNamed:#"a.png"];
rocket[1].scale = (.16f);
rocket[1].positionType = CCPositionTypeNormalized;
rocket[1].position = ccp(0.7f,0.2f);
rocket[2] = [CCSprite spriteWithImageNamed:#"p.png"];
rocket[2].scale = (.16f);
rocket[2].positionType = CCPositionTypeNormalized;
rocket[2].position = ccp(0.9f,0.2f);
}
[self addChild:rocket[0]];
[self addChild:rocket[1]];
[self addChild:rocket[2]];
}
- (void)launchRocket
{
CCActionMoveTo *actionMove = [CCActionMoveTo actionWithDuration:1.0f position:ccp(0.5f, 0.9f)];
[rocket[0] runAction:actionMove];
CCActionMoveTo *actionMove2 = [CCActionMoveTo actionWithDuration:1.0f position:ccp(0.5f, 0.9f)];
[rocket[1] runAction:actionMove2];
CCActionMoveTo *actionMove3 = [CCActionMoveTo actionWithDuration:1.0f position:ccp(0.5f, 0.9f)];
[rocket[2] runAction:actionMove3];
[self scheduleOnce:#selector(explode:) delay:1];
}
I'm also wondering if there's also a more efficient way in randomizing the words because I feel like this is the brute force method, or at least a more tedious way of doing this. Any feedback would be greatly appreciated. Thanks!
Thats because when setupWordRockets function works, you store words in rocket array and when you call launchRocket, it moves the words.
You should separate those and use 2 arrays (one for words one for rocket sprites), so you can give proper actions to rockets as well.
I have some code similar to below:
GameScene.m
CCButton *yesButton = [CCButton buttonWithTitle:"#YES"];
[yesButton setTarget: self selector#selector(yesButtonTapped:)];
[self addChild: yesButton];
-(void)yesButtonTapped: (id)sender
{
if(match == 1) {
[_hud updateBallNumber: ball];
}
}
HUDLayer.m
-(void)updateBallNumber: (Ball*)ball
{
int number = [ball getNumber];
id fadeIn = [CCActionFadeIn actionWithDuration:1];
id fadeOut = [CCActionFadeOut actionWithDuration:1];
id delay = [CCActionDelay actionWithDuration: randomDelay];
id change = [CCActionCallBlock actionWithBlock:^{
_ballLabel.string = [NSString stringWithFormat #"%i",number}];
id sequence = [CCActionSequence actions: fadeOut,delay,change,fadeIn,nil];
[ballLabel runAction:sequence];
}
It works as I would expect it to in that when the yesButton is pressed, the current number displayed in ball label fades out waits a random amount of time and then fades in a new number.
My question is how can I stop the yesButton being pressed again or ignore yesbutton presses until the sequence is complete and the new number is displayed.
I have tried using yesButton.enable, setting it at the beginning of -(void)yesButton to NO and then at the end to YES but this does not wait for the sequence to complete.
you could do that with blocks. Change updateBallNumber as
somewhere :
typedef void(^VoidBlock)();
in HUDLayer.m
-(void)updateBallNumber: (Ball*)ball withCompletion:(VoidBlock) completionBlock
{
int number = [ball getNumber];
VoidBlock atEnd = [completionBlock copy]; // MRC, not certain how this pans out in ARC
id fadeIn = [CCActionFadeIn actionWithDuration:1];
id fadeOut = [CCActionFadeOut actionWithDuration:1];
id delay = [CCActionDelay actionWithDuration: randomDelay];
id change = [CCActionCallBlock actionWithBlock:^{
_ballLabel.string = [NSString stringWithFormat #"%i",number}];
id complete = [CCActionCallBlock actionWithBlock:^{
atEnd();
[atEnd release]; // MRC
}];
id sequence = [CCActionSequence actions: fadeOut,delay,change,fadeIn,complete,nil];
[ballLabel runAction:sequence];
}
in GameScene
yesButton.enabled = NO;
[_hud updateBallNumber:ball withCompletion:^{
yesButton.enabled = YES;
}];
I am having some trouble getting the userData values from my object. I know the userData is being set for the objects because else where in my code I am able to access it without any problems.
I created a SKNode *column3 to hold all the buttons that are in that column. which was added to scene like so:
column3 = [SKNode node];
[self addChild:column3];
column3.name = #"column3";
when I create the buttons I assign them to the appropriate column like so:
[column3 addChild:newButton];
Later in my code I need to loop through the column3 node group and get the #"buttonRow" userData from each object in that group. For some reason it only gives me "NULL" for the values. The NSLog's are just what I was using to test, they have no real importance to me.
I need to get this userData in order to shift all my buttons down to take up any empty spaces on screen when any button is deleted/removed from that column. Game will shift buttons down and add new ones to top to fill column back up.
I tried changing column3.children to self.children and it game me all the column nodes IE/column1, column2, column3 etc.. so I am not really sure why it does not work. Been reading and trying to figure out for a while now.
for(SKNode * child in column3.children) { //loop through all children in Column3.
SKSpriteNode* sprite = (SKSpriteNode*)child;
NSString* name = sprite.name;
NSLog(#"child name %#", name);
NSString *childRowTmp = [child.userData objectForKey:#"buttonRow"];
NSLog(#"child row %#", childRowTmp);
int childRowNumber = [childRowTmp intValue];
NSLog(#"child row # %i", childRowNumber);
}
Any help or tips would be great, thank you.
UPDATE:
here is how I create the button using my button class file I created.
//Create Blue Button.
NSString *rand = [self genRandStringLength:10];
newButton = [[ButtonNode alloc] initWithButtonType:1 column:3 row:i uniqueID:rand];
newButton.name = rand;
[column3 addChild:newButton]; //add the button to correct column
[column3Array addObject:newButton];
blueTotal++;
totalButtons++;
column3Total++;
here is the custom class file where the Object is created.
-(id)initWithButtonType:(int)buttonType column:(int)buttonColumn row:(int)buttonRow uniqueID:(NSString *)uniqueID {
self = [super init];
uniqueStr = uniqueID;
rowNumber = buttonRow;
columnNumber = 3; //this is just hard coded for testing
buttonNumber = 1;
[self addButtonBlue];
}
here is the part of the class that creates and adds the button
- (void) addButtonBlue {
SKSpriteNode *button;
//button type 1
button = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:kBoxSize];
button.name = uniqueStr;
button.physicsBody.categoryBitMask = blueCategory;
button.physicsBody.contactTestBitMask = blueCategory;
button.physicsBody.collisionBitMask = blueCategory | redCategory | yellowCategory | greenCategory | orangeCategory;
NSString *tmpColumn = [NSString stringWithFormat:#"%i",columnNumber];
NSString *tmpType = [NSString stringWithFormat:#"%i",buttonNumber];
NSString *tmpRow = [NSString stringWithFormat:#"%i",rowNumber];
button.userData = [NSMutableDictionary dictionary];
[button.userData setValue:uniqueStr forKey:#"buttonID"];
[button.userData setValue:tmpType forKeyPath:#"buttonType"];
[button.userData setValue:tmpColumn forKeyPath:#"buttonColumn"];
[button.userData setValue:tmpRow forKey:#"buttonRow"];
button.position = CGPointMake(xPos , yPos );
[self addChild:button];
}
I am having the same issue, see below the code which generates a green or red sprite. Attempting to give it the colour value within the SKSpriteNode.userData. In my log output the userData is (null)!
+(SKSpriteNode *)randomSprite {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
if ((arc4random() %(2)-1) == 0){
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite.userData setValue:#"GREEN" forKey:#"COLOR"];
}else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"red.png"];
[sprite.userData setValue:#"RED" forKey:#"COLOR"];
}
NSLog(#"user data = %#",sprite.userData);
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
return sprite;
}
For a workaround I guess we could create a reference dictionary where the SKSpriteNode instances are the keys and a NSDictionary of data are the values.
//----- * UPDATE ** -------
And theres no wonder, after the userData was initialised, another sprite was being initialised in its place, leaving the userData nil!
+(SKSpriteNode *)randomSprite {
SKSpriteNode *sprite;
if ((arc4random() %(2)-1) == 0){
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
[sprite.userData setValue:#"GREEN" forKey:#"COLOR"];
}else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"red.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
[sprite.userData setValue:#"RED" forKey:#"COLOR"];
}
NSLog(#"user data = %#",sprite.userData);
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
return sprite;
}
I have a problem with my cocos2d game. I am trying to delete a projectile shot by an enemy every 5 seconds (each projectile is supposed to have a lifetime of 5 seconds), but I cannot figure out how to do it. I get the error of
Assertion failure in -[CCTimer initWithTarget:selector:interval:]
Here is my code:
-(void)projectileShooting:(ccTime)dt
{
[self schedule:#selector(projectileShooting:) interval:2.5];
projcount++;
if([proj count] <= 15 ){
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
for(CCSprite *projectile in proj){
[self schedule:#selector (deleteprojectile:projectile:) interval:5];
}
}
}
}
for(CCSprite *enem2 in enemytwo)
{
if( [proj count] <= 15){
CCSprite * projectilebull = [CCSprite spriteWithFile:#"Projectile.png"];
CGPoint MyVector = ccpSub(enem2.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enem2.contentSize.width/2+10);
MyVector = ccpMult(MyVector,-1);
projectilebull.position = ccpAdd(enem2.position, MyVector);
[self addChild:projectilebull];
[proj addObject:projectilebull];
for(CCSprite *projectile in proj){
}
}
}
}
-(void)deleteprojectile:(CCSprite *)protime:(ccTime)dt{
NSMutableArray *timepro = [[NSMutableArray alloc]init];
[timepro addObject:protime];
for(CCSprite *objecttime in timepro){
[proj removeObject:objecttime];
[self removeChild:objecttime cleanup:YES];
}
}
It's a bit of a hack but this is what I use in my program, until I find a more elegant solution. I have a method in my game layer that I call to remove a node from its parent, like so:
-(void)removeNode:(CCNode*)node {
[node removeFromParentAndCleanup:YES];
}
And when I want to schedule a node for deletion after a delay, I call it like this:
[self performSelector:#selector(removeNode:) withObject:node afterDelay:delay];
Simple, and it works.
Change argument name in your selector to protime instead of projectile. The selector must match the signature defined in you object's class definition.
Your selector was not defined properly and probably the Timer is checking if the object implements the given selector.
I did not have time to test it so thanks to #RamyAlZuhouri for confirming.
hi
im pretty new to both frameworks. but maybe someone can point me into the right direction:
basically i try to bounce a ball of a shape. (works fine)
but it would be great when the ball would rotate, too
here is my (copy & paste) code
// BallLayer.m
#import "BallLayer.h"
void updateShape(void* ptr, void* unused){
cpShape* shape = (cpShape*)ptr;
Sprite* sprite = shape->data;
if(sprite){
cpBody* body = shape->body;
[sprite setPosition:cpv(body->p.x, body->p.y)];
}
}
#implementation BallLayer
-(void)tick:(ccTime)dt{
cpSpaceStep(space, 1.0f/60.0f);
cpSpaceHashEach(space->activeShapes, &updateShape, nil);
}
-(void)setupChipmunk{
cpInitChipmunk();
space = cpSpaceNew();
space->gravity = cpv(0,-2000);
space->elasticIterations = 1;
[self schedule: #selector(tick:) interval: 1.0f/60.0f];
cpBody* ballBody = cpBodyNew(200.0, cpMomentForCircle(100.0, 10, 10, cpvzero));
ballBody->p = cpv(150, 400);
cpSpaceAddBody(space, ballBody);
cpShape* ballShape = cpCircleShapeNew(ballBody, 20.0, cpvzero);
ballShape->e = 0.8;
ballShape->u = 0.8;
ballShape->data = ballSprite;
ballShape->collision_type = 1;
cpSpaceAddShape(space, ballShape);
cpBody* floorBody = cpBodyNew(INFINITY, INFINITY);
floorBody->p = cpv(0, 0);
cpShape* floorShape = cpSegmentShapeNew(floorBody, cpv(0,0), cpv(320,160), 0);
floorShape->e = 0.5;
floorShape->u = 0.1;
floorShape->collision_type = 0;
cpSpaceAddStaticShape(space, floorShape);
floorShape = cpSegmentShapeNew(floorBody, cpv(0,200), cpv(320,0), 0);
cpSpaceAddStaticShape(space, floorShape);
}
-(id)init{
self = [super init];
if(nil != self){
ballSprite = [Sprite spriteWithFile:#"ball2.png"];
[ballSprite setPosition:CGPointMake(150, 400)];
[self add:ballSprite];
[self setupChipmunk];
}
return self;
}
#end
please help me out.
well when i decided psoting it i found the solution :)
void updateShape(void* ptr, void* unused)
{
cpShape* shape = (cpShape*)ptr;
Sprite* sprite = shape->data;
if(sprite){
cpBody* body = shape->body;
[sprite setPosition:cpv(body->p.x, body->p.y)];
[sprite setRotation: (float) CC_RADIANS_TO_DEGREES( -body->a )];
}
}