Come creare un menu di scorrimento verticale in spritekit?

Sto cercando di creare un negozio nel mio gioco (In SpriteKit) con pulsanti e immagini, ma ho bisogno che gli elementi siano scorrevoli in modo che il giocatore possa scorrere su e giù per il negozio (come un UITableView ma con più SKSpriteNodes e SKLabelNodes in ogni cellula). Qualche idea su come posso farlo in SpriteKit?

La seconda risposta, come promesso, ho appena capito il problema.

Raccomando di avere sempre l’ultima versione di questo codice dal mio progetto gitHub nel caso in cui ho apportato modifiche poiché questa risposta, il link è in fondo.

Passaggio 1: crea un nuovo file rapido e incolla questo codice

 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, 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, 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, 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?, 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 } } } 

Questo crea una sottoclass di UIScrollView e ne imposta le proprietà di base. Questo ha il suo metodo di touch che viene trasmesso alla scena in questione.

Step2: Nella scena che si desidera utilizzare, si crea una vista di scorrimento e una proprietà del nodo mobile come tale

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

e aggiungili alla scena in 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) 

Quello che fai qui nella riga 1 è iniziare l’helper della vista a scorrimento con le dimensioni della scena. Si passa anche la scena per riferimento e il Nodo mobile creato al passaggio 2. La riga 2 è dove si imposta la dimensione del contenuto di scrollView, in questo caso è lunga il doppio dell’altezza dello schermo.

Step3: – Aggiungi etichette o nodes ecc. E posizionali.

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

in questo esempio l’etichetta si troverà nella seconda pagina di scrollView. Qui è dove devi giocare con le tue etichette e il posizionamento.

Raccomando che se hai molte pagine nella vista di scorrimento e molte etichette fai quanto segue. Creare un SKSpriteNode per ogni pagina nella vista di scorrimento e rendere ciascuna di esse la dimensione dello schermo. Chiamali come page1Node, page2Node ecc. Tu aggiungi tutte le etichette che vuoi ad esempio nella seconda pagina a page2Node. Il vantaggio qui è che fondamentalmente puoi posizionare tutte le tue cose come al solito all’interno di page2Node e basta posizionare page2Node nella scrollView.

Sei anche fortunato perché usando lo scrollView verticalmente (che hai detto di volere) non hai bisogno di fare il flipping e il posizionamento inverso.

Ho fatto alcune funzioni di class quindi se hai bisogno di disabilitare il tuo scrollView in caso di sovrapposizione di un altro menu ontop di scrollView.

 CustomScrollView.enable() CustomScrollView.disable() 

Infine, non dimenticare di rimuovere la vista di scorrimento dalla scena prima di passare a una nuova. Uno dei dolori quando si ha a che fare con UIKit in spritekit.

 scrollView?.removeFromSuperView() 

Per lo scorrimento orizzontale, è sufficiente modificare la direzione di scorrimento del metodo init su .horizontal (passaggio 2).

E ora il più grande dolore è che tutto è al contrario quando si posiziona la roba. Quindi la vista di scorrimento va da destra a sinistra. Quindi è necessario utilizzare il metodo scrollView “contentOffset” per riposizionarlo e praticamente posizionare tutte le etichette in ordine inverso da destra a sinistra. L’uso di SkNodes rende ancora più facile una volta capito cosa sta succedendo.

Spero che questo aiuti e mi dispiace per l’enorme post, ma come ho detto è un po ‘un dolore in spritekit. Fammi sapere come va e se mi manca qualcosa.

Il progetto è su gitHub

https://github.com/crashoverride777/SwiftySKScrollView

Hai 2 opzioni

1) Usa un UIScrollView

In fondo questa è la soluzione migliore in quanto ottieni cose come lo scorrimento del momento, il paging, gli effetti di rimbalzo ecc. Comunque devi usare molte cose di UIKit o fare qualche sottoclassificazione per farlo funzionare con SKSpritenodes o etichette.

Controlla il mio progetto su gitHub per un esempio

https://github.com/crashoverride777/SwiftySKScrollView

2) Usa 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() 

Configura didMoveToView, aggiungi SKNode alla scena e aggiungi 2 etichette, una per la parte superiore e una per la parte inferiore per vederla funzionante!

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

Quindi impostare i tuoi tocchi ha iniziato a memorizzare la posizione del tuo primo touch:

 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 } 

Quindi imposta i tocchi spostati con il seguente codice per far scorrere il nodo fino ai limiti impostati, alla velocità impostata:

 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 } 

Tutto il merito va a questo articolo

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

Ecco il codice che abbiamo usato per simulare il comportamento di UIScrollView per i menu di SpriteKit .

Fondamentalmente, è necessario utilizzare un UIView fittizio che corrisponda all’altezza di SKScene quindi alimentare UIScrollView scroll e toccare gli eventi su SKScene per l’elaborazione.

È frustrante che Apple non fornisca questo in modo nativo, ma spero che nessun altro debba perdere tempo a ribuild questa funzionalità!

 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 , must manually compute because  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.addSubview(contentView) scrollView.delegate = self scrollView.contentSize = contentFrame.size // Present scene skView.presentScene(scene) // Handle taps on  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) } }