Luiz Luiz - 3 months ago 15
Swift Question

iCarousel in Sprite Kit

Explanation



I'm trying to build a character selection menu similar to Crossy Road's one (as you can see here). So I found this iCarousel, which would help me with all of it, but everything I read talk about implementing it to a
ViewController
, which isn't my case. I'm using
GameScene
and I didn't found anything talking about it. Is there anyway I could implement it to my game? or even another effect similar to the character selection menu I mentioned above?




Attempt (beyowulf)



You can download it here.



GameScene.swift

import SpriteKit

class GameScene: SKScene {

var show = SKSpriteNode()
var hide = SKSpriteNode()

func showCharPicker(){
NSNotificationCenter.defaultCenter().postNotificationName("showCharPicker", object: nil)
}
func hideCharPicker(){
NSNotificationCenter.defaultCenter().postNotificationName("hideCharPicker", object: nil)
}

override func didMoveToView(view: SKView) {
/* Setup your scene here */

print("didMoveToView")

show = SKSpriteNode(imageNamed: "show")
show.anchorPoint = CGPointZero
show.position = CGPointZero
addChild(show)

hide = SKSpriteNode(imageNamed: "hide")
hide.anchorPoint = CGPointZero
hide.position = CGPoint(x: self.frame.width / 2 - hide.frame.width / 2, y: 0)
addChild(hide)
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

for touch in touches{

let location = touch.locationInNode(self)
let node = nodeAtPoint(location)

if node == show{
print("show")
showCharPicker()
}
else if node == hide{
print("hide")
hideCharPicker()
}
}
}
}


GameViewController.swift

import UIKit
import SpriteKit

class GameViewController: UIViewController, iCarouselDataSource, iCarouselDelegate{

var squaresArray : NSMutableArray = NSMutableArray()

@IBOutlet weak var carousel: iCarousel!

deinit{
NSNotificationCenter.defaultCenter().removeObserver(self)
}

func showCarousel(){
self.carousel.hidden = false
}
func hideCarousel(){
self.carousel.hidden = true
}

override func viewDidLoad(){
super.viewDidLoad()

// Configure iCarousel
carousel.dataSource = self
carousel.delegate = self
carousel.type = .CoverFlow
carousel.reloadData()

self.carousel.hidden = true

// Register showCarousel and hideCarousel functions
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showCarousel), name: "showCharPicker", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.hideCarousel), name: "hideCharPicker", object: nil)

// Configure view
let skView = SKView()
self.view.insertSubview(skView, belowSubview: self.carousel)
skView.frame = self.view.bounds

// Additionals
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true

// Configure scene
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .ResizeFill
scene.size = self.view.bounds.size

skView.presentScene(scene)
}

//iCarousel
override func awakeFromNib(){
super.awakeFromNib()
squaresArray = NSMutableArray(array: ["square1","square2","square3"])
}
func numberOfItemsInCarousel(carousel: iCarousel) -> Int{
return squaresArray.count
}
func carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger){
//self.hideCarousel()
}
func carousel(carousel: iCarousel, viewForItemAtIndex index: Int, reusingView view: UIView?) -> UIView{

var itemView: UIImageView

if (view == nil){
itemView = UIImageView(frame:CGRect(x:0, y:0, width:200, height:200))
itemView.contentMode = .Center
}
else{
itemView = view as! UIImageView;
}

itemView.image = UIImage(named: "\(squaresArray.objectAtIndex(index))")
return itemView
}
func carousel(carousel: iCarousel, valueForOption option: iCarouselOption, withDefault value: CGFloat) -> CGFloat{

if (option == .Spacing){
return value * 2
}
return value
}
}





What's happening:



enter image description here



Thanks in advance,

Luiz.

Answer

You can use NSNotifications to show your character picker. You just need to observe the notifications posted by your SKScene. Your viewDidLoad should look something like:

override func viewDidLoad(){
    super.viewDidLoad()

    carousel.type = .CoverFlow
    carousel.reloadData()

    let spriteKitView = SKView()
    spriteKitView.frame = self.view.bounds
    self.view.insertSubview(spriteKitView, belowSubview: self.carousel)

    spriteKitView.showsFPS = true
    spriteKitView.showsNodeCount = true
    spriteKitView.ignoresSiblingOrder = true

    self.gameScene = GameScene(size:self.view.bounds.size)
    self.gameScene.scaleMode = .AspectFill
    self.gameScene.imageName = self.images[0] as! String

    self.carousel.hidden = true
    spriteKitView.presentScene(self.gameScene)

    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showCarousel), name: gameScene.kShowNotification, object: nil)
}

You'll want to implementing carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger) so you know what is selected, and so you can return to game play. For example:

func carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger)
{
    self.gameScene.imageName = self.images[index] as! String
    self.hideCarousel()
}

You also need to remove observing before your view controller is deallocated.

deinit
{
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

Your SKScene can then post a notifications:

import SpriteKit

class GameScene: SKScene {
    var imageName = "square1"{
        didSet{
            self.hidden = false
            self.childNode.texture = SKTexture(imageNamed: imageName)
        }
    }

    let kShowNotification = "showPicker"

    var childNode = SKSpriteNode()
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */

        self.childNode = SKSpriteNode(imageNamed: imageName)
        self.childNode.anchorPoint = CGPointZero
        self.childNode.position = CGPointZero
        self.addChild(self.childNode)
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        self.showCharPicker()
    }

    func showCharPicker()
    {
        self.hidden = true
        NSNotificationCenter.defaultCenter().postNotificationName(kShowNotification, object: nil)
    }

}

If you want to change hit detection, you need to subclass the view for which you need it to change. This case your iCarousel view.

You can then either override hitTest or pointInside. I've created an iCarousel subclass and overrode pointInside to only return true when the point is inside one of the carousel's contentView's subviews.

class CarouselSubclass: iCarousel {

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        var inside = false
        for view in self.contentView.subviews
        {
            inside = CGRectContainsPoint(view.frame, point)
            if inside
            {
                return inside
            }
        }
        return inside
    }
}

You need to remember to change the class of your carousel in interface builder and update your outlet as well.