Come disegnare molte bitmap sullo schermo in un gioco Android senza prestazioni lente

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

  •  07-07-2019
  •  | 
  •  

Domanda

Voglio creare un gioco basato su piastrelle per Android. Al momento sto disegnando ogni riquadro come una bitmap separata. Ho un grande ciclo for che legge da una stringa e disegna tessere diverse a seconda del personaggio che trova per disegnare il livello.

Ho permesso all'utente di scorrere lo schermo usando i gesti di scorrimento. Tuttavia, il gioco è troppo lento. L'aggiornamento dello schermo richiede molto tempo dopo che l'utente scorre. Presumo che ciò avvenga perché deve disegnare singolarmente la bitmap di ogni riquadro.

Quale sarebbe un modo più veloce per disegnare il livello? Pensavo di poter unire tutte le tessere in una bitmap. Ma non so come farlo. Qualche idea?

Comunque qui è il mio codice in modo da poter vedere il problema:

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

E la classe che fa tutto il lavoro:

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();
      }
 }
}
È stato utile?

Soluzione

Sembra che tu stia creando una nuova istanza di ogni immagine bitmap per ogni riquadro renderizzato. Forse invece di farlo, potresti creare un'istanza per ogni tipo di riquadro? es:

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

Quindi riutilizzare la stessa istanza del riquadro ogni volta che viene disegnato il riquadro. Se questo non funziona, dovresti inserire una sorta di misurazione delle prestazioni per vedere quale parte del codice impiega più tempo e minimizzare il numero di volte in cui il codice viene eseguito o provare a ridurlo.

Dichiarazione di non responsabilità: non sono un programmatore Android

Altri suggerimenti

Il problema è abbastanza ovvio. Stai perdendo un sacco di memoria. Dai un'occhiata a LogCat e vedrai cosa intendo. 1. Non allocare MAI stringhe per ogni frame. Sono immutabili quindi ogni operazione con una stringa equivale a una perdita di memoria. Utilizzare invece un semplice oggetto char [] che si modifica. 2. Interrompere la creazione ripetuta di oggetti bitmap. Suppongo che il metodo DecodeBitmap alloca internamente un oggetto bitmap per ogni chiamata. È brutto farlo ogni frame.

Come regola generale - > perdita di memoria ed è amico il GC sono operazioni molto costose da evitare quando si disegna.

Non ho mai programmato per Android prima, quindi non sono sicuro al 100% di quello che sta succedendo sotto le copertine lì, ma suppongo che:

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

questo bit di codice va e carica una bitmap in memoria, sembrerebbe che stai ricaricando ogni bitmap mentre la disegni.

Quello che ho fatto durante lo sviluppo di piccoli giochi in passato, è quando vai a caricare un livello, elabora tutte le risorse di cui hai bisogno, quindi caricalo una volta nella memoria, quindi riutilizzale.

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

    }

}

}

un altro pensiero, da parte di un non programmatore, è forse quello di creare uno sfondo di grandi tessere che scorre, ecc. e un secondo livello superiore in cui gli oggetti mobili (giocatori, PCN, oggetti) sono disegnati sopra lo sfondo. pertanto lo scorrimento dovrebbe essere più veloce e meno (ri) rendering complessivo. Immagino che potrebbe essere più dispendioso in termini di risorse rispetto al riferimento a tessere già renderizzate (come menzionato in un altro suggerimento sopra) .. ma potrebbe non esserlo, dovresti provarlo e vedere. : -)

Che ne dici di spostare l'intera vista quando l'utente scorre. Quando si scorre nelle finestre, si sposta effettivamente la finestra nella direzione opposta a quella percepita. Scorri verso il basso, le finestre si alzano ..

Sono abbastanza sicuro che potresti raccogliere tutti gli elementi in una vista (griglia) e spostare l'intera vista. Renderizza le immagini fuori dallo schermo (più velocemente) e spostale verso l'alto secondo necessità ..

Ci sono esempi disponibili. So che puoi ottenere tutto il codice per la schermata principale e scorre da sinistra a destra. Potrebbe essere qualcosa da esaminare. Sono abbastanza sicuro che abbiano fatto bene quell'app.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top