I can't seem to figure out why the bad access code appears, can someone help?
I have code to add a sprite and an emitter node, each of which have their own physics bodies. Now, the sprite moves around the map, and there is a camera that follows it. The sprite's parent is myWorld, which itself is a child of the scene. When I attempt to add a physicsJoint between the moving sprite (which is not moving when the joint is initialized), I get a bad access code. Here is the code:
self.sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
self.sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self.sprite setScale:.5];
self.sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.sprite.size];
self.sprite.physicsBody.mass = 50;
self.sprite.physicsBody.affectedByGravity = NO;
self.sprite.name = #"sprite";
self.sprite.zPosition = 5;
self.sprite.physicsBody.velocity = CGVectorMake(300, 0);
self.sprite.physicsBody.categoryBitMask = plane;
self.sprite.physicsBody.collisionBitMask = ships;
self.sprite.physicsBody.affectedByGravity = YES;
NSString *firePath = [[NSBundle mainBundle] pathForResource:#"MyParticle" ofType:#"sks"];
self.smokeTrail = [SKEmitterNode node];
self.smokeTrail = [NSKeyedUnarchiver unarchiveObjectWithFile:firePath];
self.smokeTrail.position = CGPointMake(self.sprite.position.x-2*self.sprite.size.width/5, self.sprite.position.y);
[self.smokeTrail setScale:1];
self.smokeTrail.zPosition = self.sprite.zPosition-1;
self.smokeTrail.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.smokeTrail.frame.size];
self.smokeTrail.physicsBody.dynamic = YES;
self.smokeTrail.physicsBody.affectedByGravity = NO;
self.smokeTrail.physicsBody.categoryBitMask = 0;
smokeJoint = [SKPhysicsJointFixed jointWithBodyA:self.sprite.physicsBody bodyB:self.smokeTrail.physicsBody anchor:CGPointMake(self.smokeTrail.position.x+self.smokeTrail.frame.size.width/2,self.smokeTrail.position.y)];
...
[myWorld setScale:0.4f];
self.sprite.physicsBody.velocity = CGVectorMake(0, 0);
[self addChild:myWorld];
[self addChild:backgroundNode];
[self addChild:buttonDown];
[self addChild:buttonUp];
[myWorld addChild:blueBox];
[self addChild:self.speed];
[self addChild:self.altitude];
[self addChild:self.darkBlueWave];
[self addChild:self.lightBlueWave];
[myWorld addChild:self.smokeTrail];
[myWorld addChild:self.sprite];
[myWorld addChild:randomNode];
[myWorld addChild:camera];
[self.physicsWorld addJoint:smokeJoint]; // Bad Access Code Happens Here
Related
I am trying to get collision working in my SpriteKit game using the didBeginContact function.
My problem is that the function is just not getting called at all when the ball bounces off of the bricks. This is how I have set them both up:
static const uint32_t blockCollisionCheck = 0x1 << 0;
static const uint32_t ballCollisionCheck = 0x1 << 1;
Ball:
SKShapeNode *ball = [[SKShapeNode alloc] init];
CGMutablePathRef drawPath = CGPathCreateMutable();
CGPathAddArc(drawPath, NULL, 0, 0, _ballRadius, 0, M_PI * 2, YES);
ball.path = drawPath;
CGPathRelease(drawPath);
ball.fillColor = [SKColor greenColor];
ball.position = CGPointMake(CGRectGetMidX(self.frame), 150);
ball.name = #"ball";
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_ballRadius];
ball.physicsBody.friction = 0.0;
ball.physicsBody.restitution = 1.0;
ball.physicsBody.linearDamping = 0.0f;
ball.physicsBody.allowsRotation = NO;
ball.physicsBody.dynamic = YES;
ball.physicsBody.categoryBitMask = ballCollisionCheck;
ball.physicsBody.contactTestBitMask = blockCollisionCheck;
[self addChild:ball];
Bricks:
SKSpriteNode *block = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(_blockWidth, _blockHeight)];
block.name = #"block";
block.position = CGPointMake(x, y);
block.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:block.size];
block.physicsBody.allowsRotation = NO;
block.physicsBody.friction = 0.0;
block.physicsBody.dynamic = YES;
block.physicsBody.categoryBitMask = blockCollisionCheck;
block.physicsBody.contactTestBitMask = ballCollisionCheck;
[self addChild:block];
I can't for the life of me see what is wrong with this, as I have the category bit masks correct I think? Also both of the sprites are Dynamic, which is another problem I read it could have been.
It's not that the contents of my didBeginContact function is not working, it's just never getting there as evidenced from a lack of NSLog message and breakpoints not being reached.
Any help would be greatly appreciated.
Thank you.
If, as you say, the didBeginContact method is not being called at all, I suspect you did not add the self.physicsWorld.contactDelegate = self; into your GameScene init method.
I want my character to move to the right by force. But it is not working on this code below. I don't know what I missed here, but the player stands still on the bottom without any movement. Any clue?
// MyScene.h
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
[self.player.physicsBody applyForce: CGVectorMake(100, 100)];
}
-(void)createSceneContents {
self.currentBackground = [Background generateBackground];
[self addChild: self.currentBackground];
self.physicsWorld.gravity = CGVectorMake(0, _gravity);
self.physicsWorld.contactDelegate = self;
Player *player = [[Player alloc]init];
player.size = CGSizeMake(80, 70);
player.position = CGPointMake([UIScreen mainScreen].applicationFrame.size.width/2, 40);
[self addChild:player];
}
//Player.h
#import "Player.h"
#import "common.h"
#implementation Player
- (instancetype)init {
self = [super initWithImageNamed:#"player.png"];
self.name = #"player";
NSLog(#"%f %f", self.size.width, self.size.height);
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(20, 70)];
self.physicsBody.dynamic = YES;
self.physicsBody.allowsRotation = NO;
self.physicsBody.affectedByGravity = YES;
self.physicsBody.collisionBitMask = ~poopCategory & groundCategory;
self.physicsBody.categoryBitMask = playerCategory;
self.physicsBody.contactTestBitMask = poopCategory;
self.zPosition = 100;
//self.anchorPoint = CGPointMake(0.5, 0);
return self;
}
It looks like you are using a property named player in the scene, but you are initializing a sprite named player but never storing it in the scene's property. NSLog self.player and you'll see it's nil.
I am having an issue with the sprite animation in my game.
In the current code posted below, the sprite will show on the screen but does not do the walking animation; it is only a static frame of one of the images in the walk cycle.
The init function:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize: size]) {
self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
// Create the game nodes
// Background
_backgroundNode = [self createBackgroundNode];
[self addChild: _backgroundNode];
// Foreground
_foregroundNode = [SKNode node];
[self addChild: _foregroundNode];
// Add the Player
_thePlayer = [self createPlayer];
[_foregroundNode addChild:_thePlayer];
[self walkingQueen];
}
return self;
}
Function creating the player / filling in the animation array:
-(SKSpriteNode *)createPlayer
{
SKSpriteNode *playerNode = [SKSpriteNode node];
//SKSpriteNode *_player = [SKSpriteNode node];
NSMutableArray *walkFrames = [NSMutableArray array];
SKTextureAtlas *queenAnimatedAtlas = [SKTextureAtlas atlasNamed: #"QueenSprites"];
SKTexture *walk1 = [queenAnimatedAtlas textureNamed:#"queen7"];
SKTexture *walk2 = [queenAnimatedAtlas textureNamed:#"queen8"];
SKTexture *walk3 = [queenAnimatedAtlas textureNamed:#"queen9"];
walkFrames[0] = walk1;
walkFrames[1] = walk2;
walkFrames[2] = walk3;
_queenWalkingFrames = walkFrames;
/*
int numImages = 9;
for (int i = 7; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"queen%d", i];
SKTexture *temp = [queenAnimatedAtlas textureNamed: textureName];
[walkFrames addObject: temp];
}
*/
//_queenWalkingFrames = walkFrames;
SKTexture *temp = _queenWalkingFrames[0];
SKSpriteNode *_player = [SKSpriteNode spriteNodeWithTexture:temp];
_player.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
//[self addChild:_player];
//[self walkingQueen];
[playerNode addChild:_player];
[self walkingQueen];
return playerNode;
}
And the function for starting the walk animation:
-(void)walkingQueen
{
for (int i = 0; i < _queenWalkingFrames.count; i++) {
if (_queenWalkingFrames[i] == NULL) {
NSLog(#"ERROR!");
}
}
[_thePlayer runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:_queenWalkingFrames
timePerFrame:0.1f
resize:NO
restore:YES]] withKey:#"walkingInPlaceQueen"];
//return;
}
I played around with the code a bit and the animation DOES work IF I ignore the concept of layers (background / foreground layers) and stick the creation of the player into the init function (basically taking what's inside the createPlayer() and sticking it inside initWithSize()).
I do need to have the layers for my game and everything seems like it should work; had no idea that moving a few lines of code into its own function block would create such an issue. The check inside the walkingQueen() function did not return any errors so I assume my array has been filled with the sprite images.
_thePlayer = [self createPlayer];
This means _thePlayer is nil until after the createPlayer method returns. Thus in the walkingQueen method _thePlayer is nil and the actions don't run.
Replace this:
SKSpriteNode *playerNode = [SKSpriteNode node];
with
_thePlayer = [SKSpriteNode node];
and use _thePlayer instead of playerNode. You can also skip returning and assigning it, it's an ivar after all.
Re-wrote the createPlayer() function. Correct implementation is much simpler and as follows:
-(SKSpriteNode *)createPlayer
{
NSMutableArray *walkFrames = [NSMutableArray array];
SKTextureAtlas *queenWalkingAtlas = [SKTextureAtlas atlasNamed:#"QueenSprites"];
SKTexture *walk1 = [queenWalkingAtlas textureNamed:#"queen7"];
SKTexture *walk2 = [queenWalkingAtlas textureNamed:#"queen8"];
SKTexture *walk3 = [queenWalkingAtlas textureNamed:#"queen9"];
walkFrames[0] = walk1;
walkFrames[1] = walk2;
walkFrames[2] = walk3;
_queenWalkingFrames = walkFrames;
SKTexture *temp = _queenWalkingFrames[0];
_thePlayer = [SKSpriteNode spriteNodeWithTexture:temp];
_thePlayer.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
return _thePlayer;
}
Basically doing as the post above suggested, get rid of the ivars, use the function ONLY to fill the array with the appropriate images. Then, in the init function, call createPlayer(), add it to the foreground layer, and then call walkingQueen().
I'm using SpriteKit to create a game in which objects at the bottom of the map move.
The objects are crocodiles and coins.
The scene uses an NSTimer to that calls this selector:
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(generateBottomSprite) userInfo:nil repeats:YES];
-(void) generateBottomSprite{
int random = (arc4random() % difficulty+3) + difficulty-3;
if (counter >random){
SKSpriteNode *bottomSprite;
if (random>difficulty){
bottomSprite = [SKSpriteNode spriteNodeWithImageNamed:#"crocodile"];
bottomSprite.size = CGSizeMake(70, 120);
bottomSprite.physicsBody.categoryBitMask = crocMask;
}else{
bottomSprite = [SKSpriteNode spriteNodeWithImageNamed:#"coin"];
bottomSprite.size = CGSizeMake(50, 50);
bottomSprite.physicsBody.categoryBitMask = coinMask;
}
//create a crocodile
bottomSprite.position = CGPointMake(self.frame.size.width,bottomSprite.frame.size.height/2);
bottomSprite.name = #"bottomSprite";
[self addChild: bottomSprite];
counter = 0;
[self enumerateChildNodesWithName:#"bottomSprite" usingBlock: ^(SKNode *node, BOOL *stop) {
node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node.frame.size];
node.physicsBody.affectedByGravity = NO;
node.physicsBody.mass = 8000.0;
node.physicsBody.friction = 2.50;
node.physicsBody.usesPreciseCollisionDetection = YES;
}];
}
}
The problem: all the physicsWorld's properties are re-initialized on each call to the selector and i must iterate over the children of the scene in order to re-assign them.
Why are the properties get re-initialized?
you are setting the categorybitmask before creating the skphysicsbody itself
bottomSprite = [SKSpriteNode spriteNodeWithImageNamed:#"crocodile"];
bottomSprite.size = CGSizeMake(70, 120);
//add the following
bottomSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bottomSprite.frame.size];
///////////////
bottomSprite.physicsBody.categoryBitMask = crocMask;
move all the physics settings outside the enumeration like so
if (counter >random){
SKSpriteNode *bottomSprite;
int currentBitMask;
if (random>difficulty){
bottomSprite = [SKSpriteNode spriteNodeWithImageNamed:#"crocodile"];
bottomSprite.size = CGSizeMake(70, 120);
currentBitMask = crocMask;
}else{
bottomSprite = [SKSpriteNode spriteNodeWithImageNamed:#"coin"];
bottomSprite.size = CGSizeMake(50, 50);
currentBitMask = coinMask;
}
**bottomSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bottomSprite.frame.size];**
bottomSprite.physicsBody.categoryBitMask = currentBitMask ;
bottomSprite.physicsBody.affectedByGravity = NO;
bottomSprite.physicsBody.mass = 8000.0;//way too high
bottomSprite.physicsBody.friction = 2.50;//probably high
bottomSprite.physicsBody.usesPreciseCollisionDetection = YES;//overkill, set only if critical
//create a crocodile
bottomSprite.position = CGPointMake(self.frame.size.width,bottomSprite.frame.size.height/2);
bottomSprite.name = #"bottomSprite";
[self addChild: bottomSprite];
you also might wanna make use of the update method in the skscene and nstimeintervals to measure time till you pass half a second then call the generateBottomSprite method, it should be more efficient than using an additional NSTimer.
Feel free to ask if anything is unclear, hope this helps.
I'm testing out pin joints with Sprite Kit, and I'm finding something unusual happening.
My desired setup is this: one wide, flat box, and two circles; the circles are connected via SKPhysicsPinJoints to the box, so they can act as wheels.
Here's my code. I've tried to make it as concise as possible:
- (SKNode*) createWheelWithRadius:(float)wheelRadius {
CGRect wheelRect = CGRectMake(-wheelRadius, -wheelRadius, wheelRadius*2, wheelRadius*2);
SKShapeNode* wheelNode = [[SKShapeNode alloc] init];
wheelNode.path = [UIBezierPath bezierPathWithOvalInRect:wheelRect].CGPath;
wheelNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:wheelRadius];
return wheelNode;
}
- (void) createCar {
// Create the car
SKSpriteNode* carNode = [SKSpriteNode spriteNodeWithColor:[SKColor yellowColor] size:CGSizeMake(150, 50)];
carNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carNode.size];
carNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:carNode];
// Create the left wheel
SKNode* leftWheelNode = [self createWheelWithRadius:30];
leftWheelNode.position = CGPointMake(carNode.position.x-80, carNode.position.y);
[self addChild:leftWheelNode];
// Create the right wheel
SKNode* rightWheelNode = [self createWheelWithRadius:30];
rightWheelNode.position = CGPointMake(carNode.position.x+80, carNode.position.y);
[self addChild:rightWheelNode];
// Attach the wheels to the body
CGPoint leftWheelPosition = leftWheelNode.position;
CGPoint rightWheelPosition = rightWheelNode.position;
SKPhysicsJointPin* leftPinJoint = [SKPhysicsJointPin jointWithBodyA:carNode.physicsBody bodyB:leftWheelNode.physicsBody anchor:leftWheelPosition];
SKPhysicsJointPin* rightPinJoint = [SKPhysicsJointPin jointWithBodyA:carNode.physicsBody bodyB:rightWheelNode.physicsBody anchor:rightWheelPosition];
[self.physicsWorld addJoint:leftPinJoint];
[self.physicsWorld addJoint:rightPinJoint];
}
What I'm expecting is that the pin joints are anchored at their centre points; however, when I test this, the anchors for the joints appear to be far off.
Am I missing something really obvious?
I also had this issue and the cause is setting the physics body before setting the sprites position.
carNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carNode.size];
carNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
Change the above to
carNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
carNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carNode.size];
It should work. Thanks Smick.
SpriteKit: How to create Basic Physics Joints
Try this code, I used yours and got weird issues, so started from scratch.
- (SKShapeNode*) makeWheel
{
SKShapeNode *wheel = [[SKShapeNode alloc] init];
CGMutablePathRef myPath = CGPathCreateMutable();
CGPathAddArc(myPath, NULL, 0,0, 16, 0, M_PI*2, YES);
wheel.path = myPath;
return wheel;
}
- (void) createCar
{
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0, 0, self.size.width, self.size.height)];
// 1. car body
SKSpriteNode *carBody = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(120, 8)];
carBody.position = CGPointMake(200, 200);
carBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carBody.size];
[self addChild:carBody];
// 2. wheels
SKShapeNode *leftWheel = [self makeWheel];
leftWheel.position = CGPointMake(carBody.position.x - carBody.size.width / 2, carBody.position.y);
leftWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:16];
[self addChild:leftWheel];
SKShapeNode *rightWheel = [self makeWheel];
rightWheel.position = CGPointMake(carBody.position.x + carBody.size.width / 2, carBody.position.y);
rightWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:16];
[self addChild:rightWheel];
// 3. Join wheels to car
[self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:carBody.physicsBody bodyB:leftWheel.physicsBody anchor:leftWheel.position]];
[self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:carBody.physicsBody bodyB:rightWheel.physicsBody anchor:rightWheel.position]];
// 4. drive car
[carBody.physicsBody applyForce:CGVectorMake(10, 0)];
}