Fluency/velocity problem, depends on screen resolutions

Hi,
I’m developing a 2d racing game (top-down mode) using cocos2d 3.17, box2d, ndk r16b, etc.
I’ll try to be brief. This is my problem:
The car accelerates in different velocities depending of the device. For example, in iPhone 6s Plus the car accelerates faster than iPhone 7.

After a research, maybe I discovered the problem: box2d works in a different ways depending of the phone resolutions:

For example, in iPhone 7 the game works equal than iPhone 8, because the resolutions are the same. But in iPhone 6s Plus works different. I’ve tested it in real devices.

How could I fix it?

Here I share some things for understand how my game works:

So, I’m using ApplyForce to each body-tire, each 1/60 secs.

  • My game runs in 60 FPS, so in my update function i’m updating the world step in this way:
int velocityIterations = 8;
int positionIterations = 3;
world->Step(dt, velocityIterations, positionIterations);
  • The PTMRATIO that i’m using is 32.

I’ve tested the following modifications:

  • Use a fixed dt:
world->Step(1.0/60.0f, velocityIterations, positionIterations);

I’ve read that it could help to improve the fluency, and it’s true: it improved the fluency movement of the car, but the velocity problem still happens. So, it didn’t fix the problem.

  • Update to cocos 3.17.2 (my current version is 3.17):
    Maybe some bug related to cocos framework. But not. I updated to cocos 3.17.2 and I tested in iOS and the problem still happens.

  • “Play” interpolating dt values… something like this:

void WorldNode::update(float dt)
{
    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);
    }
...
}

Same results.

  • “Play” with PTMRATIO:

Instead of this line:
#define PTMRATIO 32

I’ve tested some variations, for example a PTMRATIO that depends of the screen resolution:

#define PTMRATIO (64 / Director::getInstance()->getContentScaleFactor())
And:

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

It depends of the screen resolution, because in AppDelegate i’m configuring the ContentScaleFactor depending of the screen:

// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0f / 60);

// Set the design resolution
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER); 

auto frameSize = glview->getFrameSize();
// if the frame's height is larger than the height of medium size.
if (frameSize.height > mediumResolutionSize.height)
{        
    director->setContentScaleFactor(MIN(largeResolutionSize.height/designResolutionSize.height, largeResolutionSize.width/designResolutionSize.width));
}
// if the frame's height is larger than the height of small size.
else if (frameSize.height > smallResolutionSize.height)
{        
    director->setContentScaleFactor(MIN(mediumResolutionSize.height/designResolutionSize.height, mediumResolutionSize.width/designResolutionSize.width));
}
// if the frame's height is smaller than the height of medium size.
else
{        
    director->setContentScaleFactor(MIN(smallResolutionSize.height/designResolutionSize.height, smallResolutionSize.width/designResolutionSize.width));
}

Maybe it’s the correct way: find the “perfect” PTMRATIO. But… how to know if this value is the perfect value? The tiled map is scaled, so the current view in all devices should be similar. But… what about the velocity? How to find the perfect value for all devices?

Suggestions?

Hi. What is your design resolution policy? choose fix width or fix height and use simple box2d implementation.

My resolution policy is ResolutionPolicy::NO_BORDER. Is the best resolution policy that adapts to the design. I wouldn’t like to change that.

Simple implementation? :thinking:
I’m not doing something strange. What do you mean?

your problem in not box2d related. IT IS resolution policy. Just for proving that change it and see the result yourself.
:grinning:

I don’t understand why do you think that the problem is the resolution policy.
Anyway, I’ve tested it and i will confirm you: the problem isn’t the resolution policy :slight_smile:

Case 1:

ResolutionPolicy::FIXED_HEIGHT
PTMRATIO = 32

Same problems. iPhone 6s Plus the car accelerates a lot.

Case 2:

ResolutionPolicy::FIXED_WIDTH
PTMRATIO = 32

Same problems. iPhone 6s Plus or iPhone X the car accelerates faster than iPhone 7 (for example).

The only variable that helps me with the problem is PTMRATIO. Whenever I edit the value of PTMRATIO, it affects directly to the car velocity. So, it’s an indication that I should find the correct value of PTMRATIO to have a perfect game play in all devices.

The current value that I’m using (instead of the initial value “32” that i used) is:

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

It depends of ContentScaleFactor. It means that PTMRATIO will be different depending of the device resolution.

This value that i’m using, has improved a lot the behaviours in all devices, but i’m definitely sure that this is not the perfect value, because i still feel small differences for example in iPhone X regarding to iPhone 7.

So, this is the way, I think.
But… how to know the magic value for all devices :thinking:

This is so interesting as PTM is the pixel-to-meter ratio. I usually see it as a float. I have a older box2d books that use PTMRatio a lot, but they don’t explain where the values they use come from.

I did find this: https://www.iforce2d.net/forums/viewtopic.php?t=123
and this: https://stackoverflow.com/questions/19266899/box2d-coordinate-system-concepts-ptm-ratio-and-retina-display

1 Like

Yep, it seems that they encourage to use CC_CONTENT_SCALE_FACTOR() for calculate PTMRATIO. Exactly the “strategy” that i’m trying to implement.

I always calculate my own scale factor based upon the device resolution and the size of the artwork I am using. I usually use vector based art so I can scale it up or down by a multiplier.

If it is about setting PTMRATIO then this should help:

PTMRATIO = 64 / (designResolution.width / currentResolution.width)

Cocos content scale factor is set depending on the resolution policy and it may be set based on the height or width.

1 Like

Something like this?

Size designResolution = Size(480, 320);
Size currentResolution = director->getVisibleSize();

