Targeting multiple display resolutions

  ... discussions about development with the GLES2 branch of AndEngine.

Targeting multiple display resolutions

Postby jgibbs » Wed Feb 08, 2012 9:08 am

Hi all,

Just canvasing for ideas, opinions and experiences on what you all do to handle variable display resolutions since I'm at a bit of a crossroads right now and need to start taking this into account.

The best approach I can see to maintain resolution vs. resources on the various devices is to create a set of graphics assets for a couple of base resolutions and either scale up or down depending on the device I'm being run on.

Seeing all these new HD tablets coming out I'm thinking to aim for 800 x n and 1920 x n as my basic resolutions, build graphics assets at these two scales and then tell the camera to use the appropriate screen res and load assets from the matching subfolder in my assets dir.

Thoughts, comments and suggestions appreciated.
Check out my adventures into Android programming:

4x4 Adventures - Featured in the Top Free Play Store listings
Slippery Sid
jgibbs
 
Posts: 109
Joined: Fri Nov 25, 2011 9:32 am
Location: New Zealand

Re: Targeting multiple display resolutions

Postby jgibbs » Fri Feb 10, 2012 2:38 pm

* Update: Fixed margins, they were based on the resolution, not the camera

Ah, what a day. After pasting sprites in all corners of my screen all day I think I finally came up with a solution to my camera woes. I'm fairly sure my solution isn't implemented and I certainly didn't come across a satisfactory answer with the search. So to save some others the effort, here's what todays labours resulted in.

My need is to have a set resolution I can rely on for sprite placement of a predefined scale. So I start out with assets pre-built for something like a tablet based on a resolution of 1280x800. Everything I make will be built and squared up against that resolution and at that ratio.

Now the tricky part, dealing with all the device resolutions scattered around the globe. The existing resolution policies offer either a stretch-to-fit scale, black bars or leave it to you to do the scaling.

The class I have here is another option that scales your screen up to fill the screen, whilst retaining the 1:1 assets ratio of your graphics.

In addition I've added in two member calls which you can use to grab the margins caused by the cropping for placement of hud type features so they can be offset from the physical screen edge rather than the virtual one which could be out either horizontally or vertically depending on the variance of the device from your original planned ratio.

Hope this helps. Let me know if you see any problems with it, or native engine alternatives.

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. import org.andengine.engine.options.resolutionpolicy.BaseResolutionPolicy;
  2. import org.andengine.opengl.view.RenderSurfaceView;
  3.  
  4. import android.view.View.MeasureSpec;
  5.  
  6. public class CroppedResolutionPolicy extends BaseResolutionPolicy
  7. {
  8.         // ===========================================================
  9.         // Fields
  10.         // ===========================================================
  11.  
  12.         private final float mCameraWidth;
  13.         private final float mCameraHeight;
  14.         private float mMarginVertical;  // Pixels from top of canvas to visible area, and from bottom of canvas to visible area
  15.         private float mMarginHorizontal;        // Pixels from left of canvas to visible area, and from right of canvas to visible area
  16.  
  17.         // ===========================================================
  18.         // Constructors
  19.         // ===========================================================
  20.  
  21.         public CroppedResolutionPolicy(final float nCameraWidth, final float nCameraHeight)
  22.         {
  23.                 this.mCameraWidth = nCameraWidth;
  24.                 this.mCameraHeight = nCameraHeight;
  25.                
  26.                 this.mMarginVertical = 0;
  27.                 this.mMarginHorizontal = 0;
  28.         }
  29.        
  30.         // ===========================================================
  31.         // Getter & Setter
  32.         // ===========================================================
  33.         public float getMarginVertical()
  34.         {
  35.                 return this.mMarginVertical;
  36.         }
  37.  
  38.         public float getMarginHorizontal()
  39.         {
  40.                 return this.mMarginHorizontal;
  41.         }
  42.  
  43.  
  44.         @Override
  45.         public void onMeasure(RenderSurfaceView pRenderSurfaceView, int pWidthMeasureSpec, int pHeightMeasureSpec)
  46.         {
  47.                 BaseResolutionPolicy.throwOnNotMeasureSpecEXACTLY(pWidthMeasureSpec, pHeightMeasureSpec);
  48.  
  49.                 int measuredWidth = MeasureSpec.getSize(pWidthMeasureSpec);
  50.                 int measuredHeight = MeasureSpec.getSize(pHeightMeasureSpec);
  51.                
  52.                 final float nCamRatio = (float)mCameraWidth /  (float)mCameraHeight;
  53.                 final float nCanvasRatio = (float)measuredWidth /  (float)measuredHeight;
  54.                
  55.                 if(  (float)measuredWidth /  (float)measuredHeight < nCamRatio )
  56.                 {
  57.                         // Scale to fit height, width will crop
  58.                         measuredWidth = (int) ( measuredHeight * nCamRatio);
  59.                         this.mMarginHorizontal = ( this.mCameraWidth - ( (float) this.mCameraHeight * nCanvasRatio ) ) / 2.0f;
  60.                 }
  61.                 else
  62.                 {
  63.                         // Scale to fit width, height will crop
  64.                         measuredHeight = (int) ( measuredWidth / nCamRatio );
  65.                         this.mMarginVertical = ( this.mCameraHeight - ( (float) this.mCameraWidth / nCanvasRatio ) ) / 2.0f;
  66.                 }
  67.  
  68.                 pRenderSurfaceView.setMeasuredDimensionProxy(measuredWidth, measuredHeight);
  69.         }
  70. }
  71.  
