This post is part two of a multipart series journaling the game writing process. You can find the first post here.
The engine of a game consists of all of the components that help you create the game. It includes the graphics renderer, physics, sound, networking, and artificial intelligence, among others. For the simple game that I’m writing, the engine will be correspondingly simple. The key point is that all of the subsystems should be as separated as possible. Since Java is object-oriented, this means separating the components into classes. This facilitates easy modification in the future. I have all of the code in an Eclipse project here, and an executable Jar of the result here. Referencing the code as you read this post will make a lot more sense. I hope you will be able to bring your knowledge of Java to making a game after reading this post. It’s quite long, but I think I’ve put in a ton of detail that might be valuable to somebody out there.
The entry point of the game is the main class named Glade. I usually name my main classes Main, but that’s horrible choice: it’s not descriptive and I already have 10 of them in my workspace. This time, I named it Glade, the code name I have for this project. This class creates the Renderer, the Controller, the Stage, the Display, and the Player. These objects are accessible as static fields of the Glade class. This way, they are easily accessible to every object in the game. I could have passed them in through constructors, but that would have been cumbersome and without any benefit.
Then, Glade starts a timer that fires at a frequency determined by the frame rate. At first, I set the frame rate to be 25 FPS, but that was very sluggish, so I pumped it up to the standard 60 FPS. There is no guarantee that the game will hit his frame rate. It only acts as a maximum. If you don’t cap the frame rate, your game is going to run extremely fast on fast computers, which will make it unplayable. I didn’t do this for my first game that I made several years ago on old school computers. On my modern laptop, it is so fast that I my character dies within seconds.
The Renderer class is responsible for the graphics component of the engine. It extends JFrame which has the paint method. The method gives us a Graphics object that we can draw to in order to display things on the screen. This paint method is called each time the timer hits. The Renderer puts the window into full screen mode if it’s available. Otherwise, it just uses a regular window. The full screen mode is significantly faster because it can bypass the windowing system and draw directly to the screen. Plus, it looks much nicer; people usually don’t want to play games in windowed mode. Renderer implements a double-buffering strategy. If you directly draw to the screen, it is slower and gives you weird flickering. So, we first do all of our drawing onto an off-screen buffer and then draw it to the screen after we’re done.
All of the objects implementing the Drawable interface are stored in a HashSet in Renderer. Drawable requires the object to implement a draw method. Each object is passed in the graphics object to draw to. This way, every game object is responsible for drawing itself. The Renderer clears the screen by drawing the background onto the buffer, and then iterates through all of the Drawable objects, calling their draw methods. Every Drawable gets drawn each time paint is called. However, you are not allowed to add to or remove from a HashSet that you are iterating over. This is because the iterator won’t be able to keep track of where it is and where it’s supposed to go next if we randomly change the HashSet that we are iterating over. But, we need to be able to add and remove Drawables, because enemies and bullets need to be removed all the time. To facilitate this, two other HashSets are maintained. One contains the Drawables that need to be added to the main HashSet, and the other contains the Drawables the need to be removed. So, adding a Drawable to the Renderer doesn’t mean adding to the main HashSet, but adding to the add HashSet. This way, we never add or remove while iterating. All of the objects in the add and remove HashSets are added and removed from the HashSet storing the Drawables before iteration begins.
The Environment object is created by the Renderer. For now, all it does is draw a transparent cyan background. The transparency makes it so that objects leave a smooth shadow in their wake. Although drawing such a huge transparent rectangle each frame is somewhat expensive, it leaves a lovely effect. In the future, this class can be used for all sorts of background effects.
The Controller is responsible for the game logic. Its main job is to iterate through all of the Actables, calling their act methods, when the timer hits. (So, every object’s act and draw methods are called each frame.) The act method is where the main logic for each object is placed. Since it is called each frame, anything that must be done as the game is running must be placed there. This includes moving the object around, checking for collisions, dealing with input, among many other functions. Eventually, the controller will also deal with bigger picture aspects such as level progression, creating enemies, spawning power-ups, managing high scores and the like.
The Stage is what holds and manages the Actables. I usually include this functionality in Controller, but I decided to split it off this time. The Stage is also responsible for facilitating collision detection. Bullets can ask the Stage if its colliding with an enemy. If they are, they would do damage to the enemies and be destroyed. For now, the Stage will naively just run through all of the enemies, asking each one if they collide with the bullet in question. In the future, it can be rewritten to be much more efficient if performance is an issue.
The Display object stores stats like the score and remaining lives and displays them on the screen. It can be extended to create a snazzy display in the future.
The Player class processes keyboard and mouse inputs to manipulate the state of the player’s little ship. In addition to implementing Actable and Drawable, it also implements KeyListener, MouseListener, and MouseMotionListener. Whenever the mouse buttons or the movement keys are pressed, the corresponding boolean variables are flipped. Whenever the mouse is moved, the coordinates are stored. These variables are tested in the Player’s act method and the corresponding action is taken. If the mouse is pressed, a Photon is created at the location of the Player in the direction of the mouse cursor. In the future, the Player will also be responsible for checking if it is colliding with an enemy and reducing the health accordingly. It draws a square at the location specified by its x and y coordinates in its draw method.
A Photon is a subclass of the Bullet class which encompasses all of the projectile weapons the Player has at his disposal. Bullet is given its origin and target at construction and some trig calculates its x and y velocities. It then updates its location each step and kills itself if it is outside the boundaries of the stage. For now, all Bullets follow the same trajectory. I can move that functionality to the subclasses if need be. All sorts of funky bullets can be created this way.
Play with the code. Try changing the colors or the constants. Create new bullet types. That’s how I learned to program games. I took an existing one and changed the graphics and added bullets and power-ups. Leave me any questions or comments!