Allow Users To select Player Skin Found nil on if Statement - ios

I am trying to allow the player to select a skin from a UIViewController than when the sprite is called it is loaded with their selected skin.
ball.swift
var skin : Skins!
init(ballName name : String?, ballColor color : UIColor, ballMass mass : CGFloat, ballPosition pos : CGPoint) {
if skin.dogSkin == true{
super.init(texture: SKTexture(imageNamed: "doge"), color: color, size: CGSize(width: radius * 2, height: radius * 2))
print("was called here too")
}
else{
super.init(texture: SKTexture(imageNamed: "circle"), color: color, size: CGSize(width: radius * 2, height: radius * 2))
}
Skins.swift
var dogSkin = false
#IBAction func backbutton(sender: AnyObject) {
}
#IBAction func dogPressed(sender: AnyObject) {
dogSkin = true
print("I hit it")
}
Now getting fatal error: unexpectedly found nil while unwrapping an optional value,When the if statement is triggered, can't use a guard statment on it.
changed it to
var skin = Skins()
works-- doesn't crash. But doesn't this create a new instance of Skins? Making the BOOL false again?
Get this in the console though? and skin doesn't change.
<xxxx.Skins: 0x7fa59d825b30>
<xxxx.Skins: 0x7fa59d828500>
<xxxx.Skins: 0x7fa59d82a7a0>
<xxxx.Skins: 0x7fa59d82c970>
<xxxx.Skins: 0x7fa59d82e960>
<xxxx.Skinss: 0x7fa59d90d7b0>
<xxxx.Skins: 0x7fa59d90ffb0>
<xxxx.Skins: 0x7fa59d912000>
<xxxx.Skins: 0x7fa59d914060>
...

You made the variable skin an implicitly unwrapped optional variable. That means that any time you reference it, it assumes the variable contains a valid value and crashes if it does not.
You don't assign a value to skin before trying to reference it, so you crash.
Forget you ever knew about the force-unwrap operator ! for your first 2 months of programming in Swift. For newbies, it's the crash operator.

Related

Adding child outside of didMove scope

