我想在我的新Sprite Kit游戏中制作一个水平滚动菜单。 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及其菜单。
我知道这个问题已经有几个月了,但我也在寻找滚动菜单滑块来选择我的SpriteKit游戏中的关卡。我找不到任何已存在的代码,并且将UIScrollView强制适应我的SpriteKit游戏的想法对我没有吸引力。所以我写了自己的水平选择滑动菜单。
我已经模拟了UIScrollView的所有动作,因此看起来很自然,并且可以配置为向左或向上滚动。
作为奖励,我在菜单上创建了一个视差背景,可以轻松地换出新图像以获得自定义外观。
https://github.com/hsilived/ScrollingMenu
滚动菜单的代码评论很好,所以不应该有很多集成问题。
github项目有一个完整的工作演示,包括世界,水平和视差图像。如果你喜欢它标记我的答案是有用的,所以我可以获得一些声誉:)
你绝对可以将UIKit控件与SpriteKit结合使用。从想要拥有UIScrollView的SKScene中,将滚动视图添加到self.view,如下所示:[self.view addSubview:scroll]。您可能希望在SKScene上的 - (void)didMoveToView:(SKView *)视图方法中执行此操作。转换出正在使用它的SKScene后,请记住从SKView中删除滚动视图。您可以在 - (void)willMoveFromView:(SKView *)视图方法中执行此操作。不要忘记在UIScrollView上设置contentSize。
注意:如果要在UIKit控件上安装SpriteKit节点,则需要编写自己的SKSpriteNode,其行为类似于UIScrollView。
扩展注意:如果您的滚动视图需要包含Sprite-kit节点而不仅仅是UIKit视图,除非您按照此问题的答案中的建议做一些聪明的事情,否则您将无法使用UIScrollView here
有很多方法可以通过SKCameraNode
获得它,使用像UIKit
或其他方式的UIScrollView
元素。但我想告诉你如何使用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
我们可以创建平滑滚动到自动移动触摸后到当前最近的可见语音
输出: