Screen density issues with Android version of Cocos2d-x

Currently (using the latest version from Git), the Android version of Cocos2d-x is not using the screen properly. Android runs on a variety of screen sizes, some of them with different DPI settings. Because the AndroidManifest.xml for the Tests demo does not set minSdkVersion to 4 or up (it does not set it at all, but it should), Android will assume the minSdkVersion is 1. Support for different screen densities appear to have been added since version 4, so that probably explains the API version of 4.

Anyway, when the minSdkVersion is missing (or implicitly set to 1), Android will return skewed display settings. For example, Cocos2dxActivity uses Display.getMetrics (see Cocos2dxActivity::onCreate method) to query for the screen size, but with minSdkVersion=1, Android returns the screen size divided by the screen density instead.

This means that Cocos2d-x thinks it’s on a resolution of 320 x 533 instead of 480 x 800 on a HTC Desire or Nexus One. That’s definitly not WVGA for Cocos2d-x on Android and devices with a large screen! :slight_smile:

The issue can be seen in action by using a device with a high density screen (like Desire, Nexus One, Galaxy S, etc) and adding the following line to the AndroidManifest.xml from the Tests demo:

You’ll see that everything will be a lot smaller. Which is not good either, but that’s because the scale factor still returns 1.0. It should be 1.5 (see DisplayMetrics.density) instead on a Nexus One or HTC Desire. With the correct scaling factor, everything should look fine again (and a lot more hires if you’re a heavy user of scaling factors in a Cocos2d-x game :slight_smile: ).

Thank you for your work.
But I have a question. Did you think cocos2d-x should set the scale factor silently or
it should be set by developers?

Hmm, well, it appears that “view->create(w/2,h/2)” before setting the GL view in the Android specific code seems to fix the situation for my game. I can use both the minSdkVersion and everything scales nicely now (well, almost).

I think an automatic scale factor (set silently) would be preferable, because the Cocos2d-x app doesn’t know what screen density it’s running on. But that will require different assets to be used… ack, this is really hard stuff :stuck_out_tongue:

+1 to this. I think setting the scale factor automatically is preferable, since that seems to be what happens on other platforms. What we really need, though, is support for loading different assets automatically based on the screen density of the device. The Android SDK supports this by default, but not when you’re using the NDK, of course. See http://developer.android.com/guide/practices/screens_support.html for a description of how it works… basically, the it will automatically load drawable resources from the ldpi/mdpi/hdpi resource folders, where available, according to whether the device is low, medium or high DPI respectively. It’s very similar to how the iPhone 4’s high-DPI display is currently handled; that is, by loading files with the -hd prefix where available.

I easily did this manually, just make two subfolders in ‘Resources’, highres and lowres:

#define IMG_PATH_HIGH_RES    "assets/highres"
#define IMG_PATH_LOW_RES     "assets/lowres"

if(pDirector->getDisplaySizeInPixels().width > 320 ) {
        CCFileUtils::setRelativePath(IMG_PATH_HIGH_RES);
}
else {
        CCFileUtils::setRelativePath(IMG_PATH_LOW_RES);
}

I also have support for dynamically resizing of sprites in my fork here:

(i.e. if a screen is 800x480 it loads my high-res graphics (designed for 960x640) and scales everything down while maintaining correct aspect ratio)

The issue with setting the one path @ CCFileUtils is that I’m not storing image data only. There’s level data, there’s sound data, and so on. I’m not really interested in duplicating all that data just to support multiple resolutions, so it would be really nice if any possible improvement could take that in consideration as well.

Oh, you can share other data except image data.

I switch twhe default dir to a ‘common’ one before loading common assets….

#define IMG_PATH_HIGH_RES    "assets/highres"
#define IMG_PATH_LOW_RES     "assets/lowres"
#define IMG_PATH_COMMON     "assets/common"

I may add support to specify a default common folder for automatic asset searching in my fork eventually… But for now I’m just switching the default to common before load common assets…

I have a question. When a game wants to run in different res of real phones.
Does it should afford different res of images for corresponding phones, or it only
affort one res of image and let engine to scale the image?

Minggo, I’m not sure I understand what you’re asking. But I think the game should be able to load different assets depending on the resolution of the phone. Scaling low-res assets to a high-res display makes the graphics look poor, and scaling high-res graphics to a low-res display may result in poor performance (because low-res devices presumably have worse hardware.)

Yep. You have answered me. The game should afford different res images.

if(pDirector->getDisplaySizeInPixels().width > 320 ) {
        CCFileUtils::setRelativePath(IMG_PATH_HIGH_RES);
}
else {
        CCFileUtils::setRelativePath(IMG_PATH_LOW_RES);
}

Theoretically, you can add as many different ‘asset’ resolutions as you want…

Yeah, but add the common assets to the mix and you’re in for quite a horror story :slight_smile:

I really like how Qt (the Trolltech/Nokia GUI framework) handles it. You can add multiple paths with it and use them by adding a prefix to the requested file. It’s really nice to use and you can access any resource by using something like “images:/myimage.png”. In this example, “images:” is not part of the path, it’s a substitute for the actual path. This substitute can point to “assets/images/highres” or “assets/images/lowres” by setting the correct path @ program startup, but it also allows you to use other paths for other types of resources.

For example, “levels:/mylevel.xml” would load “assets/levels/mylevel.xml” here. And “sounds:/mysound.mp3” would obviously load “assets/sounds/mysound.mp3”.

(+1 for well defined resource file hiearchies :smiley: )

I tried to use setRelativePath. But seems like CCFileUtils do not have it anymore. Which command should I use to achieve the same purpose to separate the resource of hires against lowres?

Yes, it is removed on Android.
You can define resource like this

// xxx.h
class Resource
{
public:
    static void init();

    static char* s_pRes1 = 0;
    ...
};

// xxx.cpp
void Resource::init()
{
    if (...)
    {
        s_pRes1 = "path1/name1.png";
        ...
    }
    else
    {
        s_pRes1 = "path2/name1.png";
        ...
    }  
}

Then you can use Resource::s_pRes1 in your codes after you invoke Resource::init().

But CCSpriteWithFile can only take in filename not with the relative path or full path, am I right?
If this is the case, I can not use Resource::s_pRes1 in CCSpriteWithFile.
Thanks Minggo.

The path is relative to Resources.
Of course you can use absolute path that you know you can visit it.

Thanks Minggo.

Hi Minggo

Finally put my game in Android Market. It is called “I Love Candy”. It is a free game. Here is the link https://market.android.com/details?id=org.hexstudio.ilovecandy

Thanks for your help. I will involve in this community and share some of experience in return.