I’m working in Objective-C
, SKSpriteKit
and am trying to create a class which will work in both OS X and iOS for flexibility.
Recently, I asked the question which asked if there was a way to create a method in a new class which automatically ran itself every frame. I was answered that it would be a good idea to use CADisplayLink
. However, I soon realised I could not, as it is exclusive to iOS — and I had forgotten to specify that I wanted this class to be usable in both platforms. This was quite a surprise, and I had to dig deep into the system’s .h files to be sure this was true.
I also forgot to mention that I was using SKSpriteKit
and that my class was a subclass of SKSpriteNode
in the question. So I then tried to add the method which needed to be ran every frame using SKAction
s and performSelector:
. However, the end result was undesirable, as I observed 2 identical node
s of this class (called PhysicsObject
) move across the screen at different speeds. Using SKActions
and runBlock:
was even worse, as the runBlock:
action can’t be copied when the node
is copied.
As a result, I managed to invent my own solution which was to created a new category: SKNode (updateExtension)
. The SKNode(updateExtension).h
file (which is quite short) is pasted below.
The extension works by adding 2 new methods to all SKNodes
, and hence all subclasses of SKNode
:
- (void)update:(NSTimeInterval)currentTime;
- (void)updater:(NSTimeInterval)currentTime;
The update:
method works by calling the node
’s own updater:
method, and then calling all of its children
’s update:
methods. The updater:
method of any particular node is then filled with custom code of the user’s choice (if left blank, it will do nothing). The update:
method then cannot be touched.
The idea behind this was that the topmost node
of the node tree
would then be able to call all of its descendants
’ updater:
methods which would only take one line of code. Or even better, no lines of code, since the SKScene
, which is the topmost node
of the tree, already has its update:
method called every frame — which was why I had given my method the same name.
Or so I had hoped. Omitting the update:
method from where I would usually put it in the @implementation GameScene
doesn’t seem to have worked, as my version of update:
isn’t called at all. I am forced to copy and paste my own version of the update:
method from the category into the @implementation GameScene
for this all to work...
Why does this happen? Surely the
SKScene
should call itsupdate:
method every frame even if I had omitted it from the@implementation
since it already has been implemented by my category?In addition to this issue, I was wondering about the efficiency of my method. Given a reasonably large
tree
ofnode
s (around 1000node
s?), even if the majority of thenode
s’updater:
methods were empty, would the program be slowed to less than 60 frames per second as it would have to dig to the bottom of thenode tree
each frame? I have only been working with OS X so far — would the efficiency be much worse with iOS?I now want to add 2 boolean properties to the
SKNode
category:selfPaused
andchildrenPaused
. I would add 2if
statements inside theupdate:
method for this (selfPaused
around the call to thenode
’s ownupdater:
method andchildrenPaused
around thechildren
enumeration block). However, it turned out that I could not add properties to class categories, and to do so required access to the@implementation
file using class extensions. Is there any way around this problem that anyone could think of?
The advantage of this would be that a node
could pause all of its descendant
s’ update:
methods with one line of code saving time for the user.
The SKNode(updateExtension).h file:
#import <SpriteKit/SKNode.h>
#import <SpriteKit/SpriteKitBase.h>
@interface SKNode (updateExtension)
- (void)update:(NSTimeInterval)currentTime;
- (void)updater:(NSTimeInterval)currentTime;
@end
@implementation SKNode (updateExtension)
- (void)update:(NSTimeInterval)currentTime {
[self updater:currentTime];
[self.children enumerateObjectsUsingBlock:^(SKNode *child, NSUInteger idx, BOOL * _Nonnull stop) {
[child update:currentTime];
}
];
}
- (void)updater:(NSTimeInterval)currentTime {
}
@end // SKNode (updateExtension)
1 Answer 1
In this case, I would recommend using the built in SpriteKit
methods for the best readability and the best control of the state while the game is running.
Why do you need the nodes to automatically update themselves? It seems like this could lead to a situation where you do not know what exactly is causing things to happen to the nodes in the scene. Furthermore, it seems like you are putting all of the logic for the game into the scene, breaking encapsulation between the game model and the way it is rendered.
SKScene
s have a built in method that is called every frame: - (void)update:(NSTimeInterval)currentTime
.
Carefully reading over your question, it appears that you already know about this method. I recommend that you use that method and make the updates to the appropriate nodes inside that method (calling their update methods from there). If you confine all changes of game state to this method, then it will be easier to keep track of what happens each frame. The individual nodes could have a state that would determine whether anything happens during their update.
-
\$\begingroup\$ The aim of this is to create a class called
physicsObjects
which when given their displacement, velocity, acceleration, .... and as many derivatives as the user deems to be necessary, then the object will update its displacement (position) on the screen every frame. Therefore, the code for that needs to be in the class itself... And I don't want users of this class having to use an enumeration block to update allphysicsObjects
on the screen. \$\endgroup\$Shuri2060– Shuri20602015年10月31日 16:15:30 +00:00Commented Oct 31, 2015 at 16:15