Node movement lag in Spritekit, Xcode - ios

In my game I have a node that the player controls with the PanGesture. When the PanGesture function is called, it adds its velocity to that of the Node in question, making it move.
However, I have encountered a problem where the Node (under specific yet inconsistent circumstances) moves slower and not as smoothly as intended.
When I start the application it (sometimes) has the correct movement speed. As soon as I "die", and restart (remove all nodes and recognizers, restart the scene, remake all nodes and recognizers) the node does not move at the correct speed.
This is where things get weird.
After restarting the game and seeing that the node has the incorrect speed, I can access the "swipe up menu" and then promptly dismiss it, at which point the node will revert back to its intended speed. This however, does not always work. On top of that, I can leave the scene to have the same thing happen. This happens even less frequently.
This is the code I use to make a GestureRecognizer:
let Panned:UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(GameScene.Panned))
view.addGestureRecognizer(Panned)
These lines are only run once per scene. (Making multiple GestureRecognizers causes the entire scene to experience dramatic frame drops each time the screen is touched.)
This is the code which adds the velocity of the movement to the Node:
func Panned(sender:UIPanGestureRecognizer){
velocity = sender.velocity(in: view)
if movement == true{
Node.position.x = Node.position.x+(velocity.x/100)/1
Node.position.y = Node.position.y-(velocity.y/100)/1
}
}
This function is called each time the PanGestureRecognizer detects a "pan" movement.
This is my restart function, which prepares the scene for the next instance of the game:
func restartScene(){
removeAllChildren()
removeAllActions()
createScene()
}
removeAllChildren will kill my stuttering node, and createScene() will revive it again. (It should be noted that createScene() is the same function used at the initial start of the game, and can therefor not be related to the problem.)
The restart is however where my problem starts. This function is missing certain (in my opinion) irrelevant lines that manipulate certain values and handle labels.
The main gist of my problem is as follows:
Do I fully understand GestureRecognizers?
What effect does the "swipe up menu" have on my current scene? Does it clear/restart certain things, or does it simply pause my scene?
Edit:
The most confusing thing about the problem is how it arises after a "restart", when the restart process does not touch the PanGesture in anyway. I'm considering remaking the game to create a version where the problem isn't present, but this leaves the problem unsolved.

Related

SCNAnimationPlayer to start playing animation on gesture

I am using Scenekit/ ARKit for the code and Blender for my 3D models. I have a 3D model with some keyframe animations - created in Blender, which has been exported easily into XCode. Nothing complicated - it's just a cat moving a bit up and then down.
I am using animation controls inside my tapGestureRecognizer. As soon as my view loads I have set the animation to be in paused state :-
nonGeometryObjectNode?.childNode(withName: "Armature", recursively: true)?.animationPlayer(forKey: "cat_animated-1")?.paused = true
Code for animation controls inside my tap gesture is as below.
let animationPlayer = hitResults.first?.node.parent?.childNode(withName: "Armature", recursively: true)?.animationPlayer(forKey: "cat_animated-1")
animationPlayer?.animation.autoreverses = true
animationPlayer?.animation.repeatCount = 1
animationPlayer?.animation.duration = 0.8
animationPlayer?.animation.blendInDuration = 0.2
animationPlayer?.animation.blendOutDuration = 0.2
animationPlayer?.paused = true
if (animationPlayer?.paused)! {
animationPlayer?.play()
}
It works fine, the only problem is it works just one time when tap is performed for the very first time. I tried using .paused, .stop() inside my tapGesture code, but animationPlayer doesn't replay as it should every time I am tapping on it. There is no bool var in SCNAnimationPlayer or else which I can use to detect if the animation has played itself out, so that I can use .stop() and then .play() again.
I did see two instance vars animationDidStart and animationDidStop which I thought probably would be useful to manage what I need. But I am at loss of ways to use these. What would be really helpful is what should be used to play and stop/ pause my animation whenever I tap on the object. Any pointer would be helpful.
I know it's an old question but in case anyone stumbles across it in future, the default action is for the animation to be removed once complete. Therefore to be able to play it again you need to set:
animationPlayer?.animation.isRemovedOnCompletion = false

Recognize long press in swift without UILongPressGestureRecognizer

I'm building an app around gesture recognition.
I've already built my code with recognition of taps, swipes (even with multiples fingers), pinches.
Now I'd like to recognize long press gesture without using UILongPressGestureRecognizer because it enters in conflit with my recognition of other gesture after (I tried).
What I'm currently doing is that I get the time in touchesBegan, in touchesMoved i calculate the time difference, and if it's greater than 400ms (for exemple), i call a function.
The thing is that this function is only called when the finger moved a bit and not when it's perfectly static.
Another option is to set a kind of delay in the touchesBegan and check if the finger is still on the screen after 400ms and then call the function.
How could I do that without blocking the rest of the gesture recognition ?
The aim of this long press would be to do a variation of intensity of a light or something like that (from 0 to 1s, light increase until max is reach, and then lower until minimum etc).
Next, I'll try to recognize a rotation gesture (with only one finger), so if you also have an answer for this, that'd be perfect.
Thanks !
Do not set delay. Start a timer that will fire after 400ms. In touchesEnded invalidate the timer, in case it was called before 400ms. When the timer fires, call the desired function.
As to your second question, probably you will need to calculate the trajectory of the points in touchesMoved method. If somehow the moves resemble rotation (you will need some kind of threshold for that), call the appropriate function.

Children of a Scene not releasing fast enough?

