I've seen lots of similar questions to mine but they all seemed to be for older versions of Swift or for very different situations. In any case, none of the solutions worked for me.
I am building a simple SpriteKit game. Basically, all I want to do is add an attribute to the SKSpriteNode class: e.g.
let Ci = SKSpriteNode(imageNamed: "CBlob")
Ci.name = "C1"
Ci.pitch = "middle C"
Currently, setting the name attribute works fine because SKSpriteNode has a name attribute. In Python I would just be able to declare the pitch attribute when instantiating the object. However, in Swift to add the pitch attribute I have tried to create a SKSpriteNode subclass.
class Blob : SKSpriteNode {
var pitch = "middle C"
}
However, I can't figure out how to initialise an instance of Blob. e.g.
let Ci = Blob(imageNamed: "CBlob")
I feel like I've tried every combination of super, override etc. but all result in errors.
Many thanks in advance.
Turns out my code is fine, the bugs were due to something else. If anyone sees this - it is now that easy to add new attributes to a subclass - you don't have to faff around with super inits etc.
print(Ci.pitch)
will now give "middle C".
Related
This question seems to have been asked several times, but I have yet to find an definitive answer. I'm totally aware of the fact that NSCell is "being gradually phased out". This is great (and long overdue!), but it doesn't help in the here and now. Suppose I want to create a subclass of NSImageView which (e.g.) clips an image with an arbitraily shaped mask. I might define it like this:
class MaskedImageView : NSImageView {
public override class var cellClass: AnyClass? {
get { MaskedImageCell.self }
set {}
}
MaskedImageCell, of course, inherits from NSImageCell. This code compiles just fine under Swift 5.x, but the cell class is never changed. Adding the statement super.cellClass = newValue to the above property setter likewise makes no difference. (In fact, this isn't how the underlying AppKit machinery establishes the cell class. If I add a print statement inside the property getter, I can see that AppKit is interrogating the cell class, as I'd expect, but it never uses it.)
Like I said, I'm aware that NSCell is on the way out. But there are surely multiple use cases where it would be useful to change the cellclass to add specifics required by a particular application. If this isn't possible NOW, then is one supposed to try and replicate the entire functionality of (eg) NSImageCell. I surely wouldn't want to do that!
All constructive comments gratefully received. And please do bear in mind that I'm not actually interested in adding weird-shaped masks to NSImageViews. 😄 What I'm after is a definitive solution to the problem of introducing one's own cell class into the mix. We were told 7 years ago that NSCell was being deprecated, but while it's still with us, we should have a mechanism for introducing custom cell classes.
This is sort of a stupid question, but what does the function self.addChild() do?
I am familiar with this function, and how to use it, but I am not exactly sure what it adds the child to.
For instance, I have created and designed an SKShapeNode called spinnyNode. Then I call the function:
func touchDown(atPoint pos : CGPoint) {
if let n = self.spinnyNode?.copy() as! SKShapeNode? {
n.position = pos
n.strokeColor = SKColor.black
self.addChild(n)
}
What is the parent in this situation? Is it the view that the node is being created in?
Thank you so much for your time, and your answering of stupid questions.
Lets break it down:
self in this case, refers to the SKScene which your are currently in. So if you're in your gameScene, the node will be added to the gameScene. Note that you can add nodes to other nodes, so if you have an SKNode named gameLayer, you could add a node to gameLayer, which would then be added to the scene. (That would look like this: gameLayer.addChild(node)) If there is no specified location for the node, it default chooses self
addChild(node) is the function that actually adds the specified node to the specified location (see above). You tell the compiler which node to add to the scene by putting its declared name in the brackets.
Make sure you're setting the nodes attributes (position, size, etc...), as they stay the default values until you change them.
The parent is essentially the scene, and what you are doing is adding that node to the scene. Without this call, the node would be created, but never passed to the scene, so it wouldn't spawn.
Think of it as doing some homework for a teacher, then the addChild submits it. If you don't submit it, it isn't used. So whatever class your function is in, be it SKScene etc, that is the parent the node is being passed to.
And don't worry, it isn't a stupid question, learning the foundations of how these functions work is a great way to build better apps!
I've been working on a IOS mobile game and I wanted to take an object,duplicate it, and have the copies move all over the screen. I've looked through Google to find things relevant to this but they were all in Objective C or just didn't have what I was looking for. I want to know how to do this in Swift and SpriteKit.
If you are working with SKSpriteNode you can copy it and all it's current properties with:
let copiedNode = nodeToCopy.copy() as! SKSpriteNode
You will still need need to add copiedNode to your scene. copiedNode will also continue to run any actions that nodeToCopy was running. You can cancel them with copiedNode.removeAllActions().
Note that the documentation for the protocol NSCopying reads exactly:
Protocol
NSCopying
A protocol that objects adopt to provide functional
copies of themselves.
The exact meaning of “copy” can vary from class to class, but a copy
must be a functionally independent object with values identical to the
original at the time the copy was made...
Indeed, in the case of SKSpriteNode, Apple have interpreted that idea so that the copy() function "spawns" another instance of the item, exactly as in any game engine.
(So, for SKSpriteNode copy() works identically to the sense of Instantiate in Unity, say.)
As mogelbuster points out below, there is nowhere in the Apple documentation that they state "The spawn command in Apple is copy()" but in fact they have interpreted this "The exact meaning of “copy” can vary from class to class" in exactly that way for SKNode, since indeed it's a game engine and it's the only meaningful sense of copy there.
It's worth noting that the most completely typical way to work in games is: for your say rocketShips, you would have one "model" of your rocketShip, say modelRocketShip. The model simply sits offscreen, or is perhaps marked as invisible or inactive. You never use the model in the game, it just sits there. When you spawn rocketShips, you just dupe the model. (So in Apple that's modelRocketShip.copy() and then set the position etc.)
You can define a function to create and return a sprite :
func createSprite()->SKSpriteNode{
let sprite = SKSpriteNode(...)//Use the init function in the SKSpriteNode class
//Add some code to define the sprite's property
return sprite
}
And call this function to get some sprites that have the same property:
let spriteOne = createSprite()
let spriteTwo = createSprite()
Then you can add different SKActions to each of them so that they can behave differently.
Once you have multiple SKSpriteNodes, you can also control them by using EnumerateChildNodesWithName (assuming all your nodes have the same name) to go through all of them do do what you want in the update() function.
On a more advanced level, you could subclass SKSpriteNode and incorporate your own behaviour in your custom class.
I'm using SpriteKit .sks file Can I make a sprite in .sks into an instance of subclass of SKSpriteNode?
This is the init method in my subclass:
init(imageNamed: String) {
let blockTexture = SKTexture(imageNamed: imageNamed)
super.init(texture: blockTexture, color: nil, size: blockTexture.size())
}
In GameScene.swift I can create an instance like this:
var myObj = Block(imageNamed: "Block")
My question is how can I relate this instance with .sks file?
I tried this line of code but it doesn't work.
myObj = childNodeWithName("block1") as Block
Any help?
Thanks.
There are a couple of issues here to address...
How .sks loading works
When you load a .sks file, SpriteKit instantiates everything within using NSKeyedUnarchiver. This means that all the nodes inside are loaded as whatever base classes they were specified as by Xcode when it created the .sks file — SKSpriteNode for sprites with texture art, SKLabelNode for text, SKFieldNode for physics fields, etc. And Xcode doesn't currently provide an option for setting custom classes for the nodes inside a .sks file.
(The one exception for this is changing the runtime class of the scene itself — the top level container for everything in the .sks file. And that's only because of the custom SKNode.unarchiveFromFile implementation provided for you in the project template. Its technique for changing classes at load time works when you have one and only one instance of a particular class in an archive — good for SKScene, not so good for the many nodes in a scene.)
How casting works
When you write something like:
myObj = childNodeWithName("block1") as Block
You're telling the compiler something like: "Hey, you know that thing you got from childNodeWithName? All you know is that it's an SKNode, but I know it's really a Block, so please let me call Block methods on it." (And the compiler says, "Okay, whatever.")
But then at run time, that thing you got had better really be a Block, or your app will crash because you tried to do something Blocky with something that's not a Block. And, per the bit about .sks loading above, that thing isn't and can't be a Block — Xcode doesn't know how to put Blocks into a .sks file. So you can't get a Block out of it, so your app is guaranteed to crash.
Workarounds
So, if you can't put custom classes into a .sks file, what can you do? It depends a bit on what exactly you're trying to accomplish. But there's a good trick that might also be good game/app design in general: use the .sks file for general layout and configuration, and use a second pass to bring in things that need custom behavior.
For example, if you're building a level in a 2D platform game, you wouldn't really want to have the .sks file contain an instance of your Plumber class even if you could — that class probably has lots of details about how tall or fat the guy is, how high he jumps, the shape of his mustache, etc, and you don't want to have to set those up again every time you make a new level, much less have them saved again in each level's .sks file. Instead, the only thing you really need to know in each level file is the position he starts at. So, drag out an "Empty Node" in Xcode, and at load time, replace that node with an instance of your Plumber class, like so:
let spawnPoint = childNodeWithName("spawnPoint")
let player = Plumber()
player.position = spawnPoint.position
addChild(player)
spawnPoint.removeFromParent()
If you have more configuration details that you want to set in the .sks file, you might consider automating that process.
Make a method that does the above node-swapping trick. (Call it something like replaceNode(_:withNode:).)
Make an initializer for your custom class that takes a SKNode or SKSpriteNode, and have it set all its inherited properties (or at least the ones you care about, like color and texture) from that node.
Use enumerateChildNodesWithName:usingBlock: with a search pattern to find all the nodes in your scene with a certain kind of name, and replace them with a new node created using your initializer. Something like:
enumerateChildNodesWithName("//brick_[0-9]*") { node, stop in
self.replaceNode(node, withNode: BrickBlock(node))
}
enumerateChildNodesWithName("//question_[0-9]*") { node, stop in
self.replaceNode(node, withNode: QuestionBlock(node))
}
A few days ago, I started working with cocos2d. I really like the framework. I would like to create a game with cocos2d and have a probably simple question...
I am making a game with 4 characters, which all have similar characteristics, but have some different attributes like "type" and "points". I'd like to subclass the sprites into one class which handles all their logic, drawing, and animation.
My question though, is how do I call the sprite class with say, a "type" parameter of 1, 2, 3, or 4 and then have the class draw the correct sprite into my scene with all of it's individual logic?
Thanks!
You should have an Enemy class that contains properties of specific enemies and that are not type specific (like position, current health, a CCSprite instance?) and an EnemyType class that contains properties that are shared among all enemies of a specific type (max health, max speed, size, sprite filename). You should load your enemy types prior to loading the level, than instantiate each enemy using the appropriate type in the constructor.
For example if your enemy element in the level file looks like this
<enemy><type>spider</type>...more properties...</enemy>
The code (pseudo) would do something like
EnemyType *enemyType = nil;
if (typeElement.value == "spider")
{
enemyType = spiderType;
}
Enemy *newEnemy = [Enemy enemyWithType:enemyType];
Also the Enemy class should contain the CCSprite that represents it, not subclass it. An enemy is not a sprite, so unless I'm missing something, as i see it, an enemy should not inherit from a sprite. I'd read about when to contain and when to inherit.
EDIT:
Another nice post to read that seems very relevant and could communicate a few other things probably better than me.
Anyway, my intention was not to make you think you should just rethink your entire design. What i'm proposing is "better" MVC-wise, but it doesn't mean it's better for your game. If you spend all your time on "design correctness" you'll never get a game done, especially if you're just learning the cocos2d framework, i was actually making a learning project not too long ago and Steve McConnel himself would come over and slap me if he saw it.
If you're working on a game alone and it's a small project go ahead and subclass away if it's going to be more manageable to you, everything, including "design correctness" needs to be properly quantified (except maybe usage of "goto" statements :) ).
polymorphism in this way can be done a couple of different ways, some better than others.
1) you could try to just override the init or node method and set up your object there.
+(CCSprite *)node
{
MySprite * returnSprite = [super node];
returnSprite.hat = #"fedora";
returnSprite.hatImage = [CCSprite spriteWithImage:...];
}
2) use reflection (psuedocode)
-(void)drawingMethodHere
{
[self.hat drawAtPoint:somePoint];
}
then override -(CCNode *)hat to reflect the type.
you may have to do some combination of them, but plan a little before you start, you will end up saving a lot of time.
You should subclass CCNode instead of subclassing CCSprite.
I think your problem is quite easy. Just create a base class called Character, which has the common logic, properties etc etc. Then you create 4 other classes like, enemy, player and so on and subclass from Character base. Note the character base should be subclassing CCNode.
Now you override the logic to fit your needs in the specific class. Now you will be able to use polymorphism, which is good.
For your sprite I would say create an instance variable of the CCSprite type and then create methods to initialize with an image. Then you will just add that sprite as a child when initializing the object.