Sprite Kit: How To Subclass SKNode to create a Vehicle class (complex object with joints) - subclass

Has anyone figured out (natively) how to subclass SKNode to include multiple bodies connect by joints - for instance, a car?
There doesn't seem to be a way to add the sub class's joints to the parent scene's physicsWorld property.
Also, when trying to compile and run the object below, even without the joint, I get a BAD_EXC_ACCESS error.
Thank you #Smick for the initial vehicle code as posted here:Sprite Kit pin joints appear to have an incorrect anchor
Truck Class:
#import "Truck.h"
#implementation Truck
-(id)initWithPosition:(CGPoint)pos {
SKSpriteNode *carBody = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(120, 8)];
carBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carBody.size];
carBody.position = pos;
carBody.physicsBody.mass = 1.0;
SKSpriteNode *carTop = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(50, 8)];
carTop.position = CGPointMake(230, 708);
carTop.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carTop.size];
carTop.physicsBody.mass = 0;
SKPhysicsJointFixed *carBodyJoint = [SKPhysicsJointFixed jointWithBodyA:carBody.physicsBody
bodyB:carTop.physicsBody
anchor:CGPointMake(0, 0)];
return self;
}
+(Truck*)initWithPosition:(CGPoint)pos {
return [[self alloc] initWithPosition:pos];
}
#end
MyScene:

sorry for late post, but just hit this myself.
Physics joints dont work unless the nodes being attached have already been added to the SKScene's scene graph.
During the initWithPosition above, that's not the case.
Passing in a SKScene also didn't work for me, I suspect because the Vehicle node still hasn't been added to the scene graph.
You can still encapsulate your physics joints within the class, but you have to call another method after the
[self addChild:car]
Here's my refinement on what you already had:
Vehicle.h
#interface Vehicle : SKNode
#property (nonatomic) SKSpriteNode *leftWheel;
#property (nonatomic) SKSpriteNode *ctop;
-(id)initWithPosition:(CGPoint)pos;
-(void)initPhysics;
#end
Vehicle.m
//
#import "Vehicle.h"
#implementation Vehicle {
SKSpriteNode *chassis;
SKSpriteNode *rightWheel;
SKSpriteNode *leftShockPost;
SKSpriteNode *rightShockPost;
int wheelOffsetY;
CGFloat damping;
CGFloat frequency;
}
- (SKSpriteNode*) makeWheel
{
SKSpriteNode *wheel = [SKSpriteNode spriteNodeWithImageNamed:#"Wheel.png"];
// wheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:wheel.size.width/2];
return wheel;
}
-(id)initWithPosition:(CGPoint)pos {
if (self = [super init]) {
wheelOffsetY = 60;
damping = 1;
frequency = 4;
chassis = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(120, 8)];
chassis.position = pos;
[self addChild:chassis];
_ctop = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(70, 16)];
_ctop.position = CGPointMake(chassis.position.x+20, chassis.position.y+12);
[self addChild:_ctop];
_leftWheel = [self makeWheel];
_leftWheel.position = CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y - wheelOffsetY); //Always set position before physicsBody
[self addChild:_leftWheel];
rightWheel = [self makeWheel];
rightWheel.position = CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y - wheelOffsetY);
[self addChild:rightWheel];
//------------- LEFT SUSPENSION ----------------------------------------------------------------------------------------------- //
leftShockPost = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(7, wheelOffsetY)];
leftShockPost.position = CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y - leftShockPost.size.height/2);
[self addChild:leftShockPost];
//------------- RIGHT SUSPENSION ----------------------------------------------------------------------------------------------- //
rightShockPost = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(7, wheelOffsetY)];
rightShockPost.position = CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y - rightShockPost.size.height/2);
[self addChild:rightShockPost];
}
return self;
}
-(void) initPhysics {
chassis.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:chassis.size];
_ctop.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_ctop.size];
SKPhysicsJointFixed *cJoint = [SKPhysicsJointFixed jointWithBodyA:chassis.physicsBody
bodyB:_ctop.physicsBody
anchor:CGPointMake(_ctop.position.x, _ctop.position.y)];
_leftWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_leftWheel.size.width/2];
_leftWheel.physicsBody.allowsRotation = YES;
rightWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:rightWheel.size.width/2];
rightWheel.physicsBody.allowsRotation = YES;
leftShockPost.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:leftShockPost.size];
SKPhysicsJointSliding *leftSlide = [SKPhysicsJointSliding jointWithBodyA:chassis.physicsBody
bodyB:leftShockPost.physicsBody
anchor:CGPointMake(leftShockPost.position.x, leftShockPost.position.y)
axis:CGVectorMake(0, 1)];
leftSlide.shouldEnableLimits = TRUE;
leftSlide.lowerDistanceLimit = 5;
leftSlide.upperDistanceLimit = wheelOffsetY;
SKPhysicsJointSpring *leftSpring = [SKPhysicsJointSpring jointWithBodyA:chassis.physicsBody bodyB:_leftWheel.physicsBody
anchorA:CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y)
anchorB:_leftWheel.position];
leftSpring.damping = damping;
leftSpring.frequency = frequency;
SKPhysicsJointPin *lPin = [SKPhysicsJointPin jointWithBodyA:leftShockPost.physicsBody bodyB:_leftWheel.physicsBody anchor:_leftWheel.position];
rightShockPost.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rightShockPost.size];
SKPhysicsJointSliding *rightSlide = [SKPhysicsJointSliding jointWithBodyA:chassis.physicsBody
bodyB:rightShockPost.physicsBody
anchor:CGPointMake(rightShockPost.position.x, rightShockPost.position.y)
axis:CGVectorMake(0, 1)];
rightSlide.shouldEnableLimits = TRUE;
rightSlide.lowerDistanceLimit = 5;
rightSlide.upperDistanceLimit = wheelOffsetY;
SKPhysicsJointSpring *rightSpring = [SKPhysicsJointSpring jointWithBodyA:chassis.physicsBody bodyB:rightWheel.physicsBody
anchorA:CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y)
anchorB:rightWheel.position];
rightSpring.damping = damping;
rightSpring.frequency = frequency;
SKPhysicsJointPin *rPin = [SKPhysicsJointPin jointWithBodyA:rightShockPost.physicsBody bodyB:rightWheel.physicsBody anchor:rightWheel.position];
// Add all joints to the array.
// Add joints to scene's physics world
[self.scene.physicsWorld addJoint: cJoint];
[self.scene.physicsWorld addJoint: leftSlide];
[self.scene.physicsWorld addJoint: leftSpring];
[self.scene.physicsWorld addJoint: lPin];
[self.scene.physicsWorld addJoint: rightSlide];
[self.scene.physicsWorld addJoint: rightSpring];
[self.scene.physicsWorld addJoint: rPin];
}
#end
and call it from MyScene.m
_car = [[DMVehicle alloc] initWithPosition:location];
[self addChild:_car];
[_car initPhysics];
Hope that helps, I know it has helped me by working through it