Parsed in 0.017 seconds, using GeSHi 1.0.8.4



After your engine is loaded you can access the margins like this (the screen isn't ready during onCreateEngineOptions() so beware when testing and not getting values that you need to grab them later):

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. CroppedResolutionPolicy resPol = (CroppedResolutionPolicy) this.getEngine().getEngineOptions().getResolutionPolicy();
  2. float vertMargin = resPol.getMarginVertical()
  3. float horzMargin = resPol.getMarginHorizontal()
  4.  
Parsed in 0.010 seconds, using GeSHi 1.0.8.4


Minor update:

If you try to encapsulate this class inside another frame to do things like overlayed ads, please read the following post to solve centering issues that will occur:

post43230.html#p43230


Here is a simple example activity demonstrating usage of the class. You need to provide an image named '800x480.jpg' in the assets folder for this sample, and it will show how resolution scaling works.

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. package com.imailds.android.croppedresolutiontest;
  2.  
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5.  
  6. import org.andengine.engine.camera.Camera;
  7. import org.andengine.engine.options.EngineOptions;
  8. import org.andengine.engine.options.ScreenOrientation;
  9. import org.andengine.entity.scene.Scene;
  10. import org.andengine.entity.sprite.Sprite;
  11. import org.andengine.opengl.texture.ITexture;
  12. import org.andengine.opengl.texture.bitmap.BitmapTexture;
  13. import org.andengine.opengl.texture.region.ITextureRegion;
  14. import org.andengine.opengl.texture.region.TextureRegionFactory;
  15. import org.andengine.ui.activity.BaseGameActivity;
  16. import org.andengine.util.adt.io.in.IInputStreamOpener;
  17. import org.andengine.util.debug.Debug;
  18.  
  19. public class MainActivity extends BaseGameActivity {
  20.         // ===========================================================
  21.         // Constants
  22.         // ===========================================================
  23.  
  24.         // Set the resolution to match the art assets.  The CroppedResolutionPolicy will scale everything to fill the device, regardless of its native resolution
  25.         private static final int CAMERA_WIDTH = 800;
  26.         private static final int CAMERA_HEIGHT = 480;
  27.  
  28.         // ===========================================================
  29.         // Fields
  30.         // ===========================================================
  31.  
  32.         private ITexture mTexture;
  33.         private ITextureRegion mBackgroundTextureRegion;
  34.  
  35.         @Override
  36.         public EngineOptions onCreateEngineOptions()
  37.         {
  38.         final CroppedResolutionPolicy canvasSurface = new CroppedResolutionPolicy( CAMERA_WIDTH, CAMERA_HEIGHT );
  39.         final EngineOptions engineOptions = new EngineOptions( true, ScreenOrientation.LANDSCAPE_FIXED, canvasSurface, new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT) );
  40.                 return engineOptions;  
  41.         }
  42.  
  43.         @Override
  44.         public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception
  45.         {
  46.                 try {
  47.                         this.mTexture = new BitmapTexture(this.getTextureManager(), new IInputStreamOpener() {
  48.                                 @Override
  49.                                 public InputStream open() throws IOException {
  50.                                         return getAssets().open("800x480.jpg");
  51.                                 }
  52.                         });
  53.  
  54.                         this.mTexture.load();
  55.                         this.mBackgroundTextureRegion = TextureRegionFactory.extractFromTexture(this.mTexture);
  56.                 } catch (IOException e) {
  57.                         Debug.e(e);
  58.                 }
  59.  
  60.                 pOnCreateResourcesCallback.onCreateResourcesFinished();        
  61.         }
  62.  
  63.         @Override
  64.         public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception
  65.         {              
  66.                 pOnCreateSceneCallback.onCreateSceneFinished( new Scene() );
  67.         }
  68.  
  69.         @Override
  70.         public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception
  71.         {
  72.                 Sprite background = new Sprite(0, 0, this.mBackgroundTextureRegion, this.getVertexBufferObjectManager());
  73.                 pScene.attachChild(background);
  74.                
  75.                 pOnPopulateSceneCallback.onPopulateSceneFinished();
  76.                
  77.         }
  78. }
  79.  
