I'm building a game using Apple's SpriteKit and SKPhysics that use squares that move around on the screen based on user input. I'm having an issue with collisions in that the squares will move out of place if they collide. For example, if all the blocks move to the right, any blocks that are on the same "row" need stack next to each other and not overlap or move position vertically. As of now, they will change their vertical direction. Here is my code:
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.size];
self.physicsBody.dynamic = YES;
self.physicsBody.allowsRotation = NO;
self.physicsBody.affectedByGravity = NO;
Are there any other settings that I'm missing?
The issue could be coming from your collisionBitMask category. In order to solve that, you need to first create categories for the blocks' physics bodies as follows:
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let block : UInt32 = 0b1
}
then set the blocks' settings to the following.
block.physicsBody?.categoryBitMask = PhysicsCategory.block
block.physicsBody?.collisionBitMask = PhysicsCategory.None
This should prevent the collision calculations from being automatically carried out by spritekit.
If you're moving your sprites via user inputs(i.g. SKAction's moveTo), then you're most likely not using physics to move your sprite. In this case, you should make the velocity of the physicsbody to 0- this will make the sprite completely rigid when it comes in contact with another object.
Try:
self.physicsBody.velocity = CGVectorMake(0, 0);
You should put this code inside your update loop.
Related
I know you can detect contact collisions using SKPhysicsContactDelegate, but can you check if a physicsbody is currently touching another physicsbody?
I need this for checking which area in the scene is still available to put an item (eg. pick a random spot, and if there's something in the way, pick another random spot).
There's this function:
/* Returns an array of all SKPhysicsBodies currently in contact with this one */
- (NSArray *)allContactedBodies;
But it doesn't appear to return anything useful until after the next update of creating the node.
You can write a function to iterate manually through all the nodes and check if the two circles intersect a point.
Since you said that the radius of the circles will differ each time, you have to keep track of it. One method is to use the user data of the node.
[node.userData setObject:[NSNumber numberWithFloat:10.0] forKey:#"radius"];
Then you can find if there are intersecting circles in the following way.
-(BOOL)checkPointForNode:(CGPoint)point withRadius:(CGFloat)nodeRadius
{
for (SKNode* child in [self children])
{
NSNumber *childRadius = child.userData[#"radius"];
if (childRadius != nil)
{
CGFloat diffX = point.x - child.position.x;
CGFloat diffY = point.y - child.position.y;
CGFloat distance = sqrtf(diffX * diffX + diffY * diffY);
CGFloat sumRadius = nodeRadius + childRadius.floatValue;
if (distance <= sumRadius)
{
return YES;
}
}
}
return NO;
}
The function returns YES if there is a circle within the boundary of the circle you are going to add. This means you cannot add a new node without touching another node. Otherwise it returns NO. This means you can add a new node without touching any other nodes.
I'm a long time web developer but this is the first time I have made a game or worked with Objective C. I really (really!) like working with Objective C so far and have gotten quite far on my game. Thanks in advance for your help!
I'm trying to make 3 spike balls that are SKSpriteNodes rotate in a circle around my hero ship (see image) after the hero gets a pickup. All looks and animates great until I add physics bodies to the balls, which are required so they can collide with monsters. I want the hero ship to be unaffected by the attached physics bodies so that the hero movement can remain smooth and constant – I'm moving the hero with:
[self.heroShip.physicsBody applyForce:CGVectorMake(moveByX, moveByY)];.
Here it is without the physics bodies. I'm attaching 3 children nodes to an anchor node, attaching the anchor node to the hero ship, and then rotating the anchor. The following animation looks exactly like I want it to.
SKSpriteNode * spikeballAnchor = [[SKSpriteNode alloc] init];
SKSpriteNode * spikeball1 = [SKSpriteNode spriteNodeWithImageNamed:#"spikeball"];
spikeball1.name = HERO_SPIKEBALL;
spikeball1.position = CGPointMake(100, 0);
[spikeballAnchor addChild:spikeball1];
SKSpriteNode * spikeball2 = [SKSpriteNode spriteNodeWithImageNamed:#"spikeball"];
spikeball2.name = HERO_SPIKEBALL;
spikeball2.position = CGPointMake(-50, 86);
[spikeballAnchor addChild:spikeball2];
SKSpriteNode * spikeball3 = [SKSpriteNode spriteNodeWithImageNamed:#"spikeball"];
spikeball3.name = HERO_SPIKEBALL;
spikeball3.position = CGPointMake(-50, -86);
[spikeballAnchor addChild:spikeball3];
[spikeballAnchor runAction:[SKAction repeatActionForever:[SKAction rotateByAngle:(M_PI_2 * 2) duration:1]]];
[self.heroShip addChild:spikeballAnchor];
But the spike balls need to collide with enemy monsters! When I add the following physics body to a spike ball it quickly flies out of orbit.
SKSpriteNode * spikeball1 = [SKSpriteNode spriteNodeWithImageNamed:#"spikeball"];
spikeball1.name = HERO_SPIKEBALL;
spikeball1.position = CGPointMake(100, 0);
spikeball1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spikeball1.size];
spikeball1.physicsBody.categoryBitMask = spikeballCategory;
spikeball1.physicsBody.contactTestBitMask = monsterCategory;
spikeball1.physicsBody.collisionBitMask = defaultCategory;
[spikeballAnchor addChild:spikeball1];
Ok. So I have tried just about everything I can think of and have scoured the web for a solution. Giving examples of everything I have tried would take a lot of space so I'll summarize.
I have tried making the anchor a physics body, pinning the spike balls to the anchor, and pinning the anchor to the hero. No luck.
I have tried creating a SKPhysicsJointLimit, SKPhysicsJointPin, and a SKPhysicsJointFixed between the anchor and the balls. In all the these rigs the physics of the spike balls throw the ship off. I have tried setting the mass of the anchor and the balls to zero. Strangely, even with no mass, they still seem to affect the ship's movement. Additionally I have tried setting friction, linearDamping, and angularDamping of the physics bodies to zero.
Also the joints only seem to work when I add the child nodes before setting the physics bodies.
The following is the closest/best result I have gotten (one spike ball in example) but I am unable to make the rotation of the balls not affect the ship! When I run this the ball does not rotate consistently (the ship's movement speeds up and slows down the rotation around the joint) and the movement of the ball throws the ship's movement off, creating an undesirable pendulum effect on the ship. This needs to work with 2 and 1 ball as they get destroyed.
SKSpriteNode * spikeballAnchor = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(10, 10)];
[self.heroShip addChild:spikeballAnchor];
spikeballAnchor.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spikeballAnchor.size];
spikeballAnchor.physicsBody.pinned = true;
spikeballAnchor.name = HERO_SPIKEBALL;
SKSpriteNode * spikeball1 = [SKSpriteNode spriteNodeWithImageNamed:#"spikeball"];
spikeball1.name = HERO_SPIKEBALL;
spikeball1.position = CGPointMake(100, 0);
[spikeballAnchor addChild:spikeball1];
spikeball1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spikeball1.size];
spikeball1.physicsBody.categoryBitMask = spikeballCategory;
spikeball1.physicsBody.contactTestBitMask = monsterCategory;
spikeball1.physicsBody.collisionBitMask = defaultCategory;
[spikeballAnchor runAction:[SKAction repeatActionForever:[SKAction rotateByAngle:(M_PI_2 * 2) duration:1]]];
SKPhysicsJointFixed * joint = [SKPhysicsJointFixed jointWithBodyA:spikeballAnchor.physicsBody bodyB:spikeball1.physicsBody anchor:spikeballAnchor.position];
[self.physicsWorld addJoint:joint];
I have considered trying to move the anchor and its children independently of the ship but I'm worried they will get out of sync and it will add a little more management to remove them when the hero dies if the anchor is not a child of the hero. But that's not a deal breaker if I can keep their position's perfectly in sync.
Sorry for being so verbose. Any help is most appreciated!
// Collision Bits
static const int defaultCategory = 0;
static const int projectileCategory = 1;
static const int spikeballCategory = 2;
static const int monsterCategory = 3;
static const int heroCategory = 4;
static const int pickupCategory = 5;
static const int boundariesCategory = 50;
Not an answer to a question so a bit offtopic, but I see another problem in your code: your categories are intersecting, because they are used as bitmasks. I mean, your monsterCategory includes spikeballCategory and projectileCategory, because in binary 3 = 1 | 2.
monsterCategory 3 0b00000011
projectileCategory 1 0b00000001
spikeballCategory 2 0b00000010
So you might notice your spikeballs can act like monsters and vice versa, and projectiles can act like monsters and vice versa.
This is what I got so far:
enterName = [CCTextField textFieldWithSpriteFrame:[CCSpriteFrame frameWithImageNamed:#"Tile.png"]];
enterName.fontSize = 16.0f;
enterName.positionType = CCPositionTypeNormalized;
enterName.position = ccp(0.5f, 0.5f);
enterName.scale = 0.70f;
[self addChild:enterName z:5];
Basically, it creates a microscopic text field which you can barely click on. The only method that pops up for CCTextField is "textFieldWithSpriteImage."
Bonus help: How to store the string that was typed after enter.
Cheers.
In order to prevent microscopic CCTextFields in Cocos2d, make sure to set preferredSize.
Here is the code which prevents a microscopic textfield:
CCSprite *textSprite = [CCSprite spriteWithImageNamed:#"Tile.png"];
enterName = [CCTextField textFieldWithSpriteFrame:[CCSpriteFrame frameWithImageNamed:#"Tile.png"]];
enterName.fontSize = 16.0f;
enterName.contentSize = CGSizeMake(100.0f, 50.0f);
enterName.preferredSize = textSprite.contentSize; // don't forget this !
enterName.positionType = CCPositionTypeNormalized;
enterName.position = ccp(0.5f, 0.5f);
[self addChild:enterName z:5];
If you are using Cocos2d there are other ways to incorporate text fields. The most convenient way is Spritebuilder, similar to Storyboard. You can edit the font size, and size of text box without using code. Then you just adjust the Textfield so that you can connect it to a variable. To do this you change code connections.
I usually use this as the final option in code connections:
Doc root var: _whatever
Then in MainScene.m (automatically created by Spritebuilder in Xcode) you write this code and add on:
CCTextField * _whatever
//your code
Publish and run and the Textfield should be clickable.
I'm pretty new to iOS and cocos2d and I'm having a problem trying to create what I want the code to do. Let me give you the rundown first then i'll show what I've got.
What I got so far is a giant sprite in the middle and when that is touched, I want to have say 2000 of a different sprite generate from the center position and like a particle system, shoot off in all directions.
First off, I tried coding implementing the velocity code (written in Objective-c) over to Cocos2d and that didn't work. -code-
-(void)ccTouchBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(CGRectContainsPoint([[self getChildByTag:1] boundingBox], location))
{
for( int i = 0; i < 100; i++)
{
CCSprite *ballGuySprite = [CCSprite spriteWithFile:#"ball.png"];
[self addChild:ballGuySprite z:7];
ballGuySprite.position = ccp(((s.width + i *10) /2), (s.height + i *10) /2);
}
}
}
What that does is when I touch the first sprite, 100 of the other sprites are on top of each other leading to the top right corner.
The velocity code that I used when as followed and when I try to apply it to the sprite nothing happens. - Velocity code -
-(void) checkCollisionWithScreenEdges
{
if(ballGuysRect.origin.x <= 0)
{
ballVelocity.x = abs(ballVelocity.x);
}
if(ballGuysRect.origin.x >= VIEW_WIDTH - GUY_SIZE)
{
ballVelocity.x = -1 * abs(ballVelocity.x);
}
if(ballGuysRect.origin.y <= 0)
{
ballVelocity.y = abs(ballVelocity.y);
}
if(ballGuysRect.origin.y >= VIEW_HEIGHT - GUY_SIZE)
{
ballVelocity.y = -1 * abs(ballVelocity.y);
}
}
-(void) updateModelWithTime:(CFTimeInterval)timestamp
{
if(lastTime == 0.0)
{
lastTime = timestamp;
}
else
{
timeDelta = timestamp - lastTime;
lastTime = timestamp;
ballGuysRect.origin.x += ballVelocity.x * timeDelta;
ballGuysRect.origin.y += ballVelocity.y * timeDelta;
[self checkCollisionWithScreenEdges];
}
}
When I attach that code to the sprite, nothing happen.
I also tried adding a CCParticleExplosion which did do what I wanted but I still want to add a touch function to each individual sprite that's generated and they tend to just fade away.
So again, I'm still fairly new to this and if anyone could give any advice that would be great.
Thanks for your patients and time to read this.
Your code looks good to me, but you never seem to update the position of your sprites. Somewhere in updateModelWithTime I would expect you to set ballGuySprite.position = ballGuysRect.origin plus half of its height or width, respectively.
Also, I don't see how updateModelWithTime can control 100 different sprites. I see only one instance of ballGuysRect here. You will need a ballGuysRect for each sprite, e.g. an array.
Finally, I'd say that you don't really need ballGuysRect, ballVelocity, and the sprite. Ball could be a subclass of CCSprite, including a velocity vector. Then all you need to do is keep an array of Balls and manage those.
I am not sure what version of cocos2d you are using but a few things look a bit odd.
Your first problem appears to be that you are just using the same sprite over and over again.
Since you want so many different sprites shooting away, I would recommend that you use a CCSpriteBatchNode, as this should simplify things and speed things up.
The following code should help you get that set up and move them offscreen with CCMoveTo:
//in your header file:
CCSpriteBatchNode *batch;
//in your init method
batch = [CCSpriteBatchNode batchNodeWithFile:#"ball.png"];
//Then in your ccTouches method
for( int i = 0; i < 100; i++)
{
CCSprite *ballGuySprite = [CCSprite spriteWithFile:#"ball.png"];
[batch addChild:ballGuySprite z:7 tag:0];
ballGuySprite.position = ccp(where-ever the center image is located);
id actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(random off screen location)];
[ballGuySprite runAction:actionMove];
}
Also usually your update method looks something like the following:
-(void)update:(ccTime)delta{
//check for sprites that have moved off screen and disable them.
}
Hope this helps.
I'm trying to move all sprites with the same tag some inches to the right.
I have tried 4 different type of expressions in order to do so, but nothing worked. Here is what i ve done so far...
-(void) moveSprites {
NSArray* spritesWithTag = [lh spritesWithTag: BOXES1]
for (LHSprite* spr in spritesWithTag)
(NSLog (#"name is %#", [spr uniqueName]);
CGPoint newposition = ccpSub (ccp(-50,0), [spr position]);
//generating the new position for the elements
[spr transformPosition: newposition];
//first attemp, should work but for some reason it doesn't work
spr.position = newposition;
//2nd attemp
b2Vec2 newpositionVector (newposition.x, newposition.y);
[spr body]->SetTransform(newpositionVector, 0);
//third try
[spr setPosition:newposition];
//last form
}
When i run the app the method call works fine and all sprites with tag BOXES1 appear in the output tab, but its position hasn't changed at all. Any idea over why is it happening. What did I wrong? Is there any other way to move an sprite or are them prevented from moving in some other form i dont know? Those are static sprites, dont know if this affects... thanks!