7

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

Marshall D
  • 454
  • 3
  • 20
  • That sounds... frustrating :( you've done a good job explaining the problem you're encountering, and what you want to do, can you give a little background information around how your views/sprites are managed e.g. have you subclassed `SKSpriteNode` and overridden `touchesMoved` for the nodes that are individual items, or just the single container view? – MathewS Jan 07 '17 at 23:43
  • @MathewS yes thankyou I just added a lot of stuff. And what do you mean by overridden touchesMoved for the nodes? Isn't touchesMoved a function of scenes? – Marshall D Jan 08 '17 at 00:52
  • from what I can see your code looks fine, and I'm wondering if `touchesMoved` is a red-herring for some problem that's happening elsewhere (although I can't guess what). Just to double check, you're not setting `isUserInteractionEnabled` to true on your grandparent/parent/childs? – MathewS Jan 08 '17 at 01:26
  • I don't see how it would impact performance, but you shouldn't pass in `GameScene` to your child nodes like you're doing. You should be `addChild()` from code within scene subclass. – MathewS Jan 08 '17 at 01:31
  • @MathewS yes nowhere in my code do I use `isUserInteractionEnabled`. Would it help if I posted the whole thing on github or something? I've been able to isolate the problem to a pretty small version of the project. – Marshall D Jan 08 '17 at 02:23
  • yes if you add a link to github project I'm happy to take a look – MathewS Jan 08 '17 at 02:24
  • What is the framerate when this happens? – prototypical Jan 08 '17 at 03:32
  • @prototypical it's stable the whole time. The rest of the game is always running very smoothly even if I put it under a lot of stress. HOWEVER that's only when I run it on a phone. When I run it on my mac it's unplayable. However I'm not sure if that has to do specifically with my game. – Marshall D Jan 08 '17 at 03:40
  • @MathewS I'm having trouble with github so I made a zip file on a google drive: https://drive.google.com/file/d/0B1LXN-5-_OgoVE5sMlFyeUVnYUU/view?usp=sharing I'm still trying to upload to github so if you prefer that, I'll have it done soon. Also there's a small ReadMe in there that should explain some of the structure. – Marshall D Jan 08 '17 at 03:50
  • @MarshallD can you clarify your comment to prototypical? if you're only encountering issues on the simulator it might just be because it's the simulator, see also: http://stackoverflow.com/questions/19556867/ios-simulator-games-run-very-slow-low-fps – MathewS Jan 08 '17 at 04:21
  • @MathewS I worded that wrong - My simulator on my computer must be broken or something. However my touchesMoved() problem occurs when I test on the phone. – Marshall D Jan 08 '17 at 04:24
  • This should probably be taken to chat [chat] – Knight0fDragon Jan 08 '17 at 04:24
  • 1
    I can't recreate the issue. Is there something I need to do in order to recreate it? I have tried it on a iphone 5s and an iPad Air 2 and I can't seem to pinpoint what the problem is. Granted, I don't know what should be happening :) I just drag my finger and some lines/rectangles grow/shrink/rotate based on that. But nothing seems to indicate a touchesMoved issue. – prototypical Jan 08 '17 at 04:53
  • @prototypical lets discuss this in chat: http://chat.stackoverflow.com/rooms/132607/touchesmoved-not-working – Marshall D Jan 08 '17 at 04:57
  • @MathewS lets discuss this in chat: http://chat.stackoverflow.com/rooms/132607/touchesmoved-not-working – Marshall D Jan 08 '17 at 04:57
  • Why do you do this: `var positionInScene = touch.location(in: self.view) positionInScene.x -= self.size.width/2 positionInScene.y -= self.size.height/2; positionInScene.y *= -1` – Confused Jan 08 '17 at 11:37
  • @confused it gives me to coordinates with the center of the screen being 0,0 – Marshall D Jan 09 '17 at 00:30
  • Are you facing the same issue on your device as well? – Mehul Jan 10 '17 at 11:55
  • @Mehul yes it happens primarily on the device even when debugging has been disabled – Marshall D Jan 10 '17 at 17:25
  • I have the same issue on an iPad mini retina and an iPhone 6s but without using physics bodies (using the default sprite kit template). For periods of time touchesMoved seems to be called every frame and other times every other frame. – Ian Jun 25 '17 at 20:01
  • 1
    @Ian sadly I never fixed this. What helped me was building parts of the game from the ground up to isolate the issue, but other than that I haven't found a solution so good luck! Let me know if you find something I'd like to pick this project back up – Marshall D Jun 25 '17 at 20:09
  • @MarshallD For what it's worth, things seem stable in 10.3.2 (at least on iPhone 6s and iPad mini 2 retina) – Ian Jun 27 '17 at 14:30

2 Answers2

4

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
    }
}
NobodyNada
  • 7,529
  • 6
  • 44
  • 51
  • None of the things you suggested worked when you add an SKSpriteNode with a physics body even when I play with the 1.5 :(. I even did the update thing – Marshall D Jan 10 '17 at 03:55
  • This didn't help me enough, but hopefully this can smooth things out for other people having the problem. – Marshall D Jan 18 '17 at 00:12
  • @MarshallD Thanks for the bounty! There's a lot of other variations on this basic idea, for example you could try making the sprite "attracted" to the touch point. I don't think there's another way to fix it besides some method of interpolating between touches. – NobodyNada Jan 18 '17 at 00:36
1

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.

claassenApps
  • 1,137
  • 7
  • 14
  • This is what NobodyNada had suggested - it still doesn't work since the inconsistency becomes very large when scaled up. – Marshall D Jan 10 '17 at 17:24