Have a couple of child nodes in a scene, and do a quick transition to a new scene.
In the new scene, didMove(to view: ….) is used to add the children from the previous scene to this next scene.
SceneKit crashes when doing this quickly, seemingly because the children of the old scene haven’t yet been released.
If I add a slight delay before adding the children to the second scene, it’s fine… it seems SpriteKit isn’t releasing the child fast enough for the scene transition.
the above is the most important thing to understand with regard the purpose for this question. I (wrongly) assumed SpriteKit would make sure all nodes attached to a scene were released before attempting to add them to a subsequent scene. It doesn't do this. But it does release them, it just takes a little time. Is this one frame? One second? Dunno...
My scene transition time is 0.25 seconds
Also tried using willMove(to view: ….) in the original scene to manually remove the children. This also doesn’t work, seemingly same behaviour: not fast enough.
Also tried assuming the child still has a relationship to its parent, so tried moving to its new parent, the new scene, with move(toParent:…) this also crashes. So maybe the children are already flagged as about to be released, I suppose.
Is this known about, and if so, how is it dealt with?
You can't present a scene and have willMove function at the same time in the same frame (SK loop)--your next scene's viewDidLoad will be called before the previous scene is destroyed. You have to remove all of the nodes before calling presentScene:
I suggest making a global variable of some sort to access your SKView; with that you can have control over your scenes from anywhere:
currentScene.removeAllChildren()
gView!.presentScene(nextScene)
“Is this a known about, and if so, how is it dealt with?"
Yes, this is a known issue of the SceneKit and IOS design and operational paradigms. It is considered that a scene must always be present and upon the screen. Can't have time without a scene onscreen.
As a result, for a brief time during transitions, both scenes are loaded, and active.
There are three ways to do deal with this problem:
Remove all content from the old scene prior to executing the transition so it can be instantly loaded by the new scene in didMove(to view...
or
Wait for completion of the transition before loading any content from the old scene into the new scene so there's no ownership conflicts of children.
or
KnightOfDragon’s first suggestion to use .copy() so there’s not the same instances being fought over by the scenes.

How can I stop a UIView movement in Swift?

I'm messing around with Swift for the first time and have a square on the screen. The square starts by moving to the right. When I tap on it I want it to go up. I don't want it to continue to the right at all, I want it to just go straight up. Unfortunately the physics and gravity make it curve to the right some more before it goes up. What can I do to completely stop the gravity pull and acceleration of my object, before setting the gravity to the new value?
This turns the object in the right direction, but I need it to come to an instant stop before doing this.
if(self.gravity.gravityDirection.dx == 1){
self.gravity.gravityDirection = CGVectorMake(-1.0, 0.0)
}
The way to stop an item being affected by a behavior is to remove the item from the behavior or remove the behavior from the animator. For example, here you might remove self from the existing gravity behavior.

iOS - How to make an animation track touches

What is the best way to implement a smooth reversing animation which tracks touches? I am referring to those animations in which, for example, if the user executes a swipe gesture some elements smoothly animate on screen, and others off, but if the user instead slowly drags a pan gesture back and forth the same objects will move forward/backward as a percent in accordance with the touch position. This is seen in many app intros and also in transitions. I have found
One tutorial which discusses the built-in facility for this but it is only between view controller transitions, not providing the full granular control I see in many apps (http://www.doubleencore.com/2013/09/ios-7-custom-transitions/)
Jazzhands, which is a kit by IFTTT, but this is a packaged solution that might not cover how the solution is best implemented at a lower level (https://github.com/IFTTT/JazzHands)
A question here for which one answer shows how you might execute an animation after a gesture ends (iOS Touch, Gestures, Animation)
What I don't grasp - and I'm comfortable using CAAnimations and gestures - is how something can be both animated and interactive.
Typically, when I create an animation, I commit the animation and it goes from start to finish. While I could interrupt the animation as touches continue, that seems like it would be stilted.
On the other hand, moving things in response to user input is easy, but that is not animated.
How is the effect achieved where something can change according to an animation, but also have that exact same animation occur tied to touches, and yet still also have it so that although the animation reaches completion it doesn't really "finish" (become irreversible) unless the user releases touch, while at any point during interaction if the user releases panning then the animation either reverts backwards to its starting position or animates to completion depending on the last touch location and velocity. These requirements are baffling.
The glimpses of this technique I see all involve keyframe animations, but what I don't understand is where the touch events intersect with an animation to create these smooth effects I see.
Any tips, examples, or tutorials are most welcome.
What I don't grasp - and I'm comfortable using CAAnimations and gestures - is how something can be both animated and interactive.
It is because, having set up an animation, you can set that animation to any "frame" you wish. Thus you can track the animation in correspondence to the movement of a gesture.
The way this works is that an animation is a feature of the render tree, belonging to a CALayer. CALayer implements the CAMediaTiming protocol. The timeOffset of a CALayer thus determines what "frame" of an animation that layer displays. If a complex animation involves many different layers, no problem; just set the timeOffset of their mutual superlayer, to control the frame of the entire animation.
That in fact is exactly how the new iOS 7 interactive custom transition feature works (to which you rightly refer in your question). For instance, in this example code:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/iOS7bookExamples/bk2ch06p296customAnimation2/ch19p620customAnimation1/AppDelegate.m
... I keep updating the UIPercentDrivenInteractiveTransition to tell it how far through the gesture the user is, and the animation therefore tracks the gesture. Now ask yourself: how the heck is this possible???
Well, the UIPercentDrivenInteractiveTransition, in turn, behind the scenes, keeps adjusting the layer's timeOffset to portray the animation at the corresponding frame. (You can actually add logging code to my example to see that this is true.)
Moreover, when I end the gesture at an incomplete point, the animation either hurries to its end or runs backwards to its beginning - again, this is because of the CAMediaTiming protocol, which lets you change the speed of the animation, including a negative value to run it backwards.

Resources