성능 저하 없이 Android 게임에서 화면에 많은 비트맵을 그리는 방법

StackOverflow https://stackoverflow.com/questions/1430741

  •  07-07-2019
  •  | 
  •  

문제

Android용 타일 기반 게임을 만들고 싶습니다.지금은 각 타일을 별도의 비트맵으로 그리고 있습니다.문자열에서 읽고 레벨을 그리기 위해 찾은 문자에 따라 다른 타일을 그리는 큰 for 루프가 있습니다.

사용자가 스크롤 제스처를 사용하여 화면을 스크롤할 수 있도록 허용했습니다.그런데 게임이 너무 느립니다.사용자가 스크롤한 후 화면을 업데이트하는 데 시간이 오래 걸립니다.이는 각 타일의 비트맵을 개별적으로 그려야 하기 때문이라고 생각합니다.

레벨을 그리는 더 빠른 방법은 무엇입니까?저는 모든 타일을 하나의 비트맵으로 병합할 수 있다고 생각했습니다.하지만 어떻게 해야 할지 모르겠습니다.어떤 아이디어가 있나요?

어쨌든 문제를 확인할 수 있는 코드는 다음과 같습니다.

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

그리고 모든 작업을 수행하는 클래스는 다음과 같습니다.

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();
      }
 }
}
도움이 되었습니까?

해결책

렌더링된 모든 타일에 대해 각 비트맵 이미지의 새 인스턴스를 생성하는 것 같습니다.어쩌면 그렇게 하는 대신 각 타일 유형에 대해 하나의 인스턴스를 만들 수 있을까요?전:

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

그런 다음 타일을 그릴 때마다 동일한 타일 인스턴스를 재사용합니다.이것이 작동하지 않으면 일종의 성능 측정을 통해 코드의 어느 부분이 가장 오랜 시간이 걸리는지 확인하고 코드 실행 시간을 최소화하거나 줄여야 합니다.

부인 성명:나는 안드로이드 프로그래머가 아니다

다른 팁

문제는 매우 분명합니다.당신은 엄청난 양의 메모리를 유출하고 있습니다.LogCat을 살펴보면 무슨 뜻인지 알 수 있습니다.1.각 프레임마다 문자열을 할당하지 마세요.불변이므로 문자열을 사용한 모든 작업은 메모리 누수와 같습니다.대신 수정하는 간단한 char[] 객체를 사용하세요.2.비트맵 개체를 계속해서 생성하지 마세요.DecodeBitmap 메서드는 각 호출에 대해 내부적으로 비트맵 개체를 할당한다고 가정합니다.매 프레임마다 그렇게 하는 것은 좋지 않습니다.

경험상 -> 메모리 누수와 친구 GC는 그릴 때 피하는 매우 비용이 많이 드는 작업입니다.

저는 이전에 Android용으로 프로그래밍한 적이 없기 때문에 그 내부에서 무슨 일이 일어나고 있는지 100% 확신할 수는 없지만 다음과 같이 가정합니다.

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

이 코드가 비트맵을 메모리에 로드하면 각 비트맵을 그릴 때 다시 로드하는 것처럼 보입니다.

과거에 작은 게임을 개발할 때 제가 했던 일은 레벨을 로드하고 필요한 모든 리소스를 계산한 다음 메모리에 한 번 로드한 다음 재사용하는 것이었습니다.

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

    }

}

}

프로그래머가 아닌 사람의 또 다른 생각은 스크롤하는 등의 큰 타일 배경을 만드는 것입니다.그리고 배경 위에 이동 가능한 아이템(플레이어, npc, 아이템)이 그려지는 두 번째 최상위 레이어가 있습니다.따라서 스크롤이 더 빨라지고 전체적인 (재)렌더링이 줄어듭니다.나는 이것이 이미 렌더링된 타일을 참조하는 것보다 더 리소스 집약적일 수 있다고 생각합니다(위의 다른 제안에서 언급했듯이).하지만 그렇지 않을 수도 있습니다. 한번 시도해 보고 확인해야 합니다.:-)

사용자가 스크롤할 때 전체 보기를 이동하는 것은 어떻습니까?창을 스크롤하면 실제로는 인식하는 반대 방향으로 창이 이동합니다.아래로 스크롤하면 창이 위로 이동합니다.

뷰(그리드)의 모든 요소를 ​​수집하고 전체 뷰를 이동할 수 있다고 확신합니다.화면 밖에서 이미지를 더 빠르게 렌더링하고 필요에 따라 위로 이동합니다.

사용 가능한 예가 있습니다..홈 화면의 모든 코드를 얻을 수 있고 왼쪽에서 오른쪽으로 스크롤할 수 있다는 것을 알고 있습니다.살펴봐야 할 부분이 있을 수 있습니다.나는 그들이 그 앱을 제대로 만들었다고 확신합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top