如何在不降低性能的情况下在 Android 游戏中在屏幕上绘制大量位图

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1430741/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 03:05:14  来源:igfitidea点击:

How to draw lots of bitmaps on screen in an Android game without slow performance

androidtile

提问by RMorrisey

I want to make a tile based game for android. At the moment I am drawing each tile as a separate bitmap. I have a big for loop that reads from a string and draws different tiles depending on what character it finds to draw the level.

我想为android制作一个基于瓷砖的游戏。目前我将每个图块绘制为单独的位图。我有一个很大的 for 循环,它从一个字符串中读取并根据它找到的绘制关卡的字符绘制不同的图块。

I have allowed the user to scroll the screen using scrolling gestures. However the game is too slow. It takes a long time to update the screen after the user scrolls. I presume this is because it has to draw each tile's bitmap individually.

我允许用户使用滚动手势滚动屏幕。不过游戏速度太慢了。用户滚动后更新屏幕需要很长时间。我认为这是因为它必须单独绘制每个图块的位图。

What would be a faster way to draw the level? I was thinking I could merge all the tiles into one bitmap. But I don't know how to do this. Any ideas?

绘制关卡的更快方法是什么?我在想我可以将所有的图块合并成一个位图。但我不知道如何做到这一点。有任何想法吗?

Anyway here is my code so you can see the problem:

无论如何,这是我的代码,因此您可以看到问题:

package org.example.tutorial2d;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.GestureDetector.OnGestureListener;

import org.example.tutorial2d.Panel;

public class Tutorial2D extends Activity implements OnGestureListener {

GestureDetector gestureScanner;
Panel main;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    gestureScanner = new GestureDetector(this);

    //requestWindowFeature(Window.FEATURE_NO_TITLE);       
    main = new Panel(this);
    setContentView(main);       

}

@Override
public boolean onTouchEvent(MotionEvent me)
{
 return gestureScanner.onTouchEvent(me);
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
 main.handleScroll(distanceX,distanceY);
 return true;
}

////////////////////
///////////////////
//////////////////
@Override
public boolean onDown(MotionEvent e)
{
 return true;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
 return true;
}

@Override
public void onLongPress(MotionEvent e){    }

@Override
public void onShowPress(MotionEvent e) {   }    

@Override
public boolean onSingleTapUp(MotionEvent e)    
{
 return true;
}
////////////////////
///////////////////
//////////////////
}

And the class that does all the work:

以及完成所有工作的类:

package org.example.tutorial2d;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import org.example.tutorial2d.Point;

