Desynchronisation between frame and physics intervals

Hi,
First of all: yes, I know, there are a lot of topic discussing subjects like this. I open a new topic because I’ve tested many variants and nothing solved my problem.

I’m developing a 2d racing game (top-down mode) in cocos2d-x and box2d.
The game works perfect in iOS, but not in Android. It has problems related to delays… I see the car stuttering/flickering when it’s moving. I don’t know how to explain it, but it’s NOT smooth game-play. It gets stuck. In Apple Devices, it’s very smooth game-play. The problem is Android.

The only reason it seems to be working on iPhone is because the frame rate is steadier than on Android. On Android it tends to fluctuate quite a bit.

The game works in 60 FPS. In update function, I’m simulating the physics in this way:

int velocityIterations = 8;
int positionIterations = 3;

world->Step(dt, velocityIterations, positionIterations);

And I think it could be the problem.
I think it’s because of the desynchronisation between frame and physics intervals.
In order to achieve a smooth visualisation I should to interpolate or extrapolate physics space to render space every frame (I think).

I’ve tried, for example, this code (thanks to @R101)

const auto maximumStep = 1 / 120.f;
auto progress = 0.0f;
while (progress < dt)
{
    const auto step = std::min((dt - progress), maximumStep);
    progress += step;
    //stepping b2d world based on incoming delta
    world->Step(progress, velocityIterations, positionIterations);
}

But it didn’t help me.
Also I’ve tried many variants and i’ve read in forums, etc, but I can’t achieve the perfect smooth gameplay. Help? :pray: :pleading_face:

UP!
:pray: :pray:

Hey Tranthor,

I’ve had similar issue in the past, and I could solve it by using semi-fixed timestep as described by gafferongames . Try this , here the code if you want

    {
        static const float dt = 1.0f / 60.0f;
        static const int8_t maxSteps = 5;
        static const int8_t velocityIterations = 8;
        static const int8_t positionIterations = 3;
        
        timeAccumulator += deltaTime;
        int nSteps = static_cast<int>(floor(timeAccumulator/dt));
        if (nSteps > 0) {
            timeAccumulator -= nSteps*dt;
        }
        
        assert("Invalid time accumulator state." && timeAccumulator < dt + FLT_EPSILON);
        
        if (nSteps > maxSteps) {
            nSteps = maxSteps;
        }
        for (int i = 0; i < nSteps; ++i) {
            m_physicsCore->getBox2dWorld()->Step(dt, velocityIterations, positionIterations);
        }
        m_physicsCore->getBox2dWorld()->ClearForces();
        renderFullStep();
    }

-Raj
Please do checkout my game in Android Play store. “Shape Slice” (com.ayro.shapeslice)

@rajppandian i’m going to test it.

  • timeAccumulator is initialised in 0 out of update() ? Never it’s reseted?
  • deltaTime is the parameter of update() method ?
  • What’s renderFullStep(); ? It doesn’t matter?
  • Why maxSteps = 5; ?

@tranthor,

  • timeAccumulator is initialised in 0 out of update() ? Never it’s reseted? - yes.
  • deltaTime is the parameter of update() method ? - yes
  • What’s renderFullStep(); ? It doesn’t matter? - just rendeding the scenegraph
  • Why maxSteps = 5; ? - In my case I did not expect more than 5 frames skipped .

I replaced this lines:

void Game::update(float dt)
{
  .
  .
  int velocityIterations = 8;
  int positionIterations = 3;
  world->Step(dt, velocityIterations, positionIterations);
  .
  .
}

With these:

   void Game::update(float dt)
   {
     .
     .
     static const float fixedDt = 1.0f / 60.0f;
     static const int8_t maxSteps = 5;
     static const int8_t velocityIterations = 8;
     static const int8_t positionIterations = 3;
     static float timeAccumulator = 0.0f;
    
     timeAccumulator += dt;
     int nSteps = static_cast<int>(floor(timeAccumulator/fixedDt));
     if (nSteps > 0) {
         timeAccumulator -= nSteps*fixedDt;
     }
    
     if (nSteps > maxSteps) {
         nSteps = maxSteps;
     }
     
     for (int i = 0; i < nSteps; ++i) {
         world->Step(fixedDt, velocityIterations, positionIterations);
     }
     world->ClearForces();
     .
     .
   }

The behavior improved, but it’s not smooth yet. The car seems to shake sometimes. It’s not fluid.
Should I “play” with maxSteps value?

I’m testing the game in Galaxy A50 (Android). Test your game there if you can.
:thinking:

Try using low pass filter for position of the car.
low pass filter is helpful in occasionally fps drops.

Sorry for my ignorance, what’s “low pass filter” ? Do you have examples?
Thanks

Sure.
Save the previous position of the car.
Update the current position by something like that:

newPosition = 0.9 * previousPosition + 0.1 * newPosition
previousPosition = newPosition.

If newPosition is far from previous position (Shaking the car) this technique reduce the effect.

Sprite positions are being updated based on physics positions:

PTMRATIO = (64 / director->getContentScaleFactor());

