TouchesMoved() lagging very inconsistently when there is a SkSpriteNode with a physics body
Asked Answered
C

2

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

Costermonger answered 7/1, 2017 at 22:31 Comment(22)
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?Rolf
@Rolf 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?Costermonger
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?Rolf
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.Rolf
@Rolf 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.Costermonger
yes if you add a link to github project I'm happy to take a lookRolf
What is the framerate when this happens?Blotter
@Blotter 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.Costermonger
@Rolf I'm having trouble with github so I made a zip file on a google drive: drive.google.com/file/d/0B1LXN-5-_OgoVE5sMlFyeUVnYUU/… 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.Costermonger
@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: #19557367Rolf
@Rolf 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.Costermonger
This should probably be taken to chat Stack Overflow ChatDesquamate
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.Blotter
@Blotter lets discuss this in chat: chat.stackoverflow.com/rooms/132607/touchesmoved-not-workingCostermonger
@Rolf lets discuss this in chat: chat.stackoverflow.com/rooms/132607/touchesmoved-not-workingCostermonger
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 *= -1Antarctic
@confused it gives me to coordinates with the center of the screen being 0,0Costermonger
Are you facing the same issue on your device as well?Judo
@Judo yes it happens primarily on the device even when debugging has been disabledCostermonger
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.Misinterpret
@Misinterpret 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 upCostermonger
@MarshallD For what it's worth, things seem stable in 10.3.2 (at least on iPhone 6s and iPad mini 2 retina)Misinterpret
S
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
    }
}
Salangi answered 9/1, 2017 at 20:26 Comment(3)
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 thingCostermonger
This didn't help me enough, but hopefully this can smooth things out for other people having the problem.Costermonger
@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.Salangi
C
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.

Cimbura answered 10/1, 2017 at 14:54 Comment(1)
This is what NobodyNada had suggested - it still doesn't work since the inconsistency becomes very large when scaled up.Costermonger

© 2022 - 2024 — McMap. All rights reserved.