PTMRATIO = 32 / (designResolution.width / currentResolution.width);

Using this values:
iPhone 7 (real device): Perfect.
iPhone X (simulator): Very fast.

Hi again. I upload our AppDelegate.cpp file. this project has Box2D. check design resolution and resolution policy. PTMRATIO is const.
AppDelegate.cpp (5.5 KB)

This is how I achieve with Box2D and multi resolution. Basically you have to think out of the box. Define your data model area which the object should be spawn and move around. As my case the area of it is as same as my design resolution, which is 720, 1280. Supposed we named it with game coordinate system. Spawn object at the middle top of my game area, treats your game coordinates as cocos2dx world coordinate and convert it to Box2D coordinate for physic simulation. Convert game coordinate to node coordinate every frame for view display.

@amin13a @deflinhec
I’ve already tested it.

In AppDelegate, I’ve this config:

static Size designResolutionSize = Size(480, 320);
static Size smallResolutionSize = Size(480, 320);
static Size mediumResolutionSize = Size(1024, 768);
static Size largeResolutionSize = Size(2048, 1536);

glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);

Is the default config of a new cocos2d project.

I tried with this modifications:

PTMRATIO = 32

and:

glview->setDesignResolutionSize(480, 320, ResolutionPolicy::FIXED_WIDTH);
or
glview->setDesignResolutionSize(720, 1280, ResolutionPolicy::FIXED_WIDTH);
or
glview->setDesignResolutionSize(2048, 1152, ResolutionPolicy::FIXED_WIDTH);

Even, I tested with ResolutionPolicy::FIXED_HEIGHT

Conclusion:

  • iPhone 7 (real phone): Perfect.
  • iPhone X (simulator): Very fast. The car accelerates faster than iPhone 7.

Whenever I “fix” one phone, I break another. I can’t find the value that fits in all phones.
Up to this point, the best solution that I found out was:
Default config: glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);

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

Anyway, it’s not the best solution. I know that there are differences between the velocities and behaviour. This is so irritating :sweat:

If I want to set a position in box2d, it’s: cocos2d position / PTMRATIO (it’s a division).
I don’t know if you’re reffering to that, otherwise plz show us examples.

My entire project is base on entityx which is an entity component system, purpose is to decouple between model and view. The benefit of it is that game logic, such as acceleration, will not affect by different screen resolution, and even capable for logic test without view. The cocos2d::Vec3 position, b2Body* body I’ve quoted below are components of my game object.

#define PT_RATIO 32.0f
#define B2V(var) var * PT_RATIO
#define V2B(var) var / PT_RATIO 

Box2d coordinate to world coordinate:

b2Body* body; 
cocos2d::Vec3 position;
const auto& pos = body->GetPosition();
const auto& angle = body->GetAngle();
position.x = B2V(pos.x);
position.y = B2V(pos.y);
position.z = CC_RADIANS_TO_DEGREES(-angle);

Vice versa:

b2Body* body;
cocos2d::Vec3 position;
pos.x = V2B(position.x);
pos.y = V2B(position.y);
body->SetTransform(pos, CC_DEGREES_TO_RADIANS(position.z));

Sprite creation:

cocos2d::Vec3 position;
auto sprite = cocos2d::Sprite::createWithSpriteFrameName(name + ".png");
sprite->setScale(CC_CONTENT_SCALE_FACTOR());
sprite->setAnchorPoint(cocos2d::Vec2::ANCHOR_MIDDLE);
auto background = cocos2d::utils::findChild<Node*>(Canvas, "Background")
Vec2 pos(position.x, position.y);
pos = background->convertToNodeSpace(pos);
background->addChild(sprite, 2);
sprite->setPosition(pos);
sprite->setRotation(position.z);

Sprite update every frame:

cocos2d::Vec3 position;
cocos2d::Vec2 pos(position.x, position.y);
auto parent = sprite->getParent();
pos = parent->convertToNodeSpace(pos);
sprite->setPosition(pos);
sprite->setRotation(position.z);

Scene hierarchy:

Scene* CGameScene::createScene()
{
    auto scene = Scene::create();
	auto layout = CGameScene::create();
	auto gameview = Layer::create();
	gameview->setContentSize(scene->getContentSize());
	gameview->setName("Background");
	const Vec2 center(scene->getContentSize() * 0.5f);
	auto background = ui::ImageView::create();
	background->loadTexture("image/background02.png");	
	background->setScale(CC_CONTENT_SCALE_FACTOR());	
	background->setPosition(center);	
	scene->addChild(background);
	scene->addChild(gameview);
	scene->addChild(layout);
	return scene;
}

Hmmm… interesting.

This is part of my update method:

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);
    }
}

This is the part where I’m updating sprites positions every 1/60secs.
I’m not using convertToNodeSpace. Maybe the problem is related to that?

How would you use it here?

I tried editing this line:
s->setPosition(b->GetPosition().x * PTMRATIO, b->GetPosition().y * PTMRATIO);

With this line:
s->setPosition(s->convertToNodeSpace(Vec2(b->GetPosition().x * PTMRATIO, b->GetPosition().y * PTMRATIO)));

But probably i’m doing something wrong because it fails.

This is the sprite parent without any scaling and same size as scene.

auto gameview = Layer::create();
gameview->setContentSize(scene->getContentSize());
gameview->setName("Background");

You should convert box2d coordinate to gameview’s node space

auto parent = s->getParent();
auto pos = Vec2(b->GetPosition().x * PTMRATIO, b->GetPosition().y * PTMRATIO);
s->setPosition(parent->convertToNodeSpace(pos));