I am doing a small for fun project in Swift Xcode 6. The function thecircle() is called at a certain rate by a timer in didMoveToView(). My question is how do I detect if any one of the multiple circle nodes on the display is tapped? I currently do not see a way to access a single node in this function.
func thecircle() {
let circlenode = SKShapeNode(circleOfRadius: 25)
circlenode.strokeColor = UIColor.whiteColor()
circlenode.fillColor = UIColor.redColor()
let initialx = CGFloat(20)
let initialy = CGFloat(1015)
let initialposition = CGPoint(x: initialx, y: initialy)
circlenode.position = initialposition
self.addChild(circlenode)
let action1 = SKAction.moveTo(CGPoint(x: initialx, y: -20), duration: NSTimeInterval(5))
let action2 = SKAction.removeFromParent()
circlenode.runAction(SKAction.sequence([action1, action2]))
}
There are many problems with this.
You shouldnt be creating any looping timer in your games. A scene comes with an update method that is called at every frame of the game. Most of the time this is where you will be checking for changes in your scene.
You have no way of accessing circlenode from outside of your thecircle method. If you want to access from somewhere else you need to set up circlenode to be a property of your scene.
For example:
class GameScene: BaseScene {
let circlenode = SKShapeNode(circleOfRadius: 25)
You need to use the method touchesBegan. It should have come with your spritekit project. You can detect a touch to your node the following way:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
// detect touch in the scene
let location = touch.locationInNode(self)
// check if circlenode has been touched
if self.circlenode.containsPoint(location) {
// your code here
}
}
}
Related
I'm using Swift 3.0, SpriteKit, and Xcode 8.2.1, testing on an iPhone 6s running iOS 10.2.
The problem is simple... on the surface. Basically my TouchesMoved() updates at a very inconsistent rate and is destroying a fundamental part of the UI in my game. Sometimes it works perfectly, a minute later it's updating at half of the rate that it should.
I've isolated the problem. Simply having an SKSpriteNode in the scene that has a physics body causes the problem... Here's my GameScene code:
import SpriteKit
import Darwin
import Foundation
var spaceShip = SKTexture(imageNamed: "Spaceship")
class GameScene: SKScene{
var square = SKSpriteNode(color: UIColor.black, size: CGSize(width: 100,height: 100))
override func didMove(to view: SKView) {
backgroundColor = SKColor.white
self.addChild(square)
//This is what causes the problem:
var circleNode = SKSpriteNode(texture: spaceShip, color: UIColor.clear, size: CGSize(width: 100, height: 100))
circleNode.physicsBody = SKPhysicsBody(circleOfRadius: circleNode.size.width/2)
self.addChild(circleNode)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
var positionInScreen = touch.location(in: self)
square.position = positionInScreen
}
}
}
The problem doesn't always happen so sometimes you have to restart the app like 5 times, but eventually you will see that dragging the square around is very laggy if you do it slowly. Also I understand it's subtle at times, but when scaled up this is a huge problem.
My main question:
Why does me having an SKSpriteNode with a physics body cause TouchesMoved() to lag and nothing else to lag, and how can I prevent this?
Please for the love of code and my sanity save me from this abyss!!!
It looks like this is caused by the OS being too busy to respond to touch events. I found two ways to reproduce this:
Enable Airplane Mode on the device, then disable it. For the ~5-10 seconds after disabling Airplane Mode, the touch events lag.
Open another project in Xcode and select "Wait for the application to launch" in the scheme editor, then press Build and Run to install the app to the device without running it. While the app is installing, the touch events lag.
It doesn't seem like there is a fix for this, but here's a workaround. Using the position and time at the previous update, predict the position and time at the next update and animate the sprite to that position. It's not perfect, but it works pretty well. Note that it breaks if the user has multiple fingers on the screen.
class GameScene: SKScene{
var lastTouchTime = Date.timeIntervalSinceReferenceDate
var lastTouchPosition = CGPoint.zero
var square = SKSpriteNode(color: UIColor.black, size: CGSize(width: 100,height: 100))
override func didMove(to view: SKView) {
backgroundColor = SKColor.white
self.addChild(square)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchTime = Date().timeIntervalSinceReferenceDate
lastTouchPosition = touches.first?.location(in: self) ?? .zero
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let currentTime = Date().timeIntervalSinceReferenceDate
let timeDelta = currentTime - lastTouchTime
for touch in touches{
square.removeAction(forKey: "TouchPrediction")
let oldPosition = lastTouchPosition
let positionInScreen = touch.location(in: self)
lastTouchPosition = positionInScreen
square.position = positionInScreen
//Calculate the difference between the sprite's last position and its current position,
//and use it to predict the sprite's position next frame.
let positionDelta = CGPoint(x: positionInScreen.x - oldPosition.x, y: positionInScreen.y - oldPosition.y)
let predictedPosition = CGPoint(x: positionInScreen.x + positionDelta.x, y: positionInScreen.y + positionDelta.y)
//Multiply the timeDelta by 1.5. This helps to smooth out the lag,
//but making this number too high cause the animation to be ineffective.
square.run(SKAction.move(to: predictedPosition, duration: timeDelta * 1.5), withKey: "TouchPrediction")
}
lastTouchTime = Date().timeIntervalSinceReferenceDate
}
}
I had similar issues when dragging around an image using the touchesMoved method. I was previously just updating the node's position based on where the touch was, which was making the movement look laggy. I made it better like this:
//in touchesMoved
let touchedPoint = touches.first!
let pointToMove = touchedPoint.location(in: self)
let moveAction = SKAction.move(to: pointToMove, duration: 0.01)// play with the duration to get a smooth movement
node.run(moveAction)
Hope this helps.
The SKNode method moveToParent does not maintain its absolute position after moving to a new parent in the same scene. The node moves to another position.
The documentation for moveToParent says
Moves the node to a new parent node in the scene. The node maintains its current position in scene coordinates.
Did I miss something?
update_01:
I realize something new for me.
The moving node maintains its position only if its initial position is zero points.If it has a initial position, after moveToParent function, it moves, which makes me confuse as compared to apple documentation.
As I understand from "maintain", it does not move on the screen, just its position values are changed.
and please do not consider scene!.scaleMode, because it was tested without this(and ofcourse proper anchorPoint) and nothing changed.
This is my code and screenshots:
import SpriteKit
class GameScene: SKScene , UIGestureRecognizerDelegate{
var counter:Int = 0
var touchTracker : [UITouch : SKNode] = [:]
let yellowParentNode = SKSpriteNode()
let blueParentNode = SKSpriteNode()
override func didMoveToView(view: SKView)
{
scene!.scaleMode = SKSceneScaleMode.ResizeFill
let yellowPosition = CGPointMake(100, 100)
let bluePosition = CGPointMake(200[![enter image description here][1]][1], 100)
//yellow parent
yellowParentNode.name = "yellow"
yellowParentNode.color = UIColor.yellowColor()
yellowParentNode.size.height = 50
yellowParentNode.size.width = 50
yellowParentNode.anchorPoint = CGPoint(x: 0 , y: 0)
yellowParentNode.position = yellowPosition
self.addChild(yellowParentNode)
//blue parent
blueParentNode.name = "blue"
blueParentNode.color = UIColor.blueColor()
blueParentNode.size.height = 50
blueParentNode.size.width = 50
blueParentNode.position = bluePosition
blueParentNode.anchorPoint = CGPoint(x: 0 , y: 0)
self.addChild(blueParentNode)
//child
let childNode = SKSpriteNode()
childNode.color = UIColor.redColor()
childNode.size.width = 20
childNode.size.height = 20
childNode.name = "child"
childNode.position = CGPointMake(40, 40)
childNode.anchorPoint = CGPoint(x: 0 , y: 0)
yellowParentNode.addChild(childNode)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
let touch = touches.first as UITouch!
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
touchTracker[touch as UITouch] = touchedNode
if touchedNode.name == "child"
{
yellowParentNode.childNodeWithName("child")!.moveToParent(blueParentNode)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touch = touches.first as UITouch!
let touchLocation = touch.locationInNode(self)
let touchedNode = touchTracker[touch as UITouch]
touchedNode?.position = touchLocation
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
{
// let touch = touches.first as UITouch!
touchTracker.removeAll()
}
override func update(currentTime: CFTimeInterval)
{
/* Called before each frame is rendered */
}
}
this is the initial screen:
and this is next screen,which is after touching the child node:
You may be misinformed here, and Alessandro's answer does not explain why things are happening.
The position property is always relative to the parent. This is not what scene coordinates are. If you want to get the scene coordinates, you need to convert the position to the scene.
Now when you use moveToParent, you obviously are moving the node's parent property to the new parent. This means that the nodes position has to change, since the parent is changing. Now the node position has to change in order to adjust for the new parent, so that is still stays on the same spot on the scene.
E.G.
Node 1 lies on 10, 10 to the scene, Node 2 lies on 20,10 to the scene.
Node A is a child of node 1, and is positioned at 0,0 (scene coordinate (10,10))
We then move Node A from node 1 to node 2.
If position never changes (stays at (0,0)), this means that when viewed on screen, the scene coordinate would be 20,10, which is wrong
The position would have to change to (-10,0) because you take the parent (20,10) and need to adjust it by (-10,0) to get the scene coordinate of Node A back to (10,10)
I think there might be a misunderstanding in the interpretation of the information provided by Apple. In fact "moves the node to a new parent node" it could understand that your node is already a child of a node in the same scene.
If you make for example:
let pos1 = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
let pos2 = CGPointMake(CGRectGetMidX(self.frame)/2,CGRectGetMidY(self.frame)/2)
// make a big yellow node
let bigNode1 = SKSpriteNode.init(color: UIColor.yellowColor(), size: CGSizeMake(400,400))
addChild(bigNode1)
bigNode1.position = pos1
// make a big blue node
let bigNode2 = SKSpriteNode.init(color: UIColor.blueColor(), size: CGSizeMake(400,400))
addChild(bigNode2)
bigNode2.position = pos2
// make a little red node as a child of bigNode1
let littleNode = SKSpriteNode.init(color: UIColor.redColor(), size: CGSizeMake(100,100))
bigNode1.addChild(littleNode)
littleNode.position = CGPointZero
after you have this output:
Now if you do :
littleNode.moveToParent(bigNode2)
the output not change, exactly as explained by Apple.
Update (after 0x141E comment):
#0x141E As you can see, the position remain always 10,10 so it's correct:
I have loaded two checkbox sprites on the screen. The image to load the sprite initially in an unchecked checkbox. Then I expect to click on one checkbox and replace the image with another checked checkbox image. The code I have written behaves very strangely where the checked checkbox image never appears.
I have two class properties:
var check1: SKSpriteNode = SKSpriteNode (imageNamed: "unchecked.png")
var check2: SKSpriteNode = SKSpriteNode (imageNamed: "unchecked.png")
I am calling generateCheckboxex() method from didMoveToView(view: SKView)
func generateCheckboxes()
{
self.check1.position = CGPointMake(self.size.width * 0.75, self.size.height * 0.84)
self.check1.setScale(0.15)
self.check1.name = "check1"
self.addChild(self.check1)
self.check2.position = CGPointMake(self.size.width * 0.75, self.size.height * 0.75)
self.check2.setScale(0.15)
self.check2.name = "check2"
self.addChild(self.check2)
}
This is my touchBegan method code:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var touch = touches as! Set<UITouch>
var location = touch.first!.locationInNode(self)
var node = self.nodeAtPoint(location)
// If next button is touched, start transition to second scene
if node.name == "check1" {
//self.check1.texture = SKTexture(imageNamed: "checked.png")
var texAction = SKAction.setTexture(SKTexture(imageNamed: "checked.png"))
self.check1.runAction(texAction)
//self.check2.texture = SKTexture(imageNamed: "checked.png")
self.check2.runAction(texAction)
}
if node.name == "check2" {
self.check1.texture = SKTexture(imageNamed: "unchecked.png")
self.check2.texture = SKTexture(imageNamed: "checked.png")
}
}
Please help me and let me know if I'm approaching it in the wrong manner, thanks.
EDIT: Admins please close this question, this exact same code works for me now. Maybe it was an issue with the sprites themselves. Thanks!
I want to make a game where every time a user touches, it switches between one of two "states". In order to keep track of touches, I made a variable called userTouches, which changes from true to false each time a user touches. I want to make it so that if numberOfTouches is true, it updates the texture to state0; if it's false, it updates the texture to state1. Pretty much just toggling between state0 and state1 for each touch.
var userTouches: Bool = true
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
userTouches = !userTouches
}
let centered = CGPoint(x: size.width/2, y: size.height/2)
let state0 = SKTexture(imageNamed:"state0")
let state1 = SKTexture(imageNamed:"state1")
var activeState: SKSpriteNode = SKSpriteNode(texture: state0)
//Add new state0 if it's odd, remove old state0 if it's not.
if userTouches == true {
activeState.texture = state0
println("IT'S TRUE")
} else {
activeState.texture = state1
println("IT'S FALSE")
}
self.addChild(activeState)
activeState.name = "state0"
activeState.xScale = 0.65
activeState.yScale = 0.65
activeState.position = centered
}
When I run the code, the new textures are added according to the conditions, but the old ones are still there. They are being added as new spritenodes in the scene. I do not want this. I was expecting it to simply switch between the textures (state0 and state1) of the activeState spritenode depending on my boolean variable. How can I have my code toggle between textures each time a user taps, instead of piling new spritenodes on top of one another?
Each time you create a new object, change its texture (a new object`s texture, but not the texture of object which was set up last time) and add it to the scene. That's why new objects are added and nothing happens with the old objects.
Do this and it will solve your problem:
Create textures and SKSpriteNode object outside the touchesBegan function
If you have no init at your scene, create SKSpriteNode object at didMoveToView function for example and add it to scene
Then in touchesBegan function only set texture to SKSpriteNode object
it would look something like this:
class GameScene: SKScene {
...
let state0 = SKTexture(imageNamed:"state0")
let state1 = SKTexture(imageNamed:"state1")
var activeState: SKSpriteNode?
...
override func didMoveToView(view: SKView) {
let centered = CGPoint(x: size.width/2, y: size.height/2)
activeState = SKSpriteNode(texture: state0)
self.addChild(activeState!)
activeState!.name = "state0"
activeState!.xScale = 0.65
activeState!.yScale = 0.65
activeState!.position = centered
}
...
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if userTouches {
activeState!.texture = state0
} else {
activeState!.texture = state1
}
}
...
}
I want a game over screen and the game to stop when the screen is touched during a specific animation.
let lightTexture = SKTexture(imageNamed: "green light.png")
let lightTexture2 = SKTexture(imageNamed: "red light.png")
let animateGreenLight = SKAction.sequence([SKAction.waitForDuration(2.0, withRange: 0.1), SKAction.animateWithTextures([lightTexture, lightTexture2], timePerFrame: 3)])
let changeGreenLight = SKAction.repeatActionForever(animateGreenLight)
let animateRedLight = SKAction.sequence([SKAction.waitForDuration(2.0, withRange: 0.1), SKAction.animateWithTextures([lightTexture, lightTexture2], timePerFrame: 3)])
let changeRedLight = SKAction.repeatActionForever(animateRedLight)
let greenLight = SKSpriteNode(texture: lightTexture)
greenLight.position = CGPointMake(CGRectGetMidX(self.frame), 650)
greenLight.runAction(changeGreenLight)
self.addChild(greenLight)
let redLight = SKSpriteNode(texture: lightTexture2)
redLight.position = CGPointMake(CGRectGetMidX(self.frame), 650)
redLight.runAction(changeRedLight)
self.addChild(redLight)
When the animation for the red light is on the screen, I want it to be game over. Do I have to make an if statement, and if so for what specifically?
Thank You in advance!
You can make yourself another node which has the same size and position as the red light. Additionally, that node is able to handle touch events. Then, add that node before the animation runs. This can be done with a sequence of actions, e.g.:
let addAction = SKAction.runBlock({ self.addChild(touchNode) })
let animationAction = SKAction.repeatActionForever(animateRedLight)
redLight.runAction(SKAction.sequence([ addAction, animationAction ]))
Update
Since you want the game to end when you touch anywhere, alter the code such that the block sets a variable which indicates that the animation is executed and implement touchesBegan which checks for that variable, e.g.
let addAction = SKAction.runBlock({ self.redLightAnimationRuns = true })
[...]
// In touchesBegan
if touched
{
if redLightAnimationRuns
{
endGame()
}
}
use the touchesBegan() function to call a GameOver() function when the red light is on the screen (which you can control with a variable).
So, when the red light comes on to the screen, variable redLightCurrent is set to true. in TouchesBegan(), when redLightCurrent is true, then call the gameOver() function where you can include what to do when the game is over. This will only occur when a touch has began on the screen.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
let array = Array(touches)
let touch = array[0] as UITouch
let touchLocation = touch.locationInNode(self)
if redLightCurrent {
gameOver()
}
}
This code works with the new xCode 7 and Swift 2.0