Sprite Kit中的UIScrollView水平滚动菜单

8

我正在尝试在我的新Sprite Kit游戏中制作一个横向滚动菜单。由于Sprite Kit相对较新,没有太多关于它的好教程。UIScrollView与Sprite Kit兼容吗?我已经尝试了几种方式,比如这样:

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];
}

这个方法不起作用。我以前从未使用过UIScrollView。有人可以给出一种使用UIScrollView制作水平滚动菜单的方法吗?我想要做的一个例子是Bloons TD 5中的菜单。


不,UIScrollView与SpriteKit不兼容。但是,您可以使用带有scrollView的viewController来创建菜单,或者自己为SpriteKit创建一个scrollView。 - ZeMoon
3个回答

15

我知道这个问题已经几个月了,但我也在寻找一个用于在我的SpriteKit游戏中选择关卡的滚动菜单滑块。我找不到任何现有的代码,并且将UIScrollView强制适应我的SpriteKit游戏的想法并不吸引我。因此,我为级别选择编写了自己的滑动菜单。

我模拟了UIScrollView的所有动作,使其看起来自然,并且可以配置为左右或上下滚动。

作为奖励,我在菜单上创建了视差背景,可以轻松地更换为新图像以获得自定义外观。

https://github.com/hsilived/ScrollingMenu

滚动菜单的代码非常清晰明了,因此不应该有太多集成问题。

Github项目有一个完全工作的演示,包括世界、关卡和视差图像。如果您喜欢它,请将我的答案标记为有用,这样我就可以获得一些声誉 :)


没有仔细查看,但看起来很有前途!干得好,谢谢分享! - dennis-tra
很棒的演示,谢谢。顺便问一下怎样关闭分页,还有当使用两个手指时它会试图跳出..你能帮我解决吗?你计划制作教程吗 :-)那也太好了。感谢分享。 - A. Adam
@A. Adam 给我发一封电子邮件。信息可以在 GitHub 页面上找到。我不想在 PoKoBros 的问题中提出关于我的代码的问题。 - Ron Myschuk
@RonMyschuk尝试发送电子邮件,但提示“用户邮箱已满”。 - A. Adam
嗨,我只是想分享我的做法:http://xcodenoobies.blogspot.my/2015/09/skscrollview-skspritekit-scrollview-for.html - GeneCode

6
你可以将UIKit控件与SpriteKit结合使用。在你希望添加UIScrollView的SKScene中,像这样将滚动视图添加到self.view上:[self.view addSubview:scroll]。你可能希望在SKScene的-(void)didMoveToView:(SKView*)view方法中执行此操作。记得在不再使用它的SKScene中转换出去时从SKView中删除滚动视图。你可以在-(void)willMoveFromView:(SKView*)view方法中这样做。别忘了在UIScrollView上设置contentSize。
注意:如果你希望在UIKit控件上方有SpriteKit节点,则需要编写自己的SKSpriteNode,使其行为类似于UIScrollView。
进一步说明:如果你的滚动视图需要包含Sprite-kit节点而不仅仅是UIKit视图,则除非你按照这里的答案建议进行一些巧妙的处理,否则你将无法使用UIScrollView。

能否详细说明一下这个笔记,@Bokoskokos? - orion elenzil

1

有许多方法可以获得它,通过 SKCameraNode,使用像 UIScrollView 这样的 UIKit 元素或其他方式... 但我想向您展示如何仅使用 SpriteKit 简单地完成。 从概念上讲,您应该构建一个更大的 SKNode,其中包含菜单的每个单独声音,每个元素之间距离相等。 您可以使用一个 SKAction 来模拟滚动,将我们的节点移动到最近可见元素的中心。

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")
        }
    }
}

从上面可以看到,使用touchesBegan我们可以检测触摸的单词并选择它,使用touchesMoved我们能够将可移动节点移动到正确的位置和检测最近可见的单词,并使用touchesEnded我们可以创建平滑滚动以自动移动到当前最近可见的语音。

输出

enter image description here


您可以在我的 gitHub 这里 找到此项目。 - Alessandro Ornano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接