Zane Helton
Zane Helton

Reputation: 1062

SKNode Subclass Not Moving Children

I have a sprite that can't exist in my game without a pairing SKFieldNode so my solution was to create a subclass of SKSpriteNode and create a property for the SKFieldNode but it didn't work because the SKSpriteNode was acting weird (I don't remember exactly what happened). So my next approach was to change the subclass to SKNode and then I would make the SKSpriteNode and the SKFieldNode a property of this new SKNode. But then it turns out that touchesMoved will only move one of the properties (whichever is on top) which turns out to always be the SKSpriteNode.

What's the best approach to this problem, and how can I fix it so that I can have an SKFieldNode for every SKSpriteNode while still making sure that actions and methods still work properly.

Current code of SKNode subclass:

@interface Whirlpool : SKNode
- (instancetype)initWithPosition:(CGPoint)pos region:(float)region strength:(float)strength falloff:(float)falloff;
@property (nonatomic, strong) SKFieldNode *gravityField;
@end

#import "Whirlpool.h"
#import "Categories.h"

@implementation Whirlpool
- (instancetype)initWithPosition:(CGPoint)pos region:(float)region strength:(float)strength falloff:(float)falloff {
    if (self = [super init]) {
        // whirlpool sprite
        SKSpriteNode *whirlpoolSprite = [[SKSpriteNode alloc] initWithImageNamed:@"whirlpool"];
        whirlpoolSprite.size = CGSizeMake(100, 100);
        whirlpoolSprite.position = pos;
        //removing physicsBody and associated attributes for now so that the boat does not collide with the whirlpool
        //whirlpoolSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:whirlpoolSprite.size.width / 2];
        //whirlpoolSprite.physicsBody.dynamic = NO;
        whirlpoolSprite.zPosition = 1;
        whirlpoolSprite.name = @"whirlpool";
        [whirlpoolSprite runAction:[SKAction repeatActionForever:[self sharedRotateAction]]];

        // whirlpool gravity field
        _gravityField = [SKFieldNode radialGravityField];
        _gravityField.position = pos;
        _gravityField.strength = strength;
        _gravityField.falloff = falloff;
        _gravityField.region = [[SKRegion alloc] initWithRadius:region];
        _gravityField.physicsBody.categoryBitMask = gravityFieldCategory;
        _gravityField.zPosition = 1;

        [self addChild:whirlpoolSprite];
        [self addChild:_gravityField];
    }

    return self;
}

- (SKAction *)sharedRotateAction {
    static SKAction *rotateWhirlpool;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        rotateWhirlpool = [SKAction rotateByAngle:-M_PI * 2 duration:4.0];
    });

    return rotateWhirlpool;
}

@end

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // we don't want the player to be able to move the whirlpools after the button is pressed
    if (_isRunning) {
        return;
    }

    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        SKNode *node = [self nodeAtPoint:location];
        // if the finger touches the boat or the whirlpool, update its location
        if ([node.name isEqualToString:@"boat"]) {
            node.position = CGPointMake(location.x, node.position.y);
        } else if ([node.name isEqualToString:@"whirlpool"]) {
            node.position = location;
        }
    }
}

Upvotes: 4

Views: 197

Answers (2)

Skyler Lauren
Skyler Lauren

Reputation: 3812

I believe your issues comes down to that fact that "whirlpool" is a child of your SKNode subclass. So when you are identifying that you are indeed touching a "whirlpool" you are moving it within its parent (the SKNode subclass) and the SKFieldNode and parent stay put. This little adjustment to your original code should work...if I understand the problem correctly.

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // we don't want the player to be able to move the whirlpools after the button is pressed
    if (_isRunning) {
        return;
    }

    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        SKNode *node = [self nodeAtPoint:location];
        // if the finger touches the boat or the whirlpool, update its location
        if ([node.name isEqualToString:@"boat"]) {
            node.position = CGPointMake(location.x, node.position.y);
        } else if ([node.name isEqualToString:@"whirlpool"]) {
            //move the SKNode subclass the whirlpool is a child of
            node.parent.position = location;
        }
    }
}

Hopefully that helps.

Upvotes: 2

Knight0fDragon
Knight0fDragon

Reputation: 16837

Yikes, the problem here is the way you are grabbing nodes, you may be grabbing the wrong nodes due to all the children. Instead take this approach:

We already know that you are subclassing your sprites, so in your subclasses, add the following code:

//Whirlpool 
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self.parent];//Unless you are retaining the scene in the child, then use that
        self.position = location;
    }
}

Then:

//Boat 
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self.parent];//Unless you are retaining the scene in the child, then use that
        self.position.x = location.x;
    }
}

Then for your Scene, do this:

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // we don't want the player to be able to move the whirlpools after the button is pressed
    if (_isRunning) {
        return;
    }
    //At this point it should call the children on touch events
    [super.touchesMoved: touches withEvent:event];
}

Upvotes: 0

Related Questions