Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIScrollView horizontally scrolling menu in Sprite Kit

I am trying to make a horizontally scrolling menu in my new Sprite Kit game. With Sprite Kit being fairly new, there aren't many good tutorials for Sprite Kit. Is UIScrollView compatible with Sprite Kit? I have tried a couple of ways such as this:

UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
scroll.pagingEnabled = YES;
NSInteger numberOfViews = 3;
for (int i = 0; i < numberOfViews; i++) {
    CGFloat xOrigin = i * self.view.frame.size.width;
    UIView *awesomeView = [[UIView alloc] initWithFrame:CGRectMake(xOrigin, 0,      self.view.frame.size.width, self.view.frame.size.height)];
    awesomeView.backgroundColor = [UIColor colorWithRed:0.5/i green:0.5 blue:0.5 alpha:1];
    [scroll addSubview:awesomeView];
}

This didn't work. I have never worked with UIScrollView before. Can anyone give a way of making a horizontally scrolling menu using UIScrollView? An example of what I am trying to do is in Bloons TD 5 with their menu.

like image 359
PoKoBros Avatar asked Dec 25 '13 03:12

PoKoBros


3 Answers

I know this question is a few months old already, but I was also looking for a scrolling menu slider to select levels in my SpriteKit game. I couldn't find any code already existing and the thought of force fitting the UIScrollView into my SpriteKit game didn't appeal to me. So I wrote my own sliding menu for level selection.

I've simulated all of the motions of the UIScrollView so it looks natural, and can be configured to scroll left - right or up - down.

As a bonus I created a parallax background on the menu which can be easily swapped out with new images for a custom look.

https://github.com/hsilived/ScrollingMenu

The code for the Scrolling menu is very well commented so there shouldn't be to many integration issues.

The github project has a fully working demo with Worlds, Levels and parallax images. If you like it mark my answer as useful so I can gain some reputation :)

like image 68
Ron Myschuk Avatar answered Oct 14 '22 04:10

Ron Myschuk


You can definitely combine UIKit controls with SpriteKit. From the SKScene where you want to have a UIScrollView add the scroll view to self.view, like this: [self.view addSubview:scroll]. You may want to do this in the -(void)didMoveToView:(SKView*)view method on your SKScene. Remember to remove the scroll view from the SKView once you transition out of the SKScene that is using it. You can do that in the -(void)willMoveFromView:(SKView*)view method. Don't forget to set the contentSize on the UIScrollView.

Note: If you want to have SpriteKit nodes over the UIKit controls then you will need to write your own SKSpriteNode that behaves like a UIScrollView.

Expanding on Note: If your scroll view needs to contain Sprite-kit nodes and not just UIKit views you won't be able to use UIScrollView unless you do something clever as suggested in the answer to this question here

like image 39
Bokoskokos Avatar answered Oct 14 '22 04:10

Bokoskokos


There are many ways to obtain it, through SKCameraNode, using UIKit elements like UIScrollView or other ways.. But I want to show you how to do it simple with SpriteKit only. Conceptually, you should build a larger SKNode that containt every single voice of your menu where each element have an equal distance from the other. You can simulate the scrolling with an SKAction that move our node to the center of the nearest visible element.

GameViewController:

class GameViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        guard let view = self.view as! SKView? else { return }
        view.ignoresSiblingOrder = true
        view.showsFPS = true
        view.showsNodeCount = true
        view.showsPhysics = false
        view.showsDrawCount = true
        let scene = GameScene(size:view.bounds.size)
        scene.scaleMode = .aspectFill
        view.presentScene(scene)
    }
}

GameScene:

