If you got a bit lost anywhere along the way, you can download my files for part 3 here:
Feel free to download these and use them to get going again.
In this part we’re going to create a new controller class that handles input from the keyboard. We’ve seen before how Java is event driven. We can register an object to listen to events and respond accordingly. This is good for creating responsive applications with complex GUIs, but in simple games it can cause problems because we quite often want to control the timing of when things happen.
It’s quite common in game engines to have a “Game Loop” that loops around updating objects, responding to inputs and drawing everything. Let’s build a simple GameController
to listen for keyboard events and remember the states of the buttons we’re interested in.
Create a new .java
file called GameController.java
:
// GameController.java
package covid19;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
public class GameController implements KeyListener {
public boolean up, down, left, right, action_1;
public void keyPressed(KeyEvent e)
{
int key = e.getKeyCode();
}
public void keyReleased(KeyEvent e)
{
int key = e.getKeyCode();
}
/*
* Not used, needs to be implemented for Interface
*/
public void keyTyped(KeyEvent e){}
}
A very simple object that simply sits listening to the keyboard and not doing very much at all when a key is pressed (yet!). Notice we have 5 public attributes for our class for each of the possible inputs we’re interested in. I’ve made this public because I’m lazy, it’s probably not the best way to do it but it does simply things so we’ll run with it.
Let’s create a GameController
object and register it as the thing listening to events:
// JDojoMaster.java
private void init() {
controller = new GameController(); // Add these lines to the top
addKeyListener(controller); // of the init() method.
//... rest of the method code still here.
}
Don’t forget to add a variable for controller at the top of the class:
// JDojoMaster.java
protected Scene scene;
protected Sprite player;
protected Spritesheet spritesheet;
protected GameController controller; // this is the new line
Ok, we’re keeping things simple. Next we’ll add some code to update the action variables in the GameController
class:
// GameController.java
public void keyPressed(KeyEvent e)
{
int key = e.getKeyCode();
if (key == KeyEvent.VK_UP) {
up = true;
}
if (key == KeyEvent.VK_DOWN) {
down = true;
}
if (key == KeyEvent.VK_LEFT) {
left = true;
}
if (key == KeyEvent.VK_RIGHT) {
right = true;
}
if (key == KeyEvent.VK_X) {
action_1 = true;
}
}
Replace the keyPressed()
method with the code above. Whenever the Operating System detects an event, it looks at all of the applications that are interested in the particular event it just saw and passes the details of that event to it. In this case, we’re registered for keyboard events, so when we receive the event it comes with the various details of that event, including which key is pressed.
The KeyEvent
class has a great big long list of constants that define each possible key event. You can see here we’re interested in KeyEvent.VK_UP
, which is the up arrow key, and so on through the 4 cursor keys, plus the x
key for actions.
If we detect a key press we’re interested in, we simply set our action variable to true
to indicate that we got that key.
Next we need to detect when a key is released:
// GameController.java
public void keyReleased(KeyEvent e)
{
int key = e.getKeyCode();
if (key == KeyEvent.VK_UP) {
up = false;
}
if (key == KeyEvent.VK_DOWN) {
down = false;
}
if (key == KeyEvent.VK_LEFT) {
left = false;
}
if (key == KeyEvent.VK_RIGHT) {
right = false;
}
if (key == KeyEvent.VK_X) {
action_1 = false;
}
}
Almost identical, except this time we set the action variables to false
. Be sure to replace the keyReleased()
method, don’t just shove it in underneath the existing one!
Just before we move on to the next step and actually do something with these controls, let’s add a little method to the Sprite
class. We want to be able to change the image that is being displayed. Let’s add a setImage()
method:
// Sprite.java
public void setImage(BufferedImage img) {
image = img;
}
Nice. We can pass in tiles from our Spritesheet
to change how the sprite looks.
Hold onto your hats, because quite a lot happens here.
First, let’s create an empty gameloop method and get it running:
// JDojoMaster.java
public void gameloop() {
while (true) {
scene.repaint();
}
}
Now call that method in the main()
function:
// JDojoMaster.java
public static void main(String[] args) {
JDojoMaster window = new JDojoMaster();
window.gameloop(); // This line is new
}
Ok, we have a gameloop. It’s not a very good gameloop though, because it runs as fast as it possibly can, which has the potential to take up a lot of processing time. Our game isn’t a very good citizen. Let’s get our gameloop to run at 60Hz - 60 times per second. Since it draws every time, this means our game will run at 60 frames per second!
Replace the gameloop method with this one:
// JDojoMaster.java
public void gameloop() {
long timer;
while (true) {
timer = System.currentTimeMillis();
// Update
scene.repaint();
timer = (1000 / 60) - (System.currentTimeMillis() - timer);
if (timer > 0) {
try {
Thread.sleep(timer);
}
catch(Exception e){}
}
//else - update & draw taking too long for 60fps
}
}
Java has a built-in timer object that counts milliseconds. We can use that to time how long the scene.repaint()
(and other stuff later) takes, then sleep for the rest of the time needed to make it always take 1/60th of a second (approx 16.6 milliseconds). Whilst it’s waiting, we call Thread.sleep()
, which puts the application to sleep and lets the Operating System know that it can do other stuff if it needs to. This is a good way to wait for things.
Finally, let’s actually do something!
Add the following code underneath the // Update
comment in the gameloop()
method:
if (controller.up) {
player.setImage(spritesheet.getTile(1));
}
if (controller.down) {
player.setImage(spritesheet.getTile(0));
}
if (controller.left) {
player.setImage(spritesheet.getTile(3));
}
if (controller.right) {
player.setImage(spritesheet.getTile(2));
}
Compile and run your program. You should be able to use the arrow keys to make your little guy face in different directions!
Cool huh!?