Using the Input Manager
To try out the InputManager, first you'll create a simple game. Later, you'll add functionality to allow the player to configure the keyboard. The first game you create will have the hero be able to move left and right and to jump. Also, you'll add the capability to pause the game. It's not really a game yet-there are no bad guys or even goals-but it's a step closer.
Pausing the Game
Believe it or not, the user occasionally might want to temporarily stop playing a game to get a soda or run to the restroom. And to top it off, the user will want to-gasp!-return to the game later, expecting to play it right where he or she left off. Can you believe it? Also, you'll probably want to pause the game when switching the game to the main menu, unless you want the game to continue running while the menu is displayed. So, let's add the ability to pause the game. But what really happens when a game is paused? Basically two things: The game object and animations aren't updated, and input is ignored (except for the key to unpause the game, of course). To do this, you'll modify the animation loop a bit and check for input and update game objects only if the game isn't paused. For example:
if (!paused) { checkInput(); updateGameObjects(); }
Of course, you'll continue drawing the screen even if the game is paused. You could do extra drawing as well, such as having an animated "paused" message. Also, you'll be sure to update the paused state when the user presses the P key, for example.
Adding Gravity
The other thing you want to add is the capability for the player to jump. If this were outer space, the player would jump and keep moving forever (or, at least, until the player hits a comet or something). But this isn't outer space. This is the real world, and in the real world there's gravity. (The real world also has spherical blue people, right?) Adding gravity makes the player "fall" back down to the ground after jumping into the air. To implement gravity in your game, you need to understand what gravity really is. The effect of gravity is essentially a downward acceleration. Whereas speed is the measurement of distance over time, such as miles per hour, acceleration is the measurement of change in speed, such as when a car can accelerate from 0 to 60 miles per hour in 7 seconds. In the Sprite class from the last chapter, the sprite's position is updated based on its velocity. Likewise, for a sprite with gravity, the sprite's velocity is updated based on its acceleration. For example:
velocityY = velocityY + GRAVITY * elapsedTime;
The gravity of the Earth is about 9.8 meters per second, but that's not important for your game. You're using pixels and milliseconds as your measurements, so you'll just use whatever value "feels" right. Here, the value of GRAVITY is 0.002. This allows the player to jump but prevents the player from jumping past the top of the screen. We'll sum up the gravity feature in the Player class in Listing 3.7. The Player class extends the Sprite class, adding gravity along with the capability to jump.
Listing 3.7 Player.java
import com.brackeen.javagamebook.graphics.*; /** The Player class extends the Sprite class to add states (STATE_NORMAL or STATE_JUMPING) and gravity. */ public class Player extends Sprite { public static final int STATE_NORMAL = 0; public static final int STATE_JUMPING = 1; public static final float SPEED = .3f; public static final float GRAVITY = .002f; private int floorY; private int state; public Player(Animation anim) { super(anim); state = STATE_NORMAL; } /** Gets the state of the Player (either STATE_NORMAL or STATE_JUMPING); */ public int getState() { return state; } /** Sets the state of the Player (either STATE_NORMAL or STATE_JUMPING); */ public void setState(int state) { this.state = state; } /** Sets the location of "floor", where the Player starts and lands after jumping. */ public void setFloorY(int floorY) { this.floorY = floorY; setY(floorY); } /** Causes the Player to jump */ public void jump() { setVelocityY(-1); state = STATE_JUMPING; } /** Updates the player's position and animation. Also, sets the Player's state to NORMAL if a jumping Player landed on the floor. */ public void update(long elapsedTime) { // set vertical velocity (gravity effect) if (getState() == STATE_JUMPING) { setVelocityY(getVelocityY() + GRAVITY * elapsedTime); } // move player super.update(elapsedTime); // check if player landed on floor if (getState() == STATE_JUMPING && getY() >= floorY) { setVelocityY(0); setY(floorY); setState(STATE_NORMAL); } } }
The Player class is state-based. It has two states: NORMAL and JUMPING. Because the Player keeps track of its state, it can make checks to make sure to jump only if the player is in the normal state and also to apply gravity only if the player is jumping. The Player also must be told where the "floor" is. This is set with the setFloorY() method. Now you have everything you need to create a simple game to try out the InputManager. The InputManagerTest class in Listing 3.8 enables the user to move the player and make the player jump. It creates several GameActions to provide this functionality. Each GameAction is mapped to at least one key or mouse event. Finally, it even allows you to pause the game! Move the player by using the arrow keys, and press the spacebar to jump. The P key pauses the game, and, as always, pressing Escape exits the game.
Listing 3.8 InputManagerTest.java
import java.awt.*; import java.awt.event.KeyEvent; import javax.swing.ImageIcon; import com.brackeen.javagamebook.graphics.*; import com.brackeen.javagamebook.input.*; import com.brackeen.javagamebook.test.GameCore; /** InputManagerTest tests the InputManager with a simple run-and-jump mechanism. The player moves and jumps using the arrow keys and the spacebar. <p>Also, InputManagerTest demonstrates pausing a game by not updating the game elements if the game is paused. */ public class InputManagerTest extends GameCore { public static void main(String[] args) { new InputManagerTest().run(); } protected GameAction jump; protected GameAction exit; protected GameAction moveLeft; protected GameAction moveRight; protected GameAction pause; protected InputManager inputManager; private Player player; private Image bgImage; private boolean paused; public void init() { super.init(); Window window = screen.getFullScreenWindow(); inputManager = new InputManager(window); // use these lines for relative mouse mode //inputManager.setRelativeMouseMode(true); //inputManager.setCursor(InputManager.INVISIBLE_CURSOR); createGameActions(); createSprite(); paused = false; } /** Tests whether the game is paused or not. */ public boolean isPaused() { return paused; } /** Sets the paused state. */ public void setPaused(boolean p) { if (paused != p) { this.paused = p; inputManager.resetAllGameActions(); } } public void update(long elapsedTime) { // check input that can happen whether paused or not checkSystemInput(); if (!isPaused()) { // check game input checkGameInput(); // update sprite player.update(elapsedTime); } } /** Checks input from GameActions that can be pressed regardless of whether the game is paused or not. */ public void checkSystemInput() { if (pause.isPressed()) { setPaused(!isPaused()); } if (exit.isPressed()) { stop(); } } /** Checks input from GameActions that can be pressed only when the game is not paused. */ public void checkGameInput() { float velocityX = 0; if (moveLeft.isPressed()) { velocityX-=Player.SPEED; } if (moveRight.isPressed()) { velocityX+=Player.SPEED; } player.setVelocityX(velocityX); if (jump.isPressed() && player.getState() != Player.STATE_JUMPING) { player.jump(); } } public void draw(Graphics2D g) { // draw background g.drawImage(bgImage, 0, 0, null); // draw sprite g.drawImage(player.getImage(), Math.round(player.getX()), Math.round(player.getY()), null); } /** Creates GameActions and maps them to keys. */ public void createGameActions() { jump = new GameAction("jump", GameAction.DETECT_INITAL_PRESS_ONLY); exit = new GameAction("exit", GameAction.DETECT_INITAL_PRESS_ONLY); moveLeft = new GameAction("moveLeft"); moveRight = new GameAction("moveRight"); pause = new GameAction("pause", GameAction.DETECT_INITAL_PRESS_ONLY); inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE); inputManager.mapToKey(pause, KeyEvent.VK_P); // jump with spacebar or mouse button inputManager.mapToKey(jump, KeyEvent.VK_SPACE); inputManager.mapToMouse(jump, InputManager.MOUSE_BUTTON_1); // move with the arrow keys... inputManager.mapToKey(moveLeft, KeyEvent.VK_LEFT); inputManager.mapToKey(moveRight, KeyEvent.VK_RIGHT); // ... or with A and D. inputManager.mapToKey(moveLeft, KeyEvent.VK_A); inputManager.mapToKey(moveRight, KeyEvent.VK_D); // use these lines to map player movement to the mouse //inputManager.mapToMouse(moveLeft, // InputManager.MOUSE_MOVE_LEFT); //inputManager.mapToMouse(moveRight, // InputManager.MOUSE_MOVE_RIGHT); } /** Load images and creates the Player sprite. */ private void createSprite() { // load images bgImage = loadImage("../images/background.jpg"); Image player1 = loadImage("../images/player1.png"); Image player2 = loadImage("../images/player2.png"); Image player3 = loadImage("../images/player3.png"); // create animation Animation anim = new Animation(); anim.addFrame(player1, 250); anim.addFrame(player2, 150); anim.addFrame(player1, 150); anim.addFrame(player2, 150); anim.addFrame(player3, 200); anim.addFrame(player2, 150); player = new Player(anim); player.setFloorY(screen.getHeight() - player.getHeight()); } }
In InputManagerTest, whenever the user pauses or unpauses the game, the GameActions are reset. This makes sure that any keys pressed while the game is paused don't take effect when the game is unpaused. Another thing to note is that there are a few lines of code commented out. These lines turn on relative mouse mode and map the mouse movement to the player's movement. Uncomment these lines if you want to use the mouse to move the player. Either way, the mouse button causes the player to jump.