Swift SpriteKit: Incorrect sprite position - ios

I've created a new Xcode game project and selected SpriteKit. By default, Xcode provides a game template which I modified to add image. The image was displayed on the screen but in incorrect position. I followed this tutorial to setup the correct scene dimension and it works. But the image is still in incorrect position.
I'm trying to position the image at Y=0, but it appears at the bottom of the screen instead of the top.
Here's what I've done so far.
ViewController Code:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews();
let skView = self.view as! SKView
println(skView)
let scale:CGFloat = UIScreen.mainScreen().scale;
let size = CGSizeMake(skView.frame.size.width*scale, skView.frame.size.height*scale)
if (skView.scene == nil) {
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.size = size
skView.presentScene(scene)
}
}
else {
skView.scene!.size = size;
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
} else {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
}
}
Scene Code:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
println(self)
self.backgroundColor = UIColor(red: 31.0/255.0, green: 31.0/255.0, blue: 31.0/255.0, alpha: 1.0)
//Add the app logo
let logo = SKSpriteNode(imageNamed:"imgLogo")
logo.position = CGPoint(x:self.frame.size.width/2, y:0)
self.addChild(logo)
let lblTitle = SKLabelNode(fontNamed:"LibelSuit-Regular")
lblTitle.text = "Hello World!";
lblTitle.fontSize = 65;
lblTitle.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(lblTitle)
}
}

I assume logo is the red/green boxes on the bottom? If so, y = 0 is the bottom edge, not the top. Try:
logo.position = CGPoint(x:self.frame.size.width/2,
y:self.frame.size.height - logo.size.height / 2)

Unlike game development for other platforms, the y-axis in spritekit runs from down to up instead of up to down. In this case you'd want to replace the 0 with self.frame.size.height
logo.position = CGPoint(x:self.frame.size.width/2, y:0) //Your current code
logo.position = CGPoint(x:self.frame.size.width/2, y:self.frame.size.height)//What you want

Since the Scene anchor point was set to (0,0) - lower left of the screen by default, what I did is to set the anchor point to (0,1) - upper left instead.
self.anchorPoint = CGPointMake(0, 1);
I also set the same anchor point for each child, then I set the Y position to a negative value.
let logo = SKSpriteNode(imageNamed:"imgLogo")
logo.anchorPoint = CGPointMake(0, 1);
logo.position = CGPoint(x:0, y:-180)
self.addChild(logo)
This works perfectly. Appreciate your inputs.

Why would y = 0 be at the top? Just think of it as a graph like the ones you've learned in school. y = 0 is in the bottom and x = 0 is on the left side. So 0,0 would be on the left bottom corner.

Related

Swift SKNode position help - sprite position always 0,0 as it moves

Problem: I’m working in swift playground on an iPad Pro.
I’d like to get the current position coordinates of an SKSpriteNode with attached physicsBody as they move around the screen. No matter what it try, the sprite position returns 0,0, despite seeing it move across the screen.
I’ve attempted various implementations of convert(_:to:) and convert(_:from:) but the sprite position always ends up 0,0. My understanding is that this isn’t really necessary in the specific case here since the sprite is the direct child of the scene so the sprite’s position property should already be what it is in the scene coordinate system.
I’ve spent a lot of googling time attempting to figure this out without much success which makes me think I’m asking the wrong question. If so, please help me figure out what the right question to ask is!
The touch position print returns the correct position relative to the initial screen size. That’s what I’d like to get for the sprite.
Disclaimer - i don’t do this professionally so please forgive what I’m sure is ugly coding!
import SwiftUI
import SpriteKit
import PlaygroundSupport
var screenW = CGFloat(2732)
var screenH = CGFloat(2048)
struct ContentView: View {
var scene: SKScene {
let scene = GameScene()
scene.size = CGSize(width: 2732, height: 2048)
scene.scaleMode = .aspectFit
scene.backgroundColor = #colorLiteral(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
return scene
}
class GameScene: SKScene {
var ship1 = SKSpriteNode()
public override func didMove(to view: SKView) {
// gravity edge around screen for convenience
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
//turn off default 1g gravity
physicsWorld.gravity = .zero
//Add a ship sprite
let ship1 = SKSpriteNode(imageNamed: "ship.PNG")
ship1.setScale(0.2)
ship1.position = CGPoint(x: screenW * 0.2, y: screenH * 0.2)
ship1.physicsBody = SKPhysicsBody(rectangleOf: ship1.size)
ship1.physicsBody?.velocity = CGVector(dx: 0, dy: 100)
addChild(ship1)
}
//touch handling begin
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
//
let location = touch.location(in: self)
print(location)
print(ship1.position)
}
}
var body: some View {
SpriteView(scene: scene)
.ignoresSafeArea()
}
}
//star the view
let currentView = ContentView()
//Playground view setup
PlaygroundPage.current.setLiveView(currentView)
PlaygroundPage.current.wantsFullScreenLiveView = true
PlaygroundPage.current.needsIndefiniteExecution = true

