Hopefully someone can help. I have been pulling my hair out on this one! Below is some of the control pad code from my Sprite Kit game which uses applyForce
to move the hero ship. I like how the controls and movement feel, its got some nice drift, etc... However I have inconsistent results on the animation/movement it produces. Sometimes (about half the time) the animation is perfectly smooth and looks correct. The rest of the time the ship movement animation stutters at different intensities.
The monsters and pickups in the game run on various paths with SKAction
. Their animations do not suffer when the issue happens to the hero ship and I typically see it happening with a sold 60.0 fps in the debug, which makes me think it is not a frame drop issues. My memory usage is consistent and I don't see any large spikes.
When the bug happens it is always apparent from the beginning of the scene (it never starts happening after I start with a smooth movement – making me think its something with the initial force or multiple forces at once) and continues to happen until I restart the level or pause and un-pause the game, at which point I get a new behavior of either perfectly smooth animation, or glitchy animation.
My draws and nodes are pretty optimized with atlases and preloading and even when I turn most everything in the game off (everything but the ship and the control pad) I still experience this issue with applyForce
on the ship.
I have noticed that I experience this more often when plugged into Xcode, often when I'm logging (but not always), and especially while running instruments.
My ControlPad calls back to the game scene with a delegate
/* Game Scene */
@interface GameScene () <SKPhysicsContactDelegate, ControlPadDelegate>
...
- (void)createHero
{
self.heroShip = [Hero createHeroInFrame:self.frame];
[self.gameObject addChild:self.heroShip];
self.heroShip.position = CGPointMake(self.heroShip.size.width, self.frame.size.height / 2);
}
// Move Hero By X And Y
- (void)moveHeroByX:(float)moveByX andY:(float)moveByY
{
[self.heroShip.physicsBody applyForce:CGVectorMake(moveByX, moveByY)];
}
/* Control Pad Class */
static float const sensitivity = 10;
static float const maxMove = 10;
@interface ControlPad()
// Touch Points
@property (nonatomic) CGPoint navPadTouchPrevious;
@property (nonatomic) CGPoint navPadTouchCurrent;
// Touch Instances
@property (nonatomic) UITouch * navPadTouchInstance;
@implementation ControlPad
- (id)initWithSize:(CGSize)size { ... }
// Touches Began
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Loop through touches
for (UITouch * touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode * node = [self nodeAtPoint:location];
// Navigation Pad Touched
if (node == self.navigationPad) {
self.navPadTouchInstance = touch; // Assign Touch Instance
self.navPadTouchPrevious = location;
self.navPadTouchCurrent = location;
}
}
}
// Touches Moved
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// Loop Through Touches
for (UITouch * touch in touches) {
CGPoint location = [touch locationInNode:self];
CGPoint previous = [touch previousLocationInNode:self];
SKNode * node = [self nodeAtPoint:location];
// Move Nav Pad
if (touch == self.navPadTouchInstance) {
if (node == self.navigationPad) {
self.navPadTouchPrevious = previous;
self.navPadTouchCurrent = location;
// Move Hero
[self moveHero];
}
}
}
}
// Move Hero
- (void)moveHero
{
// Calculate Delta Move
float moveByX = self.navPadTouchCurrent.x - self.navPadTouchPrevious.x;
float moveByY = self.navPadTouchCurrent.y - self.navPadTouchPrevious.y;
// Throttle Move
moveByX = moveByX >= 0 ? MIN(moveByX, maxMove) : MAX(moveByX, -maxMove);
moveByY = moveByY >= 0 ? MIN(moveByY, maxMove) : MAX(moveByY, -maxMove);
// Add Sensitivity
moveByX = moveByX * sensitivity;
moveByY = moveByY * sensitivity;
// Move Hero
[self.delegate moveHeroByX:moveByX andY:moveByY];
}
Above are just pieces of my classes. I can provide more code and/or logs of anything. Just let me know. Thanks.
Edit
Per Epic Byte's comment that I should not be calling applyForce in touches moved, (and reading in the Apple Docs: "The acceleration is applied for a single simulation step (one frame)"), I have moved it to the update loop. I'm now setting movedByX and movedByY float properties in the game scene on touches moved and then using them in applyFoce in each step of the update loop. After I use them I'm setting them to zero so when you release the touch it stops applying the force and ship drifts.
I'm still experiencing inconsistent smoothness. Half the time it's perfect, the other half the animation stutters. I'm on an iPad 2 and wonder if testing it on a more powerful device would tell me anything. My memory usage is low.
/* Game Scene */
-(void)update:(NSTimeInterval)currentTime
{
...
[self moveHero];
...
}
- (void)moveHero
{
[self.heroShip.physicsBody applyForce:CGVectorMake(self.moveByX, self.moveByY)];
self.moveByX = 0;
self.moveByY = 0;
}
// Called from control pad class
- (void)moveHeroByX:(float)moveByX andY:(float)moveByY
{
self.moveByX = moveByX;
self.moveByY = moveByY;
}