augst6
augst6

Reputation: 83

Android Laggy onDraw() on Hexboard

I have been developing a board game using hexagon boards in android, but when I move the board, the whole 'map' or dozens of entities starts to lag. I believe that it is the cause of using a for-loop, but I have no idea how to fix it. Im using the method of threaded drawing, dont know if it affects though. I can clearly see lags on my cell phone when moving through the board.

My Game class:

public class Game extends SurfaceView implements SurfaceHolder.Callback {
// Game Vars

int mapWidth = 150, mapHeight = 150;
public static double hexagonSideLength = 80;
public static double cellWidth = 2 * hexagonSideLength, cellHeight = Math
        .sqrt(3) * hexagonSideLength;
public static double downwardShift = (0.5) * (cellHeight);
public static double rightShift = hexagonSideLength / 2;
public static int boundaryWidth = 0, boundaryHeight = 0;
public static int error = (int) ((hexagonSideLength) * (0.06));
public static int buffer = 2;
public static int draws = 0;

// Touch Handle

// Offset to the upper left corner of the map
private int xOffset = 0;
private int yOffset = 0;

// last touch point
private int _xTouch = 0;
private int _yTouch = 0;

// scrolling active?
private boolean _isMoving = false;

public Cell[][] map;
private GameThread thread;
static String TAG = Game.class.getSimpleName();
Bitmap image[] = new Bitmap[100];
Paint paint;

public Game(Context context) {
    super(context);
    Log.i(TAG, "Loaded Game");
    // adding the callback (this) to the surface holder to intercept events
    getHolder().addCallback(this);
    // make the GamePanel focusable so it can handle events
    thread = new GameThread(getHolder(), this);
    map = new Cell[mapWidth][mapHeight]; // Create new Map
    boolean isShiftedDownwards = false;
    for (int i = 0; i < mapWidth; i++) {
        for (int j = 0; j < mapHeight; j++) {
            map[i][j] = new Cell(j, i, isShiftedDownwards);
        }
        if (isShiftedDownwards == true)
            isShiftedDownwards = false;
        else
            isShiftedDownwards = true;
    }

    if (mapWidth % 2 != 0) {
        boundaryWidth = (int) ((((mapWidth - 1) / 2) * hexagonSideLength)
                + ((mapWidth / 2) * cellWidth) + (2) * hexagonSideLength);
    } else {
        boundaryWidth = (int) (((((mapWidth - 1) / 2) * hexagonSideLength) + ((mapWidth / 2) * cellWidth)) + (1.5) * hexagonSideLength);
    }
    boundaryHeight = (int) (mapHeight * cellHeight + 0.5 * cellHeight);
    setFocusable(true);
    image[0] = Bitmap.createBitmap(BitmapFactory.decodeResource(
            this.getResources(), R.drawable.hexagonrgb));
    image[1] = Bitmap.createScaledBitmap(image[0], (int) cellWidth + error,
            (int) cellHeight + error, false);
    Log.i(TAG, "Got Resources");
    paint = new Paint();
    Log.i(TAG, "Prepared paint");

}

// called every Frame
@Override
protected void onDraw(Canvas canvas) {
    Log.i(TAG, "onDraw Called");
    // BG
    canvas.drawColor(Color.BLACK);

    // Resize

    // Redraw Map
    draws = 0;
    for (int column = updateArea(0); column < updateArea(1); column++) {
        for (int row = updateArea(2); row < updateArea(3); row++) {
            canvas.drawBitmap(image[1], map[column][row].x - xOffset,
                    map[column][row].y - yOffset, paint);
            paint.setColor(Color.WHITE);
            canvas.drawText(row + "," + column, map[column][row].x
                    - xOffset + 80, map[column][row].y - yOffset + 80,
                    paint);
            draws++;
        }
    }

    /** Log */
    paint.setColor(Color.WHITE);
    paint.setTextSize(20);
    canvas.drawText("xOffset: " + xOffset, 0, 30, paint);
    canvas.drawText("yOffset: " + yOffset, 0, 50, paint);
    canvas.drawText("activeTitlesX: " + updateArea(0) + " - "
            + updateArea(1), 0, 70, paint);
    canvas.drawText("activeTitlesY: " + updateArea(2) + " - "
            + updateArea(3), 0, 90, paint);
    canvas.drawText("DimX: " + MainActivity.DimX, 0, 110, paint);
    canvas.drawText("DimY: " + MainActivity.DimY, 0, 130, paint);
    canvas.drawText("Draws: " + draws, 0, 150, paint);

    Log.i(TAG, "Cleared canvas");
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
}

// called by thread
public static void update() {
    Log.i(TAG, "Updated Game");

}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {
    // TODO Auto-generated method stub

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    thread.setRunning(true);
    thread.start();
    Log.i(TAG, "Thread Started");

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    boolean retry = true;
    while (retry) {
        try {
            thread.join();
            retry = false;
        } catch (InterruptedException e) {
            // try again shutting down the thread
        }
    }
    Log.i(TAG, "Thread Destroyed");
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    // touch down
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        // start of a new event, reset the flag
        _isMoving = false;
        // store the current touch coordinates for scroll calculation
        _xTouch = (int) event.getX();
        _yTouch = (int) event.getY();
    } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
        // touch starts moving, set the flag
        _isMoving = true;