So here's what I came up with.
My only issue with this is that it isn't completely self contained as joints must be added to the physics world outside of the class - simple enough as you'll see using two lines of code.
Editing my answer. The suspension requires the wheels to be attached to a sliding body versus attaching the wheels via the slide joint. Doing the former allows wheels to rotate. The latter does not.
Update: I've unmarked this as the answer. Reason being is that, while it runs fine on the simulator, I receive the following error when I attempt to run it on my iPad (running iOS7). When I remove my vehicle class and place one of the vehicle's components that use that UIColor method into my main scene, the error is not thrown.
UICachedDeviceWhiteColor addObject:]: unrecognized selector sent to instance 0x15e21360
2013-12-14 22:44:19.790 SKTestCase[1401:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UICachedDeviceWhiteColor addObject:]: unrecognized selector sent to instance
For some reason, I no longer receive the UICashed... error (yes, vehicle is still a class) Now I receive:
-[PKPhysicsJointWeld name]: unrecognized selector sent to instance 0x1464f810
2013-12-15 15:28:24.081 MTC[1747:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PKPhysicsJointWeld name]: unrecognized selector sent to instance 0x1464f810'
*** First throw call stack:
(0x30eaff53 0x3b5186af 0x30eb38e7 0x30eb21d3 0x30e01598 0x3352837d 0x335284d5 0x33527b97 0x33525ca5 0x87385 0x335064f5 0x87ccb 0x33620fe1 0x332aa24b 0x332a5a5b 0x332d4b7d 0x3369e39b 0x3369ca03 0x3369bc53 0x3369bbdb 0x3369bb73 0x33694681 0x3362703f 0x3369b8c1 0x3369b38d 0x3362c21d 0x33629763 0x33694a55 0x33691811 0x3368bd13 0x336266a7 0x336259a9 0x3368b4fd 0x35ab270d 0x35ab22f7 0x30e7a9e7 0x30e7a983 0x30e79157 0x30de3ce7 0x30de3acb 0x3368a799 0x33685a41 0x8a52d 0x3ba20ab7)
libc++abi.dylib: terminating with uncaught exception of type NSException
Vehicle.h:
#import <SpriteKit/SpriteKit.h>
#interface Vehicle : SKNode
#property (nonatomic,assign) NSMutableArray *joints;
#property (nonatomic) SKSpriteNode *leftWheel;
#property (nonatomic) SKSpriteNode *ctop;
-(id)initWithPosition:(CGPoint)pos;
#end
Vehicle.m
#import "Vehicle.h"
#implementation Vehicle
- (SKSpriteNode*) makeWheel
{
SKSpriteNode *wheel = [SKSpriteNode spriteNodeWithImageNamed:#"wheel.png"];
// wheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:wheel.size.width/2];
return wheel;
}
-(id)initWithPosition:(CGPoint)pos {
if (self = [super init]) {
_joints = [NSMutableArray array];
int wheelOffsetY = 60;
CGFloat damping = 1;
CGFloat frequency = 4;
SKSpriteNode *chassis = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(120, 8)];
chassis.position = pos;
chassis.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:chassis.size];
[self addChild:chassis];
_ctop = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(70, 16)];
_ctop.position = CGPointMake(chassis.position.x+20, chassis.position.y+12);
_ctop.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_ctop.size];
[self addChild:_ctop];
SKPhysicsJointFixed *cJoint = [SKPhysicsJointFixed jointWithBodyA:chassis.physicsBody
bodyB:_ctop.physicsBody
anchor:CGPointMake(_ctop.position.x, _ctop.position.y)];
_leftWheel = [self makeWheel];
_leftWheel.position = CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y - wheelOffsetY); //Always set position before physicsBody
_leftWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_leftWheel.size.width/2];
_leftWheel.physicsBody.allowsRotation = YES;
[self addChild:_leftWheel];
SKSpriteNode *rightWheel = [self makeWheel];
rightWheel.position = CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y - wheelOffsetY);
rightWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:rightWheel.size.width/2];
rightWheel.physicsBody.allowsRotation = YES;
[self addChild:rightWheel];
//------------- LEFT SUSPENSION ----------------------------------------------------------------------------------------------- //
SKSpriteNode *leftShockPost = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(7, wheelOffsetY)];
leftShockPost.position = CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y - leftShockPost.size.height/2);
leftShockPost.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:leftShockPost.size];
[self addChild:leftShockPost];
SKPhysicsJointSliding *leftSlide = [SKPhysicsJointSliding jointWithBodyA:chassis.physicsBody
bodyB:leftShockPost.physicsBody
anchor:CGPointMake(leftShockPost.position.x, leftShockPost.position.y)
axis:CGVectorMake(0, 1)];
leftSlide.shouldEnableLimits = TRUE;
leftSlide.lowerDistanceLimit = 5;
leftSlide.upperDistanceLimit = wheelOffsetY;
SKPhysicsJointSpring *leftSpring = [SKPhysicsJointSpring jointWithBodyA:chassis.physicsBody bodyB:_leftWheel.physicsBody
anchorA:CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y)
anchorB:_leftWheel.position];
leftSpring.damping = damping;
leftSpring.frequency = frequency;
SKPhysicsJointPin *lPin = [SKPhysicsJointPin jointWithBodyA:leftShockPost.physicsBody bodyB:_leftWheel.physicsBody anchor:_leftWheel.position];
//------------- RIGHT SUSPENSION ----------------------------------------------------------------------------------------------- //
SKSpriteNode *rightShockPost = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(7, wheelOffsetY)];
rightShockPost.position = CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y - rightShockPost.size.height/2);
rightShockPost.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rightShockPost.size];
[self addChild:rightShockPost];
SKPhysicsJointSliding *rightSlide = [SKPhysicsJointSliding jointWithBodyA:chassis.physicsBody
bodyB:rightShockPost.physicsBody
anchor:CGPointMake(rightShockPost.position.x, rightShockPost.position.y)
axis:CGVectorMake(0, 1)];
rightSlide.shouldEnableLimits = TRUE;
rightSlide.lowerDistanceLimit = 5;
rightSlide.upperDistanceLimit = wheelOffsetY;
SKPhysicsJointSpring *rightSpring = [SKPhysicsJointSpring jointWithBodyA:chassis.physicsBody bodyB:rightWheel.physicsBody
anchorA:CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y)
anchorB:rightWheel.position];
rightSpring.damping = damping;
rightSpring.frequency = frequency;
SKPhysicsJointPin *rPin = [SKPhysicsJointPin jointWithBodyA:rightShockPost.physicsBody bodyB:rightWheel.physicsBody anchor:rightWheel.position];
// Add all joints to the array.
[_joints addObject:cJoint];
[_joints addObject:leftSlide];
[_joints addObject:leftSpring];
[_joints addObject:lPin];
[_joints addObject:rightSlide];
[_joints addObject:rightSpring];
[_joints addObject:rPin];
}
return self;
}
#end
MyScene.m:
#import "MyScene.h"
#import "Vehicle.h"
#implementation MyScene {
Vehicle *car;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0, 0, self.size.width, self.size.height)];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
car = [[Vehicle alloc] initWithPosition:location];
[self addChild:car];
// Add joints to scene's physics world
for (SKPhysicsJoint *j in car.joints) {
[self.physicsWorld addJoint:j];
}
}
}

