Spritekit, Swift 2.0 - Scrollview in Reverse

SpriteKit, Swift 2.0 - ScrollView in reverse

You need get my updated helper on gitHub (v1.1) first before reading the rest.

My helper only really works well when your scene scale mode (gameViewController) is set to

.ResizeFill

so your scenes do not crop. If you use a different scaleMode such as

.AspectFill 

than it might crop stuff in your scrollView which you would need to adjust for.

It also doesnt work if your game/app supports both portrait and landscape, which is unlikely for a game anyway.

So as I said and you have also noticed when using a ScrollView in spriteKit the coordinates are different compared to UIKit. For vertical scrolling this doesn't really mean anything, but for horizontal scrolling everything it is in reverse. So to fix this you do the following

Set up your scrollView for horizontal scrolling, passing along the new scrollDirection property (.Horizontal in this case)

scrollView = CustomScrollView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height), scene: self, moveableNode: moveableNode, scrollDirection: .Horizontal)
scrollView.contentSize = CGSizeMake(self.frame.size.width * 3, self.frame.size.height) // * 3 makes it twice as wide as screen
view.addSubview(scrollView)

you need to also add this line of code after adding it to the view.

scrollView.setContentOffset(CGPoint(x: 0 + self.frame.size.width * 2, y: 0), animated: true)

this is the line you use to tell the ScrollView on which page to start. Now in this example the scrollView is three times as wide as the screen, therefore you need to offset the content by 2 screen lengths

Now to make things easier for positioning I would do this, create sprites for each page of the scrollView. This makes positioning much easier later on.

let page1ScrollView = SKSpriteNode(color: SKColor.clearColor(), size: CGSizeMake(self.frame.size.width, self.frame.size.height))
page1ScrollView.position = CGPointMake(CGRectGetMidX(self.frame) - (self.frame.size.width * 2), CGRectGetMidY(self.frame))
moveableNode.addChild(page1ScrollView)

let page2ScrollView = SKSpriteNode(color: SKColor.clearColor(), size: CGSizeMake(self.frame.size.width, self.frame.size.height))
page2ScrollView.position = CGPointMake(CGRectGetMidX(self.frame) - (self.frame.size.width), CGRectGetMidY(self.frame))
moveableNode.addChild(page2ScrollView)

let page3ScrollView = SKSpriteNode(color: SKColor.clearColor(), size: CGSizeMake(self.frame.size.width, self.frame.size.height))
page3ScrollView.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
moveableNode.addChild(page3ScrollView)

and now you can positioning your actual labels, sprites much easier.

/// Test label page 1
let myLabel = SKLabelNode(fontNamed:"Chalkduster")
myLabel.text = "Hello, World!"
myLabel.fontSize = 45
myLabel.position = CGPointMake(0, 0)
page1ScrollView.addChild(myLabel)

/// Test sprite page 2
let sprite = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 50, height: 50))
sprite.position = CGPointMake(0, 0)
page2ScrollView.addChild(sprite)

/// Test sprite page 3
let sprite2 = SKSpriteNode(color: SKColor.blueColor(), size: CGSize(width: 50, height: 50))
sprite2.position = CGPointMake(0, 0)
page3ScrollView.addChild(sprite2)

Hope this helps.

I also updated my GitHub project to explain this better

https://github.com/crashoverride777/Swift2-SpriteKit-UIScrollView-Helper

Swift 2.0, SpriteKit - Scrollview to not use pages.

I dont understand what you are asking here. I just checked the RayWenderlich tutorial and its exactly the same as my GitHub sample project. They just keep the sprites closer together where as in my project for demonstration purposes I put each sprite on a new page.

If you just want sprites to scroll that just add the sprites to the moveableNode and the rest as usual to the scene directly.

addChild(background)
moveableNode.addChild(sprite1)

Than change the sprite positions so they are closer together. You basically add the 1st sprite to the 1st page in the scrollView and than position the other sprites based on the previous sprites x position. You add these sprites to sprite1 as well.

let sprite1 = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 50, height: 50))
sprite1.position = CGPointMake(0, 0)
page1ScrollView.addChild(sprite1)

let sprite2 = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 50, height: 50))
sprite2.position = CGPointMake(sprite1.position.x + (sprite2.size.width * 1.5), sprite1.position.y)
sprite1.addChild(sprite2)

let sprite3 = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 50, height: 50))
sprite3.position = CGPointMake(sprite2.position.x + (sprite3.size.width * 1.5), sprite1.position.y)
sprite1.addChild(sprite3)

I updated my gitHub project to show this in action
https://github.com/crashoverride777/Swift2-SpriteKit-UIScrollView-Helper

SpriteKit, Swift - ScrollView Not Showing Up

Try this for the sprite, I made a mistake in the other question, it should be - self.frame.size.height, not +.

 sprite = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 50, height: 44))
sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - self.frame.size.height)
moveableNode.addChild(sprite)

Check my gitHub project

https://github.com/crashoverride777/Swift-2-SpriteKit-UIScrollView-Helper

