If I create an instance of the class below and call the spawn function from my controller, the sprite will appear but I won't be able to change any of its properties.
class Hero: SKSpriteNode
{
var hero = SKSpriteNode(imageNamed: "hero3")
func spawn(parentNode: SKNode, position: CGPoint, size: CGSize = CGSize(width: 50, height: 50))
{
hero.size = size
hero.position = position
hero.physicsBody = SKPhysicsBody(circleOfRadius: 25)
hero.physicsBody?.allowsRotation = false
hero.zPosition = 10
parentNode.addChild(hero)
}
}
If I get rid of the hero property and change everything to self, it works fine.
class Hero: SKSpriteNode
{
func spawn(parentNode: SKNode, position: CGPoint, size:CGSize = CGSize(width: 50, height: 50))
{
self.size = size
self.position = position
self.texture = SKTexture(imageNamed: "hero3")
self.physicsBody = SKPhysicsBody(circleOfRadius: 25)
self.physicsBody?.allowsRotation = false
self.zPosition = 10
parentNode.addChild(self)
}
}
I'm sure this is swift 101, but can someone please explain why the first version doesn't work as expected?
In your first example, you created a var (basically a SpriteNode inside the SpriteNode).
When you instantiate the class like let hero = Hero(....) you now have a SpriteNode called hero, with a property called hero. You can change either. Calling hero.size would change the base hero, and hero.hero.size would change the inside SpriteNode....this is probably not the behavior you were looking for.
The second class looks correct, if you are just trying to create a SpriteNode and modify it. The class is a subclass of SpritNode, so it's already a SpriteNode - no need to create one inside it like the first one.
Hope this helps!
Your Hero class inherits from SKSpriteNode, so it is essentially an SKSpriteNode already, which means you don't need to create a hero SKSpriteNode variable.
In your first class, when you use hero.size = size, you're accessing the properties of the variable within your class instead of on the class itself. Then you add the hero SKSpriteNode as a child along with all its properties, but your class SKSpriteNode, which holds the variable doesn't have set properties size or a physics body. You can think of the class as a container that's holding the variable SKSpriteNode.
In your second function, when you use self.size = size, you're accessing the class's properties and giving the class SKSpriteNode all the properties you need to use it in other classes.
Related
I have a custom class named Test that extends SKShapeNode with 4 SKShapeNodes as its child, and I would like to add them as a child of GameScene. However, when I try to execute addChild() in GameScene, I end up getting the error signal SIGABRT.
In my custom class I declare:
let shapeR = SKShapeNode()
let shapeG = SKShapeNode()
let shapeY = SKShapeNode()
let shapeB = SKShapeNode()
Then in override init(), I call the addChild() methods:
addChild(shapeR)
addChild(shapeG)
addChild(shapeY)
addChild(shapeB)
Finally in GameScene, I iterate through the children with (test is defined as Test()):
for child in test.children {
addChild(child)
}
How do I fix this?
Edit: I need 4 SKShapeNodes since I want 4 different colors in my custom SKShapeNode object.
Nevermind I just had to addChild() the entire class and not just separate SKShapeNodes
I'm new to Xcode 7, how do I add a black node in SpriteKit?
Can anyone point me in the right direction or link me up with any tutorials please? Thank you
You want to create a new SKSpriteNode object, and add it to the view
class GameScene: SKScene {
var node: SKSpriteNode?
override func didMoveToView(view: SKView) {
let node = SKSpriteNode(imageNamed: "BlackNode.png")
node.position.x = self.size.width / 2
node.position.y = self.size.height / 2
addChild(node)
}
}
So in your GameScene class (or whatever SKScene class you are using), this creates a SKSpriteNode that is the image of whatever "BlackNode.png" is, and sets its position to the center of the screen, and adds it to scene
Assuming your question is asking how to add an all black node to the scene, try this:
override func didMoveTo(view: SKView) {
let node = SKSpriteNode(color: UIColor.black, size: CGSize(width: 100, height: 100)) // Declare and initialize node
addChild(node) // Function that adds node to scene
}
This uses the SKSpriteNode initializer init(color: UIColor, size: CGSize), which you would pass a color for the node and size. This is then used to create a rectangle, while the nodes texture property remains nil.
Note that in this example, no position is explicitly chosen, so the default (0,0) is chosen (centre of scene if scene anchorPoint is 0.5,0.5)
To get a further understanding of scenes vs views and SpriteKit vs UIKit (which I think is where you're having trouble) check out this answer: SpriteKit vs UIKit
I want to create a SKShapeNode at a higher level than the touchesBegan of a SpriteNode so when I want to add the SKShapeNode to the screen from the touchesBegun event on this sprite, the shape already exists, and I simply add it to the screen from within the touchesBegan override.
TL;DR, in my SKSpriteNode, I'm trying to pre-build the SKShapeNode that will be used as an animated ring when the Sprite is touched.
I'd like to create the SKShapeNode with variable/constants, so I can easily edit its values...
So in the root of the subclass I have variables for color, size, and linewidth, ready to be used in the creation of the SKShapeNode...
class Balls: SKSpriteNode {
var ringSize: CGFloat = 64
var ringColor: SKColor = SKColor.white
var ringWidth: CGFloat = 16
....
Further down, but still at the root of the class, I create my ring:
let ring = SKShapeNode(circleOfRadius: ringSize)
And am instantly greeted with the lovingly cryptic:
Can not use instance member 'ringSize' within property initializer,
property initializers run before 'self' is available.
Fine. Ok. I get it. You want to think that a functional call to a class to create a property should be done before values are assigned to self. Neither here nor there, I think I'm getting cunning and can get around that by wrapping everything in a function:
class Balls: SKSpriteNode {
var ringSize: CGFloat = 64
var ringColor: SKColor = SKColor.white
var ringWidth: CGFloat = 16
var myRing = SKShapeNode()
func createRing() -> SKShapeNode{
let ring = SKShapeNode(circleOfRadius: ringSize)
ring.strokeColor = ringColor
ring.lineWidth = ringWidth
return ring
}
This generates no errors, and my excitement builds.
So I add another line, to create the actual ring:
....
myRing = createRing()
Dead again:
! Expected
declaration
I have absolutely no idea what this means and began to randomly attempt weird things.
One of them is heading into my already messy convenience initializer and adding myRing = createRing() in there... and this WORKS!
How and why does this work, and is this the best/right/proper way to be circling the drain of initialization?
:: EDIT:: UPDATE :: Full Code Context ::
Here's the full class with my bizarre and misunderstood initialisers.
import SpriteKit
class Circle: SKSpriteNode {
var ringSize: CGFloat = 96
var ringColor: SKColor = SKColor.white
var ringWidth: CGFloat = 8
var myRing = SKShapeNode()
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
}
convenience init() {
self.init(color: SKColor.clear, size: CGSize(width: 100, height: 100))
myRing = createRing()
addChild(myRing)
print("I'm on the screen")
explodeGroup = create_explosionActionGroup()
}
convenience init(color: UIColor, size: CGSize, position: CGPoint) {
self.init(color: color, size: size)
self.position = position
myRing = createRing()
explodeGroup = create_explosionActionGroup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createRing() -> SKShapeNode{
let ring = SKShapeNode(circleOfRadius: ringSize)
ring.strokeColor = ringColor
ring.lineWidth = ringWidth
return ring
}
Your createRing() method is inside Ball class so you need to create an instance of Ball first.
Simple way - You can change creation of instance to
let ball = Balls()
let myRing = ball.createRing()
I'm slightly confused as to where you placed the
myRing = createRing()
line of code but I'm wondering if this setup would help solve your problem
lazy var myRing: SKShapeNode = {
let ring = SKShapeNode(circleOfRadius: ringSize)
ring.strokeColor = ringColor
ring.lineWidth = ringWidth
return ring
}()
This way myRing would be created when it was accessed which should be after the Balls class is instantiated which would mean that ringSize, ringColor and ringWidth would all exist.
Based on your update I think your best bet might be to just make your three ring variables ‘static let’ instead. That way they will exist and have the set value before initializing the main class. The errors you’re seeing are because you created instance variables. Those will only exist when the instance has been initialized. So if you tried to call the ring method as the declaration of the variable or if you did it within the init before self/super init is called then the instance variables wouldn’t be accessible. The most recent code you’ve added should be working because you create the instance before attempting to generate the ring. I hope that makes sense and helps.
And am instantly greeted with the lovingly cryptic:
Can not use instance member 'ringSize' within property initializer, property initializers run before 'self' is available.
So one way around this problem would be to make the default ringSize available another way, e.g.
static let defaultRingSize: CGFloat = 64
var ringSize: CGFloat = Circle.defaultRingSize
let ring = SKShapeNode(circleOfRadius: Circle.defaultRingSize)
... but I question why you even have a var ringSize property like that. Shouldn't you have a didSet observer on it, so that if you change its value, you can update the shape of ring?
Dead again:
! Expected declaration
You weren't clear, in your question, how you actually triggered this, but I guess you tried something like this:
class Circle: SKSpriteNode {
var ringSize: CGFloat = 96
var myRing = SKShapeNode()
myRing = createRing() // “Expected declaration” error on this line
The problem here is that you've placed a statement in the body of your class, but only declarations are allowed in the body.
One of them is heading into my already messy convenience initializer and adding myRing = createRing() in there... and this WORKS!
How and why does this work
All of your class's own instance variables must be initialized before a super.init call. Since myRing has a default value, the compiler effectively inserts the initialization of myRing before the call to super.init in your designated initializer, like this:
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
// Compiler-inserted initialization of myRing to the default
// value you specified:
myRing = SKShapeNode()
super.init(texture: texture, color: color, size: size)
}
Since you declared var myRing, you can then change it later to the customized SKShapeNode you really want.
is this the best/right/proper way to be circling the drain of initialization?
Well, “circling the drain” means “failing”, so I guess you're asking if this is “the best/right/proper way” to fail at initialization… I suppose it's not the best way to fail, since you didn't actually fail in the end.
Or maybe you meant “I hate the way Swift does initialization so I'm going to throw some shade”, in which case, you ain't seen nothin' yet.
But maybe you really meant “is this the best/right/proper way to initialize my instance”, in which case, well, “best” and “right” and “proper” are pretty subjective.
But I can objectively point out that you're creating an SKShapeNode (as the default value of myRing) just to immediately throw it away and create another SKShapeNode. So that's a waste. You've also got calls to createRing in both of your convenience initializers, but you could factor them out into the designated initializer.
But I wouldn't even do it quite like that. SKShapeNode's path property is settable, so you can just create a default SKShapeNode and then change its path after the call to super.init. That also makes it easier to handle changes to ringSize and the other properties, because you can funnel all the changes through a single method that knows how to make myRing match the properties.
Here's how I'd probably write your class:
import SpriteKit
class Circle: SKSpriteNode {
var ringSize: CGFloat = 96 {
// Use an observer to update myRing if this changes.
didSet { configureMyRing() }
}
var ringColor = SKColor.white {
didSet { configureMyRing() }
}
var ringWidth: CGFloat = 8 {
didSet { configureMyRing() }
}
// This can be a let instead of a var because I'm never going to
// set it to a different object. Note that I'm not bothering to
// initialize myRing's path or any other property here, because
// I can just call configureMyRing in my init (after the call to
// super.init).
let myRing = SKShapeNode()
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
// Call this now to set up myRing's path and other properties.
configureMyRing()
}
convenience init() {
self.init(color: SKColor.clear, size: CGSize(width: 100, height: 100))
// No need to do anything to myRing now, because my designated
// initializer set it up completely.
addChild(myRing)
print("I'm on the screen")
// Commented out because you didn't provide this property
// or method in your question.
// explodeGroup = create_explosionActionGroup()
}
convenience init(color: SKColor, size: CGSize, position: CGPoint) {
self.init(color: color, size: size)
self.position = position
// Commented out because you didn't provide this property
// or method in your question.
// explodeGroup = create_explosionActionGroup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configureMyRing() {
myRing.path = CGPath(ellipseIn: CGRect(x: -ringSize / 2, y: -ringSize / 2, width: ringSize, height: ringSize), transform: nil)
myRing.strokeColor = ringColor
myRing.lineWidth = ringWidth
}
}
So I have a class called GameScene which has a function called makeBackground. In another class called I created an instance of it (GameScene) and called makeBackground as follows in the didMoveToView of the new class:
var gameSceneInstance:GameScene = GameScene()
gameSceneInstance.makeBackground()
However when I run it, nothing happens!
This is the function makeBackground:
func makeBackground ()
{
let bgTxt = SKTexture(imageNamed: "bg.png")
let moveBg = SKAction.moveByX(0, y: -bgTxt.size().height, duration: 5)
let replaceBg = SKAction.moveByX(0, y: bgTxt.size().height, duration: 0)
let moveBgAnimation = SKAction.repeatActionForever(SKAction.sequence([moveBg, replaceBg]))
//-----background-----
for var i:CGFloat = 0 ; i<3; i++
{
bg = SKSpriteNode(texture: bgTxt)
bg.position = CGPoint(x: CGRectGetMidX(self.frame), y: bgTxt.size().height/2 + bgTxt.size().height * i)
bg.size.width = self.frame.width
bg.runAction(moveBgAnimation)
movingObjects.addChild(bg)
}
}
Do I have to copy and paste the makeBackground function into the new class to make it work?
Similarly, in the didMoveToView of the GameScene class, I have some sprites. Can I use them in this new class?
Thanks!
if you created an instance of the GameScene object and called the makeBackground function and nothing happened then there is something wrong with your code in the makeBackground function.
Also in another topic, instance objects which are created in a class are equeals of each other and cannot be used within each others methods unless they are specifically passed in. Perhaps you can make a method in your GameScen object with which you could pass these sprites in either as a list of sprites or as individual sprites
Basically, I have an SKScene class called GameScene. In GameScene, I have a variable called playableArea that is calculated depending on the size of the user's phone.
I have an enemy class that needs to know the width of the playableArea, and then use that value to move to to the x position by an SKAction depending on the width of playableArea.
This is my class (simplified version):
class PacuPiranha: Fish {
static var s = SKSpriteNode(imageNamed: "PacuPiranha1")
var width: CGFloat = 0
struct SharedAssets {
static var move: SKAction!
static var token: dispatch_once_t = 0
}
init(position: CGPoint, width: CGFloat) {
self.width = width
}
override class func preloadAssets() -> SKTexture? {
dispatch_once(&SharedAssets.token, {
SharedAssets.move = SKAction.sequence([SKAction.moveToX(width + s.size.width, duration: 2.5), SKAction.removeFromParent()])
return SharedAssets.texture
}
}
}
The enemy fish needs to move to the other size of the screen, but the size of the screen is determined by the size of the device the user is using, so I need to pass the width of the screen to the moveToX action which occurs in the init call. But I get the error "Instance member 'width' cannot be used on type 'PacuPiranha'."
How do I get the width of a variable that is from the GameScene class, which is where the new object of the enemy is being created?
let fish = PacuPiranha(position: CGPoint(...), width: playableArea.size.width)
I have also tried making a new function in the enemy class like this:
func getWidth() -> CGFloat {
let view = scene as! GameScene
view.playableArea.size.width
}
And then using getWidth() inside the moveToX, but that didn't work either since it's asking for an argument of type self: PacuPiranha.
Retrieving the Screen Size
You can get the screen size with this code
let screenSize = UIScreen.mainScreen().bounds.size
My point of view
I think you are overcomplicating the code.
Wouldn't it be easier if PacuPiranha was a subclass of SKSpriteNode added to GameScene?
And finally, do you really need all that code to preload the resources? SpriteKit is pretty good at managing the resources, you should let it do its work unless your game specifically needs a custom approach.
Update
IF PacuPiranha was a SKSpriteNode subclass (as it should be) added to the the SKScene you would be able to use this code to get the size of the current view.
class PacuPiranha: SKSpriteNode {
func foo() {
if let viewSize = self.scene?.view?.frame {
print(viewSize)
}
}
}
UIScreen.mainScreen().bounds.size.width * UIScreen.mainScreen().scale Will give you the screens width in pixels.