Essentially, what I want is for when I touch a node, I want to be able to move it across the screen. The problem is that whenever I move my finger too fast, the node just stops following it.
The spriteNodes in particular that I'm trying to do this with have physics bodies and animating textures so I tried to do the same code with a completely plain spriteNode and I've encountered the same problem.
The code that I have here is pretty simple so I'm not sure if this is a problem with what I've written or if it's just a lag problem that I can't fix. It's also basically the same all throughout touchesBegan, touchesMoved and touchesEnded
for touch in touches {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "activeRedBomb"{
node.position = pos
}
if node.name == "activeBlackBomb"{
node.position = pos
}
if node.name == "test"{
node.position.x = pos.x
node.position.y = pos.y
}
}
What's happening is that if you move your finger too fast, then at some point, the touch location will no longer be on the sprite, so you code to move the node won't fire.
What you need to do is set a flag in touchesBegan() to indicate that this sprite is touched, move the sprite to the location of the touch in touchesMoved() if the flag is set and then reset the flag in touchesEnded().
Here's roughly what you need to add for this:
import SpriteKit
class GameScene: SKScene {
var bombIsTouched = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if activeRedBomb.contains(touch.location(in: self)) {
bombIsTouched = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if bombIsTouched {
activeRedBomb.position = (touches.first?.location(in: self))!
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if bombIsTouched {
bombIsTouched = false
}
}
Related
So I have these moving sprites with physics bodies that I initially have constrained to one area. Let's say call this area Box1. When the sprites bump the borders of Box1, they bump back and the SKConstraint is just an additional measure to make sure they stay within the box.
What I'm trying to do is make a button that causes all the sprites in Box1 move across the screen to a second box (Box2) that is basically identical to Box1 but right beside it. Box1 and Box2 share a border wall, so essentially the screen is divided in two sections.
My problem is when I call up my button function, the sprites get stuck at the wall and aren't able to go past it to Box2. I've tried setting both the physics bodies and the SKConstraints of the nodes to nil so I don't know what else is preventing them.
func moveSprites(){
if box1Sprites.isEmpty == false {
for sprite in box1Sprites{
sprite.constraints = nil
sprite.physicsBody = nil
let moveAction = SKAction.move(to: CGPoint(x: -300, y: 0), duration: 1) //This is a point within Box2
sprite.run(moveAction)
}
}
}
EDIT:
I also made it so that I could drag and drop the sprites from one box to the other and the code for that is pretty much the same and it works fine.
var currentMovingSprite: SKSpriteNode? = SKSpriteNode()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "sprite"{
node.constraints = nil
node.physicsBody = nil
currentMovingSprite = node
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pos = touch.location(in: self)
if let sprite = currentMovingSprite{
sprite.position = pos
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
spritePhysics(sprite: currentMovingSprite) // resets the sprite to how it was before
currentMovingSprite = nil
}
I have sprites moving across the screen, and if they are clicked then they disappear (i.e deleted).
I have overridden the touchesBegan func as follows:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch")
let touch = touches.first!
let location = touch.location(in: self)
for child in self.children {
if child.position == location {
child.removeFromParent()
}
}
}
This doesn't seem to have any effect, can someone tell me where I am going wrong?
In which class did you implement this method?
If it was in SKNode itself, you simply do:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.removeFromParent()
}
However, if this method is in SKScene, this way that was implemented would probably not work. Because child.position returns a point (x, y) where the touch was made. And you're trying to compare the touch point and position of the SKNode (center point), it's unlikely to work.
Instead of using this way, try using .nodeAtPoint, a method of SKScene.
For this you will need to put a value in the 'name' property of your SKNode:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch")
let touch = touches.first!
let positionInScene = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name
{
if name == "your-node-name"
{
touchedNode.removeFromParent()
}
}
}
Font: How do I detect if an SKSpriteNode has been touched
Ok so currently in my game I have a ball that can be thrown. However, I want to make so that the user can only throw the ball past a certain point. I have been trying this for a while but I can't figure it out. How can I do this?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first as! UITouch?
let location = touch?.location(in: self)
if ball.frame.contains(location!) {
touchPoint = location!
touching = true
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first as! UITouch?
let location = touch?.location(in: self)
touchPoint = location!
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func update(_ currentTime: TimeInterval) {
if touching {
let dt:CGFloat = 2.8/60.0
let distance = CGVector(dx: touchPoint.x-ball.position.x, dy: touchPoint.y-ball.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
ball.physicsBody!.velocity=velocity
}
}
Here, I have an image of my game so far. Currently, the ball can be dragged, thrown, or swiped anywhere on the screen. However, I want to make so the ball can only be touched, thrown, or dragged below the middle of the screen. But I want the ball to continue being thrown if the user's finger accidently goes about the limit.
you will have to adjust the coordinates for your bottom area based on actual size and game scene anchorPoint. for example "100" in the sample assumes that your scene has an anchorPoint of (0,0) and the size of the bottom scroll area is 100.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let location = touch.location(in: self)
guard location < 100 else { return }
//if ball.frame.contains(location!) {
touchPoint = location!
touching = true
//}
}
}
I would like to get the location of the touch in the UI, and use that location to determine what direction the sprite runs. For example, if the touch is on the left side of the screen, the user runs left, and if the touch is on the right side of the screen, the user runs right.
Try something like this
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let position = touch.locationInView(view)
print(position)
}
}
#KnightOfDragon's answer is the right one here.
You should use directly
func locationInNode(_ node: SKNode) -> CGPoint
More about Touch events in SpriteKit over here: documentation
As KnightOfDragon mentioned for SpriteKit the correct way would be
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self) // self is the current SKScene
let node = nodeAtPoint(location)
// To get the touched half of the screen I do this
if location.x < CGRectGetMidX(self.frame) {
// left half touched, do something
}
if location.x > CGRectGetMidX(self.frame) {
// right half touched, do something
}
}
}
I have an SKNode on SpriteKit, which I basically want to be able to drag around the screen, but without having to touch it! Imagine I press the screen anywhere. I then want my SKNode to keep its distance to my finger, so that when I drag it I can see it.
I have this working but the object snaps to the touch.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches{
let location = touch.locationInNode(self)
circle.position = location
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches{
let location = touch.locationInNode(self)
circle.position = location
}
}
It's a classic basic problem in game engineering.
There are two solutions...
First solution: when finger first goes down, make a note of the "grab" delta, being the delta from the position of the object, to, the finger.
When the finger moves each time to new position P, subtract the "grab" delta from P before setting the position of the object to P.
"It's that easy"
Second solution: each time the finger moves, don't bother at all with the position P of the finger.
Instead, calculate the delta the finger moved from the previous frame.
(So, simply store the previous position each time so you can calculate that - some systems give you the previous position as a property since it's so common, indeed some systems just give you the delta automatically as a property!)
Then just move the object by that delta.
"It's that easy"
Here is precisely the first solution, in iOS/SpriteKit
class FingerFollower: SKSpriteNode {
var grab: CGVector = CGVector.zero
// "grab" is the usual term for the delta from the object
// to where the finger "grabbed" it...
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let t: UITouch = touches.first! as UITouch
let l = t.location(in: parent!)
grab = (l - position).vector
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let t: UITouch = touches.first! as UITouch
let loc = t.location(in: parent!)
position = loc - grab
}
// NOTE this code uses the absolutely obvious overrides for
// subtraction etc between vectors, which you will need in 100%
// of spritekit projects (Apple forgot about them)
}
Here is precisely the second solution, in iOS/SpriteKit
class FingerFollower: SKSpriteNode {
func setup() {
// NOTE, you MUST have a "setup" call in your sprite subclasses;
// apple forgot to include a "didAppear" for SKNodes
isUserInteractionEnabled = true
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// note that Apple do in fact include the "previous location"
// in sprite kit touches. (In systems where they don't do that,
// you just make a note of it each time.)
let prev = t.previousLocation(in: parent!)
let quickDelta = loc - prev
position = position + quickDelta
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?){
let touch = touches.anyObject() as UITouch!
let touchLocation = touch.locationInNode(self)
let previousLocation = touch.previousLocationInNode(self)
let distanceX = touchLocation.x - previousLocation.x
let distanceY = touchLocation.y - previousLocation.y
circle.position = CGPointMake(circle.position.x + distanceX, circle.position.y + distanceY)
}