Is this a good foundation?

Hi, I’m new. :smiley: Looks like a great community. I was considering posting this in the newbies/problems section but I get the idea this one might be more suitable. Would appreciate a move if it’s wrong.

So, I’m looking to create a relatively basic game in Java2D. I know, I’ve looked into Slick/Pulp a bit and it may make it easier, but I want to try Java2D and my own classes first. Some may call it a waste of time, I see it more as an academic persuit. :wink: If I max out Java2D’s capabilities, then great I’ve understood it, but I don’t aim on getting near that point anyway.

At first I was using a basic buffered image as a back buffer and using “passive” rendering, and a very basic game loop. I then read up a bit on active rendering techniques (including buffer strategies, which I didn’t even realise could be used in applets!) and using a more sophisicated way of controlling game speed. Before, the inaccuracies of the timing method became more and more pronounced as you wanted the game to speed up. There is a danger of it becoming totally out of sync with the rendering at high speeds.

This forum has been a great help, especially Gudradain and ra4king with the loop/rendering (I hope you don’t mind ;)).

I’d be grateful for any feedback/criticism of this basic functionality before I get deeper into the game’s development and find I may have to go back and alter a lot of stuff (though I’m sure this will inevitably happen anyway…). I’ve just commented on some stuff to help me try to understand what’s going on. Maybe it’ll be useful for other newbies as well!

package games;

import java.applet.Applet;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferStrategy;

public class GameApplet extends Applet implements Runnable, MouseListener{
    //<editor-fold defaultstate="collapsed" desc="class declarations">
    static int width = 800;
    static int height = 600;
    Canvas canvas;
    BufferStrategy bufferStrategy;
    boolean running = true;

    //game loop control
    long desiredFPS = 60;
    long desiredDeltaLoop = (1000*1000*1000)/desiredFPS;

    Thread gameloop;
    ImageEntity background; //a class that loads images for use

    int frameCount = 0;
    int frameRate = 0;
    long startTime = System.currentTimeMillis();
    //</editor-fold>

    @Override
    public void init() {
        background = new ImageEntity(this); //uses external class as an image loader
        background.load("bg.jpg");
        System.out.println("loaded background"); //testing
        if (bufferStrategy == null) {
            setPreferredSize(new Dimension(width, height));
            this.setSize(new Dimension(width, height));
            this.setIgnoreRepaint(true); //ensure os does not interfere in painting

            canvas = new Canvas();
            canvas.setBounds(0, 0, width, height);

            canvas.setIgnoreRepaint(true);
            add(canvas);
            canvas.createBufferStrategy(2); //create front+back buffer
            bufferStrategy = canvas.getBufferStrategy(); //link the canvas to that strategy
            canvas.requestFocus();
        }
    }

    @Override
    public void start() {
        gameloop = new Thread(this);
        gameloop.start();
        running = true;
    }

    @Override
    public void run() {
        // thread run event (game loop)
        long beginLoopTime;
        long endLoopTime;
        long currentUpdateTime = System.nanoTime();
        long lastUpdateTime;
        long deltaLoop;

        while (!isActive()) {
            Thread.yield();//wait on the thread if applet is not active/visible
        }
        while (running) {
            beginLoopTime = System.nanoTime(); //time before render
            render(); //draw objects onto the canvas/buffer
            lastUpdateTime = currentUpdateTime;
            currentUpdateTime = System.nanoTime();
            update((int) ((currentUpdateTime - lastUpdateTime) / (1000 * 1000))); //update the game logic with this speed
            endLoopTime = System.nanoTime();
            deltaLoop = endLoopTime - beginLoopTime;

            //I know this may be inaccurate due to Milliseconds, but it's good enough for me for now
            //<editor-fold defaultstate="collapsed" desc="fps calculation">
            frameCount++;
            if (System.currentTimeMillis() > startTime + 1000) {//if a second passes
                startTime = System.currentTimeMillis();//update to this second
                frameRate = frameCount;
                System.out.println(frameRate);
                frameCount = 0;
            }
            //</editor-fold>

            //<editor-fold defaultstate="collapsed" desc="synchronisation">
            if (deltaLoop > desiredDeltaLoop) {
                //Do nothing. We are already late.
            } else {
                try {
                        Thread.sleep((desiredDeltaLoop - deltaLoop) / (1000 * 1000));
                } catch (InterruptedException e) {
                   }
            }
            if (!isActive()) {
                System.out.println("Inactive"); //testing
                return;
            }
            //</editor-fold>
        }
    }

    @Override
    public void stop() {
        running = false;//is this really necessary or would the thread cease on its own if running is already false elsewhere?
                        //volatile?
    }

    public void render() {
        try {
            do {
                do {
                    Graphics2D g = (Graphics2D) bufferStrategy.getDrawGraphics(); //creates a g2d object, takes graphics context from strategy
                       render(g); //draw
                    g.dispose(); //manually free up resources
                } while (bufferStrategy.contentsRestored()); //buffer contents restored since being lost
                bufferStrategy.show();
            } while (bufferStrategy.contentsLost());  //has the buffer been restored?
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    public void update(int deltaTime){
         //update game logic

    }

    protected void render(Graphics2D g) {
        g.drawImage(background.getImage(), 0, 0, this); //returns an image and draws it onto the context

    }

     @Override
    public void mouseClicked(MouseEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void mousePressed(MouseEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void mouseExited(MouseEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}

It looks really good so far!
Really feels good to have helped someone out there on the interwebz :slight_smile:

Thoughts:

  1. The “running” variable is unnecessary since all threads are killed as soon as the applet is closed.
  2. For accurate System.currentTimeMillis() readings, use this trick in init():

if(System.getProperty("os.name").startsWith("Win")) {
    new Thread() {
        {
            setDaemon(true);
            start();
        }

        public void run() {
            while(true) {
                try {
                    Thread.sleep(Long.MAX_VALUE);
                }
                catch(Exception exc) {}
            }
        }
    }
}

  1. Thread.sleep is fine for now, but for a smoother game loop, consider using Thread.yield (smoother, hogs CPU) or Thread.sleep(1) (less smooth, less CPU) in a while loop:

long startTime = System.currentTimeMillis();
while(System.currentTimeMillis()-startTime < sleepTime) {
    if(useYield)
        Thread.yield();
    else
        try {
            Thread.sleep(1);
        }
        catch(Exception exc) {}
}

Looks good, but you might want to put the MouseListener in another class or you’ll end up with a huge general Game class in the end. I’d create an input manager or something.

Thanks muchly for your responses! :slight_smile: