applyForce is not smooth
Asked Answered
A

0

1

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;
}
Aarika answered 26/1, 2015 at 7:52 Comment(8)
Try to remove all the monsters and pickups (which are using SKAction animations) from the scene and check if the problem is still occurs.Assentor
have you tried applyImpulse instead? Is the hero colliding with any node that moves via actions by chance? Because moving a node with physics body using actions will not result in physically correct collision behavior.Avie
I suggest instead of using applyImpulse or applyForce, you calculate the velocity yourself so there are no surprises. Also you should not be using applyForce within touches moved. ApplyForce should be used within the game loop and set appropriately at every step because it works continuously overtime.Krisha
The problem still occurs when monsters and pickups are off. And it also occurs when the hero is not colliding with nodes that move via actions (because everything else is off). The monster and pickup physicsBodies are not dynamic and are only used for contact test (not collision test). I have tried with applyImpulse but I get an undesired steering effect. Thanks Epic Byte. I have tried applyForce in the update loop but so far I get undesired results. I will try calculating the velocity myself tonight and applying it like this. I assume: self.heroShip.physicsBody.velocity = CGVectorMake()?Aarika
I am still experiencing this issue if I set a velocity explicitly as well as if I move applyForce to the update loop.Aarika
@LearnCocos2D I was able to reproduce similar ship controls with applyImpulse instead of applyForce. I'm calling applyImpulse from touches moved in this situation and just changed my sensitivity modifier down to 0.2. Unfortunately I'm still experiencing the stutter behavior.Aarika
@Gadget Blaster: Did you manage to find a way to fix the stuttering?Demoss
Its much better but not gone. I haven't worked on that project in quite a while but I think I remember it having something to do with when I was preloading and the first touch. I'm preloading much earlier now. Not having the iPad plugged into x Code makes it happen SIGNIFICANTLY less as well as quitting other apps. I did see it on a newer iPad Air but it was imperceptible to the people I was with. Maybe it's all in my head!Aarika

© 2022 - 2024 — McMap. All rights reserved.