class GameScene: SKScene {
    var lastX: CGFloat = 0.0
    var moveableArea = SKNode() // the larger SKNode
    var worlds: [String]! = ["world1","world2","world3"] // the menu voices
    var currentWorld:Int = 0 // contain the current visible menu voice index
    var worldsPos = [CGPoint]() // an array of CGPoints to store the menu voices positions
    override func didMove(to view: SKView) {
        //Make a generic title
        let topLabel = SKLabelNode.init(text: "select world")
        topLabel.fontSize = 40.0
        self.addChild(topLabel)
        topLabel.zPosition = 1
        topLabel.position = CGPoint(x:frame.width / 2, y:frame.height*0.80)
        // Prepare movable area
        self.addChild(moveableArea)
        moveableArea.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
        // Prepare worlds:
        for i in 0..<worlds.count {
            // add a title for i^ world
            let worldLabel = SKLabelNode.init(text: worlds[i])
            self.moveableArea.addChild(worldLabel)
            worldLabel.position = CGPoint(x:CGFloat(i)*self.frame.width ,y:moveableArea.frame.height*0.60)
            // add a sprite for i^ world
            let randomRed = CGFloat(drand48())
            let randomGreen = CGFloat(drand48())
            let randomBlue = CGFloat(drand48())
            let randomColor = UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
            let worldSprite = SKSpriteNode.init(color: randomColor, size: CGSize.init(width: 100.0, height: 100.0))
            worldSprite.name = worlds[i]
            self.moveableArea.addChild(worldSprite)
            worldSprite.position = CGPoint(x:CGFloat(i)*self.frame.width ,y:0.0)
        }
    }
    func tapNode(node:SKNode,action:SKAction) {
        if node.action(forKey: "tap") == nil {
            node.run(action, withKey: "tap")
        }
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first!
        let location = touch.location(in: self)
        let touchedNode = self.atPoint(location)
        lastX = location.x
        let sequence = SKAction.sequence([SKAction.scale(to: 1.5, duration: 0.2),SKAction.scale(to: 1.0, duration: 0.1)]) // a little action to show the selection of the world
        switch touchedNode.name {
        case "world1"?:
            print("world1 was touched..")
            tapNode(node:touchedNode,action:sequence)
        case "world2"?:
            print("world2 was touched..")
            tapNode(node:touchedNode,action:sequence)
        case "world3"?:
            print("world3 was touched..")
            tapNode(node:touchedNode,action:sequence)
        default:break
        }
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first!
        let location = touch.location(in: self)
        let currentX = location.x
        let leftLimit:CGFloat = CGFloat(worlds.count-1)
        let rightLimit:CGFloat = 1.0
        let scrollSpeed:CGFloat = 1.0
        let newX = moveableArea.position.x + ((currentX - lastX)*scrollSpeed)
        if newX < self.size.width*(-leftLimit) {
            moveableArea.position = CGPoint(x:self.size.width*(-leftLimit), y:moveableArea.position.y)
        }
        else if newX > self.size.width*rightLimit {
            moveableArea.position = CGPoint(x:self.size.width*rightLimit, y:moveableArea.position.y)
        }
        else {
            moveableArea.position = CGPoint(x:newX, y:moveableArea.position.y)
        }
        // detect current visible world
        worldsPos = [CGPoint]()
        for i in 0..<worlds.count {
            let leftLimit = self.size.width-(self.size.width*CGFloat(i))
            let rightLimit = self.size.width-(self.size.width*CGFloat(i+1))
            if rightLimit ... leftLimit ~= moveableArea.position.x {
                currentWorld = i
            }
            worldsPos.append(CGPoint(x: (rightLimit + (leftLimit - rightLimit)/2), y:moveableArea.position.y))
        }
        lastX = currentX
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if worldsPos.count>0, moveableArea.action(forKey: "moveAction") == nil {
            let moveAction = SKAction.move(to: worldsPos[currentWorld], duration: 0.5)
            moveAction.timingMode = .easeInEaseOut
            self.moveableArea.run(moveAction, withKey: "moveAction")
        }
    }
}

As you can see , with touchesBegan we can detect the touched world and select it, with touchesMoved we are able to move the moveable node to the correct position and to detect the nearest visible world and with touchesEnded we can create the smooth scrolling to automatic move to the current nearest visible voice after a touch..

Output:

enter image description here

like image 24
Alessandro Ornano Avatar answered Oct 14 '22 03:10

Alessandro Ornano