iPhone 4, 5, 5s and 6+ simulators misaligning nodes

Apologies if I've already asked this, but Im very stuck; the first question was very unclear. Created a game in SpriteKit on the iPhone 6 simulator and when run the other sized simulators, everything is thrown out of alignment. Im not using any .sks, .xib or storyboarding. Is there a way to programmatically resize the game so that all screen sizes have the correct positioning? Will post code if necessary.
Edit: GameViewController:
import UIKit
import SpriteKit
var sceneSize = UIScreen.mainScreen().bounds
var screenWidth = sceneSize.width
var screenHeight = sceneSize.height
class GameViewController: UIViewController {
var scene: GameScene!
override func viewDidLayoutSubviews() {
super.viewWillLayoutSubviews()
let skView = self.view as! SKView
skView.multipleTouchEnabled = false
scene = GameScene(size: skView.bounds.size)
//scene.scaleMode = .AspectFill
scene.scaleMode = SKSceneScaleMode.ResizeFill
skView.presentScene(scene)
scene.anchorPoint = CGPointMake(0, 0)
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
let completionBlock: () -> Void = {
}
let errorBlock: (NSError!) -> Void = { error in
print("error")
}
RevMobAds.startSessionWithAppID("", withSuccessHandler: completionBlock, andFailHandler: errorBlock);
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .AllButUpsideDown
} else {
return .All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
Edit 2 GameScene:
import Foundation
import SpriteKit
import UIKit
var sceneSize = UIScreen.mainScreen().bounds
var screenWidth = sceneSize.width
var screenHeight = sceneSize.height
class GameScene: SKScene {
player.position = CGPointMake(screenWidth / 5, screenHeight / 3)
}
First, set the GameScene SKSceneScalode (this is found pre-programmed into the GameViewController to .ResizeFill
Then copy the following code into GameScene:
Import UIKit
var screenSize = UIScreen.mainscreen().bounds
var screenWidth = screenSize.width
var screenHeight = screenSize.height
This will store the width and height of the screen in the variables screenWidth and screenHeight. Now when you create your spriteNodes and other objects, use these two variables as your basis. Here's an example
someNode.position = CGPoint(x: screenWidth/5 y: screenHeight/3)
Are you the same person that posted on the Swift section of Reddit? Here's my code that works for all iPhone devices, as I answered it well on the Reddit post the same way. Here's the more profound answer.
In your View Controller, enter the following code inside the viewDidLoad().
//Create a scene object with the max size of an iPhone 6 Plus at 1920 x 1080
//That means it can easily scale down to the 6, 5, and 4S.
let mainScene = GameScene(size: CGSize(width: 1920, height: 1080))
//To fill the scene and fit for all sizes
mainScene.scaleMode = .AspectFill
//Prepare the view
let mainView = self.view as! SKView
mainView.showsFPS = true
mainView.showsNodeCount = true
mainView.ignoresSiblingOrder = true
//Move to the required scene
mainView.presentScene(mainScene, transition: SKTransition.fadeWithDuration(1))
In the class scene, add the following variables at the top.
//Will be used to get the ratio of the device
let deviceWidth = UIScreen.mainScreen().bounds.width
let deviceHeight = UIScreen.mainScreen().bounds.height
let maxAspectRatio: CGFloat
let playableArea: CGRect
If you don't already have an override init inside the class scene that the view is moving to, add the following inside the class.
//Overrides the superclasses' SKScene init
override init(size: CGSize) {
//Initializes the maxAspectRatio variable
maxAspectRatio = deviceHeight / deviceWidth
print("The ratio of the device is: \(maxAspectRatio)")
//Prepares the rectangle view that is viewable by the user (ONLY FOR PORTRAIT MODE)
let playableWidth = size.height / maxAspectRatio
let playableMargin = (size.width - playableWidth) / 2.0
playableArea = CGRect(x: playableMargin, y: 0, width: playableWidth, height: size.height)
/*USE THIS CODE IF YOUR APP IS IN LANDSCAPE MODE*****************************************
let playableHeight = size.width / maxAspectRatio
let playableMargin = (size.height - playableHeight) / 2.0
playableArea = CGRect(x: 0, y: playableMargin, width: size.width, height: playableHeight)
*///*************************************************************************************
//Find out what kind of device the user is using
if maxAspectRatio == (4 / 3) {
//The iPhone's 4S ratio is 4:3
print("The user is using an iPhone 4S")
} else {
//That means the ratio is not 4:3, so it probably is 16:9
print("The user is using an iPhone 5, 6, 6S, or 6 Plus.")
}
//Assigns the size of the scene to the superclass init of SKScene
//Must be called after all variables are initialed in this class
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Add the following function inside the scene class too so you can see the viewable area that is seen by the user. It draws a red rectangle. Then, just call this function inside the didMoveToView() function. Now to position objects, all you have to do is use CGRectGet... properties on the playableArea to position the objects correctly.
//Used to draw the rectangle or the view of the phone
func drawPlayableArea() {
let shape = SKShapeNode()
let path = CGPathCreateMutable()
CGPathAddRect(path, nil, playableArea)
shape.path = path
shape.strokeColor = SKColor.redColor()
shape.lineWidth = 8
addChild(shape)
}
override func didMoveToView(view: SKView) {
drawPlayableArea()
//Examples used to position objects on a screen related to the playable area or viewable area
//This works for all iPhone devices as well as iPods.
let label = SKLabelNode(fontNamed: "Arial")
label.fontColor = SKColor.whiteColor()
label.text = "Hello, Redditor!"
label.position = CGPoint(x: CGRectGetMidX(playableArea), y: CGRectGetMidY(playableArea))
addChild(label)
let score = SKLabelNode(fontNamed: "Arial")
score.verticalAlignmentMode = .Top
score.horizontalAlignmentMode = .Left
score.fontColor = SKColor.whiteColor()
score.text = "Score: 100"
score.position = CGPoint(x: CGRectGetMinX(playableArea) + 20, y: CGRectGetMaxY(playableArea) - 20)
addChild(score)
backgroundColor = SKColor.blackColor()
}
Here are the github files if you need to understand the code more.
Screenshots of positions in an 4S and a 6 Plus. Notice how the objects stay positioned relative to the scene size.
OK I had time to reread this, and I think I know what is going on, I will leave my old answer in here as well.
New Answer:
What is going on here is you are setting your scene size at the time of creation of view, before any auto constraints get applied. So your view size is not the same as your screen size. Thus, your scene becomes the same size regardless of device, then you apply the math based on your screen, and everything gets thrown off. What you need to do is 1) Verify that my theory is right, and that at the time of creating your scene, the size is not the same as screen size, then 2) create the scene at the time the view does get resized for the device.
Old Answer:
The reason everything is thrown out of alignment is because your game scene is being resized based on device. You need to set a single constant size for doing things the way you are doing
let scene = GameScene(size:CGSizeMake(400,300))
Where 400 x 300 can be changed based on your needs. this will give you a play area of fixed size to work with, so your nodes will be placed the same. Then, to handle the screen size, you set your scenes scale mode to aspect fill or aspect fit, depending on if you want to lose information, but have no black bars, or have black bars and show all information. (resize fill will distort your image by stretching if that is what you want). to do this it is just scene.scaleMode = .AspectFill
Now you will have to tweak numbers and images if you are getting undesirable results, but the same logic should be applied to all devices. If you start grabbing things like the screen size again, things will start misaligning.
Inside your scene code, use the scenes width and height to manipulate your nodes, not the screen
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
player.position = CGPointMake(scene.frame.width / 5, scene.frame.height / 3)
}
Best way to think about it is the Game scene gets treated as its own screen, and in the end you want to take this screen and scale it to meet the various device settings.

Scene Kit SCNMorpher not working with primitives like SCNSphere etc

Has anybody got the SCNMorpher to work? If so, what exactly have you done?
I'm down now to a small test program, that should show a red cone and when I tap on the screen it should (should!) morphe that into a sphere/ball. But the only thing that happens is, that the first geometry goes away (get very small) and the second one (Morphers first target) never appears.
I must miss something very simple. Can anybody get me on the horse, please?
import SceneKit
private let DISTANCE_FAKTOR = CGFloat(sqrt(3.0)/2.0)
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let (scene, view) = configScene(self.view.frame)
self.view = view
// Position camera to see picture full screen and light at same position
let pov = SCNVector3(x: 0.0, y: 0.0, z: Float(max(view.frame.width, view.frame.height) * DISTANCE_FAKTOR))
let pol = SCNVector3(x: 0.0, y: 0.0, z: Float(max(view.frame.width, view.frame.height) * DISTANCE_FAKTOR))
scene.rootNode.addChildNode(makeShapes()) // Create and add background plane
scene.rootNode.addChildNode(makeCamera(pov)) // Add camera to scene
scene.rootNode.addChildNode(makeLight(pol)) // Add light to scene
// add a tap gesture recognizer
view.gestureRecognizers = [UITapGestureRecognizer(target: self, action: "handleTap:")]
}
func handleTap(gestureRecognize: UIGestureRecognizer) {
NSLog("Tapped")
if let effectNode = (view as? SCNView)?.scene?.rootNode.childNodeWithName("EFFECT", recursively: true) {
NSLog("Animating")
animate(effectNode)
}
}
func makeShapes() -> SCNNode {
// First shape is a cone
let torus = SCNCone(topRadius: 0.0, bottomRadius: view.frame.width/2.0, height: view.frame.width/4.0)
torus.firstMaterial = SCNMaterial()
torus.firstMaterial?.diffuse.contents = UIColor.redColor()
// Now an additional sphere/ball
let sphere = SCNSphere(radius: view.frame.width/2.0)
sphere.firstMaterial = SCNMaterial()
sphere.firstMaterial?.diffuse.contents = UIColor.greenColor()
// Put all in the node
let node = SCNNode()
node.geometry = torus
node.morpher = SCNMorpher()
node.morpher?.targets = [sphere]
node.name = "EFFECT"
// I would expect now something between a ball and a torus in red/green
node.morpher?.setWeight(0.5, forTargetAtIndex: 0)
return node
}
func animate(node: SCNNode) {
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(5.0)
node.morpher?.setWeight(1.0, forTargetAtIndex: 0) // From torus to ball
SCNTransaction.setCompletionBlock {
NSLog("Transaction completing")
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(2.5)
node.morpher?.setWeight(0.0, forTargetAtIndex: 0) // And back
SCNTransaction.commit()
}
SCNTransaction.commit()
}
func configScene(frame: CGRect) -> (scene: SCNScene, view: SCNView) {
let view = SCNView()
view.frame = frame
view.autoresizingMask = UIViewAutoresizing.allZeros
view.backgroundColor = UIColor.blueColor()
let scene = SCNScene()
view.scene = scene
return (scene, view)
}
func makeCamera(pov: SCNVector3) -> SCNNode {
let camera = SCNCamera()
camera.zFar = Double(pov.z)
let node = SCNNode()
node.position = pov
node.camera = camera
return node
}
func makeLight(pol: SCNVector3) -> SCNNode {
let light = SCNLight()
light.type = SCNLightTypeOmni
light.zFar = CGFloat(pol.z)
let node = SCNNode()
node.position = pol
node.light = light
return node
}
override func shouldAutorotate() -> Bool {
return true
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.rawValue)
} else {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
When I exchange the ball and the cone in the code, I can see the ball and it disappears when tapping the screen, but the cone never appears.
If you want to run this code, just create a new GameScene project in Xcode and copy this code into GameViewController.swift
Has anybody got the SCNMorpher to work?
Yes, I've gotten SCNMorpher to work between both custom geometry and geometry that has been loaded from a file.
The piece that you are missing is that all the morph targets need to have the same number of vertices and the vertices need to have the same arrangement. This is discussed in the documentation for the targets property:
The base geometry and all target geometries must be topologically identical — that is, they must contain the same number and structural arrangement of vertices.
Your example (morning between a sphere and a cone) doesn't fulfill this requirement and is not expected to work.
You can try and morph between two different spheres (with the same number of segments!) to see that it does work.

SpriteKit & Swift: Creating nodes via didBeginContact messes up the positioning

I don't know if this is a weird bug in Xcode or there's something about the SpriteKit's coordinate system I don't understand.
The premise is that the position of a node is always relative to its parent. However, whenever I call a block that creates and positions a node with a physics body from "didBeginContact" of SKPhysicsContactDelegate, the node is always positioned relative to the scene (instead of its parent). Note that calling this same block works as intended when triggered anywhere but "didBeginContact". Another thing is that, if I remove the physics body of the said node, the block will now work as intended even when called from "didBeginContact".
I've been stuck with this issue for two days and it would be too draggy to give other details about my actual code. So I've made a very simple project demonstrating this anomaly. Just create a new project in Xcode 6 with Spritekit Template, and replace GameViewController.swift and GameSwift.swift with the codes posted below. Just run in iPad Air and everything else should be self explanatory.
Final Notes:
As you press the first button and makes contact to the second
button, look at the lower left corner of the screen. You will see
that the boxes are being "wrongly" positioned there.
Try to remove the physics body of the box in "AddBox". It will now work as intended.
Please let me know if you think this is a bug or there's something in the coordinates system or physics world that I don't understand
GameViewController.swift:
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene()
scene.size = view.frame.size
let skView = self.view as SKView
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
GameScene.swift:
import SpriteKit
let kButtonSize = CGSize(width: 500, height: 100)
let kContainerSize = CGSize(width: 500, height: 300)
let kBoxSize = CGSize(width: 25, height: 25)
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
addFirstButton()
addSecondButton()
addContainer()
}
func addFirstButton() {
let button = SKSpriteNode(color: SKColor.blueColor(), size: kButtonSize)
let label = SKLabelNode(text: "Call 'addBox' from didBeginContact")
button.name = "firstButton"
label.name = "firstLabel"
button.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
button.physicsBody = SKPhysicsBody(rectangleOfSize: button.size)
button.physicsBody.allowsRotation = false
button.physicsBody.affectedByGravity = false
button.physicsBody.categoryBitMask = 0x1
button.physicsBody.contactTestBitMask = 0x1
button.addChild(label)
addChild(button)
}
func addSecondButton() {
let button = SKSpriteNode(color: SKColor.blueColor(), size: kButtonSize)
let label = SKLabelNode(text: "Call 'addBox' from touchesBegan")
button.name = "secondButton"
label.name = "secondLabel"
button.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)-200)
button.physicsBody = SKPhysicsBody(rectangleOfSize: button.size)
button.physicsBody.dynamic = false
button.physicsBody.categoryBitMask = 0x1
button.physicsBody.contactTestBitMask = 0x1
button.addChild(label)
addChild(button)
}
func addContainer() {
let container = SKSpriteNode(color: SKColor.greenColor(), size:kContainerSize)
let label = SKLabelNode(text: "Created node should fall here")
label.fontColor = SKColor.blackColor()
container.name = "container"
container.physicsBody = SKPhysicsBody(edgeLoopFromRect: container.frame)
container.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)+300)
container.addChild(label)
addChild(container)
}
func addBox() {
let container = childNodeWithName("container")
let box = SKSpriteNode(color: SKColor.blueColor(), size: kBoxSize)
box.physicsBody = SKPhysicsBody(rectangleOfSize: box.size)
box.position = CGPointMake(0, 100)
container.addChild(box)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let point = touch.locationInNode(self)
let node = nodeAtPoint(point)
if node.name == nil {return}
switch node.name! {
case "firstButton", "firstLabel":
let button = childNodeWithName("firstButton") as SKSpriteNode
button.physicsBody.applyImpulse(CGVectorMake(0, -500))
case "secondButton", "secondLabel":
addBox()
default:
break
}
}
func didBeginContact(contact: SKPhysicsContact!) {
addBox()
}
}
I have submitted a ticket regardomg this issue with Apple Bug Report. I hope this helps anyone who is encountering the same issue. Here is their response:
Apple Developer Relations24-Sep-2014 04:40 AM
Engineering has determined that this is an issue for you to resolve
based on the following:
You can't modify a tree which it is being simulated, and this is
clearly stated in Programming Guide.
It seems like you can't create physics bodies in the contact delegate (this is similar to other engines, eg. AndEngine). Instead of creting new body straight into the delegate, mark some bool flag there and do your creation in the didSimulatePhysics or didFinishUpdate - works for me.

