I'm trying to create a game with blocks that can be moved around they all belong to the same categoryBitMask. The didBeginContact does not fire unless I set the physicsBody to dynamic. How can I detect collision between two blocks that belong to the same category?
Code for generating blocks:
float blkX = 34;
float blkY = 64;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 6; j++){
block = [SKSpriteNode spriteNodeWithImageNamed:blocksArray[(arc4random()%5)]];
block.position = CGPointMake(blkX, blkY);
block.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:block.size];
block.physicsBody.usesPreciseCollisionDetection = YES;
block.physicsBody.dynamic = NO;
block.physicsBody.categoryBitMask = blockCategory;
block.physicsBody.contactTestBitMask = blockCategory;
block.physicsBody.collisionBitMask = blockCategory;
[block setName:#"block"];
[_bgLayer addChild:block];
blkX += 48;
}
blkX = 34;
blkY += 48;
}
I am using the SKPhysicsContactDelegate and have the following two lines in my init method:
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0);
My didBeginContact method:
-(void)didBeginContact:(SKPhysicsContact *)contact{
SKPhysicsBody *firstBody, *secondBody;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
NSLog(#"We Collided!");
}
Any help appreciated. Thanks!
I did come up with something that does work for those in a similar situation:
In my touchesBegan method I set the block being moved to be Dynamic which does fire the didBeginContact. Here is my touchesBegan method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.selectedNode = [self nodeAtPoint:[[touches anyObject] locationInNode:self]];
[self.selectedNode.physicsBody setDynamic:YES];
}
Then you turn it off again in the touchesEnded method.
I also set the block.physicsBody.collisionBitMask = 0 which allows the block to move through other blocks. If this helps at least one other person then it was worth asking the question!
Related
I've been trying to run the SKTexture change from a node on touch, said node is created with a loop:
int nameIndex = 0;
for (int i = 1; i <= 3; i++) {
self.card = [SKSpriteNode spriteNodeWithImageNamed:#"front"];
NSString *nodeName = [NSString stringWithFormat:#"node%d", nameIndex];
self.card.name = nodeName;
self.card.position = CGPointMake((((board-self.card.size.width)/3)*i), y);
[self addChild:self.card];
nameIndex++;
}
In the touches began method I have:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
for (SKNode *node in nodes) {
if ([node.name isEqualToString:#"node0"]) {
//Change the texture for the given node:
NSLog(#"Node 0");
[self.card setTexture:[SKTexture textureWithImageNamed:nameIndex]];
}
if ([node.name isEqualToString:#"node1"]) {
NSLog(#"Node 1");
}
if ([node.name isEqualToString:#"node2"]) {
NSLog(#"Node 2");
}
}
The touches began method works just fine, it runs the block as soon as one sprite is touched
however, as expected with the current code the texture change only occurs in the last created sprite. I've been looking for a way to run a method on a sprite with X given name, but I haven't fun a way.
Thanks in advance!
Turns out I needed to use the childNodeWithName instead of trying to force a conditional on the .name property
SKSpriteNode *card = (SKSpriteNode*)[self childNodeWithName:#"node0"];
[card setTexture:[SKTexture textureWithImageNamed:#"aTexture"]];
Not sure if this is a helpful question, mods feel free to erase if needed.
I am creating a game where a ball is suppose to bounce off from platforms. I have set up physics properties for the ball and the platform(platform only attains physics property when it's below the ball). My problem is: the ball is not bouncing (I have applied impulse in didbegincontact method) when the ball makes contact with the platform, it however detects contact.
Here is my didBeginContact Code:
- (void) didBeginContact:(SKPhysicsContact *)contact {
SKSpriteNode *firstNode, *secondNode;
firstNode = (SKSpriteNode*) contact.bodyA.node;
secondNode = (SKSpriteNode*) contact.bodyB.node;
if ((contact.bodyA.categoryBitMask == ballCategory) && (contact.bodyB.categoryBitMask == solidPlatformCategory)) {
NSLog(#"Platform Hit");
CGPoint contactPoint = contact.contactPoint;
[_ball.physicsBody applyImpulse:CGVectorMake(0, 4) atPoint:contactPoint];
}
}
///// Here is the code for SKSpriteNode Ball
- (void) addBall {
_myBall = [SKSpriteNode spriteNodeWithImageNamed:#"ball.png"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
_myBall.scale = 0.4;
} else {
_myBall.scale = 0.3;
}
_ball.position = CGPointMake(self.frame.size.width/2, _solidPlatform.position.y + 2.5*_ball.size.height);
_ball.zPosition = 2;
_ball.name = #"doodle";
_ball.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_myDoodle.frame.size];
_ball.physicsBody.mass = 1.0;
_ball.physicsBody.restitution = 0.8;
_ball.physicsBody.dynamic = YES;
_ball.physicsBody.allowsRotation = NO;
_ball.physicsBody.usesPreciseCollisionDetection = YES;
_ball.physicsBody.categoryBitMask = ballCategory;
_ball.physicsBody.collisionBitMask = solidPlatformCategory;
_ball.physicsBody.contactTestBitMask = solidPlatformCategory;
//SKAction *moveUpAction = [SKAction moveByX:0.0 y:8*numberOfPlatforms duration:0.5];
[self addChild:_ball];
}
////Platform has been defined as (not a complete code):
_solidPlatform7.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_solidPlatform7.frame.size];
_solidPlatform7.physicsBody.dynamic = NO;
_solidPlatform7.physicsBody.affectedByGravity = NO;
_solidPlatform7.physicsBody.usesPreciseCollisionDetection = YES;
_solidPlatform7.physicsBody.categoryBitMask = solidPlatformCategory;
PS: I am not getting any collusion detection if I define platform as bodyWithEdgeFromRect
difficult to answer without seeing some code, but I'll try:
If the ball does not bounce at all, check the restitution property. Higher values provide a higher "bounciness":
ball.physicsBody.restitution=0.8;
If you want the ball to bounce endless between bottom and ceiling you can invert the gravity after each collision:
self.physicsWorld.gravity = CGVectorMake(0, self.physicsWorld.gravity.dy * (-1));
Hope that helps. If not, please share some code.
I've tried your code. With some smaller changes it works:
- (void) addBall {
// Bottom platforms
for (int i=0; i<10; i++) {
SKSpriteNode *mySprite = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(40, 20)];
CGPoint location = CGPointMake(i*40+60, 10);
//mySprite.size =CGSizeMake(20, 40);
mySprite.position=location;
mySprite.physicsBody=[SKPhysicsBody bodyWithRectangleOfSize:mySprite.size];
mySprite.physicsBody.dynamic=false;
mySprite.physicsBody.categoryBitMask=solidPlatformCategory;
[self addChild:mySprite];
}
_ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball.png"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
_ball.scale = 0.4;
} else {
_ball.scale = 0.3;
}
_ball.position = CGPointMake(self.frame.size.width/2, self.frame.size.width/2);
_ball.zPosition = 2;
_ball.name = #"doodle";
//_ball.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_myDoodle.frame.size];
_ball.physicsBody=[SKPhysicsBody bodyWithCircleOfRadius:_ball.size.width/2];
_ball.physicsBody.mass = 1.0;
_ball.physicsBody.restitution = 1;
_ball.physicsBody.dynamic = YES;
_ball.physicsBody.allowsRotation = NO;
_ball.physicsBody.usesPreciseCollisionDetection = YES;
_ball.physicsBody.categoryBitMask = ballCategory;
_ball.physicsBody.collisionBitMask = solidPlatformCategory;
_ball.physicsBody.contactTestBitMask = solidPlatformCategory;
//SKAction *moveUpAction = [SKAction moveByX:0.0 y:8*numberOfPlatforms duration:0.5];
[self addChild:_ball];
}
#import "collisionTestMyScene.h"
const static int nodeBitMask = 0x1 << 0;
const static int node1BitMask = 0x1 << 1;;
#implementation collisionTestMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.physicsWorld.contactDelegate = self;
w = 0;
}
return self;
}
-(void) didBeginContact:(SKPhysicsContact *)contact {
NSLog(#"Contact Begin");
if (contact.bodyA.categoryBitMask == nodeBitMask) {
NSLog(#"Node is Body A");
}
if (contact.bodyA.categoryBitMask == node1BitMask) {
NSLog(#"Node is Body B");
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
node = [SKSpriteNode spriteNodeWithImageNamed:#"block.jpg"];
node.position = location;
[node setScale:0.07];
node.physicsBody.contactTestBitMask = node1BitMask;
node.physicsBody.categoryBitMask = nodeBitMask;
node.physicsBody.collisionBitMask = nodeBitMask;
//node.physicsBody.collisionBitMask = 0;
node1 = [SKSpriteNode spriteNodeWithImageNamed:#"block2.jpg"];
node1.position = CGPointMake(200, 200);
node1.physicsBody.categoryBitMask = node1BitMask;
node1.physicsBody.contactTestBitMask = nodeBitMask;
node1.physicsBody.collisionBitMask = node1BitMask;
//node1.physicsBody.collisionBitMask = 0;
[node1 setScale:0.07];
[self addChild:node];
[self addChild:node1];
node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(node.size.width, node.size.height)];
node1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(node1.size.width, node1.size.height)];
SKAction *moveUp = [SKAction moveToX:100 duration:3];
node1.physicsBody.affectedByGravity = NO;
node.physicsBody.affectedByGravity = NO;
[node1 runAction:moveUp];
w = 1;
}
}
It is never NSLogging anything. I have tried changing the bit masks, and more. The CGRectIntersects function would work, however it is not accurate enough. Also, the two nodes are in perfect box shapes. What could I be doing wrong? Thank you in advance!
The problem here are the bit masks. The two nodes are in different category, contact and collision groups (bit masks). Therefore they will not contact nor collide because the bitmasks are compared with AND and only if the result is non-zero will the contact/collision occur.
In short, put them in the same contact bit mask at the least, in order to receive didBeginContact messages.
i have tested my code by changing the starting lives vaule, the problem is it doesn't remove them as the statement becomes valid, How do i fix this? I have tried placing it in my .m file but it doesn't seem to work properly anywhere, any ideas on where it would go? I would post the .m but it is about 500 lines so it is a bit big so i just pasted the relevant bit of it. also i am a 15 year old, and i am fairly new to cocos2d development
The Code
- (void) addMonster {
CCSprite * monster = [CCSprite spriteWithFile:#"startH.png"];
// Determine where to spawn the monster along the Y axis
CGSize winSize = [CCDirector sharedDirector].winSize;
int minY = monster.contentSize.height / 2;
int maxY = winSize.height - monster.contentSize.height/2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
// Create the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster.position = ccp(winSize.width + monster.contentSize.width/2, actualY);
[self addChild:monster];
// Determine speed of the monster}
if (Strategyscore < 10) {
int minDuration = 5.0;
int maxDuration = 10.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
eate the actions
CCMoveTo * actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(-monster.contentSize.width/2, actualY)];
CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
[node removeFromParentAndCleanup:YES];
[_monsters removeObject:node];
Life--;
CCSprite *Life3 = [CCSprite spriteWithFile:#"heart.png"];
Life3.position = ccp(210,200);
CCSprite *Life2 = [CCSprite spriteWithFile:#"heart.png"];
Life2.position = ccp(220,200);
CCSprite *Life1 = [CCSprite spriteWithFile:#"heart.png"];
Life1.position = ccp(230,200);
[self addChild:Life3];
[self addChild:Life2];
[self addChild:Life1];
if(Life == 2) {
[self removeChild:Life3];
}
else if(Life == 1) {
[self removeChild:Life2];
[self removeChild:Life3];
}
else if(Life <= 0) {
[self removeChild:Life1];
[self removeChild:Life2];
[self removeChild:Life3];
// Cr [[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[MainMenu scene]]];
}
}];
[monster runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
//collision stuff
monster.tag = 1;
[_monsters addObject:monster];
}
Also the .h file
int StrategyBullet;
int Strategyscore;
int high;
int Life;
CCLabelTTF *highlabel;
CCLabelTTF *StrategyBulletLabel;
CCLabelTTF *StrategyscoreLabel;
#interface Strategy: CCLayer
{
NSMutableArray * _monsters;
NSMutableArray * _projectiles;
int _monstersDestroyed;
}
+(CCScene *) scene;
#end
Every time you add a new monster, you add a new set of sprites Life1,Life2, and Life3, superimposed on the previous ones. You probably want to have a single set of life hearts.
in .h
CCSprite *Life1,*Life2,*Life3;
in .m, init method
Life3 = [CCSprite spriteWithFile:#"heart.png"];
Life3.position = ccp(210,200);
Life2 = [CCSprite spriteWithFile:#"heart.png"];
Life2.position = ccp(220,200);
Life1 = [CCSprite spriteWithFile:#"heart.png"];
Life1.position = ccp(230,200);
[self addChild:Life1];
[self addChild:Life2];
[self addChild:Life3];
and in your actionMoveDone call block, dont remove them, just make them not visible
CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
[node removeFromParentAndCleanup:YES];
[_monsters removeObject:node];
Life--;
if(Life == 2) {
Life3.visible=NO;
}
else if(Life == 1) {
Life3.visible=NO;
Life2.visible=NO;
}
else if(Life <= 0) {
Life3.visible=NO;
Life2.visible=NO;
Life1.visible=NO;
}
}];
for starters. I just made this as 'like your coding style' as possible, but eventually you will find different patterns to do this as you game becomes more complex. Read about normal iOS code and naming conventions, it will help you and also make your code samples more palatable for the people trying to help you here.
Where are you montering the lives value ? In a tick method ?
if(Life == 2) {
[self removeChild:Life3];
}
else if(Life == 1) {
[self removeChild:Life2];
[self removeChild:Life3];
}
else if(Life <= 0) {
[self removeChild:Life1];
[self removeChild:Life2];
[self removeChild:Life3];
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[MainMenu scene]]];
}
I have created an array of enemies and when I add an enemy I add the appropriate box2d code, however I have found that none of my enemies can be touched, I am not sure what is causing this but from what I can tell it never returns a fixture.
I have tried setting the user data but then I do not get multiple items.
This is how I add my sprite etc
for (int i = 0; i < EnemyType_MAX; i++)
{
CCArray* enemiesOfType = [enemies objectAtIndex:i];
int numEnemiesOfType = [enemiesOfType capacity];
for (int j = 0; j < numEnemiesOfType; j++)
{
EnemyEntity* enemy = [[EnemyEntity alloc]init:_gameScene enemyType:EnemyTypeBreadman];
[batch addChild:enemy z:0 tag:i];
[enemiesOfType addObject:enemy];
[allEnemies addObject:enemy];
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(self.position.x/PTM_RATIO, self.position.y/PTM_RATIO);
bodyDef.userData = self;
b2Body *body = _gameScene.world->CreateBody(&bodyDef);
b2CircleShape circle;
circle.m_radius = 26.0/PTM_RATIO;
// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &circle;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
body->CreateFixture(&fixtureDef);
}
}
I then use my touch handler to try and return what item has been touched
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//NSLog(#"ccTouchesBegan %#", (_mouseJoint!= NULL) ? #"YES" : #"FALSE" );
if (_gameScene.mouseJoint != NULL) return;
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
float move = 0.0f, x1, y1, z1;
[_gameScene.camera centerX:&x1 centerY:&y1 centerZ:&z1];
b2Vec2 locationWorld = b2Vec2((location.x+x1)/PTM_RATIO, (location.y+y1)/PTM_RATIO);
NSLog(#"ccTouchesBegan %#",NSStringFromCGPoint(location));
b2AABB aabb;
aabb.lowerBound.Set(-1.0f+locationWorld.x, -1.0f+locationWorld.y);
aabb.upperBound.Set(1.0f+locationWorld.x, 1.0f+locationWorld.y);
b2Vec2 callPoint;
callPoint.Set (locationWorld.x,locationWorld.y);
QueryCallback callback(callPoint);
_gameScene.world->QueryAABB(&callback, aabb);
b2Body* nbody = NULL;
if (callback.m_fixture)
{
nbody= callback.m_fixture->GetBody();
}
if (nbody)
{
b2BodyDef bodyDef;
b2Body* groundBody = _gameScene.world->CreateBody(&bodyDef);
b2MouseJointDef md;
md.bodyA = groundBody;
md.bodyB = nbody;
md.target = locationWorld;
#ifdef TARGET_FLOAT32_IS_FIXED
md.maxForce = (nbody->GetMass() < 16.0)? (1000.0f * nbody->GetMass()) : f loat32(16000.0);
#else
md.maxForce = 1000.0f * nbody->GetMass();
#endif
_gameScene.mouseJoint = (b2MouseJoint *)_gameScene.world->CreateJoint(&md);
nbody->SetAwake(true);
}
}
In your init method, right after the if statement, is this in your code:
if(self = [super init]){
self.isTouchEnabled = YES;
EDIT------------------
Instead of using ccArray, you should use this:
CCSprite *_anArray[x];
When I deal with sprites I always put them in a sprite array, I declared it in the header. You also have to do the #property(nonatomic, retain) NSMutableArray *arrowArray; in the .h file and in the .m #synthesize arrowArray = _arrowArray;
Then I just added all my sprites into that array. Should work.