1) Adding joints: Why don't you just have the vehicle's init method takes an SKScene object? Then you can add the joint within that method. Otherwise, what you have done with the _joints array works, but seems less clean with the extra code needed there.
2) BAD_EXC_ACCESS: I got this as well when I was learning about joints. It went away when the nodes participating in the joint were added to SKScene and not some other sub nodes. The closest info in the Apple documentation that I can find is this: "Attach the physics bodies to a pair of SKNode objects in the scene." That doesn't specify whether this means directly to the SKScene, or to any node within the node tree in the SKScene.

I had the same issue, and this way works for me:
you will receive an Exc_bad_access if you try to add the joint(defined in the truck class)from your scene. You have to run
[self.scene.physicsWorld addJoint:joint]
in your Truck class.
However, you can't add the
[self.scene.physicsWorld addJoint:joint]
into your init method of the Truck, because the Truck has not been added into your scene when running the Truck's init method in your scene.
You'll have to write another method (let's say addJointsIntoScene) in your Truck class to run
[self.scene.physicsWorld addJoint:joint].
After adding your Truck to your scene, run the 'addJointsIntoScene' method to add the joint.
for example,
Truck.m
-(instancetype)initWithPosition:(CGPoint) triggerPosition{
....
joint = .....
....
}
//and another method
-(void)addJointsIntoScene{
[self.scene.physicsWorld addJoint:joint];enter code here
}
MyScene.m
Truck *truck = [[Truck alloc]initWithPosition:triggerPosition];
[self addChild:truck];
[truck addJointsIntoScene];

