I'm playing around with Sprite Kit in Swift on 10.9 using XB6. I have some code in my Scene that adds a sprite at the location of a mouse click. It loads the sprite thus:
let location = theEvent.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.position = location
sprite.setScale(0.5)
self.addChild(sprite)
This code runs fine for a while; I click and a sprite appears where I expect it to. But if I keep clicking, eventually that second line will result in a:
EXEC_BAD_ACCESS (code=EXC_I386_GPFLT)
(I wish they let you copy the error...). Sometimes it takes 5 clicks, other times 20, there's no apparent pattern to it. Googling the error, its obviously something that's happening deep in the bowels of SK or Swift.
Is anyone else seeing this?
Is this in the touchesBegan function? If so, did you use a loop?
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches{
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.position = location
sprite.setScale(0.5)
self.addChild(sprite)
}
}
OK this turned out to be an error in the beta builds. Replacing my Xcode with the 6.0 release and then copying the 10.10 SDK from 6.1B fixes this problem.
Related
I am creating a game where the user can move some fruits around in the scene. I want to user to be able to move only the fruits and not any other SKSpriteNode in the scene, so I wrote the code below to implement it. However the code doesn't work properly as I can't seem to be able to drag any of my sprites around, but rather they change position only when I stop touching the screen and they don't move by much anyway.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let location = touch?.location(in: self){
let nodesTouched = nodes(at: location)
for node in (nodesTouched) {
if node is Fruit{
for t in touches {
let locationMoved = t.location(in: self)
node.position.x = locationMoved.x
node.position.y = locationMoved.y
}
}
}
}
}
Anyone knows what's wrong with it?
Thanks in advance!
I found a solution to this, which was to basically set the physicsBody.affectedByGravity property to false for that specific Fruit instance every time I touch it, and then set it back to true as soon as I stop touching it. that makes it possible to drag all the fruits everywhere I want.
I have recently send out a version of my game to TestFlight for internal testing.
My friend noticed that on his iPhone 6S Plus the controls to shoot do not work correctly.
The controls are as follow
1) left half of screen = jump
2) right half of screen = shoot
3) Slide and hold on right half of screen = start slow motion
Now in my touches began method I have a slight delay for the shooting, so if touches moved is called it cancels the shooting and starts slow mo.
This seems to be causing the problem on 3D Touch enabled phones.
Digging further into this I came across this post
iOS 9 Spritekit Double Tap Not Working on iPhone 6S
where it basically states that on 3D Touch enabled devices the touches moved method gets called automatically.
On 3D Touch capable devices, touch pressure changes cause
touchesMoved:withEvent: to be called for all apps running on iOS
9.0. When running iOS 9.1, the method is called only for apps
linked with the iOS 9.0 (or later) SDK.
Apps should be prepared to receive touch move events with no
change in the x/y coordinates. "
This seems to make sense of why my friend has problems, if touches moved is always called it will cancel the shooting and start slow mo, even though he is not sliding on the screen.
So my questions is
1) Can you disable 3D Touch in SpriteKit games?
2) Is this a bug?
3) any other way to use touches moved correctly on 3D Touch devices?
4) alternatively can I use gesture recognisers to simulate a slide and hold feature that I am currently doing in touches moved.
Thanks for any help
After trying various other things that didnt work, including UISwipeGestureRecognizer subclasses, I finally figured it out.
The answer was actually staring me right in the face in that same stackOverflow post I linked in my question
iOS 9 Spritekit Double Tap Not Working on iPhone 6S
The solution is to create a property
var startingTouchLocation: CGPoint?
than in touches Began you set that property
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
/// Set starting touch. this is for 3d touch enabled devices, see touchesMoved method below
startingTouchLocation = location
....
}
}
and than add a check in touchesMoved to return from the method until the touch has actually moved.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
/// WAY 1 - Check if touch moved because on 3d touch devices this method gets called on a force touch.
guard location != startingTouchLocation else { return }
// Your code
...
/// WAY 2 - Add a min distance for the finger to travel in any direction before calling touches moved code
let minValue: CGFloat = 30
guard let startingLocation = startingLocation else { return }
guard currentLocation.y < startingLocation.y - minValue ||
currentLocation.y > startingLocation.y + minValue ||
currentLocation.x < startingLocation.x - minValue ||
currentLocation.x > startingLocation.x + minValue else { return false }
// Your code
....
}
}
I am trying to implement an overriden touchesMoved func to enable SKSpriteNodes to be moved around by the user. However, I have to move the node very slowly for it to follow my touches when I drag. In addition, there are three SKSpriteNodes in the background (which you will see I explicitly set to .userInteractionEnabled = false) and these nodes will occasionally respond to the touches. Any help would be greatly appreciated. Let me know if there are any other parts of the code you need.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
var positionInScene = CGPoint(x: 375.0, y: 400.0) //sets a default position
titleLabel.userInteractionEnabled = false
drawingBoard.userInteractionEnabled = false
sideBar.userInteractionEnabled = false
for touch in touches {
positionInScene = touch.locationInNode(self)
if self.nodeAtPoint(positionInScene) is SKSpriteNode {
if (self.nodeAtPoint(positionInScene)).name == movableNodeName { //movableNodeName is the name assigned to all SKSpriteNodes that should be draggable
//I know this might be a strange way of doing it
(self.nodeAtPoint(touch.previousLocationInNode(self))).position = positionInScene
}
}
}
}
I've managed to fix this issue by just setting the coordinates of the node that shouldn't move back to what they were originally whenever it is moved. This seems to work as I haven't been able to replicate the bug again in testing. If anyone could come up with a better solution though, I would love to hear it.
Your problem is you are using touch.locationInNode(self), where self is your SKSpriteNode. This means that your touch move code will only respond to what is going on inside of your SKSpriteNode. What you need to do, is use either the parent of the sprite, or the scene, based on how you want to apply this logic.
I'm developing a game using Swift SpriteKit. All things have gone fine but the only thing made me freaked out is detecting Game Over. My game look like picture below, it is a top down view game.
The Brown Color is Wood and the Blue Color(that one looks like water) is River. If player step on river instead of wood then the game is over.
The problem is how can i detect it step on river. I have tried using SpriteKit Contact which is func didBeginContact. But it only get called when startup, after that it not calling anymore even i step on it.
There are two possible ways that came in my head.
1. In your touchesBegan you do this.
NOTE: This will not work if the player is able to Fall into the river without interaction. In addition to that it calls Game Over a bit to early.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if wood.containsPoint(location) {
} else {
// Call Game Over func
}}
2. In your update func (This should work all the time)
override func update(currentTime: NSTimeInterval) {
super.update(currentTime)
if wood.containsPoint(Player.position) {
} else {
// Call Game Over func
}}
UPDATE: The code should be working
I am building a game in which the player drags a piece around the gameboard. I wish to know what are all of the nodes underneath that piece, and I am getting odd results. Here is the touchesMoves func:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self.parent)
self.position = location
println("checker x: \(location.x)")
println("node at point: \(self.nodeAtPoint(location))")
println("nodes at point: \(self.nodesAtPoint(location))")
}
The sprite moves around the board just fine, but what is reported as the nodeAtPoint is always the sprite being moved around (which kind of makes sense but is not useful. Any oddly, the nodesAtPoint is always reported as an empty array! How is this possible? What should I be doing differently?
Update: This continues to be a struggle. I want to keep the touch methods in the node itself, and not the scene. The most recent version of the touchesMoved is the following:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
// Turn off the touched bool:
touched = false
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self.parent)
let loc = touch.locationInView(scene.view)
let loc2 = touch.locationInNode(scene)
self.position = location
println("checker: \(loc2.x), \(loc2.y)")
println("node at point: \(self.nodeAtPoint(loc2).name)")
println("nodes at point: \(self.nodesAtPoint(loc2))")
}
The nodesAtPoint array continues to be empty with one really odd exception. When hovering near the center of the scene, I get this:
nodes at point: [ name:'(null)' accumulatedFrame:{{-30, -19.80000114440918}, {60, 39.600002288818359}}]
There is not shape node there that I am aware of! Why am I not detecting the nodes I pass over?
I discovered the answer to this. Essentially I was trying to detect nodesAtPoint on self, which in this case was the node being moved around the screen. Once I changed that to self.scene, the nodesAtPoint array populated as expected. So, to be clear:
println("nodes at point: \(self.nodesAtPoint(loc2))")
Needed to be changed to this:
println("nodes at point: \(self.scene.nodesAtPoint(loc2))")
If self is SKScene, try to change
let location = touch.locationInNode(self.parent)
to
let location = touch.locationInNode(self)
because SKScene's parent is nil