我想在我的游戏中(使用SpriteKit)创建一个商店,其中包含按钮和图像,但我需要让物品可滚动,以便玩家可以在商店内上下滚动(类似于UITableView,但每个单元格中有多个SKSpriteNode和SKLabelNode)。您有什么办法可以在SpriteKit中实现这一点吗?
我想在我的游戏中(使用SpriteKit)创建一个商店,其中包含按钮和图像,但我需要让物品可滚动,以便玩家可以在商店内上下滚动(类似于UITableView,但每个单元格中有多个SKSpriteNode和SKLabelNode)。您有什么办法可以在SpriteKit中实现这一点吗?
如承诺的第二个答案,我刚解决了这个问题。
我建议从我的gitHub项目中始终获取此代码的最新版本,以防自回答以来进行了更改。链接在底部。
步骤1:创建一个新的Swift文件并粘贴此代码
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的子类,并设置了它的基本属性。然后它有自己的触摸方法,会将其传递给相关场景。
步骤2:在你想使用它的相关场景中,你需要创建一个可滚动的视图和可移动节点属性,如下所示:
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)
在第一行的操作是使用场景尺寸初始化滚动视图助手。您还需要传递场景以供参考,以及在步骤2中创建的可移动节点。第2行设置scrollView的内容大小,在本例中为屏幕高度的两倍。label1.position.y = CGRectGetMidY(self.frame) - self.frame.size.height
moveableNode.addChild(label1)
在这个示例中,标签将位于scrollView的第2页上。在此处,您需要调整标签位置并进行一些尝试。CustomScrollView.enable()
CustomScrollView.disable()
最后,在切换到新场景之前,不要忘记从您的场景中删除滚动视图。这是在SpriteKit中处理UIKit时的一个问题。
scrollView?.removeFromSuperView()
要实现水平滚动,只需在初始化方法中将滚动方向更改为.horizontal(第2步)。
现在最大的问题是,定位元素时一切都是相反的。 因此,滚动视图从右到左移动。 因此,您需要使用scrollView“contentOffset”方法重新定位它,并将所有标签基本上按从右到左的相反顺序放置。 一旦理解发生的情况,再次使用SkNodes使这更加容易。
希望这有所帮助,很抱歉我发了一个庞大的帖子,但正如我所说,这是SpriteKit的一个痛点。 让我知道进展情况以及是否有遗漏的内容。
项目在gitHub上
你有两个选项
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
}
然后使用以下代码设置touches moved,以按设置的速度将节点滚动到设置的限制:
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
SpriteKit
菜单的 UIScrollView
行为的代码。SKScene
的高度相匹配的虚拟 UIView
,然后将 UIScrollView
滚动和点击事件传递给 SKScene
进行处理。class ScrollViewController: UIViewController, UIScrollViewDelegate {
// IB Outlets
@IBOutlet weak var scrollView: UIScrollView!
// General Vars
var scene = ScrollScene()
// =======================================================================================================
// MARK: Public Functions
// =======================================================================================================
override func viewDidLoad() {
// Call super
super.viewDidLoad()
// Create scene
scene = ScrollScene()
// Allow other overlays to get presented
definesPresentationContext = true
// Create content view for scrolling since SKViews vanish with height > ~2048
let contentHeight = scene.getScrollHeight()
let contentFrame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: contentHeight)
let contentView = UIView(frame: contentFrame)
contentView.backgroundColor = UIColor.clear
// Create SKView with same frame as <scrollView>, must manually compute because <scrollView> frame not ready at this point
let scrollViewPosY = CGFloat(0)
let scrollViewHeight = UIScreen.main.bounds.size.height - scrollViewPosY
let scrollViewFrame = CGRect(x: 0, y: scrollViewPosY, width: UIScreen.main.bounds.size.width, height: scrollViewHeight)
let skView = SKView(frame: scrollViewFrame)
view.insertSubview(skView, at: 0)
// Configure <scrollView>
scrollView.addSubview(contentView)
scrollView.delegate = self
scrollView.contentSize = contentFrame.size
// Present scene
skView.presentScene(scene)
// Handle taps on <scrollView>
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(scrollViewDidTap))
scrollView.addGestureRecognizer(tapGesture)
}
// =======================================================================================================
// MARK: UIScrollViewDelegate Functions
// =======================================================================================================
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scene.scrollBy(contentOffset: scrollView.contentOffset.y)
}
// =======================================================================================================
// MARK: Gesture Functions
// =======================================================================================================
@objc func scrollViewDidTap(_ sender: UITapGestureRecognizer) {
let scrollViewPoint = sender.location(in: sender.view!)
scene.viewDidTapPoint(viewPoint: scrollViewPoint, contentOffset: scrollView.contentOffset.y)
}
}
class ScrollScene : SKScene {
// Layer Vars
let scrollLayer = SKNode()
// General Vars
var originalPosY = CGFloat(0)
// ================================================================================================
// MARK: Initializers
// ================================================================================================
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// ================================================================================================
// MARK: Public Functions
// ================================================================================================
func scrollBy(contentOffset: CGFloat) {
scrollLayer.position.y = originalPosY + contentOffset
}
func viewDidTapPoint(viewPoint: CGPoint, contentOffset: CGFloat) {
let nodes = getNodesTouchedFromView(point: viewPoint, contentOffset: contentOffset)
}
func getScrollHeight() -> CGFloat {
return scrollLayer.calculateAccumulatedFrame().height
}
fileprivate func getNodesTouchedFromView(point: CGPoint, contentOffset: CGFloat) -> [SKNode] {
var scenePoint = convertPoint(fromView: point)
scenePoint.y += contentOffset
return scrollLayer.nodes(at: scenePoint)
}
}
我喜欢添加SKCameraNode来滚动我的菜单场景的想法。我发现this article非常有用。你只需要改变相机位置来移动你的菜单。在Swift 4中实现。
var boardCamera = SKCameraNode()
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
let deltaY = location.y - previousLocation.y
boardCamera.position.y += deltaY
}
}
print("代码刚刚执行了什么操作的描述")
,然后在尝试滚动时观察控制台,它应该告诉你出错的地方,并希望能让你意识到为什么按钮不能工作(至少这就是我做的)。很抱歉我无法提供更多帮助,我现在无法完全记起当时解决问题所采取的方法。 - user5669940