BeChris 100
BeChris 100

Reputation: 83

Performance Issues on a Swing GUI Game

Let me brief up the performance issue on how I would explain about gaming: When the game starts up, it starts normally at a good performance. No issues. After a few seconds, it slows down the performance, as it would go at around 10 to 30 FPS, but if I hold the corresponding Arrow / WASD Key that the snake runs into that given direction, then it runs like it would run smoothly. Tried to comment out the code that may impact the performance (every method, including move(Graphics graphics)), but to no avail. I even commented out the Key Listener, so I could check if there was any problem. The performance issue still persists.

I tried including Game Threads or other workers to at least bypass this performance issue. If my code is correct, then it was definitely something from my OS performance, although I even checked it with "System Monitor" since I am dual-booting Linux (Ubuntu) and Windows 11. No memory or CPU draining. The java process was at around 50-70 MB and used only 1% of my CPU usage. I checked the Main Class and the Window class, but there was nothing that impacted the performance. If the code is too big in one class file, then how should I reduce the class file size? All other classes will be made later on, but I had to make at least the sample level for it.

Leaving the code in case someone needs to check out:

public class GamePanel extends JPanel implements ActionListener {

private static final int SCREEN_WIDTH = 800, SCREEN_HEIGHT = 800;

private static final int UNIT_SIZE = 25;
private static final int GAME_UNITS = (SCREEN_WIDTH * SCREEN_HEIGHT) / UNIT_SIZE;

private static final int DELAY = 75;

private int[] x = new int[GAME_UNITS];
private int[] y = new int[GAME_UNITS];

private int bodyParts = 1;
private int score = 0;
private int appleX = 0, appleY = 0;
private char direction = 'R';
private boolean gameRunning = false;

private final Random random;
private Timer timer;

public GamePanel() {
    random = new Random();

    setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
    setBackground(Color.BLACK);
    setFocusable(true);
    addKeyListener(new KeyPressAdapter());

    startGame();
}

public void startGame() {
    createApple();

    gameRunning = true;

    timer = new Timer(DELAY, this);
    timer.start();
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    draw(g);
}

// render the grid view
private void gridView(Graphics graphics) {
    for (int i = 0; i < SCREEN_HEIGHT / UNIT_SIZE; i++) {
        graphics.drawLine(i * UNIT_SIZE, 0, i * UNIT_SIZE, SCREEN_HEIGHT);
        graphics.drawLine(0, i * UNIT_SIZE, SCREEN_WIDTH, i * UNIT_SIZE);
    }
}

// draws game onto the window
public void draw(Graphics graphics) {
    if (gameRunning) {
        gridView(graphics);

        graphics.setColor(Color.RED);
        graphics.fillRect(appleX, appleY, UNIT_SIZE, UNIT_SIZE);

        for (int i = 0; i < bodyParts; i++) {
            if (i == 0)
                graphics.setColor(Color.GREEN);
            else
                graphics.setColor(Color.WHITE);

            graphics.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
        }

        graphics.setColor(Color.CYAN);
        graphics.setFont(new Font(Font.SERIF, Font.PLAIN, 32));
        FontMetrics fontMetrics = getFontMetrics(graphics.getFont());
        graphics.drawString("Score: " + score, (SCREEN_WIDTH - fontMetrics.stringWidth("Score: " + score)) / 2, graphics.getFont().getSize());
    } else
        _GameOverScreen_(graphics);
}

// creates a new apple
public void createApple() {
    appleX = random.nextInt(SCREEN_WIDTH / UNIT_SIZE) * UNIT_SIZE;
    appleY = random.nextInt(SCREEN_HEIGHT / UNIT_SIZE) * UNIT_SIZE;
}

// moves the player
public void move() {
    for (int i = bodyParts; i > 0; i--) {
        x[i] = x[i - 1];
        y[i] = y[i - 1];
    }

    switch (direction) {
        // case for "up"
        case 'U' -> y[0] = y[0] - UNIT_SIZE;

        // case for "left"
        case 'L' -> x[0] = x[0] - UNIT_SIZE;

        // case for "right"
        case 'R' -> x[0] = x[0] + UNIT_SIZE;

        // case for "down"
        case 'D' -> y[0] = y[0] + UNIT_SIZE;
    }
}

public void appleCheck() {
    if (x[0] == appleX && y[0] == appleY) {
        bodyParts++;
        score++;

        createApple();
    }
}

public void collisionCheck() {
    // body collision checker
    for (int i = bodyParts; i > 0; i--) {
        if (x[0] == x[i] && y[0] == y[i]) {
            gameRunning = false;
            break;
        }
    }

    // border collision detect
    if (x[0] < 0)
        gameRunning = false;
    if (x[0] > SCREEN_WIDTH)
        gameRunning = false;
    if (y[0] < 0)
        gameRunning = false;
    if (y[0] > SCREEN_HEIGHT)
        gameRunning = false;

    if (!gameRunning)
        timer.stop();
}

private void _GameOverScreen_(Graphics graphics) {
    String contentTitle = "! GAME OVER !";
    String contentText = "Press R to restart";
    String contentScore = "Reached score: " + score;

    graphics.setColor(Color.RED);
    graphics.setFont(new Font(Font.SERIF, Font.BOLD, 75));
    FontMetrics metricsTitle = getFontMetrics(graphics.getFont());
    graphics.drawString(contentTitle, (SCREEN_WIDTH - metricsTitle.stringWidth(contentTitle)) / 2, (SCREEN_HEIGHT / 2) - 110);

    graphics.setColor(Color.DARK_GRAY);
    graphics.setFont(new Font(Font.SERIF, Font.PLAIN, 40));
    FontMetrics metricsText = getFontMetrics(graphics.getFont());
    graphics.drawString(contentText, (SCREEN_WIDTH - metricsText.stringWidth(contentText)) / 2, (SCREEN_HEIGHT / 2) - 60);

    graphics.setColor(Color.GREEN);
    graphics.setFont(new Font(Font.SERIF, Font.PLAIN, 40));
    FontMetrics metricsScore = getFontMetrics(graphics.getFont());
    graphics.drawString(contentScore, (SCREEN_WIDTH - metricsScore.stringWidth(contentScore)) / 2, (SCREEN_HEIGHT / 2) + 70);
}

@Override
public void actionPerformed(ActionEvent actionEvent) {
    if (gameRunning) {
        move();
        appleCheck();
        collisionCheck();
    }

    repaint();
}

public class KeyPressAdapter extends KeyAdapter {
    @Override
    public void keyPressed(KeyEvent e) {
        super.keyPressed(e);

        switch (e.getKeyCode()) {
            case KeyEvent.VK_LEFT, KeyEvent.VK_A -> {
                if (direction != 'R')
                    direction = 'L';
            }
            case KeyEvent.VK_UP, KeyEvent.VK_W -> {
                if (direction != 'D')
                    direction = 'U';
            }
            case KeyEvent.VK_DOWN, KeyEvent.VK_S -> {
                if (direction != 'U')
                    direction = 'D';
            }
            case KeyEvent.VK_RIGHT, KeyEvent.VK_D -> {
                if (direction != 'L')
                    direction = 'R';
            }
            case KeyEvent.VK_Q -> System.exit(0);
            case KeyEvent.VK_R -> {
                score = 0;
                bodyParts = 1;

                x = new int[GAME_UNITS];
                y = new int[GAME_UNITS];

                x[0] = 0;
                y[0] = 0;

                direction = 'R';

                createApple();

                if (!gameRunning) {
                    gameRunning = true;
                    timer.start();
                }
            }
        }
    }
}

}

Upvotes: 0

Views: 42

Answers (0)

Related Questions