How to properly scale a game on Android

2019-03-13 20:16发布

问题:

for the sake of simplicity let's assume that I'm making a simple Pong clone game for Android. Let's assume that it would only be playable in the landscape mode. (ignore square phones for now).

I'd like the game to look in the same scale on each phone, to the extent that if you took a screenshot of the game on QVGA phone, and resized the screenshot to WVGA size, it would look almost the same as would the game look on WVGA phone. In other words, the paddle's width should always be 1/32 of the screen width, the ball's diameter should always be 1/16 of the screen width.

What would be the proper way to paint the application? It would run in standard SurfaceView that would be drawn onto a Canvas.

Let's say that I have a high-resolution PNGs for the paddle, ball and for the game font (scoreboard, main menu).

Do I find out the physical resolution, then scale the Canvas via scale(float sx, float sy) to make all my Canvases (on QVGA and WVGA) have the same virtual resolution, and then draw exactly the same primitives on each position on each screen size?

Or can I use density-independent pixels (dip) somehow in the Canvas?

回答1:

I only played once with the draw canvas functions and then switched all to opengl but the logic stays the same (I think).

first issue you'll want to keep a ratio constant form one phone to the other. in my app I add a " black band" effect on each side. in onSurfaceChanged, you'll want to calculate a ratio, this ratio will allow you to determine how much space you have to remove on each side to keep a consistent aspect to your drawing. this will give you a delta X or Y to apply to all your draws the following code is something I adapted from the ogl version so it might need to be tweeked a bit

@Override
   public void onSurfaceChanged(int w, int h){
   float ratioPhysicScreen = nativeScreenResoltionW/nativeScreenResoltionH;
   float ratioWanted = GameView.getWidth()/GameView.getHeight();
   if(ratioWanted>ratioPhysicScreen){
      newHeight = (int) (w*GameView.getHeight()/GameView.getWidth());
      newWidth = w;
      deltaY = (int) ((h-newHeight)/2);
     deltaX = 0;
   }else{
      newWidth = (int) (h/GameView.getHeight()*GameView.getWidth());
      newHeight = h;
      deltaX = (int) ((w-newWidth)/2);
      deltaY = 0;       
}

then you'll also want to be able to draw your pictures on the canvas by knowing there size as well on the canvas than there real size and that where the difference in between image.getWidth() (actual size of the picture) and a image.getScaledWidth(canvas) which give you the size of the element in dp which means how big it will appear on the screen) is important. look at the example underneath.

public class MainMenuView extends PixelRainView{
private Bitmap bmpPlay = null;
private float playLeft = 0;
private float playRight = 0;
private float playBottom = 0;
private float playTop = 0;

public MainMenuView(Context context, AttributeSet attrs) {
    super(context,attrs);
}



@Override
public void unLoadBitmaps() {
    super.unLoadBitmaps();
    if(bmpPlay != null){
        bmpPlay.recycle();
        bmpPlay = null;
    }
}



@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if(bmpPlay == null){
        bmpPlay = getBitmapFromRessourceID(R.drawable.play_bt);
        playLeft = (this.getWidth()-bmpPlay.getScaledWidth(canvas))/2; 
        playRight = playLeft + bmpPlay.getScaledWidth(canvas);
        playTop = (this.getHeight()-bmpPlay.getScaledHeight(canvas))/2;
        playBottom = playTop+bmpPlay.getScaledHeight(canvas);
    }
    canvas.drawBitmap(bmpPlay,playLeft, playTop, null);

    }       
}


@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
}



@Override
public boolean onTouchEvent(MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        float x = event.getX();
        float y = event.getY();
        //test central button
        if(x>playLeft && x<playRight && y<playBottom && y>playTop){
            Log.e("jason", "touched play");
            PixelRainView.changeView(R.layout.composedlayoutgroup);
        }
    return super.onTouchEvent(event);
}
}

This should solve all your ratio problem cross plateforms I would suggest opengl because it will simplify your need for keeping a constant aspect but I guess its not an option ^^

Hope this helps you enough



回答2:

Use dips and paint the paddles instead of using PNGs



回答3:

I think that there are only 3 standard resolutions available (ldip, mdip, hdip) and you should have a resource folder for each of them.

http://developer.android.com/guide/practices/screens_support.html

If you manually scale your PNG's to fit each of the 3 available resolutions, the phone should load the specific resource folder in a transparent way