Search code examples
javamultithreadingperformanceframes

FPS Counter in my game (Java)


I need to add a working FPS counter in my game (Pong). To make an FPS counter I apparently also need a game loop, I tried making a game loop and FPS counter separately and it seems to be working. However, I am having some problems adding the FPS counter to my Pong game.

This is the code for the game loop/FPS counter:

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.Timer;

public class Main extends Canvas implements Runnable{

JFrame frame;
int windowWidth, windowHeight;
Point bollXY;
int screenWidth, screenHeight;
Timer timer;
Image bildPaddel;
Image bildBollen;
int paddelY;
int paddel2Y;
boolean paddelUp, paddelDown;
Random rand;
int score, score2;
boolean bollUp, bollRight, changeDirection;
boolean paused;
int fruktDistansRand;
long time;
int fps, newfps;

private boolean running = false;
private Thread thread;



private synchronized void start() {
    if(running)
        return;

    running = true;
    thread = new Thread(this);
    thread.start();
}

private synchronized void stop(){
    if(!running)
        return;

    running = false;
    try {
        thread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.exit(1);
}

public static void main(String args[]) {

    Main main = new Main();

    JFrame frame = new JFrame();
    frame.add(main);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setResizable(false);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);

    main.start();
}


@Override
public void run() {
    long lastTime = System.nanoTime();
    final double amountOfTicks = 60.0;
    double ns = 1000000000 / amountOfTicks ;
    double delta = 0;
    int updates = 0;
    int frames = 0;
    long timer = System.currentTimeMillis();

    while(running) {
        long now = System.nanoTime();
        delta += (lastTime - now) / ns;
        lastTime = now;
        if(delta >= 1) {
            updates++;
            delta--;
        }
        frames++;

        if(System.currentTimeMillis() - timer > 1000) {
            timer += 1000;
            System.out.println("Ticks: " + updates + ", FPS: " + frames);
            updates = 0;
            frames = 0;
        }
    }
    stop();
}
}

I tried adding the FPS counter to the game but it did not work for some reason. If you know how to make a better FPS counter and/or how to add it to the game please help. By the way, I do NOT want the FPS locked at 60.