public class Panel extends View {

private int scrollX = 0;
private int scrollY = 0;

public Panel(Context context)
{
    super(context);
}

@Override
public void onDraw(Canvas canvas)
{
    /*Bitmap scratch;
    //Drawable scratch;
    //scratch = getContext().getResources().getDrawable(
    //        R.drawable.icon);
    canvas.drawColor(Color.BLACK);
    //scratch.draw(canvas);
    int origin = 0;
    scratch = BitmapFactory.decodeResource(getResources(), R.drawable.horizontal5);
    canvas.drawBitmap(scratch, origin, origin, null);
    int width = scratch.getWidth();
    int height = scratch.getHeight();
    scratch = BitmapFactory.decodeResource(getResources(), R.drawable.room4entrynesw3x3);
    canvas.drawBitmap(scratch, origin + width, origin - 32, null);
    */

    String sucide_mission = 
"                  wwwww\n" +
"                  wfffw\n" +
"                  wfffw\n" +
"                  wfffw\n" + 
"                  wwfww\n" +
"                   wfw\n" + 
"                   wfw\n" +
"                   wfw\n" +
"             wwwwwwwfwwwwwfw\n" +
"             wfffffffffffffw\n" +
"             wfwwwwwfwwwwwfw\n" +
"     wwwww   wfw   wfw   wfw\n" +
"wwwwwwfffwwwwwfwwwwwfwwwwwfw\n" +
"fffffffffffffffffffffffffffw\n" +
"wwwwwwfffwwwwwwwwwwwfwwwwwfw\n" +
"     wwfww         wfw\n" +
"      wfw          wfw\n" +
"      wfw          wfw\n" +
"      wfw          wfw\n" +
"      wfw          wfw\n" +
"     wwfww         wfw\n" +
"     wfffwwfw      fff\n" +
"     wffffffw      www\n" +
"     wfffwwfw\n" +
"     wwwww";

    canvas.drawColor(Color.BLACK);
    int x = 0, y = 0;

    for (int i = 0; i < sucide_mission.length(); i++)
    {
        Bitmap tileImage;
        char tile = sucide_mission.charAt(i);

        Log.d("Draw tiles", Character.toString(tile) + " " + x + "," + y);

        switch (tile)
        {
            case 'w':
                if (x < tileImage = BitmapFactory.decodeResource(getResources(), R.drawable.walla);
                canvas.drawBitmap(tileImage, x - scrollX, y - scrollY, null);
                x += 32;
                break;
            case 'f':
                tileImage = BitmapFactory.decodeResource(getResources(), R.drawable.floore);
                canvas.drawBitmap(tileImage, x - scrollX, y - scrollY, null);
                x += 32;
                break;
            case ' ':
                x += 32;
                break;
            case '\n':
                y += 32;
                x = 0;
                break;
        }

    }

    //canvas.drawBitmap(adapt, 0, 0, paint);
    //canvas.drawBitmap(corner, origin -scrollX , origin -scrollY, paint);

}

 public void handleScroll(float distX, float distY)
 {
      // X-Axis ////////////////////////////////

      if(distX > 6.0)
      {
           if(scrollX < 460)
           {
                scrollX += 30;
           }
      }
      else if(distX < -6.0)
      {
           if(scrollX >= 30)
           {
                scrollX -= 30;
           }
      }
      ////////////////////////////////////////////

      // Y-AXIS //////////////////////////////////
      if(distY > 6.0)
      {
           if(scrollY < 100)
           {
                scrollY += 30;
           }
      }
      else if(distY < -6.0)
      {
           if(scrollY >= 30)
           {
                scrollY -= 30;
           }
      }              
      ////////////////////////////////////////////

      if((scrollX <= 480) && (scrollY <= 120))
      {
           //adapt = Bitmap.createBitmap(bmp, scrollX, scrollY, 320, 480);
           invalidate();
      }
 }
}

回答by RMorrisey

It looks like you are creating a new instance of each bitmap image for every tile rendered. Maybe instead of doing that, you could create one instance for each tile type? ex:

看起来您正在为渲染的每个图块创建每个位图图像的新实例。也许不是这样做,您可以为每种磁贴类型创建一个实例?前任:

private Bitmap wallTile = BitmapFactory.decodeResource(getResources(), R.drawable.walla);
private Bitmap floorTile = BitmapFactory.decodeResource(getResources(), R.drawable.floore);

Then reuse the same tile instance each time the tile is drawn. If this doesn't work, you should put in some kind of performance measurement to see what part of the code is taking the longest time, and minimize the amount of times that code is run, or try to slim it down.

然后在每次绘制瓷砖时重用相同的瓷砖实例。如果这不起作用,您应该进行某种性能测量以查看代码的哪一部分花费的时间最长,并最大限度地减少代码运行的次数,或者尝试将其精简。

Disclaimer: I am not an Android programmer

免责声明:我不是 Android 程序员

回答by RMorrisey

The problem is pretty obvious. You are just leaking a hell lot of memory. Take a look in the LogCat and you'll see what I mean. 1. NEVER allocate strings each frame. They are immutable so every operation with a strings equals memory leaking. Use instead a simple char[] object that you modify. 2. Stop creating bitmap objects over and over again. I suppose that the DecodeBitmap method internally allocates a bitmap object for each call. It's bad to do that every frame.

问题很明显。你只是在泄漏大量的内存。看看 LogCat,你就会明白我的意思。1. 永远不要在每一帧分配字符串。它们是不可变的,所以每个带有字符串的操作都等于内存泄漏。改用您修改的简单 char[] 对象。2. 停止一遍又一遍地创建位图对象。我想 DecodeBitmap 方法在内部为每个调用分配一个位图对象。每一帧都这样做是不好的。

As a rule of thumb -> leaking memory and it's buddy the GC are very expensive operations that be avoided when drawing.

根据经验 -> 内存泄漏和它的哥们 GC 是非常昂贵的操作,在绘制时要避免。

回答by Benny Hallett

I've never programmed for Android before, so I'm not 100% sure about what's going on underneath the covers there, but assuming that:

我以前从未为 Android 编程过,所以我不能 100% 确定那里发生了什么,但假设:

BitmapFactory.decodeResource(getResources(), R.drawable.walla);

this bit of code goes and loads up a bitmap into memory, it would appear that you're reloading each bitmap as you draw it.

这段代码将位图加载到内存中,看起来您在绘制每个位图时正在重新加载它。

What I've done when developing small games in the past, is when you go to load a level, work out all of the resources that you'll need then load them up into memory once, then reuse them.

我过去在开发小游戏时所做的是,当你加载一个关卡时,计算出你需要的所有资源,然后将它们加载到内存中,然后重用它们。

回答by Benny Hallett

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MapLoader extends SurfaceView implements SurfaceHolder.Callback,
    Runnable {

SurfaceHolder holder;
Thread thread;

Bitmap grass = BitmapFactory.decodeResource(getResources(),
        R.drawable.tilemapdemo);
boolean running = false;

int[][] grassCoords = new int[][] { { 0, 16, 32, 48, 64 },
        { 0, 16, 32, 48, 64 }, { 0, 16, 32, 48, 64 },
        { 0, 16, 32, 48, 64 }, { 0, 16, 32, 48, 64 } };

public MapLoader(Context context, int width, int height) {
    super(context);

    holder = getHolder();
    holder.addCallback(this);
}

public MapLoader(Context context, AttributeSet attrs) {
    super(context, attrs);

    holder = getHolder();
    holder.addCallback(this);
}

public MapLoader(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    holder = getHolder();
    holder.addCallback(this);
}

public void pause() {
    running = false;

    while (running) {
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        break;
    }
    thread = null;
}

public void resume() {
    running = true;
    thread = new Thread(this);
    thread.start();

}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {

    running = true;
    thread = new Thread(this);
    thread.start();

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    Canvas c = holder.lockCanvas();
    draw(c);
    holder.unlockCanvasAndPost(c);

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}

@Override
public void run() {

    while (running == true) {

        // performs drawing to the canvas
        if (!holder.getSurface().isValid()) {

            continue;
        }

        Canvas c = holder.lockCanvas();

        int x = 0;
        int y = 0;

        for (x = 0; x < grassCoords.length; x += grass.getWidth()) {

            for (y = 0; y < grassCoords.length; y += grass.getHeight()) {

                c.drawBitmap(grass, x, y, null);
            }

        }

        holder.unlockCanvasAndPost(c);

    }

}

}

}

