最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Optimising or making 60 FPS for Android game wrote using Java - Stack Overflow

programmeradmin4浏览0评论

I want to make own game using Java in Android studio. I have idea: make online pixel shooter. By the way when I added map nevermind which size I have 'laggs' like I testing with 15 FPS. I used SurfaceView to make this game. The game runs at 60 FPS, with the update() method handling game logic and the draw() method rendering the game. update()

    private void update() {
        // Оновлення стану прискорення
        if (isAccelerated && System.currentTimeMillis() - accelerationStartTime > ACCELERATION_DURATION) {
            isAccelerated = false;
            playerSpeed = DEFAULT_PLAYER_SPEED;
        }


        // Оновлення позиції гравця
        PointF direction = joystick.getDirection();
        int newPlayerX = playerX + (int) (direction.x * playerSpeed);
        int newPlayerY = playerY + (int) (direction.y * playerSpeed);

        if (!tileMap.isWall(newPlayerX / tileMap.getTileSize(), newPlayerY / tileMap.getTileSize())) {
            playerX = newPlayerX;
            playerY = newPlayerY;
        }

        // Оновлення стрільби
        if (isShooting) {
            player.shoot();
        }

        camera.update(playerX, playerY);
    }

draw()

 private void draw() {
        Canvas canvas = getHolder().lockCanvas();
        if (canvas != null) {
            try {
                canvas.drawColor(0xFFFFFFFF);

                tileMap.draw(canvas, camera.getX(), camera.getY());

                canvas.drawBitmap(
                        playerBitmap,
                        playerX - camera.getX(),
                        playerY - camera.getY(),
                        null
                );

                // Малювання кулі
                if (isShooting) {
                    canvas.drawBitmap(
                            bulletBitmap,
                            bulletX - camera.getX(),
                            bulletY - camera.getY(),
                            null
                    );
                }

                joystick.draw(canvas);

                // Малювання кнопок
                shootButton.draw(canvas);
                accelerateButton.draw(canvas);
            } finally {
                getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }

The player can move using a joystick, shoot bullets, and activate a temporary speed boost. The TileMap class handles the map rendering

//TileMap.java
package coms.mempixel.game;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.SparseArray;

import coms.mempixel.R;

public class TileMap {
    private int[][] map; // Масив, що описує карту
    private Bitmap[] tileImages; // Зображення для кожного типу тайла
    private int tileSize; // Розмір одного тайла (наприклад, 32x32 пікселів)
    private Bitmap cachedVisibleMap; // Кешована видима частина карти
    private int cachedCameraX, cachedCameraY; // Кешовані координати камери
    private SparseArray<Boolean> collisionCache = new SparseArray<>();

    public boolean isWall(int x, int y) {
        int key = y * map[0].length + x; // Унікальний ключ для кешування
        Boolean cachedResult = collisionCache.get(key);
        if (cachedResult != null) {
            return cachedResult;
        }

        boolean result;
        if (y >= 0 && y < map.length && x >= 0 && x < map[y].length) {
            result = map[y][x] == 1; // 1 - стіна
        } else {
            result = true; // Якщо координати поза межами карти, вважаємо це стіною
        }

        collisionCache.put(key, result);
        return result;
    }


    public void draw(Canvas canvas, int cameraX, int cameraY) {
        if (cachedVisibleMap == null || cachedCameraX != cameraX || cachedCameraY != cameraY) {
            // Якщо кеш неактуальний, створюємо новий
            cachedCameraX = cameraX;
            cachedCameraY = cameraY;
            cachedVisibleMap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
            Canvas cacheCanvas = new Canvas(cachedVisibleMap);

            int screenWidth = canvas.getWidth();
            int screenHeight = canvas.getHeight();

            int startX = Math.max(0, cameraX / tileSize);
            int startY = Math.max(0, cameraY / tileSize);
            int endX = Math.min(map[0].length, (cameraX + screenWidth) / tileSize + 1);
            int endY = Math.min(map.length, (cameraY + screenHeight) / tileSize + 1);

            for (int y = startY; y < endY; y++) {
                for (int x = startX; x < endX; x++) {
                    int tileType = map[y][x];
                    if (tileType >= 0 && tileType < tileImages.length) {
                        cacheCanvas.drawBitmap(
                                tileImages[tileType],
                                x * tileSize - cameraX,
                                y * tileSize - cameraY,
                                null
                        );
                    }
                }
            }
        }

        // Малюємо кешовану видиму частину карти
        canvas.drawBitmap(cachedVisibleMap, 0, 0, null);
    }

    public TileMap(Context context, int[][] map, int tileSize) {
        this.map = map;
        this.tileSize = tileSize;

        // Завантаження зображень для тайлів
        tileImages = new Bitmap[2]; // 0 - підлога, 1 - стіна
        tileImages[0] = BitmapFactory.decodeResource(context.getResources(), R.drawable.floor1); // Підлога
        tileImages[1] = BitmapFactory.decodeResource(context.getResources(), R.drawable.wall1); // Стіна

        // Масштабування зображень до розміру тайла
        for (int i = 0; i < tileImages.length; i++) {
            tileImages[i] = Bitmap.createScaledBitmap(tileImages[i], tileSize, tileSize, false);
        }
    }


    // Отримання розмірів карти в пікселях
    public int getMapWidth() {
        return map[0].length * tileSize;
    }

    public int getMapHeight() {
        return map.length * tileSize;
    }

    // Отримання розміру тайла
    public int getTileSize() {
        return tileSize;
    }

}

and the Camera class ensures the player stays centered on the screen.

//Camera.java
package coms.mempixel.game;

public class Camera {
    private int x, y; // Позиція камери
    private int screenWidth, screenHeight; // Розміри екрану
    private int mapWidth, mapHeight; // Розміри карти
    private float smoothSpeed = 0.1f; // Швидкість плавного переміщення
    private int lastPlayerX = -1;
    private int lastPlayerY = -1;

    public Camera(int screenWidth, int screenHeight, int mapWidth, int mapHeight) {
        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;
        this.mapWidth = mapWidth;
        this.mapHeight = mapHeight;
    }

    // Оновлення позиції камери (слідкування за гравцем)
    public void update(int playerX, int playerY) {
        if (lastPlayerX != playerX || lastPlayerY != playerY) {
            lastPlayerX = playerX;
            lastPlayerY = playerY;

            int targetX = playerX - screenWidth / 2;
            int targetY = playerY - screenHeight / 2;

            // Плавне переміщення камери
            x += (targetX - x) * smoothSpeed;
            y += (targetY - y) * smoothSpeed;

            // Обмеження руху камери
            x = Math.max(0, Math.min(x, mapWidth - screenWidth));
            y = Math.max(0, Math.min(y, mapHeight - screenHeight));
        }
    }

    // Отримання позиції камери
    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
}

There main method which launch the whole code:

@Override
    public void run() {
        long lastTime = System.nanoTime();
        double nsPerUpdate = 1000000000.0 / 60.0; // 60 FPS
        double delta = 0;

        while (isRunning) {
            long now = System.nanoTime();
            delta += (now - lastTime) / nsPerUpdate;
            lastTime = now;

            while (delta >= 1) {
                update();
                delta--;
            }

            draw();
        }
    }

My trying to speed up the code:

  1. Add hardware performance in manifest:
<application
        android:hardwareAccelerated="true"

From all of this I tested using android profiler (View Live Telemetry) and application don't use lots of resources: Also I have pictures, but all of them have less then 20 kilobytes of memory:

So I want to share how it works (tested on much of phones and tablets):

In summary I want to share whole class of game logic:

package coms.mempixel.game;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.annotation.NonNull;

import coms.mempixel.R;
import coms.mempixel.joystick.Joystick;

import java.util.Random;

public class GameView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private Thread gameThread;
    private boolean isRunning;
    private Joystick joystick;
    private TileMap tileMap;
    private Camera camera;
    private int playerX, playerY;
    private int playerSpeed = 5;
    private Bitmap playerBitmap;
    private Bitmap bulletBitmap; // Зображення кулі
    private Bitmap speedButtonBitmap;
    private Bitmap shootButtonBitmap;
    private boolean isAccelerated = false; // Чи активовано прискорення
    private boolean isShooting = false; // Чи відбувається стрілянина
    private int bulletX, bulletY; // Позиція кулі
    private int bulletSpeed = 20; // Швидкість кулі
    // Стани кнопок
    private boolean isSpeedButtonPressed = false;
    private boolean isShootButtonPressed = false;

    private long accelerationStartTime = 0; // Час активації прискорення
    private final long ACCELERATION_DURATION = 10000; // Тривалість прискорення (10 секунд)
    // Кнопки
    private Button accelerateButton;
    private Button shootButton;
    private Player player;
    private static final int DEFAULT_PLAYER_SPEED = 5;
    private static final int ACCELERATED_PLAYER_SPEED = 8;

    public GameView(Context context, AttributeSet attrs) {
        super(context, attrs);
        getHolder().addCallback(this);
        setZOrderOnTop(true);
        getHolder().setFormat(PixelFormat.TRANSLUCENT);
        playerBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.player);
        bulletBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.bullet);

        speedButtonBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.run_btn);
        shootButtonBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.shoot_btn);


        bulletBitmap = Bitmap.createScaledBitmap(bulletBitmap, 1, 1, false); // Зменшуємо кулю до 1x1 пікселя
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        final int[][] map = new int[100][100]; // Створюємо карту 100x100

        for (int y = 0; y < 100; y++) {
            for (int x = 0; x < 100; x++) {
                // Визначаємо межі карти (стіни по краях)
                if (x == 0 || x == 99 || y == 0 || y == 99) {
                    map[y][x] = 1; // Стіна
                } else {
                    // Внутрішній простір: чергування стін та підлоги
                    if ((x / 10) % 2 == 0 && (y / 10) % 2 == 0) {
                        map[y][x] = 1; // Стіна
                    } else {
                        map[y][x] = 0; // Підлога
                    }
                }
            }
        }

        tileMap = new TileMap(getContext(), map, 96);
        playerBitmap = Bitmap.createScaledBitmap(playerBitmap, 96, 96, false);
        camera = new Camera(getWidth(), getHeight(), tileMap.getMapWidth(), tileMap.getMapHeight());

        Point startPosition = getRandomFloorPosition(map);
        playerX = startPosition.x * tileMap.getTileSize();
        playerY = startPosition.y * tileMap.getTileSize();

        joystick = new Joystick(100, getHeight() - 100, 80, 40);

        // Ініціалізація кнопок
        shootButton = new Button(getWidth() - 200, getHeight() - 200, 100, 50, "Shoot", shootButtonBitmap);

        // Кнопка прискорення (ліворуч внизу)
        accelerateButton = new Button(50, 50, 100, 50, "Accelerate", speedButtonBitmap);


        isRunning = true;
        gameThread = new Thread(this);
        gameThread.start();
    }

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

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

    }

    @Override
    public void run() {
        long lastTime = System.nanoTime();
        double nsPerUpdate = 1000000000.0 / 60.0; // 60 FPS
        double delta = 0;

        while (isRunning) {
            long now = System.nanoTime();
            delta += (now - lastTime) / nsPerUpdate;
            lastTime = now;

            while (delta >= 1) {
                update();
                delta--;
            }

            draw();
        }
    }

    private void update() {
        // Оновлення стану прискорення
        if (isAccelerated && System.currentTimeMillis() - accelerationStartTime > ACCELERATION_DURATION) {
            isAccelerated = false;
            playerSpeed = DEFAULT_PLAYER_SPEED;
        }


        // Оновлення позиції гравця
        PointF direction = joystick.getDirection();
        int newPlayerX = playerX + (int) (direction.x * playerSpeed);
        int newPlayerY = playerY + (int) (direction.y * playerSpeed);

        if (!tileMap.isWall(newPlayerX / tileMap.getTileSize(), newPlayerY / tileMap.getTileSize())) {
            playerX = newPlayerX;
            playerY = newPlayerY;
        }

        // Оновлення стрільби
        if (isShooting) {
            player.shoot();
        }

        camera.update(playerX, playerY);
    }

    private void draw() {
        Canvas canvas = getHolder().lockCanvas();
        if (canvas != null) {
            try {
                canvas.drawColor(0xFFFFFFFF);

                tileMap.draw(canvas, camera.getX(), camera.getY());

                canvas.drawBitmap(
                        playerBitmap,
                        playerX - camera.getX(),
                        playerY - camera.getY(),
                        null
                );

                // Малювання кулі
                if (isShooting) {
                    canvas.drawBitmap(
                            bulletBitmap,
                            bulletX - camera.getX(),
                            bulletY - camera.getY(),
                            null
                    );
                }

                joystick.draw(canvas);

                // Малювання кнопок
                shootButton.draw(canvas);
                accelerateButton.draw(canvas);
            } finally {
                getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float touchX = event.getX();
        float touchY = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                if (accelerateButton.isTouched(touchX, touchY)) {
                    playerSpeed = ACCELERATED_PLAYER_SPEED;
                    isSpeedButtonPressed = true;
                    isShootButtonPressed = false;
                    return true;
                }

                if (shootButton.isTouched(touchX, touchY)) {
                    isShootButtonPressed = true;
                    isSpeedButtonPressed = false;
                    return true;
                }

                isSpeedButtonPressed = false;
                isShootButtonPressed = false;
                joystick.updateHandle(touchX, touchY);
                break;

            case MotionEvent.ACTION_UP:
                isSpeedButtonPressed = false;
                isShootButtonPressed = false;
                joystick.resetHandle();
                break;
        }
        return true;
    }


    private Point getRandomFloorPosition(int[][] map) {
        Random random = new Random();
        int x, y;

        // Шукаємо випадкову позицію, де є підлога (0)
        do {
            x = random.nextInt(map[0].length);
            y = random.nextInt(map.length);
        } while (map[y][x] != 0);

        return new Point(x, y);
    }
}
发布评论

评论列表(0)

  1. 暂无评论