COCOS2D - Removing a child if it collides with another sprite - cocos2d-iphone

I am working on a game that has the player controlling a spaceShip. I want to make it such that if the spaceShip sprite collides with spaceJunk the junk disappears.
Here is my code so far.
HelloWorldLayer.h
#import <GameKit/GameKit.h>
// HelloWorldLayer
#interface HelloWorldLayer : CCLayerColor
{
CCSprite *_starShip;
CCSprite *_Paddle1;
CCSprite *_paddle2;
}
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
#end
HelloWorldLayer.M
// Import the interfaces
#import "HelloWorldLayer.h"
// Needed to obtain the Navigation Controller
#import "AppDelegate.h"
#pragma mark - HelloWorldLayer
// HelloWorldLayer implementation
//Import SpaceThings
#import "SpaceThings.h"
#implementation HelloWorldLayer{
}
// Helper class method that creates a Scene with the HelloWorldLayer as the only child.
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(void)gameLgoic:(ccTime)dt
{
[self addSpaceThings];
}
-(void) addSpaceThings
{
// _junk = [CCSprite spriteWithFile:#"catplaceholder.png"];
SpaceThings * spaceThings = nil;
if (arc4random() % 2 == 0){
spaceThings = [[[Astroids alloc] init] autorelease];
}else{
spaceThings = [[[SpaceJunk alloc]init] autorelease];
}
//determine where to spawn the space junk along the X-axis
CGSize winSize = [CCDirector sharedDirector].winSize;
int minX = spaceThings.contentSize.width/2;
int maxX = winSize.width - spaceThings.contentSize.width/2;
int rangeX =maxX - minX;
int actualX = (arc4random() % rangeX) + minX;
//spawn the sprite slightly above the screen
spaceThings.position = ccp(actualX, winSize.height+_starShip.contentSize.height/2);
[self addChild:spaceThings];
//set the speed of junk
int minDuration = spaceThings.minMoveDirection;
int maxDuarton = spaceThings.maxMoveDirection;
int rangeDuration = maxDuarton - minDuration;
float actualDuraton = (arc4random() % rangeDuration)+ minDuration;
//move junk logic
CCMoveTo * actionMove = [CCMoveTo actionWithDuration:actualDuraton position:ccp(actualX, -spaceThings.contentSize.height/2)];
CCCallBlockN *actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node){[node removeFromParentAndCleanup:YES];}];
[spaceThings runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
if (CGRectIntersectsRect(_starShip.boundingBox, spaceThings.boundingBox)){
[self removeChild:spaceThings cleanup:YES];
}
}
// on "init" you need to initialize your instance
-(id) init
{
if (self =[super initWithColor:ccc4(255,255,255,255)]){
CGSize winSize = [CCDirector sharedDirector].winSize;
_starShip = [CCSprite spriteWithFile:#"theShip.gif"];
_starShip.position = ccp(winSize.width/2, 1.25*_starShip.contentSize.height);
//make the starship appear
[self addChild:_starShip];
//make the paddles
//bottom left one
_Paddle1 = [CCSprite spriteWithFile:#"spritePaddle.jpeg"];
int bottomOfScreenX = 0 + _Paddle1.contentSize.width/2;
int bottomOfScreenY = 0+_Paddle1.contentSize.height/2;
_Paddle1.position = ccp(bottomOfScreenX,bottomOfScreenY);
[self addChild:_Paddle1];
//bottom right one
_paddle2 = [CCSprite spriteWithFile:#"spritePaddle.jpeg"];
int bottomRightOfScreenX = winSize.width - _paddle2.contentSize.width/2;
_paddle2.position = ccp(bottomRightOfScreenX, bottomOfScreenY);
[self addChild:_paddle2];
//make thingsInSpace spawn at a set interval
int setInterval = (arc4random() % 3);
[self schedule:#selector(gameLgoic:) interval:setInterval];
//enable touch
[self setIsTouchEnabled:YES];
}
return self;
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//make the screens and create events
CGSize winSize = [CCDirector sharedDirector].winSize;
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
//check to see if the left paddle is being pressed
if (CGRectContainsPoint([_Paddle1 boundingBox], location)){
int bottomLeftOfScreenX = 0 + _Paddle1.contentSize.width/1.25;
//make an action
//the Y-coordinate of starShip must be the same since the ship must move parallel to it.
id action = [CCMoveTo actionWithDuration:2 position:ccp(bottomLeftOfScreenX,1.25*_starShip.contentSize.height) ];
//have starship call this action
[_starShip runAction:action];
[action setTag:1];
}
//check to see if the right paddle is being pressed
if (CGRectContainsPoint([_paddle2 boundingBox], location)){
int bottomRightOfScreenX = winSize.width - _paddle2.contentSize.width/1.25;
//make an action
//the Y-coordinate of starShip must be the same since the ship moves parallel to it
id action2 = [CCMoveTo actionWithDuration:2 position:ccp(bottomRightOfScreenX,1.25*_starShip.contentSize.height) ];
//starShip is now running this specified action
[_starShip runAction:action2];
[action2 setTag:2];
}
}
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//if the player stops touching the pads, all the aciton should stop.
//left button stop
[_starShip stopActionByTag:1];
//right button stop
[_starShip stopActionByTag:2];
}
#pragma mark GameKit delegate
-(void) achievementViewControllerDidFinish:(GKAchievementViewController *)viewController
{
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
[[app navController] dismissModalViewControllerAnimated:YES];
}
-(void) leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController
{
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
[[app navController] dismissModalViewControllerAnimated:YES];
}
#end
spaceThings.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface SpaceThings : CCSprite
#property (nonatomic, assign) int minMoveDirection;
#property (nonatomic, assign) int maxMoveDirection;
-(id)initWithFile:(NSString *)file minMoveDirection:(int)minMoveDirection maxMoveDirection:(int)maxMoveDirection;
#end
#interface Astroids : SpaceThings
#end
#interface SpaceJunk : SpaceThings
#end
SpaceThings.M
#import "SpaceThings.h"
#implementation SpaceThings
-(id)initWithFile:(NSString *)file minMoveDirection:(int)minMoveDirection maxMoveDirection:(int)maxMoveDirection
{
if ((self =[super initWithFile:file])){
self.minMoveDirection = minMoveDirection;
self.maxMoveDirection = maxMoveDirection;
}
return self;
}
#end
#implementation Astroids
-(id)init
{
if (self = [super initWithFile:#"astroid.jpeg" minMoveDirection:2 maxMoveDirection:5]){
}
return self;
}
#end
#implementation SpaceJunk
-(id)init
{
if (self = [super initWithFile:#"blueDot.jpg" minMoveDirection:4 maxMoveDirection:7]){
}
return self;
}
#end
I'm wondering as to where to put CGRectInteresectRect part of the code. As I've currently tried placing within the SpaceJunk alloc init part and it doesn't remove the child when the ship collides with the junk.

int setInterval = (arc4random() % 3);
[self schedule:#selector(gameLgoic:) interval:setInterval];
Make setInterval value to float and give small value for schedular.
Also not do [spaceThings runAction inside loop. if required then do some condition check and run only if required.
Then your CGRectIntersectsRect will fire when intersect each other.
Happy Coding.

Related

Cocos2d Using 1 class for multiple enemies Score error

Thank you for taking the time to look at my question, As I am very new to programming any help or pointers in the right direction is much appreciated.
Overview...
I have a simple game I am trying to create and it includes a class for 'Ball' sprite that is fired from the left of the screen to the right. The speed at which this "Ball" sprite is created is random and each time the "Ball" moves past the right of the screen a point is added to the score.
Problem....
The problem I have is when 2 "Balls" fire at almost the same time (which happens some times as its random) they pass the right hand of the screen less then a second apart and when this happens it only seems to add 1 when it should be adding 2 to the score (as 2 balls has passed the right side).
Below is the code for my Ball launcher class (use this class to call the ball randomly).
If you have used or gone through 'Ray Wenderlichs' Learn Cocos2D then allot of this will look familiar as after going through his book I have tried to edit his code to do what I want (to me after reading the book its the best way to learn, by messing with the code etc).
#implementation BL
#synthesize delegate;
#synthesize lauchingAnim;
#synthesize afterlauchingAnim;
-(void) dealloc {
delegate = nil;
[lauchingAnim release];
[afterlauchingAnim release];
[super dealloc];
}
//--The below shootPhaser method takes the current direction the launcher is facing and asks the delegate (gameplaylayer) to create the ball moving in that direction.
//--The below createPhaserWithDirection method was declared (created) in the GamePlayLayerDelegate protocol.
-(void)shootPhaser {
CGPoint phaserFiringPosition;
PhaserDirection phaserDir;
CGPoint position = [self position];
float xPosition = position.x + position.x * 0.51f;
float yPosition = position.y + position.y * 0.045f;
{
phaserDir = kDirectionRight;
}
phaserFiringPosition = ccp(xPosition, yPosition);
[delegate createPhaserWithDirection:phaserDir andPosition:phaserFiringPosition];
}
-(void)changeState:(CharacterStates)newState {
[self stopAllActions];
id action = nil;
characterState = newState;
switch (newState) {
case kStatespawning:
// CCLOG(#“launcher->Changing State to Spwaning");
[self setDisplayFrame:
[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:#“lancher_1.png"]];
break;
case kStateIdle:
// CCLOG(#“laucher->Changing state to idle");
[self setDisplayFrame:
[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:#"lancher_1.png"]];
break;
case kStateFiring:
// CCLOG(#“launcher->Changing State to firing");
action = [CCSequence actions:
[CCAnimate actionWithAnimation:lauchingAnim],
[CCCallFunc actionWithTarget:self
selector:#selector(shootPhaser)],
[CCAnimate actionWithAnimation:afterlauchingAnim],
[CCDelayTime actionWithDuration:2.0f],
nil];
lauchingAnim.restoreOriginalFrame = NO;
afterlauchingAnim.restoreOriginalFrame = NO;
[self changeState:kStateIdle];
break;
case kStateDead:
CCLOG(#“launcher->changing state to dead");
break;
default:
CCLOG(#"unhandled state %d in launcher", newState);
break;
}
if (action !=nil) {
[self runAction:action];
}
}
-(void)updateStateWithDeltaTime: (ccTime)deltaTime andListOfGameObjects:(CCArray*)listOfGameObjects {
if (characterState == kStateFiring) {
// 5
if (characterState != kStateFiring) {
// If RadarDish is NOT already taking Damage
[self changeState:kStateFiring];
return;
}
}
if ((([self numberOfRunningActions] == 0) && (characterState != kStateDead)) ) {
// CCLOG(#"launcher Going to Idle!!!");
[self changeState:kStateIdle];
return;
}
}
-(void)initAnimations {
[self setLauchingAnim:[self loadPlistForAnimationWithName:#"lauchingAnim" andClassName:NSStringFromClass([self class])]];
[self setAfterlauchingAnim:[self loadPlistForAnimationWithName:#"afterlauchingAnim" andClassName:NSStringFromClass([self class])]];
}
-(id) initWithSpriteFrameName:(NSString*)frameName{
if ((self=[super init])) {
if ((self = [super initWithSpriteFrameName:frameName])) {
CCLOG(#"### Laauncher initialized");
[self initAnimations];
characterHealth = 3.0f;
gameObjectType = kBallLaucher;
// 3
[self changeState:kStateIdle];
}
}
return self;
}
#end
Below is the code for my "Ball" class...
#import “Ball.h"
#implementation Ball
#synthesize delegate;
#synthesize myDirection;
#synthesize travelingAnim;
#synthesize ScoreAnim;
-(void) dealloc {
delegate = nil;
[travelingAnim release];
[ScoreAnim release];
[super dealloc];
}
-(void)changeState:(CharacterStates)newState {
[self stopAllActions];
[self setCharacterState:newState];
CGSize screenSize1 = [CCDirector sharedDirector].winSize;
CGPoint position = [self position];
CGPoint endPosition = ccp(screenSize1.width*1.5f, screenSize1.height*0.20f);
id action = nil;
//id action1 = nil;
switch (newState) {
case kStatespawning:
CCLOG(#“Spawning Ball");
[self setDisplayFrame:[[CCSpriteFrameCache
sharedSpriteFrameCache]
spriteFrameByName:#"Ball_1.png"]];
break;
case kStateTravelling: {
movementAction = [CCMoveTo actionWithDuration:5.0f
position:endPosition];
}
break;
case kStateScore:
PLAYSOUNDEFFECT(SCORE);
CCLOG(#“Ball Past Left Of Screen => Add 1 to Score");
action = [CCSequence actions:
[CCCallFunc actionWithTarget:self selector:#selector(removeSelf)],
nil];
break;
default:
CCLOG(#“Ball -> Unknown CharState %d",
characterState);
break;
}
if (action !=nil)
[self runAction:action];
}
-(void)removeSelf{
CCLOG(#"Removing Ball Object Has Scored.");
[self setVisible:NO];
[self removeFromParentAndCleanup:YES];
return;
}
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
CGPoint currentSpitePosition = [self position];
CGSize screenSize = [CCDirector sharedDirector].winSize;
if (currentSpitePosition.x > screenSize.width*1.1f) {{
[self changeState:kStateScore];
}
}
return;
}
if ([self numberOfRunningActions] == 0) {
if (characterState == kStatespawning) {
[self changeState:kStateTravelling];
return;
}
}
}
-(void)initAnimations {
[self setTravelingAnim:[self loadPlistForAnimationWithName:#"travelingAnim" andClassName:NSStringFromClass([self class])]];
[self setScoreAnim:[self loadPlistForAnimationWithName:#"ScoreAnim" andClassName:NSStringFromClass([self class])]];
}
-(id) initWithSpriteFrameName:(NSString*)frameName{
if ((self=[super init])) {
if ((self = [super initWithSpriteFrameName:frameName])) {
CCLOG(#"### Ball Initialised");
[self initAnimations];
characterHealth = 3.0f;
gameObjectType = kEnemyTypeBall;
[self changeState:kStatespawning];
}
}
return self;
}
#end
Below is the code for my "GameplayLayer" class...
#import "GamePlayLayer.h"
#import “Ball.h"
#import “BL.h"
#implementation GamePlayLayer
#synthesize delegate;
-(void) dealloc {
delegate = nil;
[super dealloc];
}
-(void) update:(ccTime)deltaTime {
CCArray *listOfGameObjects =
[sceneSpriteBatchNode children];
for (GameCharacter *tempChar in listOfGameObjects) {
[tempChar updateStateWithDeltaTime:deltaTime andListOfGameObjects:listOfGameObjects];
}
GameCharacter *tempChar = (GameCharacter*)[sceneSpriteBatchNode
getChildByTag:kEnemyTypeBall];
if ([tempChar characterState] == kStateScore) <==HERE I AM SEEING IF THE BALL HAS SCORED - THIS IS ALSO WHERE I THINK I MAY BE GOING WRONG SOMEHOW.
{
CCLOG(#"Add 1 Points To Score");
[self addPoint];
return;
}
}
-(void)addPoint
{
score = score + 1;
[scoreLabel setString:[NSString stringWithFormat:#"$%i", score]]; <===ADDING THE POINT TO THE SCORE
return;
}
-(void) createObjectOfType: (GameObjectType)objectType
withHealth:(int)initialHealth atLocation:(CGPoint)spawnLocation withZValue:(int)ZValue {
if (kBallLaucher == objectType) {
CCLOG(#"Creating launcher Object");
BL *ballLauncher = [[[BL alloc] init] initWithSpriteFrameName:#“launcher_1.png"];
[ballLauncher setCharacterHealth:initialHealth];
[ballLauncher setPosition:spawnLocation];
[sceneSpriteBatchNode addChild:tBT z:ZValue tag:kBallLaucher ];
[ballLauncher setDelegate:self];
[ballLauncher release];
}
}
**BELOW IS HOW I CONTROL WHEN THE BALL IS FIRED**
-(void)ChangeStateLaucher:(int)brandnewState withState:(CharacterStates)newState andObject:(GameObjectType)objectType; {
BL *bL = (BL*)
[sceneSpriteBatchNode getChildByTag:kBallLaucher];
int x = (arc4random() % 2);
if (x==0) {
CCLOG(#"Start Laucher Firing");
[bL changeState:kStateFiring];
count = 0;
}
if (x==1) {
CCLOG(#"No Laucher Firing");
count = count + 1;
if (count == 2) {
CCLOG(#"No Laucher Firing x2 - Start Laucher Firing");
[bL changeState:kStateFiring];
} else if (count > 3) {
CCLOG(#"No Laucher Firing x3 - Start Laucher Firing");
[bL changeState:kStateFiring];
}
}
[delegate ChangeStateLaucher:x withState:kStateFiring andObject:objectType];
}
-(void)createPhaserWithDirection:(PhaserDirection)phaserDirection andPosition:(CGPoint)spawnPosition {
CCLOG(#"Creating Ball from Gameplay Layer");
Ball *ballSprite = [[Ball alloc]initWithSpriteFrame:[[CCSpriteFrameCache
sharedSpriteFrameCache]
spriteFrameByName:#"Ball_1.png"]];
[ballSprite setPosition:spawnPosition];
[ballSprite setMyDirection:phaserDirection];
[ballSprite setCharacterState:kStatespawning];
[ballSprite setCharacterHealth:3.0f];
[sceneSpriteBatchNode addChild:ballSprite z:20 tag:kEnemyTypeBall];
[ballSprite release];
}
-(id)init {
self = [super init];
if (self !=nil) {
CGSize screenSize = [CCDirector sharedDirector]. winSize;
self.TouchEnabled = YES;
srandom(arc4random()); // Seeds the random number generator
[[CCSpriteFrameCache sharedSpriteFrameCache]
addSpriteFramesWithFile:#"scene1atlas.plist"]; // 1
sceneSpriteBatchNode =
[CCSpriteBatchNode batchNodeWithFile:#"scene1atlas.png"]; // 2
[self createObjectOfType:kBallLaucher withHealth:3 atLocation:ccp(screenSize.width * 0.05f, screenSize.height * 0.822f) withZValue:10];
[gameBeginLabel setPosition:ccp(screenSize.width/2,screenSize.height/2)]; // 2
[self addChild:gameBeginLabel]; // 3
id labelAction = [CCSpawn actions:
[CCFadeOut actionWithDuration:2.5f],
nil]; // 4
[gameBeginLabel runAction:labelAction];
lives = 3;
scoreLabel = [CCLabelBMFont labelWithString:#"$0"
fntFile:#“BallTest.fnt"];
scoreLabel.position = ccp(screenSize.width * 0.5f, screenSize.height * 0.9f);
[self addChild:scoreLabel
z:-1 tag:kNewScoreTagValue];
[self scheduleUpdate];
}
return self;
}
#end
Once again I would like to thank anyone that has taken the time to look at this question regardless if you have posted a pointer or answer or not.
Thanks.
I think that when there are more than one Ball objects on the screen, the GamePlayLayer update method is unable to pick up on the state change on one of the balls at times because that ball is removed as soon as it enters kStateScore.
If you add a certain delay before removing the ball , the update method should pick up the changes in all the balls and your problem should be resolved.
In your Ball.m -(void)changeState:(CharacterStates)newState function , modify the action on state kStateScore like
action = [CCSequence actions:[CCDelayTime actionWithDuration:0.2],
[CCCallFunc actionWithTarget:self selector:#selector(removeSelf)],
nil];
Try this out and let me know if this worked for you!
EDIT
The above solution would end up adding more than one point as the ball would remain in kStateScore state for a long time due to delay. In order to counter that problem , we can introduce a new state call kStateDead and change the ball state to kStateDead after adding the point. This will ensure a point is added only once and also we can put the responsibility of removing the ball in kStateDead state. The new code would like as follows :
Ball.m
case kStateScore:
PLAYSOUNDEFFECT(SCORE);
CCLOG(#“Ball Past Left Of Screen => Add 1 to Score");
break;
case kStateDead:
action = [CCSequence actions:
[CCCallFunc actionWithTarget:self selector:#selector(removeSelf)],
nil];
default:
CCLOG(#“Ball -> Unknown CharState %d",
characterState);
break;
and in your Gameplayer.m
if ([tempChar characterState] == kStateScore) <==HERE I AM SEEING IF THE BALL HAS SCORED - THIS IS ALSO WHERE I THINK I MAY BE GOING WRONG SOMEHOW.
{
CCLOG(#"Add 1 Points To Score");
[self addPoint];
[tempChar changeState:kStateDead]; //change the state to dead after the point is added
return;
}

Constantly moving object with Cocos2d

I'm looking for some guidance on how, using Cocos2d, I can have an object (let's say a rocket) that is in constant motion at a constant speed, and I have two buttons to simply change its direction.
I've found a few pieces of information on how to change the orientation of the rocket, however I'm stuck on just having the rocket be in constant motion.
I'll later want to make the rocket be able to fly off the screen and re-appear on the other side of the screen, but that's for another question (I've found some help on this already).
I'm new to Cocos2d and have searched for a few hours, but haven't been able to find what I'm after.
Could somebody point me in the right direction in being able to get my rocket constantly moving, and responding to changes in direction?
So, here's a first pass at how you could do something like that:
// ProjectileTest holds a projectile class that simply knows how to
move itself
// WrapBoundaryTest is the example scene showing potential usage of
ProjectileTest
ProjectileTest.h
#import "cocos2d.h"
#interface ProjectileTest : CCNode
{
CCSprite *sprite;
CGPoint velocity;
CGRect bounds;
BOOL isActive;
float steerAmount;
}
#property CCSprite *sprite;
#property CGPoint velocity;
#property CGRect bounds;
#property BOOL isActive;
#property float steerAmount;
+(id) projectileWithBounds:(CGRect) boundary
andVelocity:(CGPoint) v
andSteeringAmount:(float) steeringAmount;
-(void) step:(CCTime) dt;
-(void) steerLeft;
-(void) steerRight;
#end
ProjectileTest.m
#import "ProjectileTest.h"
#implementation ProjectileTest
#synthesize sprite, velocity, bounds, isActive, steerAmount;
+(id) projectileWithBounds:(CGRect) boundary andVelocity:(CGPoint) v andSteeringAmount:(float) steeringAmount
{
ProjectileTest *proj = [[self alloc] init];
proj.bounds = boundary;
proj.velocity = v;
proj.steerAmount = steeringAmount;
proj.isActive = YES;
[proj calculateAngleForVelocity: v];
return proj;
}
-(id) init
{
if(( self = [super init]))
{
sprite = [CCSprite spriteWithImageNamed:#"Icon.png"];
[self addChild:sprite];
[sprite setScale:0.50f];
bounds = CGRectZero;
velocity = CGPointZero;
isActive = NO;
steerAmount = 1.0f;
}
return self;
}
-(void) calculateAngleForVelocity:(CGPoint) v
{
float rads = ccpToAngle(v);
float degs = -CC_RADIANS_TO_DEGREES(rads);
sprite.rotation = degs + 90.0f;
}
-(void) steerLeft
{
[self steer: YES];
}
-(void) steerRight
{
[self steer: NO];
}
-(void) steer:(BOOL) left
{
if(left)
{
velocity = ccpRotateByAngle(velocity, CGPointZero, -CC_DEGREES_TO_RADIANS(-steerAmount));
}
else // right
{
velocity = ccpRotateByAngle(velocity, CGPointZero, -CC_DEGREES_TO_RADIANS(steerAmount));
}
}
-(void) step:(CCTime)dt
{
if(isActive)
{
if(CGRectContainsPoint(bounds, self.position))
{
self.position = ccpAdd(self.position, velocity);
[self calculateAngleForVelocity: velocity];
}
else
{
float nudge = 0.5f;
if(self.position.x >= bounds.size.width && velocity.x > 0.0f)
{
self.position = ccp(bounds.origin.x + nudge, self.position.y);
}
else if(self.position.x <= bounds.origin.x && velocity.x < 0.0f)
{
self.position = ccp(bounds.size.width - nudge, self.position.y);
}
if(self.position.y >= bounds.size.height && velocity.y > 0.0f)
{
self.position = ccp(self.position.x, bounds.origin.y + nudge);
}
else if(self.position.y <= bounds.origin.y && velocity.y < 0.0f)
{
self.position = ccp(self.position.x, bounds.size.height - nudge);
}
}
}
}
#end
WrapBoundaryTest.h
#import "cocos2d.h"
#import "cocos2d-ui.h"
#interface WrapBoundaryTest : CCScene
{
NSMutableArray *projectiles;
CCButton *left;
CCButton *right;
}
#property __strong NSMutableArray *projectiles;
+(id) scene;
#end
WrapBoundaryTest.m
#import "WrapBoundaryTest.h"
#import "ProjectileTest.h"
#implementation WrapBoundaryTest
#synthesize projectiles;
+(id) scene
{
return [[self alloc] init];
}
-(id) init
{
if(( self = [super init]))
{
projectiles = [NSMutableArray array];
CGRect projectileBounds = CGRectMake(
0.0f,
0.0f,
[CCDirector sharedDirector].designSize.width,
[CCDirector sharedDirector].designSize.height
);
CGPoint origin = ccp(200.0f, 150.0f);
[self addProjectileWithBoundsRect:projectileBounds andVelocity:ccp( 1.0f, 5.0f) andSteeringAmount:2.0f atStartingPosition:origin];
[self addProjectileWithBoundsRect:projectileBounds andVelocity:ccp(-1.0f, -5.0f) andSteeringAmount:1.0f atStartingPosition:origin];
[self addProjectileWithBoundsRect:projectileBounds andVelocity:ccp(-1.0f, 1.0f) andSteeringAmount:0.5f atStartingPosition:origin];
[self addProjectileWithBoundsRect:projectileBounds andVelocity:ccp( 8.0f, 1.0f) andSteeringAmount:7.5f atStartingPosition:origin];
left = [CCButton buttonWithTitle:#"Left" fontName:#"Arial" fontSize:16.0f];
left.positionType = CCPositionTypeNormalized;
left.position = ccp(0.1f, 0.1f);
[self addChild:left];
right = [CCButton buttonWithTitle:#"Right" fontName:#"Arial" fontSize:16.0f];
right.positionType = CCPositionTypeNormalized;
right.position = ccp(0.9f, 0.1f);
[self addChild:right];
}
return self;
}
-(void) addProjectileWithBoundsRect:(CGRect) boundsRect
andVelocity:(CGPoint) velocity
andSteeringAmount:(float) steeringAmount
atStartingPosition:(CGPoint) startingPosition
{
ProjectileTest *proj = [ProjectileTest projectileWithBounds: boundsRect
andVelocity: velocity
andSteeringAmount: steeringAmount
];
proj.position = startingPosition;
[self addChild:proj];
[projectiles addObject:proj];
}
-(void) update:(CCTime)delta
{
for(ProjectileTest *p in projectiles)
{
if(left.touchInside)
{
[p steerLeft];
}
if(right.touchInside)
{
[p steerRight];
}
[p step:delta];
}
}
#end
// HTH

EXC_BAD_ACCESS while using box2d GetUserData and SetUserData

I've been straggling with this problem for a few days now and I'm desperate for your help. As I've been following Ray Wenderlich's tutorials and I still get EXC_BAD_ACCESS error when I'm trying to getUserData from b2Body inside a ContactListener
so WHContactListener.h:
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "Box2D.h"
class WHContactListener : public b2ContactListener
{
private:
void BeginContact(b2Contact* contact);
void EndContact(b2Contact* contact);
};
WHContactListener.mm
#import "WHContactListener.h"
#import "cocos2d.h"
#import "ExSprite.h"
void WHContactListener::BeginContact(b2Contact *contact)
{
b2Body *bodyA = contact->GetFixtureA()->GetBody();
b2Body *bodyB = contact->GetFixtureB()->GetBody();
/*The problem occurs here, while it returns a non-null value it just crashes when I implement any method here, such as NSLog */
ExSprite *spriteB = (ExSprite*)bodyB->GetUserData();
NSLog(#"output %#", spriteB);
}
void WHContactListener::EndContact(b2Contact *contact)
{
}
Sprite class ExSprite.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "Box2D.h"
#interface ExSprite : CCSprite {
BOOL _exploded;
b2Body *_explBody;
}
#property (nonatomic, readwrite) BOOL exploded;
#property (nonatomic, assign) b2Body *explBody;
-(void)setPhysicsBody:(b2Body*)body;
#end
ExSprite.mm
#import "ExSprite.h"
#import "HelloWorldLayer.h"
#pragma mark - ExSprite
#implementation ExSprite
#synthesize exploded = _exploded;
#synthesize explBody = _explBody;
-(void)setPhysicsBody:(b2Body *)body
{
_explBody = body;
_explBody->SetUserData(self);
}
// this method will only get called if the sprite is batched.
// return YES if the physics values (angles, position ) changed
// If you return NO, then nodeToParentTransform won't be called.
-(BOOL) dirty
{
return YES;
}
// returns the transform matrix according the Chipmunk Body values
-(CGAffineTransform) nodeToParentTransform
{
b2Vec2 pos = _explBody->GetPosition();
float x = pos.x * PTM_RATIO;
float y = pos.y * PTM_RATIO;
if ( ignoreAnchorPointForPosition_ ) {
x += anchorPointInPoints_.x;
y += anchorPointInPoints_.y;
}
// Make matrix
float radians = _explBody->GetAngle();
float c = cosf(radians);
float s = sinf(radians);
if( ! CGPointEqualToPoint(anchorPointInPoints_, CGPointZero) ){
x += c*-anchorPointInPoints_.x + -s*-anchorPointInPoints_.y;
y += s*-anchorPointInPoints_.x + c*-anchorPointInPoints_.y;
}
// Rot, Translate Matrix
transform_ = CGAffineTransformMake( c, s,
-s, c,
x, y );
return transform_;
}
-(void) dealloc
{
//
[super dealloc];
}
#end
Finally the layer that I'm using HelloWorld.h
#import <GameKit/GameKit.h>
// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"
#import "Box2D.h"
#import "GLES-Render.h"
#import "WHContactListener.h"
//Pixel to metres ratio. Box2D uses metres as the unit for measurement.
//This ratio defines how many pixels correspond to 1 Box2D "metre"
//Box2D is optimized for objects of 1x1 metre therefore it makes sense
//to define the ratio so that your most common object type is 1x1 metre.
#define PTM_RATIO 32
// HelloWorldLayer
#interface HelloWorldLayer : CCLayer <GKAchievementViewControllerDelegate, GKLeaderboardViewControllerDelegate>
{
CCTexture2D *spriteTexture_; // weak ref
b2World* world; // strong ref
GLESDebugDraw *m_debugDraw; // strong ref
WHContactListener* contactListener;
CCRenderTexture *target;
CCSprite *brush;
b2Body *groundBody;
NSMutableArray *shapeVert;
}
#property (nonatomic, readwrite)b2Body *groundBody;
#property (nonatomic, assign)NSMutableArray *shapeVert;
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
#end
and HelloWorld.mm
#import "HelloWorldLayer.h"
// Needed to obtain the Navigation Controller
#import "AppDelegate.h"
#import "PhysicsSprite.h"
#import "ExSprite.h"
enum {
kTagParentNode = 1,
};
#pragma mark - HelloWorldLayer
#interface HelloWorldLayer()
-(void) initPhysics;
-(void) addNewSpriteAtPosition:(CGPoint)p;
#end
#implementation HelloWorldLayer
#synthesize groundBody = groundBody_;
#synthesize shapeVert = shapeVert_;
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(id) init
{
if( (self=[super init])) {
// enable events
self.isTouchEnabled = YES;
self.isAccelerometerEnabled = YES;
CGSize s = [CCDirector sharedDirector].winSize;
// init physics
[self initPhysics];
target = [CCRenderTexture renderTextureWithWidth:s.width height:s.height pixelFormat:kCCTexture2DPixelFormat_RGBA8888];
[target retain];
[target setPosition:ccp(s.width/2, s.height/2)];
[self addChild:target z:0];
brush = [CCSprite spriteWithFile:#"bird.png"];
[brush retain];
[self scheduleUpdate];
}
return self;
}
-(void) dealloc
{
delete world;
world = NULL;
delete contactListener;
contactListener = NULL;
delete m_debugDraw;
m_debugDraw = NULL;
[super dealloc];
}
-(void) initPhysics
{
CGSize s = [[CCDirector sharedDirector] winSize];
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
world = new b2World(gravity);
// Do we want to let bodies sleep?
world->SetAllowSleeping(true);
world->SetContinuousPhysics(true);
contactListener = new WHContactListener();
world->SetContactListener(contactListener);
m_debugDraw = new GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(m_debugDraw);
uint32 flags = 0;
flags += b2Draw::e_shapeBit;
// flags += b2Draw::e_jointBit;
// flags += b2Draw::e_aabbBit;
// flags += b2Draw::e_pairBit;
// flags += b2Draw::e_centerOfMassBit;
m_debugDraw->SetFlags(flags);
// Define the ground body.
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(s.width/2/PTM_RATIO, s.height/2/PTM_RATIO); // bottom-left corner
//SET USERDATA
//groundBodyDef.userData = groundBody;
// Call the body factory which allocates memory for the ground body
// from a pool and creates the ground box shape (also from a pool).
// The body is also added to the world.
groundBody = world->CreateBody(&groundBodyDef);
// Define the ground box shape.
b2EdgeShape groundBox;
float32 x1 = 2*cosf(0.0f * b2_pi/180);
float32 y1 = 2*sinf(0.0f * b2_pi/180);
for(int32 i=1; i<=18; i++){
float32 x2 = 2*cosf((i*20) * b2_pi / 180);
float32 y2 = 2*sinf((i*20) * b2_pi / 180);
groundBox.Set(b2Vec2(x1,y1), b2Vec2(x2,y2));
groundBody->CreateFixture(&groundBox, 0);
x1 = x2;
y1 = y2;
}
}
-(void) draw
{
//
// IMPORTANT:
// This is only for debug purposes
// It is recommend to disable it
//
[super draw];
ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position );
kmGLPushMatrix();
world->DrawDebugData();
kmGLPopMatrix();
}
-(void) addNewSpriteAtPosition:(CGPoint)p
{
//CCLOG(#"Add sprite %0.2f x %02.f",p.x,p.y);
CCNode *parent = [self getChildByTag:kTagParentNode];
//We have a 64x64 sprite sheet with 4 different 32x32 images. The following code is
//just randomly picking one of the images
int idx = (CCRANDOM_0_1() > .5 ? 0:1);
int idy = (CCRANDOM_0_1() > .5 ? 0:1);
//PhysicsSprite *sprite = [PhysicsSprite spriteWithTexture:spriteTexture_ rect:CGRectMake(32 * idx,32 * idy,32,32)];
ExSprite *sprite = [ExSprite spriteWithTexture:spriteTexture_ rect:CGRectMake(32*idx, 32*idy, 32, 32)];
[parent addChild:sprite];
sprite.position = ccp( p.x, p.y);
// Define the dynamic body.
//Set up a 1m squared box in the physics world
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
//USERDATA set
//bodyDef.userData = sprite;
b2Body *body = world->CreateBody(&bodyDef);
// Define another box shape for our dynamic body.
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(.5f, .5f);//These are mid points for our 1m box
// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.restitution = 0.0f;
//fixtureDef.density = rand() * 20.0f;
fixtureDef.friction = 0.3f;
body->CreateFixture(&fixtureDef);
[sprite setPhysicsBody:body];
//NSLog(#"here %#", body->GetUserData());
}
-(void) update: (ccTime) dt
{
//It is recommended that a fixed time step is used with Box2D for stability
//of the simulation, however, we are using a variable time step here.
//You need to make an informed choice, the following URL is useful
//http://gafferongames.com/game-physics/fix-your-timestep/
int32 velocityIterations = 8;
int32 positionIterations = 1;
// Instruct the world to perform a single step of simulation. It is
// generally best to keep the time step and iterations fixed.
world->Step(dt, velocityIterations, positionIterations);
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//Add a new body/atlas sprite at the touched location
for( UITouch *touch in touches ) {
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
[self addNewSpriteAtPosition: location];
}
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
/*UITouch *touch = (UITouch*)touches.anyObject;
CGPoint start = [touch locationInView:[touch view]];
start = [[CCDirector sharedDirector] convertToGL:(start)];
CGPoint end = [touch previousLocationInView:[touch view]];
end = [[CCDirector sharedDirector] convertToGL:end];
[target begin];
float distance = ccpDistance(start, end);
for(int i =0; i < distance; 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))];
[brush visit];
}
[target end];
*/
}
#pragma mark GameKit delegate
-(void) achievementViewControllerDidFinish:(GKAchievementViewController *)viewController
{
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
[[app navController] dismissModalViewControllerAnimated:YES];
}
-(void) leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController
{
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
[[app navController] dismissModalViewControllerAnimated:YES];
}
#end
Any help will be appreciated on the matter.
I've found the problem. It seems that I've left CCNode undeclared, and when I attach body with the UserData to the unavailable CCNode, it then crashes when I retrieve it in the contactlistener class

COCOS2d CCLabelTTF not updating with player score - using a CCLayer Class

I would be very grateful for any advice you can offer as I am growing increasingly frustrated with an issue I am having - I also appreciate that the issue I am having is due to my lack of knowledge / understanding.
In an attempt to further my knowledge and stretch myself, I have chosen to create a PlayerStats class that handles the players scoring - and in time, health, etc.
I have the GameLevelLayer and PlayerStats classes implemented as follows:
GameLevelLayer.m as follows:
#import "GameLevelLayer.h"
#import "Player.h"
#import "PlayerStats.h"
#interface GameLevelLayer() {
CCTMXTiledMap *map;
Player *player;
PlayerStats *playerStats;
}
#end
#implementation GameLevelLayer
#synthesize grabber = _grabber;
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
GameLevelLayer *layer = [GameLevelLayer node];
PlayerStats *hudLayer = [PlayerStats node];
[scene addChild: layer];
[scene addChild: hudLayer];
return scene;
}
-(id) init {
if( (self=[super init]) ) {
CGSize screenSize = [[CCDirector sharedDirector] winSize];
playerStats = [[PlayerStats alloc]init];
...........
}
PlayerStats.m is as follows:
-(id) init
{
if ((self = [super init])) {
CGSize screenSize = [[CCDirector sharedDirector] winSize];
score = [CCLabelTTF labelWithString:#"Score : 0" dimensions:CGSizeMake(100,20) hAlignment:UITextAlignmentRight fontName:#"Marker Felt" fontSize:(18.0)];
int margin = 5;
score.position = ccp(screenSize.width - (score.contentSize.width/2) - margin, score.contentSize.height/2 + margin);
[self addChild:score z:99];
}
return self;
}
-(void)numberOfItemsCollected:(int)collected {
NSString *str = [score string];
CCLOG(#"What does the label say %#", str);
// This is actually displaying the correct string of what the score should be ..
[score setString:[NSString stringWithFormat:#"Score : %d", collected]];
}
When (from the GameLevelLayer.m) I initiate
[playerStats numberOfItemsCollected:5];
the CCLog shows that the label should show Score : 5 but the label itself does not update.
Any advice would be greatly appreciated as I am very aware that I am misunderstanding the issue.
I think the issue is to do with the Layer that I am updating not being the one I believe it is....
Thanks in advance.
I had declared the CCLabelTTF *score as an instance variable in the PlayerStats Class header. What a difference a night away from code makes.
Finally I managed to solve this issue set string to an empty string then reSet it to your string
[label setString:#""];
[label setString:yourString];

How to popScene with transitions in cocos2d?

When i call relpaceScene or pushScene in cocos2d, I can add some transitions to it like:
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1 scene:scene]];
but when i call popScene, it takes no parameters, and no transition can be added. Is that so?
How can i popScene with desired transitions?
Per Guy in the cocos2d forums this seems to work: Link to Forum
Add this to CCDirector.h, just after the declaration -(void)popScene (line 402):
- (void) popSceneWithTransition: (Class)c duration:(ccTime)t;
Then add this to CCDirector.m, just after the definition of -(void)popScene (line 768):
-(void) popSceneWithTransition: (Class)transitionClass duration:(ccTime)t;
{
NSAssert( runningScene_ != nil, #"A running Scene is needed");
[scenesStack_ removeLastObject];
NSUInteger c = [scenesStack_ count];
if( c == 0 ) {
[self end];
} else {
CCScene* scene = [transitionClass transitionWithDuration:t scene:[scenesStack_ objectAtIndex:c-1]];
[scenesStack_ replaceObjectAtIndex:c-1 withObject:scene];
nextScene_ = scene;
}
}
You can call the method like this:
[[CCDirector sharedDirector] popSceneWithTransition:[CCSlideInRTransition class] durat
You can use Category as follows:
#interface CCDirector (PopTransition)
- (void)popSceneWithTransition:(Class)transitionClass duration:(ccTime)t;
#end
#implementation CCDirector (PopTransition)
- (void)popSceneWithTransition:(Class)transitionClass duration:(ccTime)t {
[self popScene];
// Make Transition
if (nextScene_) {
CCScene* scene = [transitionClass transitionWithDuration:t scene:nextScene_];
[scenesStack_ replaceObjectAtIndex:([scenesStack_ count] - 1) withObject:scene];
nextScene_ = scene;
}
}
#end
This way you need not to modify CCDirector class.
I modified Kailash's answer to work with cocos2d 2.1.
CCDirector+additions.h
#import "cocos2d.h"
#interface CCDirector (additions)
-(void)popSceneWithTransition:(Class)transitionClass duration:(ccTime)t;
#end
CCDirector+additions.m
#import "CCDirector+additions.h"
#implementation CCDirector (additions)
-(void)popSceneWithTransition:(Class)transitionClass duration:(ccTime)t {
[self popScene];
// Make Transition
if (_nextScene) {
CCScene* scene = [transitionClass transitionWithDuration:t scene:_nextScene];
[_scenesStack replaceObjectAtIndex:([_scenesStack count] - 1) withObject:scene];
_nextScene = scene;
}
}
#end
A more concise version of the above that actually releases the scene so it and its textures can be cleaned up as needed.
CCDirector+addition.h
#import "cocos2d.h"
#interface CCDirector (addition)
- (void) popSceneWithTransition:(Class)transitionClass duration:(ccTime)t;
#end
CCDirector+addition.m
#import "CCDirector+addition.h"
#implementation CCDirector (addition)
- (void) popSceneWithTransition:(Class)transitionClass duration:(ccTime)t {
[_scenesStack removeLastObject];
NSUInteger count = [_scenesStack count];
NSAssert(count > 0, #"Don't popScene when there aren't any!");
CCScene* scene = [transitionClass transitionWithDuration:t scene:[_scenesStack lastObject]];
[self replaceScene:scene];
}
#end
Without having to mess with Cocos2d internals you can do it like:
CCDirector *director = [CCDirector sharedDirector];
CCScene *currentScene = [director runningScene];
CGSize contentSize = [currentScene contentSize];
CCLayerColor *black = [[CCLayerColor alloc] initWithColor:ccc4(0, 0, 0, 0) width:contentSize.width height:contentSize.height];
[currentScene addChild:black];
[black runAction:[CCSequence actionOne:[CCFadeTo actionWithDuration:1.0f opacity:255]
two:[CCCallFunc actionWithTarget:director selector:#selector(popScene)]]];