Related

Using SKPhysicsBody to create a vehicle

I am trying to make a vehicle (in this case a train) move based on player input. If I move the train via an SKAction, the wheels do not rotate. I could use the applyForce method on its physics body, but it I need more control. I need to make it move a certain distance over a certain amount of time. How can this be accomplished?
-(void)didMoveToView:(SKView *)view {
SKTexture *trainBodyTexture = [SKTexture textureWithImageNamed:#"levelselect_trainbody"];
SKSpriteNode *trainBody = [[SKSpriteNode alloc] initWithTexture:trainBodyTexture];
trainBody.zPosition = 0;
trainBody.position = CGPointMake(300, 500);
trainBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:trainBody.size];
[self addChild:trainBody];
SKTexture *trainWheelTexture = [SKTexture textureWithImageNamed:#"levelselect_trainwheel"];
SKSpriteNode *trainWheel1 = [[SKSpriteNode alloc] initWithTexture:trainWheelTexture];
trainWheel1.zPosition = 1;
trainWheel1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:trainWheel1.size.width/2];
trainWheel1.physicsBody.allowsRotation = YES;
trainWheel1.position = CGPointMake(220, 400);
[self addChild:trainWheel1];
SKSpriteNode *trainWheel2 = [[SKSpriteNode alloc] initWithTexture:trainWheelTexture];
trainWheel2.zPosition = 1;
trainWheel2.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:trainWheel2.size.width/2];
trainWheel2.physicsBody.allowsRotation = YES;
trainWheel2.position = CGPointMake(380, 400);
[self addChild:trainWheel2];
SKShapeNode *dot = [SKShapeNode shapeNodeWithCircleOfRadius:10];
dot.zPosition = 2;
dot.fillColor = [NSColor redColor];
dot.position = CGPointMake(0, -20);
[trainWheel1 addChild:dot];
SKPhysicsJointPin *pin = [SKPhysicsJointPin jointWithBodyA:trainBody.physicsBody bodyB:trainWheel1.physicsBody anchor:trainWheel1.position];
SKPhysicsJointPin *pin2 = [SKPhysicsJointPin jointWithBodyA:trainBody.physicsBody bodyB:trainWheel2.physicsBody anchor:trainWheel2.position];
[self.scene.physicsWorld addJoint:pin];
[self.scene.physicsWorld addJoint:pin2];
//[trainWheel1 runAction:[SKAction moveByX:300 y:0 duration:3]];
[trainBody.physicsBody applyForce:CGVectorMake(3000, 0)];
}
UPDATE: Implemented With A Train Class (suggested by Jaffer Sheriff)
Train.h
#import <SpriteKit/SpriteKit.h>
#interface Train : SKSpriteNode
-(void) createPhysics;
-(void) moveLeft;
-(void) moveRight;
#end
Train.m
#import "Train.h"
#interface Train()
#property SKSpriteNode *trainBody, *trainWheelFront, *trainWheelRear;
#property SKPhysicsWorld *physicsWorld;
#end
#implementation Train
-(instancetype) init {
if (self = [super init]) {
[self initTrainBody];
[self initWheels];
}
return self;
}
-(void) initTrainBody {
SKTexture *trainBodyTexture = [SKTexture textureWithImageNamed:#"levelselect_trainbody"];
_trainBody = [[SKSpriteNode alloc] initWithTexture:trainBodyTexture];
_trainBody.zPosition = 0;
_trainBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_trainBody.size];
[self addChild:_trainBody];
}
-(void) initWheels {
SKTexture *_trainWheelTexture = [SKTexture textureWithImageNamed:#"levelselect_trainwheel"];
_trainWheelFront = [[SKSpriteNode alloc] initWithTexture:_trainWheelTexture];
_trainWheelFront.zPosition = 1;
_trainWheelFront.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_trainWheelFront.size.width/2];
_trainWheelFront.physicsBody.allowsRotation = YES;
_trainWheelFront.position = CGPointMake(-80, -82);
[self addChild:_trainWheelFront];
_trainWheelRear = [[SKSpriteNode alloc] initWithTexture:_trainWheelTexture];
_trainWheelRear.zPosition = 1;
_trainWheelRear.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_trainWheelRear.size.width/2];
_trainWheelRear.physicsBody.allowsRotation = YES;
_trainWheelRear.position = CGPointMake(80, -82);
[self addChild:_trainWheelRear];
//dot used to see if wheels are rotating, no other point
SKShapeNode *dot = [SKShapeNode shapeNodeWithCircleOfRadius:10];
dot.zPosition = 2;
dot.fillColor = [NSColor redColor];
dot.position = CGPointMake(0, -20);
[_trainWheelFront addChild:dot];
}
//this method is called after the train node is added to the scene in GameScene otherwise will get error adding joints before node is in scene
-(void) createPhysics {
SKPhysicsJointPin *pin = [SKPhysicsJointPin jointWithBodyA:_trainBody.physicsBody bodyB:_trainWheelFront.physicsBody anchor:_trainWheelFront.position];
SKPhysicsJointPin *pin2 = [SKPhysicsJointPin jointWithBodyA:_trainBody.physicsBody bodyB:_trainWheelRear.physicsBody anchor:_trainWheelRear.position];
[self.scene.physicsWorld addJoint:pin];
[self.scene.physicsWorld addJoint:pin2];
}
-(void) moveLeft {
SKAction *rotateLeft = [SKAction rotateByAngle:6*M_PI duration:0.2];
[_trainWheelFront runAction:rotateLeft];
[_trainWheelRear runAction:rotateLeft];
}
-(void) moveRight {
SKAction *rotateRight = [SKAction rotateByAngle:-6*M_PI duration:0.2];
[_trainWheelFront runAction:rotateRight];
[_trainWheelRear runAction:rotateRight];
}
#end
GameScene.m
#import "GameScene.h"
#import "Train.h"
#interface GameScene()
#property Train *train;
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
[self initTrain];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil]; //using notifications and custom view class to handle key presses
}
-(void) initTrain {
_train = [[Train alloc] init];
_train.position = CGPointMake(500, 300);
[self addChild:_train];
[_train createPhysics];
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
-(void) keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[#"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
NSLog(#"keycode = %lu", keyCode);
switch (keyCode) {
case 123:
[self leftArrowPressed];
break;
case 124:
[self rightArrowPressed];
break;
}
}
-(void) leftArrowPressed {
SKAction *moveLeft = [SKAction moveByX:-200 y:0 duration:0.2];
[_train runAction:moveLeft];
[_train moveLeft];
}
-(void) rightArrowPressed {
SKAction *moveRight = [SKAction moveByX:200 y:0 duration:0.2];
[_train runAction:moveRight];
[_train moveRight];
}
#end
Note: This solution causes the entire train to flip and freak out when the left/right keys are pressed. It seems like my pin joints are incorrect, but they seem correct to me :/
Default Initializer of SkSpriteNode is - (instancetype)initWithTexture:(SKTexture *)texture color:(SKColor *)color size:(CGSize)size; Try this,
#interface Train : SKSpriteNode
- (instancetype)initTrainWithColor:(UIColor *) color andSize:(CGSize) size;
-(void) animateWheelsWithTime:(float) time;
#end
#interface Train ()
{
SKSpriteNode *wheel1;
SKSpriteNode *wheel2;
NSMutableArray *wheelsArray;
}
#end
#implementation Train
- (instancetype)initTrainWithColor:(UIColor *) color andSize:(CGSize) size
{
self = [super initWithTexture:nil color:color size:size];
if (self)
{
[self addWheels];
}
return self;
}
-(void)addWheels
{
wheelsArray = [[NSMutableArray alloc]init];
wheel1 = [SKSpriteNode spriteNodeWithImageNamed:#"wheel"];
[wheel1 setPosition:CGPointMake(-(self.frame.size.width/2.0f-wheel1.frame.size.width/2.0f), - (self.frame.size.height/2.0f - wheel1.frame.size.height/2.0f))];
[self addChild:wheel1];
wheel2 = [SKSpriteNode spriteNodeWithImageNamed:#"wheel"];
[wheel2 setPosition:CGPointMake((self.frame.size.width/2.0f-wheel2.frame.size.width/2.0f), - (self.frame.size.height/2.0f - wheel2.frame.size.height/2.0f))];
[self addChild:wheel2];
[wheelsArray addObject:wheel1];
[wheelsArray addObject:wheel2];
}
-(void) animateWheelsWithTime:(float) time
{
for (SKSpriteNode *wheel in wheelsArray)
{
SKAction *act = [SKAction rotateByAngle:3*M_PI duration:time];
[wheel runAction:act];
}
}
#end
In GameScene.m add train like this,
-(void) addTrain
{
Train *train1 = [[Train alloc]initTrainWithColor:[UIColor yellowColor] andSize:CGSizeMake(150, 100)];
[train1 setPosition:CGPointMake(self.size.width/2.0f, self.size.height/2.0f)];
[self addChild:train1];
SKAction *act = [SKAction moveTo:CGPointMake(self.size.width/2.0f, self.size.height - 50) duration:2];
[train1 runAction:act];
[train1 animateWheelsWithTime:2];
}
I tried and it works.

Double Tap makes crash SpriteKit Game

I am new to SpriteKit game dev.
In my game when character touch enemy , it will show game over scene and restart it.
However after Game Over Scene showed, double in game is making app crash.
Here is my codes in MainGameScene.
- (void)gameOver
{
GameOver *over = [[GameOver alloc] initWithSize:self.size];
SKTransition *trans = [SKTransition flipHorizontalWithDuration:0.5];
[self.view presentScene:over transition:trans];
}
And here is GameOver Scene.
- (instancetype)initWithSize:(CGSize)size
{
self = [super initWithSize:size];
{
SKLabelNode *lblGameOver = [[SKLabelNode alloc] initWithFontNamed:#"Chalkduster"];
lblGameOver.text = #"Game Over";
lblGameOver.position = CGPointMake(self.size.width/2, self.size.height/2);
lblGameOver.fontSize = 35;
lblGameOver.fontColor = [UIColor whiteColor];
lblGameOver.zPosition = 2;
[self addChild:lblGameOver];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
[super didMoveToView:self.view];
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(newGame)];
[view addGestureRecognizer:tapper];
}
- (void)newGame
{
GameScene * scene = [[GameScene alloc] initWithSize:self.size];
SKTransition *trans = [SKTransition flipHorizontalWithDuration:0.5];
[self.view presentScene:scene transition:trans];
}
That make crash my app when i double tap in New Game Scene.
Error message is
Thread 1 : EXC_BAD_ACCESS (code=1,address=0x0)
How could i fix it?
Try this in your didMoveToView method:
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(newGame)];
[self.view addGestureRecognizer:tapRecognizer];
tapRecognizer.numberOfTapsRequired = 2; // 1 for a single tap or 2 for a double tap

Xcode Accessing the Childnode of a Childnode

I added a Childnode(Bg) to the scene and to this Childnode(Bg) i added another Childnode(Rainbow) (so it moves exactly like it's parent node). How i can i access node2?
For example i can't remove Rainbow with [Rainbow removeFromParent]. I can only remove Bg and all of it's children.
Thanks in advance!
Sample Code:
-(void) didMoveToView:(SKView *)view{
SKNode *Bg = (SKNode *)[self childNodeWithName:#"Bg"];
[self addChild: Bg];
[Bg addChild: Rainbow];
}
-(SKSpriteNode *)Bg{
SKSpriteNode *Bg = [SKSpriteNode spriteNodeWithTexture:BgTexture];
Bg.name = #"Bg";
Bg.size = CGSizeMake(600, 330);
Bg.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
Bg.zPosition = -2000;
return Bg;
}
-(SKSpriteNode *) Rainbow{
SKSpriteNode *Rainbow = [SKSpriteNode spriteNodeWithImageNamed:#"Rainbow2x.png"];
Rainbow.name = #"Rainbow";
Rainbow.size = CGSizeMake(905/2, 478/2);
Rainbow.position = CGPointMake(CGRectGetMidX(self.view.bounds), 70 );
Rainbow.zPosition = -200;
Rainbow.alpha = 0.6;
return Rainbow;
}
How can i access the Rainbownode?
Got it!
I needed to make a property of the SKSpriteNode Rainbow!
#property (nonatomic,strong) SKSpriteNode *Rainbow;

Cocos2d - only one scheduler called

I'm trying to trace 2 paths on screen through a series of vertices (connect the dots style). Each should be a different color, and each has its own list of vertices.
I started out by creating a class which can trace a path, then creating 2 instances of this class, one for each path. I overrode the draw method. It worked just fine except for some reason only the first instance of the class called the draw method. I figured it was a problem with OpenGL so I did it again using CCDrawNode and it still had the same bug.
Only one instance (blackPath) draws any objects on screen. In fact the scheduled updateEndpoint: method is not even called for the whitePath object, although it is successfully created.
My Drawer.m Class:
const float size = 10;
const float speed = 5;
ccColor4F pathColor;
int numPoints;
NSArray * path;
CGPoint endPoint;
#implementation Drawer
-(id)initWithPath:(NSArray*)p andColorIsBlack:(BOOL)isBlack{
self = [super init];
// Record input
path = p.copy;
pathColor = ccc4f(1.0f, 1.0f, 1.0f, 1.0f);
if(isBlack){
pathColor = ccc4f(0.0f, 0.0f, 0.0f, 1.0f);
}
// Set variables
numPoints = 1;
endPoint = [[path firstObject] position];
NSLog(#"Drawer initialized with path of length %u and color %hhd (isblack)", p.count, isBlack);
[self schedule:#selector(updateEndpoint:)];
return self;
}
-(void)updateEndpoint:(ccTime)dt{
NSLog(#"(%f, %f, %f, %f) Path", pathColor.r, pathColor.g, pathColor.b, pathColor.a);
[self drawDot:endPoint radius:size color:pathColor];
CGPoint dest = [[path objectAtIndex:numPoints] position];
float dx = dest.x - endPoint.x;
float dy = dest.y - endPoint.y;
// new coords are current + distance * sign of distance
float newX = endPoint.x + MIN(speed, fabsf(dx)) * ((dx>0) - (dx<0));
float newY = endPoint.y + MIN(speed, fabsf(dy)) * ((dy>0) - (dy<0));
endPoint = ccp(newX, newY);
if(endPoint.x == dest.x && endPoint.y == dest.y){
if(numPoints < path.count-1){
numPoints+=1;
}
else{
[self unschedule:#selector(updateEndpoint:)];
}
}
}
And here is where I instantiate the objects:
-(id) init{
self = [super init];
[self addAllCards];
[self addScore];
xShrinkRate = [[Grid getInstance] sqWidth] / shrinkTime;
yShrinkRate = [[Grid getInstance] sqHeight] / shrinkTime;
dropList = [NSMutableArray new];
notDropList = [NSMutableArray new];
[self schedule:#selector(dropCard:) interval:0.075];
[self schedule:#selector(shrinkCards:)];
Drawer * whitePath = [[Drawer alloc] initWithPath:[[Score getInstance] whitePath] andColorIsBlack:false];
[self addChild:whitePath];
Drawer * blackPath = [[Drawer alloc] initWithPath:[[Score getInstance] blackPath] andColorIsBlack:true];
[self addChild:blackPath];
return self;
}
Change the (non-const) global variables to instance variables of the class like so:
#implementation Drawer
{
ccColor4F pathColor;
int numPoints;
NSArray * path;
CGPoint endPoint;
}

Animate masked image in SpriteKit (SKCropNode)

I'm using Sprite Kit to create my first iOS app, and I'm stumbling over what seems to be a pretty basic problem.
I'm trying to animate the contents (fillNode) of a SKCropNode (cropNode). The code below sets up the SKScene just fine (I see the illustration masked correctly, placed over the background image in the right location)...
SKSpriteNode *logoBG = [[SKSpriteNode alloc] initWithImageNamed:logoBackground];
logoBG.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:logoBG];
SKCropNode *cropNode = [[SKCropNode alloc] init];
cropNode.maskNode = [[SKSpriteNode alloc] initWithImageNamed:logoMask];
SKSpriteNode *fillNode = [[SKSpriteNode alloc] initWithImageNamed:logoImage];
[cropNode addChild:fillNode];
fillNode.position = CGPointMake(0,300);
cropNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.logoContent = cropNode;
[self addChild:self.logoContent];
...but I can't access the masked illustration to animate its position (I want it to start off outside the mask and slide into position):
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
SKAction *moveUp = [SKAction moveByX: 0 y: 100 duration: 5];
SKAction *moveSequence = [SKAction sequence:#[moveUp]];
[self.logoContent.target_illustration runAction: moveSequence completion:^{ // '.target_illustration' = the SKSpriteNode that I want to animate; cropNode.fillNode
SKScene *titleScreen = [[TitleScreen alloc] initWithSize:self.size];
SKTransition *fade = [SKTransition fadeWithDuration:(1)];
[self.view presentScene:titleScreen transition:fade];
}];
I can only animate the whole logoContent rather than its child nodes.
Any ideas where I'm going wrong?
Just for reference if anyone wants to know how to mask, here is a simple snippet
- (void) cropNodes
{
// the parent node i will add to screen
SKSpriteNode *picFrame = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(100, 100)];
picFrame.position = CGPointMake(200, 200);
// the part I want to run action on
SKSpriteNode *pic = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
pic.name = #"PictureNode";
SKSpriteNode *mask = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:CGSizeMake(80, 50)];
SKCropNode *cropNode = [SKCropNode node];
[cropNode addChild:pic];
[cropNode setMaskNode:mask];
[picFrame addChild:cropNode];
[self addChild:picFrame];
// run action in this scope
//[pic runAction:[SKAction moveBy:CGVectorMake(30, 30) duration:10]];
// outside scope - pass its parent
[self moveTheThing:cropNode];
}
- (void) moveTheThing:(SKNode *) theParent
{
// the child i want to move
SKSpriteNode *moveThisThing = (SKSpriteNode *)[theParent childNodeWithName:#"PictureNode"];
[moveThisThing runAction:[SKAction moveBy:CGVectorMake(30, 30) duration:10]];
}
Got it!
[self.logoContent.target_illustration runAction: moveSequence completion:^{
should be
[self.logoContent.children[0] runAction: moveSequence completion:^{
My illustration now moves as directed :)

Resources