Solution

  • In this answer, I'm not finding the cause of your counter's problem. It's fairly complicated for me. But I will give you some code to help get started:

    This is a code for an FPS counter:

    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.Timer;
    
    public class FPSCounter implements ActionListener {
        private final Timer resetTimer;
        private int current, last;
    
        public FPSCounter() {
            resetTimer = new Timer(1000, this);
        }
    
        public synchronized void start() {
            resetTimer.start();
            current = 0;
            last = -1;
        }
    
        public synchronized void stop() {
            resetTimer.stop();
            current = -1;
        }
    
        public synchronized void frame() {
            ++current;
        }
    
        @Override
        public synchronized void actionPerformed(final ActionEvent e) {
            last = current;
            current = 0;
        }
    
        public synchronized int get() {
            return last;
        }
    
        public static void main(final String[] args) {
            final FPSCounter cnt = new FPSCounter();
            cnt.start();
            new Timer(2000, e -> { System.out.println(cnt.get()); }).start();
            while (true)
                cnt.frame();
    //        cnt.stop();
        }
    }
    

    The main method tests that the counter works (every 2 seconds, it prints the number of empty loops per second).

    You can use it like so:

    1. Add a private FPSCounter fpscnt; in your game class.
    2. Construct the object in your game's constructor like so: fpscnt = new FPSCounter();.
    3. Start the counter before the game starts, by calling: fpscnt.start();. You can call this inside the game's constructor for example.
    4. Now, whenever a frame is painted, you shall call the frame() method of the counter. I'm demonstrating up next...
    5. Now, whenever you want to get the number of frames per second, you shall call the get() method of the counter. I'm demonstrating up next...

    So now, inside your game's paintComponent() method, the lines printing the number of frames/sec shall look like this:

    fpscnt.frame(); //Because this is a new frame paint operation.
    g.setFont(new Font("Arial", Font.BOLD, 25));
    g.drawString("" + fpscnt.get(), 5, 22); //Paint the FPS.
    

    Note:
    Each value returned by get() method is delayed for 1 second. I mean if you see 64 fps, then 64 where the frames in the last (past) second.

    So your game's Main class should then look like this:

    import java.awt.*;
    import java.awt.event.*;
    import java.util.Random;
    import javax.swing.*;
    
    public class Main extends JPanel implements KeyListener, ActionListener{
        public static class FPSCounter implements ActionListener {
            private final Timer resetTimer;
            private int current, last;
    
            public FPSCounter() {
                resetTimer = new Timer(1000, this);
            }
    
            public synchronized void start() {
                resetTimer.start();
                current = 0;
                last = -1;
            }
    
            public synchronized void stop() {
                resetTimer.stop();
                current = -1;
            }
    
            public synchronized void frame() {
                ++current;
            }
    
            @Override
            public synchronized void actionPerformed(final ActionEvent e) {
                last = current;
                current = 0;
            }
    
            public synchronized int get() {
                return last;
            }
        }
    
    
    // Variabler
    JFrame frame;
    int windowWidth, windowHeight;
    Point ballXY;
    int screenWidth, screenHeight;
    Timer timer;
    Image imagePaddle;
    Image imageBall;
    int paddleY;
    int paddle2Y;
    boolean paddelUp, paddelDown;
    Random rand;
    int score, score2;
    boolean ballUp, ballRight, changeDirection;
    boolean paused;
    int DistanceRand;
    long time;
    int fps, newfps;
    
    private final FPSCounter fpscnt; //added line (step 1).
    
    // Konstruktor
    public Main(){
        // Definera variabler
        frame = new JFrame();
        imagePaddle = new ImageIcon("src/images/Player.png").getImage();
        imageBall = new ImageIcon("src/images/Pong.png").getImage();
        ballXY = new Point(673, 352);
        paddleY = 312;
        paddle2Y = 312;
        paddelUp = false;
        rand = new Random();
        score = 0;
        score2 = 0;
        ballUp = false;
        ballRight = false;
        changeDirection = false;
        paused = false;
        DistanceRand = 0;
        time = System.currentTimeMillis();
        fps = 0;
        newfps = 0;
    
        // Lyssnare
        frame.addKeyListener(this);
    
        // Bygga fönstret
    
        frame.add(this);
    
        // Obligatoriska egenskaper
        frame.setTitle("Pong");
        frame.setSize(1366, 724);
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        this.setBackground(Color.BLACK);
        frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
        ballUp = true;
        ballRight = true;
        changeDirection = false;
        ballXY = new Point();
    
        frame.add(this);
    
        timer = new Timer(5, this);
        timer.start();
    
        fpscnt = new FPSCounter(); //added line (step 2).
        fpscnt.start(); //added line (step 3).
    }
    // Metoder
    public static void main(String[] args) {
        new Main();
    }
    
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
    
        g.drawImage(imageBall, ballXY.x, ballXY.y, null);
        g.drawImage(imagePaddle, 50, paddleY, null);
        g.drawImage(imagePaddle, 1300, paddle2Y, null);
        g.setFont(new Font("Arial", Font.BOLD, 100));
        g.setColor(Color.WHITE);
        g.drawString(score2 + "", 1002, 100);
        g.drawString(score + "", 314, 100);
    
        if(paused){ 
            g.setFont(new Font("Arial", Font.BOLD, 80));
            g.drawString("PAUSED", 510, 370);
            timer.stop();
        }
        if(System.currentTimeMillis() - time >= 1000){
            time = System.currentTimeMillis();
            fps = newfps;
            newfps = 0;
        }
        else{
            newfps++;
        }
        fpscnt.frame(); //added line (step 4).
        g.setFont(new Font("Arial", Font.BOLD, 25));
        g.drawString("" + fpscnt.get(), 5, 22); //added line (step 5).
    }
    
    public void AI(){
        DistanceRand = rand.nextInt(200) + 960;
        if(ballXY.x > DistanceRand && ballXY.x < 1380 && ballRight && paddle2Y > 0 && paddle2Y < 596){
            if(paddle2Y + 50 < ballXY.y){
                paddle2Y = paddle2Y + 3;
            }
            else{
                paddle2Y = paddle2Y - 3;
            }
            if(paddle2Y <= 0){
                paddle2Y = paddle2Y + 3;
            }
            if(paddle2Y >= 596){
                paddle2Y = paddle2Y - 3;
            }
        }
    }
    
    public void ifUp(){
        if(ballUp){
            if(changeDirection){
                if(ballXY.y < 0){
                    ballUp = false;
                }
                else{
                    ballXY.y = ballXY.y - 3;
                }
            }
            else{
                if(ballXY.y < 0){
                    ballUp = false;
                }
                else{
                    ballXY.y = ballXY.y - 3;
                }
            }
        }
    
        else{
            if(changeDirection){
                if(ballXY.y > 675){
                    ballUp = true;
                }
                else{
                    ballXY.y = ballXY.y + 3;
                }
            }
            else{
                if(ballXY.y > 675){
                    ballUp = true;
                }
                else{
                    ballXY.y = ballXY.y + 3;
                }
            }
        }
    }
    
    public void update(){
        if(paddelUp){
            if(paddleY > 0){
                paddleY = paddleY - 3;
            }
        }
        if(paddelDown){
            if(paddleY < 596){
                paddleY = paddleY + 3;
            }
        }
    
        if(ballRight){
    
            if(ballXY.x > 1290 && ballXY.x < 1300 && ballXY.y < paddle2Y + 100 && ballXY.y > paddle2Y-20){
    
                if(!ballUp && ballXY.y < paddle2Y){
                    changeDirection = true;
                    ballUp = true;
                }
                else if(ballUp && ballXY.y > paddle2Y + 80){
                    changeDirection = true;
                    ballUp = false;
                }
    
                ballRight = false;
    
            }
            else if(ballXY.x > 1600){
                score++;
                ballXY.y = rand.nextInt(690);
                ballXY.x = 678;
            }
            else
                ballXY.x = ballXY.x + 3;
    
    
            ifUp();
        }
        else{
            if(ballXY.x > 50 && ballXY.x < 60 &&ballXY.y < paddleY + 100 && ballXY.y > paddleY-20){
                if(!ballUp && ballXY.y < paddleY){
                    changeDirection = true;
                    ballUp = true;
                }
                else if(ballUp && ballXY.y > paddleY + 80){
                    changeDirection = true;
                    ballUp = false;
                }
                ballRight = true;
            }
            else if(ballXY.x < -244){
                score2++;
                ballXY.x = 678;
                ballXY.y = rand.nextInt(596);
            }
            else
                ballXY.x = ballXY.x - 3;
    
            ifUp();
        }
        AI();
        repaint();
    }
    
    @Override
    public void actionPerformed(ActionEvent e) {
        update();
    }
    
    @Override
    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_W || e.getKeyCode() == KeyEvent.VK_UP){
            paddelUp = true;
        }
        if(e.getKeyCode() == KeyEvent.VK_S || e.getKeyCode() == KeyEvent.VK_DOWN){
            paddelDown = true;
        }
        if(e.getKeyCode() == KeyEvent.VK_SPACE){
            if(paused){
                paused = false;
                timer.start();
            }
            else{
                paused = true;
            }
        }
    }
    
    @Override
    public void keyReleased(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_W || e.getKeyCode() == KeyEvent.VK_UP){
            paddelUp = false;
        }
        if(e.getKeyCode() == KeyEvent.VK_S || e.getKeyCode() == KeyEvent.VK_DOWN){
            paddelDown = false;
        }
    }
    
    @Override
    public void keyTyped(KeyEvent e) {
    
    }
    }