2015-12-11 68 views
6

我正在寻找在我的游戏中创建一个商店(在SpriteKit中)的按钮和图像,但我需要的项目是可以滚动的,所以玩家可以上下滚动商店(就像一个UITableView,但有多个SKSpriteNodes和SKLabelNodes在每个单元格中)。任何想法如何我可以在SpriteKit中做到这一点?如何在spritekit中创建一个垂直滚动菜单?

+0

丹,你有没有弄清楚如何获得与Crashoverride777的滚动视图工作的按钮?我遇到了麻烦 –

+0

@JoshSchlabach是的我做了,我似乎记得这是为了确保滚动视图完成滚动并变为静态,因为它不会识别触摸,但它认为它仍在滚动,尽管有时候我发现它有点bug,所以我最终没有使用它! – Dan2899

+0

那你用了什么?我需要我的应用程序的滚动视图帮助! –

回答

12

第二个答案如所承诺的,我刚才发现了这个问题。

我建议始终从我的gitHub项目中获取此代码的最新版本incase我做了更改,因为此答案,链接位于底部。

第1步:创建一个新的文件,迅速在这个代码

import SpriteKit 

/// Scroll direction 
enum ScrollDirection { 
    case vertical // cases start with small letters as I am following Swift 3 guildlines. 
    case horizontal 
} 

class CustomScrollView: UIScrollView { 

// MARK: - Static Properties 

/// Touches allowed 
static var disabledTouches = false 

/// Scroll view 
private static var scrollView: UIScrollView! 

// MARK: - Properties 

/// Current scene 
private let currentScene: SKScene 

/// Moveable node 
private let moveableNode: SKNode 

/// Scroll direction 
private let scrollDirection: ScrollDirection 

/// Touched nodes 
private var nodesTouched = [AnyObject]() 

// MARK: - Init 
init(frame: CGRect, scene: SKScene, moveableNode: SKNode) { 
    self.currentScene = scene 
    self.moveableNode = moveableNode 
    self.scrollDirection = scrollDirection 
    super.init(frame: frame) 

    CustomScrollView.scrollView = self 
    self.frame = frame 
    delegate = self 
    indicatorStyle = .White 
    scrollEnabled = true 
    userInteractionEnabled = true 
    //canCancelContentTouches = false 
    //self.minimumZoomScale = 1 
    //self.maximumZoomScale = 3 

    if scrollDirection == .horizontal { 
     let flip = CGAffineTransformMakeScale(-1,-1) 
     transform = flip 
    } 
} 

required init?(coder aDecoder: NSCoder) { 
    fatalError("init(coder:) has not been implemented") 
    } 
} 

// MARK: - Touches 
extension CustomScrollView { 

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

    for touch in touches { 
     let location = touch.locationInNode(currentScene) 

     guard !CustomScrollView.disabledTouches else { return } 

     /// Call touches began in current scene 
     currentScene.touchesBegan(touches, withEvent: event) 

     /// Call touches began in all touched nodes in the current scene 
     nodesTouched = currentScene.nodesAtPoint(location) 
     for node in nodesTouched { 
      node.touchesBegan(touches, withEvent: event) 
     } 
    } 
} 

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

    for touch in touches { 
     let location = touch.locationInNode(currentScene) 

     guard !CustomScrollView.disabledTouches else { return } 

     /// Call touches moved in current scene 
     currentScene.touchesMoved(touches, withEvent: event) 

     /// Call touches moved in all touched nodes in the current scene 
     nodesTouched = currentScene.nodesAtPoint(location) 
     for node in nodesTouched { 
      node.touchesMoved(touches, withEvent: event) 
     } 
    } 
} 

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

    for touch in touches { 
     let location = touch.locationInNode(currentScene) 

     guard !CustomScrollView.disabledTouches else { return } 

     /// Call touches ended in current scene 
     currentScene.touchesEnded(touches, withEvent: event) 

     /// Call touches ended in all touched nodes in the current scene 
     nodesTouched = currentScene.nodesAtPoint(location) 
     for node in nodesTouched { 
      node.touchesEnded(touches, withEvent: event) 
     } 
    } 
} 

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

    for touch in touches! { 
     let location = touch.locationInNode(currentScene) 

     guard !CustomScrollView.disabledTouches else { return } 

     /// Call touches cancelled in current scene 
     currentScene.touchesCancelled(touches, withEvent: event) 

     /// Call touches cancelled in all touched nodes in the current scene 
     nodesTouched = currentScene.nodesAtPoint(location) 
     for node in nodesTouched { 
      node.touchesCancelled(touches, withEvent: event) 
     } 
    } 
    } 
} 