for (b2Body* b = world->GetBodyList(); b != NULL; b = b->GetNext())
{
    if (b->GetUserData() != NULL)
    {
        Sprite* s = (Sprite*)b->GetUserData();
        
        s->setPosition(b->GetPosition().x * PTMRATIO, b->GetPosition().y * PTMRATIO);
        s->setRotation(CC_RADIANS_TO_DEGREES(b->GetAngle()) * -1);
    }
}

The physics positions (for example, I move the car like this: car_body->ApplyForce(...) ) are being updated based on Step:

    int velocityIterations = 8;
    int positionIterations = 3;
    world->Step(dt, velocityIterations, positionIterations);

This is the normal behaviour of any game. I mean, the physics positions are the most important. Sprite positions are updated based on physics positions… but the most important are physics positions.

I can’t update sprite positions using this logic:

Because I will generate a desynchronisation between sprite positions and physics positions…
:thinking:

I think the point is to “play” with world Step and dt variable…

Hi. Yes the b2body positions is very important but why you can’t update sprite position using that logic?
This logic only smooth the movements.

Online multiplayer games using this logic a lot.

Because I will generate a desynchronisation between sprite positions and physics positions.
I would updating sprite positions with a different logic than physics positions. It could improve the smooth movement but I would generate other problems… like desynchronisation between sprite positions and physics positions. What it mean? for example, the car could collide with some object however the sprite not. That doesn’t sound good…

In the worst case desynchronisation is ONE frame.

I understand your point of view. But why should I edit sprite positions? Why not physics positions? It would reduce the problems.

You are right. Just add this to the bag of your Idea.

The point is how to deal with world Step and dt
I’ve tested many variants. Nothing worked.

I’m completely sure that some game developer here took care of this problem and solved it :pray:

@tranthor,

I also have made changes in onDrawFrame to fix the framerate. I remember this helping me a lot.
Check if this helps you .

replace your onDrawFrame in Cocos2dxRenderer.Java with

    float smoothedDeltaRealTime_ms=17.5f; // initial value, 
    float movAverageDeltaTime_ms=smoothedDeltaRealTime_ms; // mov Average start with default value
    long lastRealTimeMeasurement_ms; // temporal storage for last time measurement

    // smooth constant elements to play with
    static final float movAveragePeriod=40; // #frames involved in average calc (suggested values 5-100)
    static final float smoothFactor=0.1f; // adjusting ratio (suggested values 0.01-0.5)

    @Override
    public void onDrawFrame(GL10 gl){
        Cocos2dxRenderer.nativeRender(smoothedDeltaRealTime_ms);

        // Moving average calc
        long currTimePick_ms= SystemClock.uptimeMillis();
        float realTimeElapsed_ms;
        if (lastRealTimeMeasurement_ms>0){
            realTimeElapsed_ms=(currTimePick_ms - lastRealTimeMeasurement_ms);
        } else {
            realTimeElapsed_ms=smoothedDeltaRealTime_ms; // just the first time
        }
        movAverageDeltaTime_ms=(realTimeElapsed_ms + movAverageDeltaTime_ms*(movAveragePeriod-1))/movAveragePeriod;

        // Calc a better aproximation for smooth stepTime
        smoothedDeltaRealTime_ms=smoothedDeltaRealTime_ms +(movAverageDeltaTime_ms - smoothedDeltaRealTime_ms)* smoothFactor;

        lastRealTimeMeasurement_ms=currTimePick_ms;
    }

You’ll also have to modify Cocos2dxRenderer.nativeRender to take float and invoke the mainLoop(float) instead of mainLoop

JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env, jobject thiObj, jfloat dt) {
        cocos2d::Director::getInstance()->mainLoop(dt);
    }

Let me know if this helps

Thanks @rajppandian
I’m analysing it. However I want to confirm you that it provoques some problems related with other things different than gameplay. For example, different behaviours with:

  • Button effects:

btn->runAction(RepeatForever::create(Sequence::create(ScaleTo::create(btn->getScale(), btn->getScale()), ScaleBy::create(1, 1.05f), NULL)));

  • Replace Scene:

director->replaceScene(TransitionFade::create(2, initScene));

  • Effects opening a new window:

layoutWindow->runAction(Sequence::create(actionWindow, DelayTime::create(0.30), addContentWindow, NULL));

Etc.
I mean, maybe it improves the gameplay but it generates other problems.
Because of this problems I couldn’t test the gameplay yet, but I’ve that “other” problems (button effects, replace scene, etc). I’m testing in Galaxy A50.

For example, I don’t feel the same effect replacing the scene (now it happens very faster). The button effect automatically enlarges and shrinks the button but now it happens faster. In short, different behaviours in general.

I’ve edited the following files with your codes:

  • cocos2d/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxRenderer.java
  • cocos2d/cocos/platform/android/jni/Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp

In Cocos2dxRenderer.java i also had to edit the declaration of the method:

private static native void nativeRender(float smoothedDeltaRealTime_ms);

And I had to import the library SystemClock:

import android.os.SystemClock;