Let’s Make a Video Game! Part 1

Understanding Game Architecture

We need a Window for the game to run in.  So let’s take a second here and think about the implications of using a UI.  Whatever if we tightly pack our UI into our game logic we will be tied to the platform that the UI is tied to.  So if we Choose Swing for the desktop we will be tied to using Swing.  If we want to run  our game on Android later we’d have tear apart our game to replace the UI with Android stuff.  Let’s keep all of our GUI/Graphics stuff in our test folder so that we don’t tie our logic to a platform.  In the programming world, this is known as Separation of Concerns.

Let’s build that window.

Just below the class declaration create a private instance variable:

public class IntegrationTest {

 

   private boolean isRunning;

 

I renamed the hello method to runGame

   @Test

   public void runGame() throws InterruptedException {

       isRunning = true;

       JFrame frame = new JFrame();

       frame.addWindowListener(new WindowAdapter() {

 

           @Override

           public void windowClosing(WindowEvent e) {

               isRunning = false;

           }

       });

 

       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

       JPanel contentPanel = (JPanel) frame.getContentPane();

 

       contentPanel.setPreferredSize(new Dimension(800, 600));

       contentPanel.setLayout(null);

 

       contentPanel.setPreferredSize(new Dimension(800, 600));

 

       frame.pack();

 

       frame.setVisible(true);

       frame.setResizable(false);

 

       while (isRunning) {

           TimeUnit.MILLISECONDS.sleep(50000);

       }

   }

As you are typing this out, or if you’ve copy/pasted it, you will notice some squiggly lines under classes that can’t be resolved,  Like JFrame. Press alt + enter while the cursor is on that and it will give you the option to import the missing class.

Now all the references are resolved, let’s talk about what’s going on here.

First we have a global boolean and a while loop that runs as long as that global is true.  This keeps the test from exiting before we’re done with it. The TimeUnit.sleep reference keeps the loop from running as fast as it can.  Consider that the loop does nothing so without a throttle it would try to use as much cpu time as it possibly could get its hands on… F that noise.  

The JFrame is our window.  We set it to close when we click the X and we attach a listener to fire on close that will run some code that changes the global boolean to false.  Basically the next time the loop checks the condition it will see that it’s no longer true and the test will end. This gives us a nice clean way to exit.  

I don’t really want to focus in on the Swing UI, it’s a dinosaur and we’re only using it to validate that the code we write will work without having to import natives or configure an Android SDK.  So we’ll be hitting the “I believe” button on the Swing stuff.
Run the test and let’s see what we get.

Window12

 

Nice, we have a blank window that will exit on close that is tied to our test Environment.

Let’s make some aliens!

In our Source Packages, click the package and create a new file, a Java Interface. Call it an Entity. In our Test Packages we also create an interface called EntityView.

An interface is a contract.  We can define the signatures of methods without defining their implementation.  We can implement the interface and we can handle any class that implements the interface as the interface rather than as the class.  

In our case, Entity is the interface that is going to define the model of an Alien, Blast shot or Player and the EntityView will represent what gets painted to the frame graphically.

Let’s make an alien graphic.  Normally, we’d use a sprite but since we don’t have any sprites, we’re going to use a placeholder that we can replace later if we want to .  

Our graphic is going to be as dumb as possible, it’s only going to be a representation of the model to the be drawn later, so we need to create 3 method signatures in our EntityView:

public interface EntityView {

 

   void draw(Graphics g);

 

   void update();

 

   boolean isAlive();

}

 

Now create a new file, a Java Class, in the test packages called AlienView.  After the class name, and before the { open bracket, type implements EntityView.  You’ll see some Squiggles, hit alt + enter and select, “Implement all Abstract methods” to save some time typing.

Our IDE has generated some code for us, but if any of these methods are invoked they will throw a runtime exception that will torpedo our program because we haven’t instantiated the object yet.

Since we want our Entity class to control this class we need to create an instance level variable for an Entity.

We will initialize the variable through the constructor to ensure that this class can’t exist without a model.

} public class AlienView implements EntityView {

 

   private Entity model;

 

   public AlienView(Entity model) {

       this.model = model;

   }

 

   @Override

   public void draw(Graphics g) {

       throw new UnsupportedOperationException(“Not supported yet.”);

   }

 

   @Override

   public void update() {

       throw new UnsupportedOperationException(“Not supported yet.”);

   }

 

   @Override

   public boolean isAlive() {

       throw new UnsupportedOperationException(“Not supported yet.”);

   }

 

We need to start filling on some details here, let’s start with low hanging fruit.  The isAlive method should be easy.

We don’t want to be out of synch with our model so let’s just do a pass through to it to check if we are alive and make an assumption that the model can tell us this.

   @Override

   public boolean isAlive() {

       return model.isAlive();

   }

 

Clearly we haven’t defined anything in the Entity interface so this is going to report that it cannot find the symbol.  Move your cursor over the model.isAlive(); squiggle hit Alt + Enter and select “Create method “isAlive ()” “ in com.drakos.invaders.Entity.

If all goes well, Netbeans will have generated a signature for us in Entity.

public interface Entity {

 

   public boolean isAlive();

   

}

 

The “public” keyword in the method signature isn’t necessary, since anything declared in an interface is by default public, but it was code that I didn’t have to type so I will take it.

Now we have the methods update() and draw().  Either way we need something to update and something to draw so it’s time to figure out what that is.  Earlier I said we’d use a Java 2-d shape, so let’s use a Rectangle class.

Since this class can’t exist without its model, there should never be a time when we can’t define the graphic from its model so let’s initialize it in the constructor. To instantiate a Rectangle we need 2 integers: one to represent width and one to represent height.  Let’s assume the model keeps track of that. Either manually create those signatures in Entity or have Netbeans generate them for you.

draw()

Draw gets called every time a frame is drawn, so let’s say you have 60 Frames per second. That’s 60 draws per second. Java’s Swing framework is going to pass us a Graphics object that we’ll manipulate to draw our alien.  First, we should make sure that we are still alive.  There’s no sense in drawing our graphic if we aren’t even in the game right? Then we will do the draw itself.

   @Override

   public void draw(Graphics g) {

       if (model.isAlive()) {

           Graphics2D g2 = (Graphics2D) g;

           g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

           g2.setPaint(Color.red);

           g2.fill(graphic);

           g2.setPaint(Color.YELLOW);

           g2.draw(graphic);

       }

   }

 

update()

In the update, we’ll update this Alien’s position.  We’ll need to add some methods to Entity at this point, you got that covered right?

   @Override

   public void update() {

       graphic.setLocation(model.getXPos(), model.getYPos());

   }

 

That’s all we should need in our view.

Author: Bruce Brown

Share This Post On

Submit a Comment

Your email address will not be published. Required fields are marked *