回答by Bob

another thought, from a non programmer, is maybe create a large tile background that scrolls, etc.. and a second top layer where moveable items (players, npc, items) are drawn on top of the background. therefore scrolling should be faster and less overall (re)rendering. I imagine this might be more resource intensive than referencing already rendered tiles (as mentioned in another suggestion above).. but it might not be, you'd have to give it a shot and see. :-)

来自非程序员的另一个想法是,可能会创建一个滚动等的大图块背景,以及第二个顶层,其中可移动项目(玩家、NPC、项目)被绘制在背景之上。因此滚动应该更快,整体(重新)渲染更少。我想这可能比引用已经渲染的图块更占用资源(如上面的另一个建议中所述)..但它可能不是,你必须试一试看看。:-)

回答by baash05

How about moving the whole view when the user scrolls. When you scroll in windows, you actually move the window in the opposite direction that you perceive. You scroll down, the windows moves up..

当用户滚动时移动整个视图如何。当您在窗口中滚动时,实际上是在您感知的相反方向上移动窗口。你向下滚动,窗户向上移动..

I am quite sure you could collect all the elements in a view (grid) and move the whole view.. Render images off screen (faster) and move them up as needed..

我很确定您可以收集视图(网格)中的所有元素并移动整个视图.. 将图像渲染出屏幕(更快)并根据需要将它们向上移动..

There are examples available.. I know you can get all the code for the home screen, and it scrolls left to right. Might be something to look into. I'm quite sure they did that app right.

有可用的示例.. 我知道你可以获得主屏幕的所有代码,它从左到右滚动。可能有什么值得研究的。我很确定他们做的那个应用程序是正确的。