// MARK: - Touch Controls 
extension CustomScrollView { 

    /// Disable 
    class func disable() { 
     CustomScrollView.scrollView?.userInteractionEnabled = false 
     CustomScrollView.disabledTouches = true 
    } 

    /// Enable 
    class func enable() { 
     CustomScrollView.scrollView?.userInteractionEnabled = true 
     CustomScrollView.disabledTouches = false 
    } 
} 

// MARK: - Delegates 
extension CustomScrollView: UIScrollViewDelegate { 

    func scrollViewDidScroll(scrollView: UIScrollView) { 

     if scrollDirection == .horizontal { 
      moveableNode.position.x = scrollView.contentOffset.x 
     } else { 
      moveableNode.position.y = scrollView.contentOffset.y 
     } 
    } 
} 

这使的UIScrollView的子类,并建立了它的基本属性粘贴。它比自己的触摸方法传递到相关的场景。

第二步:在您的相关场景,你想用它创建一个滚动视图和可移动节点属性,像这样

weak var scrollView: CustomScrollView! 
let moveableNode = SKNode() 

和他们didMoveToView

scrollView = CustomScrollView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height), scene: self, moveableNode: moveableNode, scrollDirection: .vertical) 
scrollView.contentSize = CGSizeMake(self.frame.size.width, self.frame.size.height * 2) 
view?.addSubview(scrollView) 


addChild(moveableNode) 

添加到场景什么你在第1行中执行此操作是您使用场景尺寸初始化滚动视图助手。您还可以沿着场景传递参考​​以及在步骤2中创建的moveableNode。 第2行是设置scrollView内容大小的位置,在这种情况下,它的长度是屏幕高度的两倍。

第3步: - 添加标签或节点等并定位它们。

label1.position.y = CGRectGetMidY(self.frame) - self.frame.size.height 
moveableNode.addChild(label1) 

在这个例子中,标签将位于scrollView的第2页。这是你不得不随身携带标签和定位的地方。

我建议如果你在滚动视图中有很多页面并且有很多标签要做以下操作。在滚动视图中为每个页面创建一个SKSpriteNode,并使它们都成为屏幕的大小。将它们称为page1Node,page2Node等。然后将所需的所有标签添加到page2Node中。这样做的好处是,你基本上可以像往常一样在page2Node中定位所有的东西,而不仅仅是在scrollView中定位page2Node。

你也很幸运,因为使用垂直scrollView(你说你想要的),你不需要做任何翻转和反向定位。

我做了一些类的func,所以如果你需要禁用你的scrollView incase你覆盖scrollView的顶部的另一个菜单。

CustomScrollView.enable() 
CustomScrollView.disable() 

最后别忘过渡到一个新的之前删除从场景中的滚动视图。在spritekit中处理UIKit时的一个难题。

scrollView?.removeFromSuperView() 

对于水平滚动,只需将init方法的滚动方向更改为.horizo​​ntal(步骤2)即可。

现在最大的痛苦就是在定位东西时一切都反过来。所以滚动视图从右到左。因此,您需要使用scrollView“contentOffset”方法重新定位它,并且基本上将所有标签以相反的顺序从右到左排列。一旦你明白发生了什么,再次使用SkNodes使得这更容易。

希望这有助于并为大量的帖子感到抱歉,但正如我所说的那样,spritekit有点痛苦。让我知道它是如何去的,如果我错过了任何东西。

项目在GitHub上

https://github.com/crashoverride777/SwiftySKScrollView

+0

这真是太神奇了,我明天就会着手实施它,我会告诉你它是如何发生的。谢谢你,你是一个绝对的救星 – Dan2899

+0

不客气。这应该defo的工作,我只是用xCode与基本模板来刷新我的记忆。请让我知道它是如何去只是因为我错过了什么。 – crashoverride777

+0