I have came across a very unusual problem with - to my understand - swift 3. I am create a SpriteKit iOS game. The problem is with a SKSpriteNode. I create the node by
var gameOverBackground : SKSpriteNode!
This is located just inside the class. I then initialize the node in the didMove() function like so
override func didMove(to view: SKView) {
gameOverBackground = SKSpriteNode.init(texture: SKTexture(image: #imageLiteral(resourceName: "UIbg")), size: CGSize(width: (self.scene?.size.width)! / 1.5, height: (self.scene?.size.height)! / 1.5))
gameOverBackground.position = CGPoint(x: 0, y: 0)
gameOverBackground.zPosition = 10
gameOverBackground.name = "GameOverBackground"
}
Now the problem arises when I later try and add the node as a child to the scene. I am doing this in a contact function like so
func didBegin(_ contact: SKPhysicsContact) {
if (contact with many nodes) {
self.addChild(self.gameOverBackground)
}
}
I keep getting the error that the node is equal to nil. I have found a work around by just simply adding the child node in didMove() and making it hidden. Then just making it unhidden when contact happens; but I was more curious on why this problem is happening and if i'm doing something wrong.
Thanks in advance!
I have found a work around by just simply adding the child node in didMove() and making it hidden. Then just making it unhidden when contact happens; but I was more curious on why this problem is happening and if i'm doing something wrong.
This means that your node is notnil in didMove, but then somewhere along the way has become nil by the time of your contact delegate.
Here is an example of your code, only simplified a bit:
var node: SKSpriteNode!
override func didMove(to view: SKView) {
node = SKSpriteNode(color: .blue, size: CGSize(width: 25, height: 25))
}
// Touchesbegan on ios:
override func mouseDown(with event: NSEvent) {
addChild(node)
}
The code runs fine and when you click the screen the node appears.
Touchesbegan / didBegin won't make a difference.
The issue is that somewhere in your code the node is getting set to nil.
Here is an example of how that could happen:
// Touchesended on ios:
override func mouseUp(with event: NSEvent) {
node.removeFromParent() // or, node = nil
addChild(node) // crash!!
}

Value of type 'Any' has no member 'removeFromSuperview'

Q.1 Why does 'Any' has no member 'removeFromSuperview'?
Q.2 Why does it say cast 'Any' to 'AnyObject' or use 'as!' to force downcast to a more specific type to access members?
var allImgViews = [Any]()
viewDidLoad(){
// sample code
for v in 0..<4 {
myImageView.image = UIImage(named: something)
allImgViews.append(myImageView)
}
allImgViews[0].removeFromSuperview()
}
Any help is appreciated
Because Any means literally ANY data type. There's nothing that requires an Any typed value to have to be able to be a view that's capable of being added or removed from a view. Int can be an Any, String can be an Any. What would you expect something like this to do?
let any: Any = 123
any.removeFromSuperView() // What should this call do??? o.0'
Because you only know the members of a value by the type that you know the value has. That's what a type is: it's a categorization of values by what operations you can perform on them.
In a type-safe language like Swift, you can't perform any operation unless your sure the value your doing it to supports that operation. And you gain that certainty when you know the values exact type, because then you can check to see whether that type supports the operation or member you're trying to use.
There's no reason to be using Any here, in the first place. Here's how to write this with nice, specific types:
var imageViews = [UIImageView]()
func viewDidLoad() {
for v in 0..<4 {
let imageView = UIImageView(frame: CGRect(x: 0 , y: 192, width: 98, height: 94))
imageView.image = UIImage(named: something)
imageViews.append(imageView)
}
imageView[0].removeFromSuperview()
}
If imageViews has no values before the append calls in viewDidLoad, this code can be better written as:
let imageViews = [UIImageView]()
func viewDidLoad() {
imageViews = (0..<4).map { v in
let imageView = UIImageView(frame: CGRect(x: 0 , y: 192, width: 98, height: 94))
imageView.image = UIImage(named: something)
return imageView
}
imageView[0].removeFromSuperview()
}

How to consolidate all characters under one main player to perform same action in SpriteKit

I am building a game in SpriteKit. The game will include 5 players and instead of coding SKActions five times for every player, I want to consolidate all characters into one and code one time for each SKAction, but the code below doesn't seem to work. Am I missing something?
import SpriteKit
var player1 = SKSpriteNode()
var player2 = SKSpriteNode()
var player3 = SKSpriteNode()
var player4 = SKSpriteNode()
var player5 = SKSpriteNode()
var mainPlayer = SKSpriteNode()
// in view did load
player1 = mainPlayer
player2 = mainPlayer
player3 = mainPlayer
player4 = mainPlayer
player5 = mainPlayer
//in touches began
let rightMoveAction = SKAction.moveByX(50, y: 0, duration: 0.1)
mainPlayer.runAction(SKAction.repeatActionForever(rightMoveAction))
I'm assuming your motivation is to make the code shorter and because mainPlayer is probably a complex node that you don't want to replicate the code 5 times. You don't show where you add the nodes to the scene, but the way you currently have it will crash when you try to add player2 because it is already added. It looks like you are trying to simply make 5 of the same character that can perform the same action.
Here is a general strategy I would use to try to accomplish what you're after. Since your mainPlayer node is probably some complex character that you will be reusing (possibly even in other scenes), then you might consider making it into its own class (and put the code in a separate .swift file). You can then add the functions it will use to move and perform other SKActions
For example:
import SpriteKit
class Character : SKSpriteNode {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
//setup your node here
self.userInteractionEnabled = true//you need this to detect touches
//log so you know it was created - take this out at some point
print("character created")
}
//create an example action that will move the node to the right by 50
func moveRight() {
print("moving right")
self.runAction(SKAction.moveByX(50, y: 0, duration: 0.1))
}
//you can also override the touch functions so you'll know when the node was touched
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("charater touched")
}
}
Then in your scene, when you want to create and use a new character, you can do this:
//create the character
let newPlayer = Character.init(texture: nil, color: UIColor.blueColor(), size: CGSizeMake(50, 50))
//position the character where you want
newPlayer.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
//add the character to the scene
self.addChild(newPlayer)
/move the character to the right
newPlayer.moveRight()
Making things more "modular" like this is great for re-usability of code. Now you can reuse the Character class whenever and wherever you need. Even in an entirely different game.
In terms of making all characters run the same action with one function call, I don't know of anything that would allow this. Although you could write your own function for it by perhaps placing all of the character nodes into an array and passing the array as an argument to a custom function, but personally I would just call the moveRight function on each of the nodes after you instantiated them.