Parsed in 0.013 seconds, using GeSHi 1.0.8.4
Last edited by jgibbs on Tue Dec 18, 2012 12:42 am, edited 2 times in total.
Check out my adventures into Android programming:

4x4 Adventures - Featured in the Top Free Play Store listings
Slippery Sid
jgibbs
 
Posts: 109
Joined: Fri Nov 25, 2011 9:32 am
Location: New Zealand

Re: Targeting multiple display resolutions

Postby sake » Sun Apr 01, 2012 8:49 pm

I just read this posting and I think I will use your class. That's cool stuff you did there :).
BlockBreaker
BlockBreaker @ Google Play!

Everything I post here is licensed under the Beerware License.
sake
 
Posts: 67
Joined: Sun Dec 11, 2011 3:15 pm
Location: Guttenthau, Germany

Re: Targeting multiple display resolutions

Postby alex » Sat Dec 08, 2012 10:43 am

So where, in what callback, do you instantiate your CroppedResolutionPolicy object ?

This is what I'm looking at now :D

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.         @Override
  2.         public EngineOptions onCreateEngineOptions()
  3.         {
  4.                 // TODO Auto-generated method stub
  5.                 return null;
  6.         }
  7.  
  8.         @Override
  9.         protected void onCreateResources()
  10.         {
  11.                 // TODO Auto-generated method stub
  12.                
  13.         }
  14.  
  15.         @Override
  16.         protected Scene onCreateScene()
  17.         {
  18.                 // TODO Auto-generated method stub
  19.                 return null;
  20.         }
Parsed in 0.010 seconds, using GeSHi 1.0.8.4
alex
 
Posts: 275
Joined: Thu Jul 01, 2010 8:21 pm

Re: Targeting multiple display resolutions

