SpriteKit Fixed Joints - ios

I have a SKSpriteNode called "SpikyRedBall" which is a red ball. I wanted to add spikes to it so I used the following code. I can see the Spike attached to the ball but when the ball collides with another ball it does not take the fixed joints into consideration and moves them separately. I am using the following implementation:
#implementation SpikyRedBall
-(instancetype) init
{
self = [super init];
[self attachSpikes];
return self;
}
-(void) attachSpikes
{
Spike *spike = [[Spike alloc] init];
spike.position = CGPointMake(0, 0);
// attach the joint
SKPhysicsJointFixed *ballAndSpikeJointFixed = [SKPhysicsJointFixed jointWithBodyA:self.physicsBody bodyB:spike.physicsBody anchor:CGPointZero];
[self.scene.physicsWorld addJoint:ballAndSpikeJointFixed];
[self addChild:spike];
}
#end

It sounds like you don't have collision or contact categories setup for the spikes themselves. I would try setting all physicsBody properties on the spikes to be identical to those of the balls, but obviously ensuring that they don't have collision or contact categories setup in a way that they would collide with their own parent ball.

If you can require iOS 7.1, you could use +bodyWithBodies: instead of attaching any joints.

Why don't you just add the spikes to the sprite image? If they need to disappear or fall off you can just create multiple versions of the image without spikes.

Related

Cocos2d Sprite collision detection during action

I am unable to detect collision between two sprites, one moving in action.
I have a sprite of class "Enemy", moving across the screen via CCMoveTo, and another sprite of class "Hero", controlled by touch, both added onto a scene on class "MainGame"
The following indicates how the two sprites are added onto the scene and Enemy actioned:
MainGame.m
-(id) init{
if( (self=[super init]) ) {
_enemies = [[NSMutableArray alloc]init];
_enemyLink = [Enemy nodeWithTheGame:self];
_enemyLink.position = ccp(10, 10);
[self.enemies addObject:_enemyLink];
CCMoveTo *test = [CCMoveTo actionWithDuration:40 position:ccp(500, 250)];
[_enemyLink runAction:test];
_heroArray = [[NSMutableArray alloc]init];
_heroLink = [Hero nodeWithTheGame:self location:ccp(100,100)];
[_heroArray addObject:_heroLink];
[self scheduleUpdate];
}
}
-(void)update:(ccTime)delta{
if (CGRectIntersectsRect([self.enemyLink.enemySprite boundingBox], [self.heroLink.heroSprite boundingBox])) {
NSLog(#"rect intersects rect");
}
for (CCSprite *enemies in self.enemies) {
NSLog(#"enemy position: %f, %f",enemies.position.x,enemies.position.y);
}
}
I am unable to detect collision between these two sprites during and after the action. However if I move the Hero to the position on the scene (0,0) the log in my code will trigger, and the two will engage in attacks.
The for loop in the update indicates that the Enemy sprite position is constantly moving during the action. Hence why I am stumped as to why the collision is not being detected.
Have you checked that both enemy and hero positions are tested within the same coordinates space?
This is because boundingBox() is local, relative to its parent and if the nodes you compare do not have the same parent, the check will be invalid.
You can use convertToNodeSpace()/convertToWorldSpace() prior to checking bounding boxes.
Another answer that may be relevant to your case here: A sprite bounding box shows wrong position after moving the layer.

Why does creating and removing SKShapeNode and SKNode repeatedly cause a memory leak?

Using the sprite kit template that comes with Xcode, I modify the scene to be as follows :
#import "MyScene.h"
#interface MyScene ()
#property (nonatomic,strong)SKNode *floor;
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
}
return self;
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
[self removeAllChildren];
self.floor = nil;
self.floor = [SKNode node];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, 0, 10);
for(int i = 2; i<self.frame.size.width; i+=2)
{
CGPathAddLineToPoint(path, nil, i, 10);
}
self.floor.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:path];
SKShapeNode *shape = [SKShapeNode node];
shape.path = path;
shape.strokeColor = [UIColor redColor];
[self.floor addChild:shape];
[self addChild:self.floor];
CGPathRelease(path);
}
#end
The app seems to keep using more memory, until it either hangs or crashes (after reaching about 180MB). Using the leaks and allocations tools, I have found the following:
Leaks:
Allocations:
As can be seen from the images, there are a large number of Malloc calls using memory. I do not call Malloc directly - it seems these calls are made by SpriteKit. Likewise, there are a number of memory leaks, which also seem to be due to SKShapeNode, SKNode or other Sprite Kit objects.
How do I work around or solve this memory(leak) problem? I have to create SKShapeNodes, and SKNodes every frame. This code is just a sample to illustrate the problem - my actual project is much more complex with dynamically generated paths (not static like the one in this example).
This is a bug in sprite kit. The problem isn't just with SKShapeNode, it is also with SKPhysicsBody.
Creating either a physics body, or a shape, using a CGPath causes a memory leak.
You can verify this by commenting out either the physics body, or the shape, and running instruments with leaks, allocations and memory monitor.
Even if you release the path properly, sprite kit doesn't internally. Nothing you can do!
Fire up instruments, and watch the memory grow! XD
EDIT: This bug was present in iOS 7.0. Whether it has been fixed in 7.1 or later is not known to me as I stopped using SpriteKit because of this bug. One can verify if it is fixed by simply testing it.
I became aware of this issue while reading another post. I messed around with SKShapeNode a bit and indeed verified the memory leak issue pinpointed here.
While doing that, I had an idea...
Not really a new idea, more of a repurposed one. This wonderful idea actually allowed me to use SKShapeNodes to my hearts content :)
POOLING
Yep... I just created a pool of SKShapeNodes that I reused as needed. What a difference that makes :)
You simply redefine the path whenever needed, when done using return to your pool, and it'll be waiting there for you to play with again at a later time.
Create a ivar or property NSMutableArray in your SKScene called pool and create it when you init the SKScene. You can either populate the array with your shape nodes during init, or you can create them as needed.
This is something quick method I created for grabbing a new node from the pool :
-(SKShapeNode *)getShapeNode
{
if (pool.count > 0)
{
SKShapeNode *shape = pool[0];
[pool removeObject:shape];
return shape;
}
// if there is not any nodes left in the pool, create a new one to return
SKShapeNode *shape = [SKShapeNode node];
return shape;
}
So wherever in the scene you need a SKShapeNode you'd do this :
SKShapeNode *shape = [self getShapeNode];
// do whatever you need to do with the instance
When you are done using the shape node, just return it to the pool and set the path to . For example :
[pool addObject:shape];
[shape removeFromParent];
shape.path = NULL;
I know it's a workaround and not an ideal solution, but certainly this is a very viable workaround for anyone wanting to use a large number of SKShapeNodes and not bleed memory.
I'm using iOS 7.1.1 and had the same problem. However, by setting the SKShapeNode's path property to nil before removing a shape from a parent did fix this issue - no more leaks.
I've the same problem.
Scene added one compound node (SKShape with SKSprite) then I removed this node from scene and added again(I got leak two nodes).
My leak is fixed with recreation compound node after remove from scene