Double to String error in Swift

How do I display the value from a custom range slider to the user as the user drags the knob?
Context
I am new to iOS dev, so please forgive me if I am missing something obvious. I am creating a custom range slider, and now I am at the point where I would like to display the value from the slider to the user in the UI as the user drags the knob.
For background, I have completed this tutorial and am now attempting to take the values printed in the console and convert them into a readable string to display to the user. The values are printed via this function:
func rangeSliderValueChanged(rangeSlider: RangeSlider) {
print("Range slider value changed: (\(rangeSlider.lowerValue) \(rangeSlider.upperValue))")
}
Which is called in viewDidLoad in the ViewController:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rangeSlider)
rangeSlider.addTarget(self, action: "rangeSliderValueChanged:", forControlEvents: .ValueChanged)
}
ValueChanged is in an override of continueTrackingWithTouch in RangeSlider.swift:
override func continueTrackingWithTouch(touch: UITouch,
withEvent event: UIEvent?) -> Bool {
// .. other stuff
self.sendActionsForControlEvents(UIControlEvents.ValueChanged)
return true
}
My Attempt
At this point I figured the way to solve this was to add a text field in the view, then update the text field with the value from the slider.
I created a text field in viewDidLoad:
var lowNumber: UITextField = UITextField(frame: CGRect(x: 0, y: 0, width: 200.00, height: 40.00))
override func viewDidLoad() {
lowNumber.backgroundColor = UIColor.redColor()
lowNumber.text = "some string"
lowNumber.borderStyle = UITextBorderStyle.Line
self.view.addSubview(lowNumber)
// .. more stuff
}
Then I tried to change the text field string in the rangeSliderValueChanged function:
func rangeSliderValueChanged(rangeSlider: RangeSlider) {
lowNumber.text = rangeSlider.lowerValue
}
Error
At this point I encountered an error which states:
Cannot assign a value of type 'Double' to a value of type 'String?'
Double is used in the RangeSlider class such as:
class RangeSlider: UIControl {
// .. stuff
var lowerValue: Double = 0.2 {
didSet {
updateLayerFrames()
}
}
// .. more stuff
}
This is where I am stuck, and the googling I have done on converting a double to a string has left me wondering how I could apply those solutions to my case. More fundamentally, I am wondering if I am approaching the problem in the right fashion.
Question
How do I solve the problem of displaying the value from the slider to the user? Do I need to use a different type of layer than UITextField? Have I approached the problem correctly, or would you recommend a different approach?
You need to convert the number to String. For example:
lowNumber.text = "\(rangeSlider.lowerValue)"

limit input options in init() in swift

I'm making a simple Dice class in swift.
I would like the Dice initializer to be called with the desired amount of eyes/sides the dice should have. I have two variables to set a min and max of number of sides you should be able to give the dice upon init...
However, I'm not quite sure of how to make the init fail if the dice is being initialized with a number outside of this range, when I can't make sue of try/catch in swift.
My code is as follows:
class Dice : SKSpriteNode {
let sides : UInt32
var score : Int
init(sides : Int){
let min_sides = 2
let max_sides = 6
self.sides = UInt32(sides)
self.score = 1
let imageName = "1.png"
let cardTexture = SKTexture(imageNamed: imageName)
super.init(texture: cardTexture, color: nil, size: CGSize(width: 100, height: 100))
userInteractionEnabled = true
}
Use a failable initializer instead. From that you can return a nil value if the condition doesnt satisfied
init?(sides : Int){
if sides > max_sides{
return nil
}
}

Resources