How to initialize a variable using another variable in an SKScene - swift

I want to be able to make two variables available to the entire SKScene, and all functions inside of it. One of these variable using the other one to create its value. I understand why I cannot do this, but I don't know a fix for it. I have this code:
class GameScene: SKScene {
let num : CGFloat = 1.25
let reciprocal = 1 / num // <— This Line
override func sceneDidLoad() {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
But I am obviously getting an error the line 4.
Cannot use instance member 'num' within property initializer; property
initializers run before 'self' is available
This means that I cannot use the variable because it is connected to the skscene, and the scene hasn't been implemented fully yet. Is there a way to declare this variable without throwing an error and making it assessable everywhere within this class?

Since reciprocal depends directly upon num, it could make sense to let the prior be a computed property based on the latter
class GameScene: SKScene {
let num: CGFloat = 1.5
var reciprocal: CGFloat { return 1/self.num }
// ...
}
Since num is an immutable property and will never change at runtime, another alternative is to let reciprocal be a lazy variable, computed upon its first use
class GameScene: SKScene {
let num: CGFloat = 1.5
lazy var reciprocal: CGFloat = { return 1/self.num }()
// ...
}
(Or, implement your own custom initializer for the GameScene, where you can initialize num and reciprocal to e.g. a given value and its reciprocal, respectively).

Related

Swift property observer issue

I've an issue with my property observer and would like to know a little bit more about the Swift behaviour.
I've the following architecture which use callbacks:
A higher lever class
class MyFirstClass : NSScrollView {
var colorDidUpdate: (() -> ())?
var color = NSColor.black {
didSet {
colorDidUpdate?()
}
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
/* Some init */
var mySecondClass = MySecondClass(frame: frame)
colorDidUpdate = mySecondClass.enclosingViewDidUpdateColor
mySecondClass.color = color
documentView = mySecondClass
/* Some other init */
}
}
Then a second class that act like a link between the first and third class
class MySecondClass {
var viewColorDidUpdate: (() -> ())?
var color: NSColor?
var myThirdClass = MyThirdClass(frame: frame)
init(frame frameRect: NSRect) {
/* Some init */
viewColorDidUpdate = myThirdClass.updateColor
myThirdClass.color = color
/* Some other init */
}
func enclosingViewDidUpdateColor() {
viewStyleDidUpdate?()
}
}
And finally a third class where the draw is done.
class MyThirdClass {
var color: NSColor?
func draw(_ dirtyRect: NSRect) {
guard let color = color else { return }
// The color is black instead of green.
}
/* proerties and functions... */
func updateColor() {
functionThatWillTriggerTheDrawFunction() // Will trigger draw function
}
}
If I set a new color to MyClass property like
var myClass = MyClass()
myClass.color = .green
The printed color is not "green" but it still black...
I thought that when we were in the didSet scope the variable was already set, am I wrong?
Should I use an other pattern?
I thought that when we were in the didSet scope the variable was already set, am I wrong?
No that's true, but something else is happening I guess (although the relevant code doesn't seem to be included)
A closure as the name suggests closes over variables it uses at the time of its definition. So you are most likely defining/using the closure at the time where your color still is black. You can not use a closure to give you the current value of a variable that is captured but you could pass the value into the closure as a parameter. I hope this makes sense.
If you provide a more complete code sample I can get a better idea about what the problem in your case might be.
Update (after you provided more code):
You are only ever setting the colors of the second and third classes on their init methods. You are never updating their color properties when you update the first classes color property.
I am sure the simplification of your presented code is partly to blame, but there are a few things you might want to consider to make things easier to follow:
Try not saving the color in each and every component separately but rather pass it along as parameters of your functions/methods. This makes it easier to see, what is happening. For example, you could call your change handler colorDidUpdate(to color: NSColor) and pass in the new value. This way, at least your second class doesn't need to store the color but rather pass it along into updateColor(_ color: NSColor) which could set the third class' color property and trigger a redraw.
In general I think it is beneficial to pass in any change to a change handler and not read it from a global state. Try not to store everything but pass along the information you need without storing it in between (if possible). This makes it easier to see where and how the data and information flows in your app and might indicate problems with the architecture.
It is a bit hard suggesting to architect the entire thing differently since the code is just fragments here, but it looks like you could improve and simplify a bit.

I keep getting an error saying "expected declaration"

I'm following a tutorial to make a version of flappy bird. I'm using swift and this error keeps coming up. The "addChild(self.myFloor1) keeps saying expected declaration error. What did I do wrong?
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var myBackground = SKSpriteNode()
var myFloor1 = SKSpriteNode()
var myFloor2 = SKSpriteNode()
addChild(self.myFloor1)
override func didMoveToView(view: SKView) {
myBackground = SKSpriteNode(imageNamed: "background")
myBackground.anchorPoint = CGPointZero;
myBackground.position = CGPointMake(100, 0);
self.backgroundColor = SKColor(red: 80.0/255.0, green: 192.0/255.0, blue: 203.0/255.0, alpha: 1.0)
addChild(self.myBackground)
myFloor1 = SKSpriteNode(imageNamed: "floor")
myFloor2 = SKSpriteNode(imageNamed: "floor")
myFloor1.anchorPoint = CGPointZero;
myFloor1.position = CGPointMake(0, 0);
myFloor2.anchorPoint = CGPointZero;
myFloor2.position = CGPointMake(myFloor1.size.width-1, 0);
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
When you write
addChild(self.myFloor1)
You are calling a method, which must be done inside another method declaration.
Within your class declaration, the "highest level" "things" need to be declared as something: "var", "let", "func". Then within a "func", you can call your addChild method.
That's why you are getting the error: At that "class" level, it's expecting only things that you can specify what they are. Which here, you are not. You are trying to directly call that method.
What I suspect you may want, is add the viewDidLoad method and call addChild from within there. Or something like that...whatever makes sense for your view lifecycle.
Declaration refers to creating a new variable or method. The location you wrote addChild() seems as if you're creating a new variable. For example, let's look at the following simple class.
class GameScene: SKScene {
var myBackground = SKSpriteNode()
}
The variable myBackground is being declared as a new variable. You are creating a new instance of an SKSpriteNode object. SKSpriteNode is also a class. Now let's add a method to your GameScene class that prints hello. All the things you declare in the class is referred as being in the top level, which is where you create variables and functions, etc.
class GameScene: SKScene {
var myBackground = SKSpriteNode()
//This is a method/function of the class GameScene
func sayHello() {
print("Hello.")
}
//CAN'T CALL ITS OWN METHOD AT THE TOP LEVEL
sayHello()
}
To help you understand, addChild is a method/function of the SKNode class.
class SKNode: UIResponder {
func addChild(node: SKNode) {...}
}
So when you have something like you have it, it doesn't make sense because addChild is a function and you can't call a function at the top level of a class.
class GameScene: SKScene {
var myBackground = SKSpriteNode()
var myFloor1 = SKSpriteNode()
//CAN'T CALL METHOD ON TOP LEVEL OF CLASS
addChild(self.myFloor1)
}
Xcode thinks you're creating a new function called addChild, so it's expecting you to declare it by using the "func" keyword, which is why it's giving you the error, but obviously you're not creating a function call addChild, you need to call it.
You have to call addChild() inside a function/method because it calls the SKNode's addChild method.

How do I get access to a variable's property to my class if it's in another class?

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.

SubClassing a sub-class in swift (skspritenode)

I'm currently making a game that will have multiple types of enemies, I currently made the basic enemy using a subclass of SKSpriteNode and everything is working fine. I now want to implement another sub class of the basic enemy since all the enemies are similar. I am having trouble creating the new subclass. Any help would be much appreciated. Here is my code...
class Basic_Fighter : SKSpriteNode {
var health : Int = 3
var ship_speed : Double = 7.0
var bullet_rapidness : Double = Double ((arc4random_uniform(3) + 2) )
var bullet_speed : Double = 3.0
var action = SKAction()
var bullet_Timer : NSTimer?
func subtract_health(){ health-- }
func enemy_killed(){
let action = SKAction.moveToY(-1000, duration: 5)
self.runAction(SKAction.repeatActionForever(action))
let rotateAction = SKAction.rotateByAngle(CGFloat(M_PI), duration: 3)
self.runAction(SKAction.repeatActionForever(rotateAction))
//Set a timer to destoy the node
_ = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: Selector("destroy"), userInfo: nil, repeats: false)
}
func destroy(){
self.removeFromParent()
}
}
and the next enemy ....
class new_fighter : Basic_Fighter {
convenience init(){
self.init()
health = 5
}
}
I am not sure which exactly question are you asking, but this part is wrong:
class new_fighter : Basic_Fighter {
convenience init(){
self.init()
health = 5
}
}
You can not have convenience initializer declared just as is, without a designated initializer. Perhaps you should revisit Swift language reference for the part about designated vs. convenience initializers.
From the documentation:
... Swift applies the following three rules for delegation calls between initializers:
Rule 1
A designated initializer must call a designated initializer from its immediate superclass.
Rule 2
A convenience initializer must call another initializer from the same class.
Rule 3
A convenience initializer must ultimately call a designated initializer.
When you subclass SKSPriteNode you must use this designated initializer:
init(texture: SKTexture?, color: UIColor, size: CGSize)
and in your convenience initializer call: self.init(texture: texture, color: color, size: size)

Swift: Use constant property to define another property fails

I'm trying to declare an array of a static size. I'd like a constant to define the size of the array.
I'm trying the following in Swift
class foo {
let size = 10
let myArray = [Int](count: size, repeatedValue: 0)
}
But this fails with an error,
'foo.Type' does not have a member named 'size'
If I don't use the size constant, the compiler is happy with it but isn't what I'd like. And there's no #define capability that I'm aware of.
let myArray = [Int](count: 10, repeatedValue: 0)
Swift gives you a couple ways to do this. The simplest, and most in line with the #define style you mention, is to declare size as a global constant:
let FOOSIZE = 10
class Foo {
let myArray = [Int](count: FOOSIZE, repeatedValue: 0)
}
Alternatively, you can define myArray as a lazy variable, and use a closure to populate its value. By the time the closure is executed you'll be able to access self.size:
class Foo {
let size = 10
lazy var myArray: [Int] = { [Int](count: self.size, repeatedValue: 0) }()
}
In swift self is unavailable until all class/struct properties have been initialized, and a subclass initializer has been called (in case of an inherited class).
In your case you are initializing properties outside of an initializer, but that doesn't change the result: you cannot initialize a variable implicitly referencing self (which you do when accessing to the size property).
However, size looks like a constant, and as such it's better to instantiate it once (as a static property) rather than having it created in each class instance. Swift doesn't support static class properties, but structs do, so a trick is to define an inner private struct, containing the static immutable properties you may need:
class foo {
private struct Static {
static let size = 10
}
let myArray = [Int](count: Static.size, repeatedValue: 0)
}
With Swift 1.2 you can simply add static before let size, making it a class constant and as such defined before myArray is being defined:
class foo {
static let size = 10
let myArray = [Int](count: size, repeatedValue: 0)
}
Be aware though, that using size later in your code requires you to fully qualify it as foo.size.
One way is for it to be in a function. This worked in a playground:
import UIKit
class ViewController: UIViewController {
var myArray = [Int]()
func appendArray (#index: Int, value: Int) {
myArray[index] = value
}
override func viewDidLoad() {
super.viewDidLoad()
let size = 10
myArray = [Int](count: size, repeatedValue: 0)
appendArray(index: 3, value: 4)
println(myArray)
}
}
Okay, I used a ViewController because it was convenient, but not necessary. No problem declaring the array out side of a function. I still used a function to create the array and used another one t change a value.