今天早上去实施它,它的工作就像一个梦。一个非常聪明的方法,实际上,使用可移动节点并将节点添加到可移动节点真的起作用。再次非常感谢 - 您一直在为我们提供大量帮助,并做出了卓越的工作 – Dan2899

4

您有2个选项

1)使用一个UIScrollView

下山的路,这是更好的解决方案,你得到的东西,如势头滚动,分页,反弹免费的效果等。然而,你必须使用很多UIKit的东西,或者做一些子类来使它与SKSpritenodes或标签一起工作。

检查我在GitHub上的项目为例

https://github.com/crashoverride777/SwiftySKScrollView

2)使用SpriteKit

Declare 3 class variables outside of functions(under where it says 'classname': SKScene): 
var startY: CGFloat = 0.0 
var lastY: CGFloat = 0.0 
var moveableArea = SKNode() 

设置您didMoveToView的SKNode添加到场景中,并添加2个标签,一个是顶部和底部看到它的工作!

override func didMoveToView(view: SKView) { 
    // set position & add scrolling/moveable node to screen 
    moveableArea.position = CGPointMake(0, 0) 
    self.addChild(moveableArea) 

    // Create Label node and add it to the scrolling node to see it 
    let top = SKLabelNode(fontNamed: "Avenir-Black") 
    top.text = "Top" 
    top.fontSize = CGRectGetMaxY(self.frame)/15 
    top.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMaxY(self.frame)*0.9) 
    moveableArea.addChild(top) 

    let bottom = SKLabelNode(fontNamed: "Avenir-Black") 
    bottom.text = "Bottom" 
    bottom.fontSize = CGRectGetMaxY(self.frame)/20 
    bottom.position = CGPoint(x:CGRectGetMidX(self.frame), y:0-CGRectGetMaxY(self.frame)*0.5) 
    moveableArea.addChild(bottom) 
} 

然后设置您的触摸开始保存您的第一触摸的位置:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) { 
    // store the starting position of the touch 
    let touch: AnyObject? = touches.anyObject(); 
    let location = touch?.locationInNode(self) 
    startY = location!.y 
    lastY = location!.y 
} 

然后设置触摸下面的代码移动通过设置限制,以滚动的节点上,在速度设置:

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) { 
    let touch: AnyObject? = touches.anyObject(); 
    let location = touch?.locationInNode(self) 
    // set the new location of touch 
    var currentY = location!.y 

    // Set Top and Bottom scroll distances, measured in screenlengths 
    var topLimit:CGFloat = 0.0 
    var bottomLimit:CGFloat = 0.6 

    // Set scrolling speed - Higher number is faster speed 
    var scrollSpeed:CGFloat = 1.0 

    // calculate distance moved since last touch registered and add it to current position 
    var newY = moveableArea.position.y + ((currentY - lastY)*scrollSpeed) 

    // perform checks to see if new position will be over the limits, otherwise set as new position 
    if newY < self.size.height*(-topLimit) { 
     moveableArea.position = CGPointMake(moveableArea.position.x, self.size.height*(-topLimit)) 
    } 
    else if newY > self.size.height*bottomLimit { 
     moveableArea.position = CGPointMake(moveableArea.position.x, self.size.height*bottomLimit) 
    } 
    else { 
     moveableArea.position = CGPointMake(moveableArea.position.x, newY) 
    } 

    // Set new last location for next time 
    lastY = currentY 
} 

一切归功于这篇文章

http://greenwolfdevelopment.blogspot.co.uk/2014/11/scrolling-in-sprite-kit-swift.html

+0

Thankyou真的很有用!此方法是否允许UIScrollView附带的惯性?或者我必须尝试自己补充一下? – Dan2899

+0

不幸的是,你不会得到很酷的效果。使用滚动视图在skscenes中使用是一件很痛苦的事情。你必须做一些欺骗手段来注册触摸,添加SKNode而不是UIKit对象,当你在滚动视图顶部叠加菜单时必须停用它,除非你翻动滚动视图,否则事物将以相反的方向滚动,你必须以不同的方式定位不同的东西,因为SpriteKit套件与UKit具有不同的坐标。这是相当混乱的,但它是可行的。取决于菜单对你有多重要 – crashoverride777

+0

看起来你以前推荐的方法就是这样的答案!我想知道为什么SpriteView在SpriteKit上的文档太少... – Dan2899