Comment dessiner beaucoup de bitmaps à l'écran dans un jeu Android sans ralentir les performances

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

  •  07-07-2019
  •  | 
  •  

Question

Je veux créer un jeu basé sur des tuiles pour Android. Pour le moment, je dessine chaque carreau en tant que bitmap distinct. J'ai une grosse boucle for qui lit une chaîne et dessine différentes tuiles en fonction du personnage trouvé pour dessiner le niveau.

J'ai permis à l'utilisateur de faire défiler l'écran en utilisant des gestes de défilement. Cependant, le jeu est trop lent. La mise à jour de l'écran après le défilement de l'utilisateur prend beaucoup de temps. Je suppose que c'est parce qu'il doit dessiner le bitmap de chaque tuile individuellement.

Quel serait un moyen plus rapide de dessiner le niveau? Je pensais que je pouvais fusionner toutes les tuiles en une bitmap. Mais je ne sais pas comment faire ça. Des idées?

En tout cas voici mon code pour que vous puissiez voir le problème:

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

Et la classe qui fait tout le travail:

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();
      }
 }
}
Était-ce utile?

La solution

On dirait que vous créez une nouvelle instance de chaque image bitmap pour chaque mosaïque rendue. Peut-être qu'au lieu de cela, vous pourriez créer une instance pour chaque type de mosaïque? ex:

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

Ensuite, réutilisez la même instance de mosaïque à chaque fois que celle-ci est dessinée. Si cela ne fonctionne pas, vous devez utiliser une sorte de mesure de performance pour voir quelle partie du code prend le plus de temps, et minimiser le nombre de fois où le code est exécuté, ou essayez de le réduire.

Avertissement: je ne suis pas un programmeur Android

Autres conseils

Le problème est assez évident. Vous perdez juste beaucoup de mémoire. Regardez dans le LogCat et vous verrez ce que je veux dire. 1. NE JAMAIS allouer de chaînes à chaque image. Ils sont immuables, donc chaque opération avec une chaîne équivaut à une fuite de mémoire. Utilisez à la place un simple objet char [] que vous modifiez. 2. Arrêtez de créer encore et encore des objets bitmap. Je suppose que la méthode DecodeBitmap alloue en interne un objet bitmap pour chaque appel. C’est mauvais de le faire à chaque image.

En règle générale - > il y a une fuite de mémoire et c'est copain le GC sont des opérations très coûteuses qu'il faut éviter en tirant.

Je n'ai jamais programmé pour Android auparavant, je ne suis donc pas tout à fait sûr de ce qui se passe sous les couvertures, mais en supposant que:

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

Ce morceau de code va charger une image en mémoire, il semblerait que vous rechargiez chaque image en la dessinant.

Ce que j’avais fait lors du développement de petits jeux par le passé, c’était de charger un niveau, de calculer toutes les ressources nécessaires, de les charger une fois en mémoire, puis de les réutiliser.

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

    }

}

}

une autre pensée, d’un non programmeur, est peut-être de créer un grand fond de tuile qui défile, etc. et une deuxième couche supérieure où des éléments mobiles (lecteurs, npc, éléments) sont dessinés sur le fond. par conséquent, le défilement doit être plus rapide et moins rendu (re) global. J'imagine que cela pourrait nécessiter plus de ressources que de référencer des tuiles déjà rendues (comme mentionné dans une autre suggestion ci-dessus) .. mais ce n'est peut-être pas le cas, vous devriez essayer et voir. : -)

Pourquoi ne pas déplacer l’ensemble de la vue lorsque l’utilisateur défile? Lorsque vous faites défiler une fenêtre, vous déplacez la fenêtre dans la direction opposée que vous percevez. Vous faites défiler l'écran, les fenêtres se déplacent vers le haut.

Je suis certain que vous pouvez rassembler tous les éléments d'une vue (grille) et déplacer l'ensemble de la vue. Rendez les images hors écran (plus rapidement) et déplacez-les au besoin.

Des exemples sont disponibles. Je sais que vous pouvez obtenir tout le code de l'écran d'accueil et il défile de gauche à droite. Peut-être quelque chose à examiner. Je suis assez sûr qu'ils ont bien fait cette application.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top