        // get the new offset
        xOffset += _xTouch - (int) event.getX();
        yOffset += _yTouch - (int) event.getY();

        // secure, that the offset is never out of view bounds
        if (xOffset < 0) {
            xOffset = 0;
        } else if (xOffset > boundaryWidth - getWidth()) {
            xOffset = boundaryWidth - getWidth();
        }
        if (yOffset < 0) {
            yOffset = 0;
        } else if (yOffset > boundaryHeight - getHeight()) {
            yOffset = boundaryHeight - getHeight();
        }

        // store the last position
        _xTouch = (int) event.getX();
        _yTouch = (int) event.getY();
    } else if (event.getAction() == MotionEvent.ACTION_UP) {
        /*
         * // touch released if (!_isMoving) { // calculate the touched cell
         * int column = (int) Math.ceil((_xOffset + event.getX()) /
         * _cellSize) - 1; int row = (int) Math.ceil((_yOffset +
         * event.getY()) / _cellSize) - 1; Cell cell =
         * _mapCells.get(row).get(column); // show the id of the touched
         * cell Toast.makeText(getContext(), "Cell id #" + cell._id,
         * Toast.LENGTH_SHORT).show(); }
         */
    }
    return true;
}

public int updateArea(int i) {
    switch (i) {
    case 0: // Left
        return Math.max(
                (int) (xOffset / (cellWidth - rightShift)) - buffer, 0);
    case 1: // Right
        return Math
                .min((int) (xOffset / (cellWidth - rightShift) + (int) (MainActivity.DimX / (cellWidth - rightShift)))
                        + buffer, mapWidth);
    case 2: // Up
        return Math.max((int) (yOffset / cellHeight) - buffer, 0);
    case 3: // Down
        return Math.min(((int) (yOffset / cellHeight))
                + (int) (MainActivity.DimY / cellHeight) + buffer,
                mapHeight);
    }
    return 0;
}

/* CELL CLASS */
class Cell {
    int x, y;
    boolean isShiftedDown;

    public Cell(int row, int col, boolean isShifted) {
        if (col % 2 != 0) {
            isShiftedDown = true;
            y = (int) (row * Game.cellHeight + Game.downwardShift);
            x = (int) (col * Game.cellWidth - col * Game.rightShift);
            Log.i("Shift", "Shifted" + Game.downwardShift);
        } else {
            isShiftedDown = false;
            y = (int) (row * Game.cellHeight);
            x = (int) (col * Game.cellWidth - col * Game.rightShift);
            Log.i("Shift", "Not Shifted");
        }
    }
}

}

Thanks in Advance :)

Upvotes: 0

Views: 508

Answers (1)

anthropomo
anthropomo

Reputation: 4120

Basic algorithmic analysis shows you are doing this operation:

map[i][j] = new Cell(j, i, isShiftedDownwards);

22500 times. Do you really need to create a new object each time, or can you just change a variable in an existing object?


Update:

for (int column = updateArea(0); column < updateArea(1); column++) {
    for (int row = updateArea(2); row < updateArea(3); row++) {
        canvas.drawBitmap(image[1], map[column][row].x - xOffset,
                map[column][row].y - yOffset, paint);
        paint.setColor(Color.WHITE);
        canvas.drawText(row + "," + column, map[column][row].x
                - xOffset + 80, map[column][row].y - yOffset + 80,
                paint);
        draws++;
    }
}

So this is the double for-loop in question. I'm going to run with the assumption that this needs to be run as many times as it runs. Can the function calls in the loop statements be reduced to variables prior to the loop (they are evaluated with each loop)? This paint.setColor(Color.WHITE); should not be called on each iteration. If you need to change to white after the very first draw, just make a second paint object on startup and and change it before you enter the loop.

If this doesn't help, you need to determine if you can iterate over a smaller area.


Update 2 Rewrite your for loops like so

int minCol = updateArea(0);
int maxCol = updateArea(1);
int minRow = updateArea(2);
int maxRow = updateArea(3);
for(int column = minCol; column < maxCol; column++)
    for (int row = minRow; column < maxRow; row++)
 ....

This will represent a significant reduction in the operations in each iteration.

Upvotes: 1

Related Questions