0
votes

I'm using the IOS SpriteKit and wanted to know if there's some way of adding client data to a sprite node?

For example I want to attach specific behaviour to a node, additional properties, relationships to other nodes etc.

I'm reluctant to sub-class SKNode as that would appear to break the model/view/controller model where the nodes are just the "view".

Currently I'm keeping a separate hierarchy of controller objects but this feels clunky, and it's not great performance-wise.

What's best practice?

3
I have found it best to implement behaviour specific to a node in it's own class. However, if you just want to store information in a node on the basis of which your controller object can make decisions, you can use the userData property.ZeMoon

3 Answers

0
votes

Subclassing won't change anything in that regard. You still retain the full functionality, and break nothing. In fact I think it's almost unavoidable if you are making a large game.

But if you really don't want to then use userData: SKNode -> userData

0
votes

All SKNode classes provide a userData property which can be used for exactly this purpose.

You can write a Objective-C category through which you can add additional properties without needing to subclass. A proven pattern below.

Interface:

#import "YourCustomObject.h"

@interface SKNode (customObject)
@property (nonatomic) YourCustomObject* yourCustomObject;
@end

Implementation:

static NSString* YourCustomObjectUserDataKey = @"YourCustomObjectUserDataKey";

@implementation SKNode (customObject)
-(NSMutableDictionary*) internal_getOrCreateUserData
{
    NSMutableDictionary* userData = self.userData;
    if (userData == nil)
    {
        userData = [NSMutableDictionary dictionary];
        self.userData = userData;
    }

    return userData;
}

-(void) setYourCustomObject:(YourCustomObject*)customObject
{
    NSMutableDictionary* userData = [self internal_getOrCreateUserData];
    [userData setObject: customObject forKey:YourCustomObjectUserDataKey];
}

-(YourCustomObject*) yourCustomObject
{
    NSMutableDictionary* userData = [self internal_getOrCreateUserData];
    return (YourCustomObject*)[userData objectForKey:YourCustomObjectUserDataKey];
}
@end

Replace YourCustomObject with your actual class name.

The category can be extended to host multiple properties of course, you don't have to write a separate category for each property. The category does not have to be on SKNode either, if you need an additional property but only on SKSpriteNode you would make this a category on SKSpriteNode.

Very often you'll need access to the owning node in YourCustomObject.

For that reason it's a good idea to provide an initializer and/or property that takes the owning node as input. I prefer readonly properties and a custom initializer because usually you don't want the owner to change throughout the object's lifetime. Very important: this property must be weak to not create a retain cycle.

@interface YourCustomObject
@property (weak, readonly) SKNode* owningNode;
+(instancetype) customObjectWithOwningNode:(SKNode*)owningNode;
@end

@implementation YourCustomObject
+(instancetype) customObjectWithOwningNode:(SKNode*)owningNode
{
    return [[self alloc] initWithOwningNode:owningNode];
}
-(id) initWithOwningNode:(SKNode*)owningNode
{
    self = [super init];
    if (self) 
    {
        _owningNode = owningNode;
    }
    return self;
}
@end

You can then create and assign your custom object within a node class:

YourCustomObject* customObject = [YourCustomObject customObjectWithOwningNode:self];
self.yourCustomObject = customObject;
0
votes

While the other answers here already mention that you can use the userData dictionary for arbitrary key-values, let me add that you're fine subclassing SKNodes because SpriteKit is not MVC in the first place (or at least not in the sense of UIKit's way).

SKNodes were designed to fit common gaming architectures (Component Pattern and friends) and can actually be Models, Views, or Controllers on their own. SKNode for example is not a displayable object, but can hold information about game entities/states or other children nodes.