• No results found

Continuing with our MySuperAwesomeGame example, here is a very simple implementation of the MySuperAwesomeStartScreen class:

public class MySuperAwesomeStartScreen extends Screen { Pixmap awesomePic;

int x;

public MySuperAwesomeStartScreen(Game game) { super(game);

awesomePic = game.getGraphics().newPixmap("data/pic.png", PixmapFormat.RGB565);

}

@Override

public void update(float deltaTime) { x += 1;

CHAPTER 3: Game Development 101

99

if (x > 100) x = 0; }

@Override

public void present(float deltaTime) { game.getGraphics().clear(0);

game.getGraphics().drawPixmap(awesomePic, x, 0, 0, 0,

awesomePic.getWidth(), awesomePic.getHeight()); }

@Override

public void pause() { // nothing to do here

} @Override

public void resume() { // nothing to do here

} @Override

public void dispose() { awesomePic.dispose(); }

}

Let’s see what this class in combination with the MySuperAwesomeGame class will do: 1. When the MySuperAwesomeGame class is created, it will set up the window,

the UI component we render to and receive events from, the callbacks to receive window and input events, and the main loop thread. Finally, it will call its own MySuperAwesomeGame.getStartScreen() method, which will return an instance of the MySuperAwesomeStartScreen() class. 2. In the MySuperAwesomeStartScreen constructor, we load a bitmap from

disk and store it in a member variable. This completes our screen setup, and the control is handed back to the MySuperAwesomeGame class. 3. The main loop thread will now constantly call the

MySuperAwesomeStartScreen.update() and

MySuperAwesomeStartScreen.render() methods of the instance we just created.

4. In the MySuperAwesomeStartScreen.update() method, we increase a member called x by one each frame. This member holds the x- coordinate of the image we want to render. When the x-coordinate is bigger than 100, we reset it to 0.

5. In the MySuperAwesomeStartScreen.render() method, we clear the framebuffer with the color black (0x00000000 = 0) and render our Pixmap at position (x,0).

6. The main loop thread will repeat steps 3 to 5 until the user quits the game by pressing the back button on his device. The Game instance will call then call the MySuperAwesomeStartScreen.dispose() method, which will dispose of the Pixmap.

And that’s our first (not so) exciting game! All a user will see is that an image is moving from left to right on the screen. Not exactly a pleasant user experience, but we’ll work on that later. Note that on Android, the game can be paused and resumed at any point in time. Our MyAwesomeGame implementation will then call the

MySuperAwesomeStartScreen.pause() and MySuperAwesomeStartScreen.resume() methods. The main loop thread will be paused for as long as the application itself is paused.

There’s one last problem we have to talk about: frame-rate independent movement.

Frame Rate–Independent Movement

Let’s assume that the user’s device can run our game from the last section at 60 FPS. Our Pixmap will advance 100 pixels in 100 frames as we increment the

MySuperAwesomeStartScreen.x member by 1 pixel each frame. At a frame rate of 60 FPS, it will take roughly 1.66 seconds to reach position (100,0).

Now let’s assume that a second user plays our game on a different device. That device is capable of running our game at 30 FPS. Each second, our Pixmap advances by 30 pixels, so it takes 3.33 seconds to reach position (100,0).

This is bad. It may not have an impact on the user experience our simple game

generates. But replace the Pixmap with Super Mario and think about what it would mean to move him in a frame-dependent manner. Say we hold down the right D-pad button so that Mario runs to the right. In each frame, we advance him by 1 pixel, as we do in case of our Pixmap. On a device that can run the game at 60 FPS, Mario would run twice as fast as on a device that runs the game at 30 FPS! This would totally change the user experience depending on the performance of the device. We need to fix this.

The solution to this problem is called frame-independent movement. Instead of moving our Pixmap (or Mario) by a fixed amount each frame, we specify the movement speed in units per second. Say we want our Pixmap to advance 50 pixels per second. In addition to the 50-pixels-per-second value, we also need information on how much time has passed since we last moved the Pixmap. And this is where this strange delta time comes into play. It tells us exactly how much time has passed since the last update. So our MySuperAwesomeStartScreen.update() method should look like this:

@Override

public void update(float deltaTime) { x += 50 * deltaTime;

if(x > 100) x = 0; }

CHAPTER 3: Game Development 101

101

If our game runs at a constant 60 FPS, the delta time passed to the method will always

be 1 / 60 ~ 0.016 seconds. In each frame we therefore advance by 50 0.016 ~ 0.83

pixels. At 60 FPS we advance 60 0.85 ~ 100 pixels! Let’s test this with 30 FPS: 50 1

/ 30 ~ 1.66. Multiplied by 30 FPS, we again move 100 pixels total each second. So, no matter how fast the device our game is running on can execute our game, our animation and movement will always be consistent with actual wall clock time.

If we actually tried this with our preceding code, our Pixmap wouldn’t move at all at 60 FPS, though. This is because of a bug in our code. I’ll give you some time to spot it. It’s rather subtle, but a common pitfall in game development. The x member we increase each frame is actually an integer. Adding 0.83 to an integer will have no effect. To fix this

we simply have to store x as a float instead of an int. This also means that we have to

add a cast to int when we call Graphics.drawPixmap().

NOTE: While floating-point calculations are usually slower on Android than integer operations, the impact is mostly negligible, so we can get away with using more costly floating-point arithmetic.

And that is all there is to our game framework. We can directly translate the screens of our Mr. Nom design to our classes and interface of the framework. Of course, there are still some implementation details to tend to, but we’ll leave that for a later chapter. For now you can be mighty proud of yourself that you kept on reading this chapter to the end: you are now ready to become a game developer for Android (and other platforms)!

Summary

Fifty highly condensed and informative pages later, you should have a good idea of what is involved in creating a game. We checked out some of the most popular genres on the Android Market and drew some conclusions. We designed a complete game from the ground up using only a scissor, a pen, and some paper. Finally, we explored the theoretical basis of game development, and even created a set of interfaces and abstract classes that we’ll use throughout this book to implement our game designs based on those theoretical concepts. If you feel like you want to go beyond the basics covered here, then by all means consult the Web for more information. You are holding all the keywords in your hand. Understanding the principles is the key to developing stable and well-performing games. With that said, let’s implement our game framework for Android!

103

Chapter

Android for Game