I'm attempting to make a game with objects that bounce between the left and right walls of the screen until they reach the bottom.
I create my objects inside these methods which stores the objects in an NSMutable array and is called when the game begins.
-(void)createContent
{
self.backgroundColor = [SKColor colorWithRed:0.54 green:0.7853 blue:1.0 alpha:1.0];
world = [SKNode node];
[self addChild:world];
crabs = [NSMutableArray new];
[self performSelector:#selector(createCrabs) withObject:nil afterDelay:1];
}
-(void)createCrabs {
crab = [HHCrab crab];
[crabs addObject:crab];
[world addChild:crab];
crab.position = CGPointMake(world.scene.frame.size.width/12 , world.scene.frame.size.height/3.2);
[crab moveLeft];
//Next Spawn
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:10],
[SKAction performSelector:#selector(createCrabs) onTarget:self],
]]];
}
This will endlessly create new objects, however a problem begins with collision detection. Originally I had my collision detection set up like this:
-(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 (firstBody.categoryBitMask==crabCategory && secondBody.categoryBitMask == leftWallCategory) {
NSLog(#"Crab Hit Left Wall");
[crab stop];
[crab moveRight];
} else if (firstBody.categoryBitMask == crabCategory && secondBody.categoryBitMask == rightWallCategory) {
NSLog(#"Crab Hit Right Wall");
[crab stop];
[crab moveLeft];
}
}
But, after more than one object is on the map, when the original object collides with a wall it begins to glitch and stop moving. This results in a pile up which bugs the new objects being spawned.
I've also tried to use the update CurrentTime method to see if the collision detection would improve, however as predicted, only one object will move at a time while the rest will stay still.
-(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 (firstBody.categoryBitMask==crabCategory && secondBody.categoryBitMask == leftWallCategory) {
NSLog(#"Crab Hit Left Wall");
self.crabTouchLeftWall = YES;
} else if (firstBody.categoryBitMask == crabCategory && secondBody.categoryBitMask == rightWallCategory) {
NSLog(#"Crab Hit Right Wall");
self.crabTouchRightWall = YES;
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
for (HHCrab *crabNode in crabs){
if (self.crabTouchLeftWall){
[crabNode stop];
[crabNode moveRight];
self.crabTouchLeftWall = NO;
}
if (self.crabTouchRightWall){
[crabNode stop];
[crabNode moveLeft];
self.crabTouchRightWall = NO;
}
}
}
How can I fix this so when one object collides with the wall, it does not effect the movement of the other objects, only itself?
There are a couple of suggestions I have.
As you are creating multiple instances of crab and adding them into the crabs array, I suggest you give each crab a unique name property. Your can do this by having a running int counter. For example:
#implementation GameScene {
int nextObjectID;
}
Then when you create a crab instance:
nextObjectID++;
[crab setName:[NSString stringWithFormat:#"crab-%i",nextObjectID]];
I personally prefer coding my didBeginContact like this:
- (void)didBeginContact:(SKPhysicsContact *)contact {
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (crabCategory | leftWallCategory)) {
// figure out which crab in the array made the contact...
for(HHCrab *object in crabs) {
if(([object.name isEqualToString:contact.bodyB.node.name]) || ([object.name isEqualToString:contact.bodyA.node.name])) {
NSLog("I am %#",object.name);
}
}
}
}
Your code self.crabTouchLeftWall = YES; is not the right way to go. You should create a crab class property and set that to YES. Reason being that each crab instance needs to have these properties. The way you currently have it, all crab instances all share the same BOOLs self.crabTouchLeftWall and self.crabTouchRightWall.
You are checking for the BOOL values contacts in the update method and then running [crabNode stop];. You could do this directly when the contact is registered in the didBeginContact method.
The reason I earlier suggested you use unique names for each crab instance is because when it comes time to remove them from your array, you need to be able to point to which specific crab instance needs removing. Having a unique name just makes things easier.
Be aware that moving nodes manually like that can produce odd results. When using physics engine in Spritekit you have to let it to do calculation by itself, meaning you have to move objects by using forces. This not applies for contact detection, and you can move nodes around if you only need contact detection. But if you need more than just contact detection eg. to simulate collisions and restrict nodes from penetrating each other, then I suggest you to use forces appropriately (applyImpulse:, applyForce: are suitable methods).
You could probably solve your issue by moving the code from your update: method to didSimulatePhysics method, but that is not a solution...Because even if you move everything manually after simulation is done, you can make a mistake and for example manually initiate collision by positioning your nodes to overlap each other. So, the advice is, if you are using phyisics use it all, or don't use it at all and move nodes manually or using actions.In the case you decide not to use physics engine, you can handle collisions by using methods like CGRectIntersectsRect and similar.
And to point once again , which is not related to your question, but it can be useful: You can use physics engine for contact detection even if you move your nodes, for example by SKAction methods. Read more here.
Related
after testing almost everything, i hope that someone of you got an idea.
It's a complex app, so i hope i get all relevant parts:
I want to "page" through SKNode's, containing some SKSpriteNodes.
After paging,the childnodes don't react on runAction:.
The Paging:
To page, i use a UIPanGestureRecognizer.
The current "page" = SKNode *currentForegroundNode.
The next "page" = SKNode *nextForegroundNode.
in (recognizer.state == UIGestureRecognizerStateChanged) {..} i'm moving the current & next page. (working)
in (recognizer.state == UIGestureRecognizerStateEnded) {...}
SKAction *moveNextForegroundToFront = [SKAction moveToX:0 duration:0.2f];
[self.nextForegroundNode runAction:moveNextForegroundToFront completion:^{
[self.currentForegroundNode removeAllChildren];
[self.nextForegroundNode enumerateChildNodesWithName:#"node_1" usingBlock:^(SKNode *node, BOOL *stop) {
//Node is kind of class SKSpriteNode
[node removeFromParent];
[self.currentForegroundNode addChild:node];
}
}];
[self.nextForegroundNode removeAllChildren];
[self.nextForegroundNode removeFromParent];
self.nextForegroundNode = nil;
}
After this, everything look's good. BUT i can't run any Actions on the child-nodes of self.currentForegroundNode with iOS7! With iOS8 installed, it works fine as expected.
Im making a brick breaker type game and need to know if all the bricks have been broken in order to transition to the win screen.
The way I've been thinking of solving this is to create a BOOL method that is ran each time a brick is removed to calculate how many bricks are left. If there are no bricks left... move to the win scene.
Im struggling with the logic on how to do this.
So far I have:
-(BOOL)isGameWon{
for (SKNode* node in self.children){
if ([node.name isEqual:brickCategoryName]){
//some logic
}
}
return YES;
}
didBeginContactMethod:
if (notTheBall.categoryBitMask == brickCategory) {
[self runAction:_smashSound];
[notTheBall.node removeFromParent];
if ([self isGameWon]) {
NSLog(#"YOU WIN!");
}
}
I have been trying all my resources and thanks to my dear friend Sangon, a member here and the IOS Games by tutorial book from Ray Wenderlich and what I am trying to do is to first of all set categories for each node and then if my player's bullet gets collided with the enemy, I want the enemy to disappear. Now I got some suggestions where to start from but this thing is driving me nuts I have tried and tried and the enemy does not disappear. Please help me on this. thanks.
static const uint32_t EnemyCategory = 1;
static const uint32_t bulletCategory = 2;
- (void) fire:(float)targetAngle {
bullet = [SKSpriteNode spriteNodeWithImageNamed:#"cannonbullet"];
bullet.position = _Player.position;
bullet.physicsBody = [SKPhysicsBody
bodyWithCircleOfRadius:bullet.frame.size.width/2];
bullet.physicsBody.categoryBitMask = bulletCategory;
bullet.physicsBody.collisionBitMask = bulletCategory | EnemyCategory;
bullet.physicsBody.contactTestBitMask = bulletCategory | EnemyCategory;
[self addChild:bullet];
int x = _Player.position.x + 1000 * cos(targetAngle);
int y = _Player.position.y + 1000 * sin(targetAngle);
[bullet runAction:[SKAction moveTo:CGPointMake(x, y) duration:2]];
}
- (void) didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *temp;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
temp = contact.bodyB;
} else {
temp = contact.bodyA;
}
if (temp.categoryBitMask == EnemyCategory) {
[temp.node removeFromParent];
}
}
- (void) Enemy:(CGSize)size {
Enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy1#2x"];
Enemy.position = CGPointMake(self.size.width/2, self.size.height/2);
Enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:Enemy.frame.size];
Enemy.physicsBody.dynamic = NO;
Enemy.physicsBody.categoryBitMask = EnemyCategory;
[self addChild:Enemy];
}
The only problem that I can see is that you didn't set the SKPhysicsContactDelegate delegate properly
You will have to do something like this:
#interface MyScene : SKScene<SKPhysicsContactDelegate>
Once you do that you will also have to set the scene as the contactDelegate. It would look something like this:
self.physicsWorld.contactDelegate = self;
And after this is done your scene will receive the didBeginContact: and didEndContact: messages automatically. Those methods are not something that you call manually.
Here you can find a tutorial that uses collision detection (and removes the objects after collision detection)
From the documentation ’SKPhysicsContactDelegate’
An object that implements the SKPhysicsContactDelegate protocol can respond when two physics bodies are in contact with each other in a physics world. To receive contact messages, you set the contactDelegate property of a SKPhysicsWorld object. The delegate is called when a contact starts or ends.
Hope this helps
Using the method below, how can one refer to specific sprites when checking to see if they intersect?
- (void)update:(ccTime)dt {
for (CCSprite *sprite in movableSprites) {
if (CGRectIntersectsRect(sprite.boundingBox, sprite.boundingBox)) {
break;
}
}
}
It appears that all sprites are available in the moveableSprites object, but I don't know how to check if specific sprites are colliding... I don't know how to refer to them. If there is an easier way to perform collision detection I'm interested.
It appears your code above will always return TRUE because you are checking if the boundingbox of sprite collides with sprite, and since they are the same it always will.
if (CGRectIntersectsRect(sprite.boundingBox, sprite.boundingBox)) {//
break;
}
Should be comparing to a different sprite not the same sprite.
if (CGRectIntersectsRect(sprite.boundingBox, otherSprite.boundingBox)) {//
break;
}
If that does not answer your question maybe you are looking to avoid enumerating through the array? If that is the case try using tags. Someting like below.
CCSprite *aSprite = [CCSprite spriteWithFile:#"hurdle1.png"];
[self addChild:aSprite tag:2];
Now [self getChildByTag:2] can take the place of sprite
and you can just add boundingBox to check collisions, as below.
if (CGRectIntersectsRect([self getChildByTag:2].boundingBox, checkSprite.boundingBox)) {//
break;
}
I have an Array of CCSprites that being displayed all at once.
Every sprite has a movement path, a movement path is a random point on screen.
All the sprites are moving all at once to random points on screen.
What I want to do is to detect collision between the sprites and then change their movement path.
Is it possible?
Iterate through every CCSprite in your array (call it A), and for every iteration iterate again through every CCSprite in the array (excluding A itself of course) (call this one B). Now, use CGRectIntersectsRect along with boundingBox to find a collision between them. It goes something like this:
for (CCSprite *first in mySprites) {
for (CCSprite *second in mySprites) {
if (first != second) {
if (CGRectIntersectsRect([first boundingBox], [second boundingBox])) {
// COLLISION! Do something here.
}
}
}
}
Edit: But of course, it is possible that if two sprites collide, the "collision event" will occur twice (first from the point of view of sprite A, and then from the point of view of sprite B).
If you only want the collision event to trigger once every check, you will need to memorize the pairs so that you can ignore collisions that already did happen on that check.
There are countless ways you could check for that, but here's an example (updated code):
Edited again:
NSMutableArray *pairs = [[NSMutableArray alloc]init];
bool collision;
for (CCSprite *first in mySprites) {
for (CCSprite *second in mySprites) {
if (first != second) {
if (CGRectIntersectsRect([first boundingBox], [second boundingBox])) {
collision = NO;
// A collision has been found.
if ([pairs count] == 0) {
collision = YES;
}else{
for (NSArray *pair in pairs) {
if ([pair containsObject:first] && [pair containsObject:second]) {
// There is already a pair with those two objects! Ignore collision...
}else{
// There are no pairs with those two objects! Add to pairs...
[pairs addObject:[NSArray arrayWithObjects:first,second,nil]];
collision = YES;
}
}
}
if (collision) {
// PUT HERE YOUR COLLISION CODE.
}
}
}
}
}
[pairs release];
Have a look at this S.O. answers.
You can do simple collision detection using CGRectIntersectsRect and the node boundingBox. If you need more advanced features, have a look at a physics engine like chipmunk or Box2D.
Ray Wenderlich has written a good tutorial about using Box2D just for collision detection, if you're interested in going that way. http://www.raywenderlich.com/606/how-to-use-box2d-for-just-collision-detection-with-cocos2d-iphone
Check first that your sprites can be approximated by rectangles. If so then #Omega's answer was great. If they can't be, perhaps because they contain a lot of transparency or for some other reason, you might need to approximate your sprites with polys and work with those.