Postby jgibbs » Sun Dec 09, 2012 2:33 am

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.         @Override
  2.         public EngineOptions onCreateEngineOptions()
  3.         {      
  4.         final CroppedResolutionPolicy canvasSurface = new CroppedResolutionPolicy( DEV_CAMERA_WIDTH, DEV_CAMERA_HEIGHT, false );
  5.  
  6.         final EngineOptions engineOptions = new EngineOptions( true, ScreenOrientation.LANDSCAPE_FIXED, canvasSurface, this._camera );
  7.  
  8.  
Parsed in 0.010 seconds, using GeSHi 1.0.8.4
Check out my adventures into Android programming:

4x4 Adventures - Featured in the Top Free Play Store listings
Slippery Sid
jgibbs
 
Posts: 109
Joined: Fri Nov 25, 2011 9:32 am
Location: New Zealand

Re: Targeting multiple display resolutions

Postby ChrisB2404 » Wed Dec 12, 2012 5:41 pm

Hey,

This is great, I have just put it into my project and does exactly what I needed so thanks a lot!

One little thing though, the comment in your code says...

"Pixels from left of canvas to visible area, and from right of canvas to visible area"

But it doesnt appear to do that, it looks as if the scene "0.0" point is aligned to the left so to position everything correctly in the horizontal I need to do "objectsX - 2 * margin"

Is that correct?
User avatar
ChrisB2404
 
Posts: 80
Joined: Sat Feb 04, 2012 10:39 pm
Location: Dundee

Re: Targeting multiple display resolutions

Postby jgibbs » Wed Dec 12, 2012 9:52 pm

Two parts to this answer, as I've just run into a problem with this myself. Check my recent update to the OP about framing this class. If you do this, it moves the canvas from centre to 0,0 which messes up the way the class works. You need to change the gravity of scene canvas back to centre to have the class work the way it was intended.

Second part of the answer is that the margin will always be 0 on at least one of the axis. The other axis will either be 0 or it will be out of the screen by the margin size.

So if you want a sprite to appear at the top left of the screen, you'd do 'setPosition(resPol.getMarginHorizontal(), resPol.getMarginVertial()' and regardless of which margin is offset, the sprite will be in the corner.

In my scenes I initialize a 'topLeft' and 'bottomRight' Vector2 variable so I have quick access to the corner locations at any time I need to use them.


ChrisB2404 wrote:Hey,

This is great, I have just put it into my project and does exactly what I needed so thanks a lot!

One little thing though, the comment in your code says...

"Pixels from left of canvas to visible area, and from right of canvas to visible area"

But it doesnt appear to do that, it looks as if the scene "0.0" point is aligned to the left so to position everything correctly in the horizontal I need to do "objectsX - 2 * margin"

Is that correct?
Check out my adventures into Android programming:

4x4 Adventures - Featured in the Top Free Play Store listings
Slippery Sid
jgibbs
 
Posts: 109
Joined: Fri Nov 25, 2011 9:32 am
Location: New Zealand

Re: Targeting multiple display resolutions

Postby Coder_For_Life22 » Sat Dec 15, 2012 8:02 pm

This works well for me, but for some reason it is cutting off a the egdes of the bottom and top of the scene in landscape mode.

I am using 800x480 camera width and height.
User avatar
Coder_For_Life22
 
Posts: 1036
Joined: Wed Nov 09, 2011 5:37 pm

Re: Targeting multiple display resolutions

Postby jgibbs » Sat Dec 15, 2012 10:27 pm

That's by design, and the intention of this policy.

Because you don't know what x/y ratio the screen will have, but want square pixels, unless you only target a single device you're always going to have missing screen area. The problem with the policies that are in andengine is that the only solution is to add the black bars, either top/bottom or left/right, depending on the display aspect ratio.

This policy is a compromise between wanting to fill the screen, wanting square pixels, and not wanting black bars.

The class has the two margin variables which tell you how much of your scene is cropped. You use this to reposition your HUD sprites into your scene.

So, for example, if you have a score that sits at the top right of your screen, you'd add the object, then reposition it taking the margins into account so that it sits at the same place regardless of screen type:

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. myText.setPosition(crp.mMarginVertial + visibleWidth - myText.getWidth(), crp.mMarginVertical);
Parsed in 0.010 seconds, using GeSHi 1.0.8.4
Check out my adventures into Android programming:

4x4 Adventures - Featured in the Top Free Play Store listings
Slippery Sid
jgibbs
 
Posts: 109
Joined: Fri Nov 25, 2011 9:32 am
Location: New Zealand

Re: Targeting multiple display resolutions

Postby Coder_For_Life22 » Sat Dec 15, 2012 11:06 pm

I see.

But in my case i am using a tiled based layout, so the issue for me is some of the tiles are being cut off..

Not sure if there is a fix for this..I just dont want those black bars on my new game.
User avatar
Coder_For_Life22
 
Posts: 1036
Joined: Wed Nov 09, 2011 5:37 pm

Next

Return to GLES2

Who is online

Users browsing this forum: Exabot [Bot], Yahoo [Bot] and 9 guests