How to rotate sprite in sprite kit with swift

Hi my problem is regarding the rotation of two sprites. When I touch the right half of the screen the rotation starts and moving sprite2 and sprite3. If I touch the left half of the screen the rotation stops because velocity-velocity = 0. If I touch the left half again the rotation begins.
However, if I touch the half of the screen corresponding with the current rotational-direction the velocity is duplicated. I want to be able to change the direction of the rotation, but for the speed to remain constant.
Video demonstrating the problem: http://youtu.be/HxLwl1QZiNM
import SpriteKit
class GameScene: SKScene {
let sprite = SKSpriteNode(imageNamed:"bWhite")
let sprite2 = SKSpriteNode(imageNamed:"bBlue")
let sprite3 = SKSpriteNode(imageNamed:"bRed")
let circle = SKSpriteNode(imageNamed:"bCircle")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
backColor = SKColor(red: 0, green: 0, blue: 0, alpha: 1)
self.backgroundColor = backColor
sprite.setScale(1.25)
sprite2.setScale(1)
sprite3.setScale(1)
sprite.position = CGPointMake(self.frame.size.width/2, (self.frame.size.height/2)-200);
circle.position = CGPointMake(self.frame.size.width/2, (self.frame.size.height/2)-200);
sprite3.zRotation = 172.77
sprite2.anchorPoint = CGPointMake(-1.10, 0.5);
sprite3.anchorPoint = CGPointMake(-1.10, 0.5);
self.addChild(sprite)
self.addChild(circle)
sprite.addChild(sprite2)
sprite.addChild(sprite3)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
let action2 = SKAction.rotateByAngle(CGFloat(-M_PI), duration:1)
if (location.x > self.frame.size.width / 2)
{
sprite2.runAction(SKAction.repeatActionForever(action2))
sprite3.runAction(SKAction.repeatActionForever(action2))
} else {
sprite2.runAction(SKAction.repeatActionForever(action))
sprite3.runAction(SKAction.repeatActionForever(action))
}
}
}
OK, take three. I'm not 100% sure about the specifics of when the rotation should end etc. but all the pieces should be in place with the below code. It will:
start rotation clockwise or counterclockwise based on the position of the first touch
stop the rotation if the user touches for the same direction again
switch the rotation if the user touches on the other half of the screen
import SpriteKit
enum rotationDirection{
case clockwise
case counterClockwise
case none
}
class GameScene: SKScene {
var currentRotationDirection = rotationDirection.none
let sprite = SKSpriteNode(color: UIColor.yellowColor(), size: CGSizeMake(100, 100))
override func didMoveToView(view: SKView) {
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
sprite.physicsBody.affectedByGravity = false
sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addChild(sprite)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch : UITouch = touches.anyObject() as UITouch
let touchPosition = touch.locationInNode(self)
let newRotationDirection : rotationDirection = touchPosition.x < CGRectGetMidX(self.frame) ? .clockwise : .counterClockwise
if currentRotationDirection != newRotationDirection && currentRotationDirection != .none{
reverseRotation()
currentRotationDirection = newRotationDirection
} else if currentRotationDirection == newRotationDirection{
stopRotation()
currentRotationDirection = .none
} else if (currentRotationDirection == .none){
setupRotationWith(direction: newRotationDirection)
currentRotationDirection = newRotationDirection
}
}
func reverseRotation(){
let oldRotateAction = sprite.actionForKey("rotate")
let newRotateAction = SKAction.reversedAction(oldRotateAction)
sprite.runAction(newRotateAction(), withKey: "rotate")
}
func stopRotation(){
sprite.removeActionForKey("rotate")
}
func setupRotationWith(#direction: rotationDirection){
let angle : Float = (direction == .clockwise) ? Float(M_PI) : -Float(M_PI)
let rotate = SKAction.rotateByAngle(angle, duration: 1)
let repeatAction = SKAction.repeatActionForever(rotate)
sprite.runAction(repeatAction, withKey: "rotate")
}
}
Edit: Changed example to cater the specific needs in the question. Something odd with the code formatting not quite sure what's going on there.
I suggest removing all current actions before adding new, since they may conflict. I'm still not sure what you are trying to achieve, can you explain more?
Have you just tired removing the actions at touchesEnded? I had a similar problem before where I wanted my object to move left to right based on which half of the screen the user touched. I had a similar issue where if I touched right and right again my velocity would double, and if I touched left my object would just stop.
I fixed this issue by adding the touchesEnded method and just removed the actions whenever I let go. This essentially reset my move action so the object would stop when I let go and would go to the opposite direction right away instead of having to tap it twice.

Resources