How to create a vertical scrolling menu in spritekit?

The second answer as promised, I just figured out the issue.

I recommend to always get the latest version of this code from my gitHub project incase I made changes since this answer, link is at the bottom.

Step 1: Create a new swift file and paste in this code

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

This make a subclass of UIScrollView and sets up the basic properties of it. It than has its own touches method which get passed along to the relevant scene.

Step2: In your relevant scene you want to use it you create a scroll view and moveable node property like so

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

and add them to the scene 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)

What you do here in line 1 is you init the scroll view helper with you scene dimensions. You also pass along the scene for reference and the moveableNode you created at step 2.
Line 2 is where you set up the content size of the scrollView, in this case its twice as long as the screen height.

Step3: - Add you labels or nodes etc and position them.

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

in this example the label would be on the 2nd page in the scrollView. This is where you have to play around with you labels and positioning.

I recommend that if you have a lot pages in the scroll view and a lot of labels to do the following. Create a SKSpriteNode for each page in the scroll view and make each of them the size of the screen. Call them like page1Node, page2Node etc. You than add all the labels you want for example on the second page to page2Node. The benefit here is that you basically can position all your stuff as usual within page2Node and than just position page2Node in the scrollView.

You are also in luck because using the scrollView vertically (which u said you want) you dont need to do any flipping and reverse positioning.

I made some class func so if you need to disable your scrollView incase you overlay another menu ontop of the scrollView.

CustomScrollView.enable()
CustomScrollView.disable()

And finally do not forget to remove the scroll view from your scene before transitioning to a new one. One of the pains when dealing with UIKit in spritekit.

scrollView?.removeFromSuperView()

For horizontal scrolling simply change the scroll direction on the init method to .horizontal (step 2).

And now the biggest pain is that everything is in reverse when positioning stuff. So the scroll view goes from right to left. So you need to use the scrollView "contentOffset" method to reposition it and basically place all your labels in reverse order from right to left. Using SkNodes again makes this much easier once you understand whats happening.

Hope this helps and sorry for the massive post but as I said it is a bit of a pain in spritekit. Let me know how it goes and if I missed anything.

Project is on gitHub

https://github.com/crashoverride777/SwiftySKScrollView

Sprite moves two places after being paused and then unpaused

As the member above me said the player is not really moving 2 spaces.

Also you should maybe change your strategy when pausing your game, because pausing the scene.view makes it very hard to add SpriteKit elements afterwards.

I think a better way is to create a worldNode in your GameScene and add all the sprites that need to be paused to that worldNode. It basically gives you more flexibility pausing a node rather than the whole scene.

First create a world node property

 let worldNode = SKNode()

and add it to the scene in ViewDidLoad

 addChild(worldNode)

Than add all the sprites you need paused to the worldNode

 worldNode.addChild(sprite1)
worldNode.addChild(sprite2)
...

Create a global enum for your game states

enum GameState {
case Playing
case Paused
case GameOver
static var current = GameState.Playing
}

Than make a pause and resume func in your game scene

func pause() {
GameState.current = .Paused

// show pause menu etc
}

func resume() {
GameState.current = .Playing
self.physicsWorld.speed = 1
worldNode.paused = false
}

And finally add the actual pause code to your updateMethod. This way it will not resume the game even if spriteKit itself tries to resume (e.g. reopened app, dismissed alert etc)

 override func update(currentTime: CFTimeInterval) {

if GameState.current == .Paused {
self.physicsWorld.speed = 0
worldNode.paused = true
}
}

In regards to your tapGesture recogniser, in the method that gets called after a tap you can add this before the rest of the code

 guard GameState.current != .Paused else { return }
....

Hope this helps

How to find the distance between two CG points?

Distance between p1 and p2:

CGFloat xDist = (p2.x - p1.x);
CGFloat yDist = (p2.y - p1.y);
CGFloat distance = sqrt(xDist * xDist + yDist * yDist);

Put in a function:

func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(sqrt(xDist * xDist + yDist * yDist))
}

Background: Pythagorean theorem

If you only need to calculate if the distance between the points increases or decreases, you can omit the sqrt() which will make it a little faster.

swift: Move animation

Use UiVew.animate and CGAffineTransform

Setup

Create a UIView that will contain your squares. Make sure that UIView has clip to bounds enabled. Then, add your four squares in, with the blue ones nested inside the UIView, but with their coordinates outside of the container so that they're getting clipped.

View of Storyboard editor

Then, when you want to move them, you simply translate all of your squares x to the left. By putting this movement inside of a UIView.animate block, iOS will perform the animation for you.

UIView.animate(withDuration: 2.0) {
self.redTopView.transform = CGAffineTransform(translationX: -160, y: 0)
self.redBottomView.transform = CGAffineTransform(translationX: -160, y: 0)
self.blueTopView.transform = CGAffineTransform(translationX: -160, y: 0)
self.blueBottomView.transform = CGAffineTransform(translationX: -160, y: 0)
}

Final result: http://g.recordit.co/SToMSZ77wu.gif



Related Topics



Leave a reply



Submit