Collision detection in Sprite Kit

I'm using collision detection in Sprite Kit. It is working and preventing my sprites from crossing paths. However, I'm not getting notifications in didBeginContact: and I don't seem to have any control over how the physics engine responds when a collision occurs.
I have various cars (SKSpriteNodes) moving around following paths using the SKAction followPath:asOffset:orientToPath:duration:
Previously, if two cars crossed paths they would both just continue as normal with one driving over the other. To implement collision detection I have made the following changes...
Added this to my #interface:
<SKPhysicsContactDelegate>
Added this to my #implementation:
static const uint32_t carCategory = 0x1 << 0;
Added this in my init method:
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0);
I create my blue car:
- (void)addBlueCar
{
_blueCar = [SKSpriteNode spriteNodeWithImageNamed:#"Blue Car.png"];
_blueCar.position = CGPointMake(818.0, -50.0);
_blueCar.name = #"car";
[self addChild:_blueCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
_blueCar.physicsBody.categoryBitMask = carCategory;
_blueCar.physicsBody.collisionBitMask = carCategory;
_blueCar.physicsBody.contactTestBitMask = carCategory;
}
And I also create a red car:
- (void)addRedCar
{
_redCar = [SKSpriteNode spriteNodeWithImageNamed:#"Red Car.png"];
_redCar = CGPointMake(818.0, -50.0);
_redCar = #"car";
[self addChild: _redCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_redCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
_redCar.physicsBody.categoryBitMask = carCategory;
_redCar.physicsBody.collisionBitMask = carCategory;
_redCar.physicsBody.contactTestBitMask = carCategory;
}
There are other cars as well, but I'm just using these two until I figure out what the problem is. I want to be notified of any collision between any two cars. That's why I'm just using a single category, carCategory.
The first problem is that I get no notifications. I have this yet it never logs anything to the console:
- (void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"Contact");
}
The second problem is that when two cars cross paths they start nudging each other off course. This is ok, but I don't seem to able to manage any aspects of how the collision is handled. The bizarre thing is that nothing changes when I do this to both of my car creation methods:
- (void)addBlueCar
{
_blueCar = [SKSpriteNode spriteNodeWithImageNamed:#"Blue Car.png"];
_blueCar.position = CGPointMake(818.0, -50.0);
_blueCar.name = #"car";
[self addChild:_blueCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
// _blueCar.physicsBody.categoryBitMask = carCategory;
// _blueCar.physicsBody.collisionBitMask = carCategory;
// _blueCar.physicsBody.contactTestBitMask = carCategory;
}
Collision detection still works the same with those three lines commented out for both cars. This just doesn't seem right to me. Collision detection only ceases when I also comment out this line:
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
So my main question is: why is the contactTest not reporting anything? My second question is: why is collision detection happening when I don't assign a categoryBitMask, collisionBitMask or contactTestBitMask to any car?
collisionBitMask's default value is 0xFFFFFFFF (all bits set). Therefore the node will collide with each physicBody on the scene.
Just a small observation, perhaps you should setup your nodes and set the contact delegate in the scene's didMoveToView method.
If that doesn't solve it, any chance you can post some working code to github for debugging?
Something that helped me with this: Collisions and contacts are different things.
Collision is handled by the physics engine and setting collisionBitMask will determine which bodies collide and interact with each other in the physics world. By default, all your bodies will collide with each other, which kinda makes sense.
Contacts are events- when bodies make contact, will they create a notification that you can use in the delegate method to do more interesting things. If you have a ball you want to hit with a paddle, you want them to collide at a minimum. But if you have a ball moving through a cloud, you probably don't want them to collide in the physics world, but you might want to register the contact.

Cocos2D, animated sprites blur

I've returned to using Cocos2d after an 18 month gap. My initial source for guidance has been the learn Cocos2d book by Steffen Itterheim and Andreas Low.
I have to say I've found the guide rather intuitive to follow but have hit a couple of issues.
The first is the animation of the sprites. I am without a doubt using the HD sprites correctly (this has been proved by renaming the HD sprite and getting warnings that the HD sprite atlas isn't available).
When the sprites animate, they blur. At first I thought this was a simulator issue, however, while testing on my iPhone 5 and my partners iPhone 4, the same blurring is experienced. The method I have used for updating the graphics is as follows: -
-(void) update:(ccTime)delta
{
if (self.parent.visible)
{
CGSize screenSize = [CCDirector sharedDirector].winSize;
if (self.parent.position.x > - 50)//screenSize.width * 0.5f)
{
self.parent.position = ccpAdd(self.parent.position, ccpMult(velocity, delta));
}
else
{
self.parent.visible = NO; // We've gone off screen so set to invisible.
}
}
}
This is invoked with the following method : -
-(id) initWithRate:(int)rate
{
if ((self = [super init]))
{
velocity = CGPointMake(rate, 0); //-100
[self scheduleUpdate];
}
return self;
}
This is used within a "StandardMoveComponent" class, which is added to the sprite node. I should add, the graphics all come from 2 sprite sheets. They're in separate batch notes, but added to the same frameCache.
If anyone out there has any suggestions what I can do to prevent the blurring, I'd be very grateful.

Cocos2D - Better to keep array of CGRects or array of CCSprites to track hit areas?

I am putting together a simple board game in cocos2d to get my feet wet.
To track user clicks I intend to listen for click on game squares, not the game pieces to simplify tracking game pieces. It would be 8x8 board.
Is it more efficient to:
A. Make an array of CGRects to test against and need to put the struct in an NSObject before adding to array. Seams simple but looks like a lot of work going into access the CGRects every time they are needed.
or
B. Make actual CCSprites and test against their bounding rectangle. Simple to code, but that there are an extra 64 unneeded visual objects on the screen, bloating memory use.
or even
C. Some other method, and I am fundamentally misunderstanding this tool.
I agree it seems unnecessary to create a sprite for each game square on your board if your board is totally static.
However CGRect is not an object type so it can not be added to an NSMutableArray, and I will also assume that at some point you will want to do other things with your game square, such as highlighting them and other stuff. What I suggest you do is that you create a class called GameSquare that inherits from CCNode and put them into an array:
// GameSquare.h
#interface GameSquare : CCNode {
//Add nice stuff here about gamesquares and implement in GameSquare.m
}
After that, you can create gamesquares as nodes:
// SomeLayer.h
#interface SomeLayer : CCLayer {
NSMutableArray *myGameSquares;
GameSquare *magicGameSquare;
}
#property (nonatomic, strong) GameSquare *magicGameSquare;
// SomeLayer.m
/* ... (somewhere in the layer setup after init of myGameSquares) ... */
GameSquare *g = [[GameSquare alloc] init];
g.position = CGPointMake(x,y); //replace x,y with your coordinates
g.size = CGSizeMake(w,h); //replace w,h with your sizes
[myGameSquares addObject:g];
self.magicGameSquare = [[GameSquare alloc] init];
magicGameSquare.position = CGPointMake(mX,mY); //replace with your coordinates
magicGameSquare.size = CGSizeMake(mW,mH); //replace with your sizes
After that, you can do hit test against the gamesquares like this (in your CCLayer subclass):
// SomeLayer.m
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [self convertTouchToNodeSpace: touch];
// Example of other objects that user might have pressed
if (CGRectContainsPoint([magicSquare getBounds], location)) {
// User pressed magic square!
[magicSquare doHitStuff];
} else {
for (int i=0; i<myGameSquares.count; i++) {
GameSquare *square = [myGameSquares objectAtIndex:i];
if (CGRectContainsPoint(square.boundingBox, location)) {
// This is the square user has pressed!
[square doHitStuff];
break;
}
}
}
return YES;
}
Yes, you will have to look through the list, but unless the player can press many squares in one touch, you can stop the search as soon as the right one is found, as demonstrated in the example.
(Assumed use of ARC)
PS. If you at some point need to add a any sprite for the GameSquare, simply add a CCSprite member in your GameSquare class and refer to that.

Resources