3

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: enter image description here

Community
  • 1
  • 1
  • Have you stepped through in debug mode to determine which line it crashes on ? Also is there any other information in the output before the crash ? Also, if this is subclassing shouldn't you call `[super init]` ? Would be good to also see your .h code – prototypical Nov 30 '13 at 22:45
  • No subclass needed, just use multiple instances of SKNode. Each node can only have one body. – CodeSmile Dec 01 '13 at 09:52
  • @prototypical Thank you, `if (self = [super init])` worked. Now I just need to find a way to attach the joints from my Truck object into the main scene's `physicsWorld` property. I need to make `physicsWorld` property from MyScene available to my Truck object so I can attach the joints within the class. Code pending. –  Dec 01 '13 at 23:24
  • @LearnCocos2D I want the vehicle code - chassis parts, wheels, joints (slide and spring) in its own class. Wouldn't this be the best approach versus having all that code in the main scene? –  Dec 02 '13 at 03:51
  • It's best to limit your questions to specific issues. I thought the issue was the error you were receiving when trying to subclass, which is why I asked for more information and questioning the lack of a init call. The joints issue is a separate issue. You say "joints" , yet I only see one joint in the code above. You need to break this all down and form a question with all the information someone needs. Not taking the time to do that, just lessens the chances of someone taking the time to help you. – prototypical Dec 02 '13 at 04:05
  • multiple nodes/bodies doesn't imply multiple classes, only multiple **instances** of classes – CodeSmile Dec 02 '13 at 10:25

4 Answers4

5

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

Cuespeak
  • 149
  • 1
  • 13
  • I am definitely in the same boat you were in, dealing with the encapsulation frustration. This helped me out a lot. Thank you. – michaelsnowden May 01 '14 at 06:08
  • Got the EXC_BAD_ACCESS somewhere in `b2DistanceJointDef::Initialize`. You just saved my day! – jboi Apr 17 '15 at 10:36
1

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];
        }
    }

}
0

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.

marsairic
  • 221
  • 3
  • 8
  • Thank you for your response. I will go back to my SK project however and try your suggestion you made on point 1). For the time being, I've gone back to Chipmunk (Chipmunk Pro). –  Dec 26 '13 at 03:38
0

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];
park
  • 3
  • 3