Author: Brian A. Ree
Sections
Software and Scripts
The software and scripts used in this tutorial can be downloaded at the following links. There are also links provided
in the tutorial at the step where they are needed. I've placed a set of links here for convenience.
Link: GPIO Scripts v2.0.0 (Up, Down Button)
Link: Mmg Game API Netbeans Project v0.5.1
Link: Odroid Game Project v0.1.0
-- 0: Environment Setup --
In order to take full advantage of this tutorial you'll need to have your ODROID development environment setup.
A detailed tutorial on how to build and configure an ODROID-N2 environment can be found at the following link.
**There was a mistake in the first tutorial. I forgot to mention you'll have to install the default-jdk.
sudo apt-get install default-jdk -y;
-- 1: Introduction and Tutorial Goals --
This tutorial is the second part of the in a series of tutorials about ODROID-N2 game development using Java.
It is designed for those who are starting out in game development and just learning how to code. In order to take
full advantage of this tutorial series you'll need to setup an ODROID-N2 development environment. The link
above has a detailed tutorial on getting your environment up and running.
We'll create our first game in this, tutorial #2, a clone of the classic game, Pong. This tutorial will focus on
the classes that power the game and only briefly mention the underlying APIs and supporting classes. You'll get more
experience with then and I'll cover a little bit more about how the API works as we progress in this tutorial series.
This tutorial will expose you to Java, Java programming, GPIO, and general topics that will help you when writing your
own games. You'll also get free code to use, including source, so you can explore on your own and further your knowledge
of software and game development.
-- 2: The API Structure --
Let's take a look at some of the features of the code we're using starting with the overall structure of the API's.
An API is an Application Programming Interface and is really a fancy way of saying "a collection of classes, interfaces,
enumerations and packages that are designed to work together and accomplish a specific task." If you are new to programming
then the last sentence will probably make almost no sense to you so I'll go over some of the definitions of key words.
This should help you develop your understanding of what coding in Java entails.
Class: A class is a user defined blueprint or prototype from which objects are created. It represents the set of properties
or methods that are common to all objects of one class type. For instance in 2D game programming you need to draw 2D images
to the screen a class that handles keeps track of the information involved, image size, image position, etc, could be called
a GameObject. In this project the objects being drawn to the screen are instances of the MmgObj class.
Interface: An interface is a reference type in Java. It is similar to class. It is a collection of abstract methods. A class
implements an interface, thereby inheriting the abstract methods of the interface. Think of this as a defining a general protocol,
a general way of doing something. One interface contained in the provided API is GamePadSimple. This creates a template
for handling gamepad input. It doesn't define exactly how to do it, it only defines a template on how to handle input, handling direction
pad input, handling A button presses, etc. We don't case what class implements the interface and how it implements the interface we
just care that the classes that do so all handle the same methods, fancy word for functions, so they can be used in the same way.
Enumeration: Enumeration means a list of named constant. In Java, enumeration defines a class type. A simple enumeration might be the
days in a week, and you would see a list of those days in the enumeration definition. In the code provided we have a lot of enumeration
but one main enumeration is one that lists different game states. For example splash screen, loading screen, main menu. Many of these screens
are common to every game so it makes sense to define them in an enumeration.
Packages: A package in Java is used to group related classes. Think of it as a folder in a file directory.
In Java common classes, classes that are designed to work together are grouped into a package.
Now that you have a deeper understanding of some Java fundamentals let's take a look at the code included with this tutorial.
If you've completed the first tutorial you'll need to download the updated code. Each tutorial will come with it's own full set
of source code and libraries with updates and fixes to lower level classes that might not be in copies from other tutorials.
The overall structure of the API's involved with the game development library are shown below.
Review the image above and make sure you understand, at least a little bit, what's going on. You'll get more
experience with the actual code in just a bit so that may help your understanding. The key thing to realize is that
each API is designed to handle a focused task. The MmgApi is lower level and responsible for things like drawing
images, creating new images, loading image files, defining events, game screens, etc.
The MmgCore API is a slightly higher level API. This means that it is more specialized. It uses the MmgApi
but adds to it, almost directing the functionality of the more general lower level API into a narrow, more focused, usage.
For instance the game screens in the core API are splash, loading, and main menu. These are screens that are very game oriented.
The API adds a lot of support for loading and managing game settings, handling input from different devices, drawing to a window
on your screen, etc.
Lastly the package for the actual game sits on top of the core API. This creates almost a stack of libraries with each one becoming
more focused and uses the generalized power of the API below it. So for instance the core API is designed to draw a very general definition
of splash, loading, and main menu screens the classes in the game's package, Odroid_Tutorial2_PongClone, are designed to draw screens customized
for the Pong clone game. I'll refer the the set of API's, MmgApi and MmgCore as the Game Engine because they work together
to provide the foundation of a Java 2D game for your ODROID-N2.
-- 3: Game Engine Features --
In this section we'll cover some key game engine features. The first things I'll mention is the ability to configure the GameSettings
class from an XML file. This allows you to load game specific data, at runtime, without recompiling the game. It's used to set game specific
resource directories, images and sounds, without having to hardcode the changes in the GameSettings class. Below is a screen shot of the
engine config file used in the Pong clone game.
Now I won't go into too much depth here about XML files. Just think of it as a structured way to store data that can be easily
read by methods setup in your Java game. Secondly the information set in the engine config file is used to set the values of static
class fields. Classes have fields, they are sometimes also referred to as attributes or properties. They are essential aspects of how a class
is defined. You can think of them as the pieces of information an instance of a particular class will keep track of. Now I just said instance.
An instance of a class is a unique copy of a class created by using the new key word. An example would be MmgObj myObj = new MmgObj().
That line of code creates a new instance, or copy, of the MmgObj class. Sometimes it makes sense to create classes with static fields.
Think of these a global to all copies of that class.
An example of static class fields can be found in the MmgCore API's GameSettings class. Go to the MmgGameApiJava project
and expand the MmgCore API's package to browse the Java classes it contains to find the file. Because we want to share those settings
we don't really need an instance of the GameSettings class because that would create a unique copy with potentially different data. What
we want is a simple class with static fields that can be accessed anywhere. Normally you would reference class fields by using an instance of that
class. To build on the example in the previous paragraph you would access fields and methods of the myObj class instance like this,
myObj.isVisible. If the isVisible class field were static we wouldn't need the myObj instance so we would reference this
field like so, MmgObj.isVisible. This has the benefit of making access to the field simple and global.
We can apply this functionality to the GameSettings class and because all its fields are static fields we can set their values with the
data from the XML engine config file. Notice that the name and title are being set to values that are specific to this game. If you look at the
MmgPongClone class, this is the main execution class for the game. The file is located in the OdroidGameTutorialsJava project in the
PongClone package. You'll see the following line near the top of the static main class, MmgPongClone. This allows us to load custom
settings for our specific game.
Now that we have a specific name for our game loaded from a game specific engine config file we can focus on loading game specific resources.
If you scroll down in the MmgPongClone class you'll see the two lines shown below.
The game engine is designed to load resources automatically. To do so the images and sounds have to be places in certain key folder. Two such
folders are stored in the GameSettings.PROGRAM_IMAGE_LOAD_DIR and the GameSettings.PROGRAM_SOUND_LOAD_DIR static class fields.
Notice above that those two folders are now customized by the name of the game. The game engine will inspect that folder and load any images found
into a repository of game images. The same thing is done for sound files. Currently sound files are limited to wav files. Images can be loaded
from most of the major types of images but I would recommend using png files whenever possible. An image of the key resource loading paths setup
by default in the GameSettings class is shown below.
A detailed listing of what each resource path game setting is used for is shown below.
IMAGE_LOAD_DIR = "../cfg/drawable/": A directory for manually loading image files. Use this if you need to add custom loading
of image resources to the RunResourceLoad class.
SOUND_LOAD_DIR = "../cfg/playable/": A directory for manually loading sound files. Use this if you need to add custom loading
of sound resources to the RunResourceLoad class.
PROGRAM_IMAGE_LOAD_DIR = "../cfg/drawable/": A directory for automatically loading image files. The value of this static class field
will get appended with the value of the NAME field. It is meant to be customized to point to a directory that has the same name as the
game you're working on. This way automatic loading of images can be separated, game by game.
PROGRAM_SOUND_LOAD_DIR = "../cfg/playable/": A directory for automatically loading sound files. The value of this static class field
will get appeneded with the value of the NAME field. It is meant to be customized to point to a directory that has the same name as the
game you're working on. This way automatic loading of sounds can be separated, game by game.
AUTO_IMAGE_LOAD_DIR = "../cfg/drawable/auto_load/": A default directory for automatically loading image files.
This is a non-custom directory used by the MmgCore API. You can use it too but your game files will get mixed in with
the default images used by the API. It can serve as a quick use directory for loading up images and testing them out if you haven't
setup your game fully yet.
AUTO_SOUND_LOAD_DIR = "../cfg/playable/auto_load/": A default directory for automatically loading sound files.
This is a non-custom directory used by the MmgCore API. You can use it too but your game files will get mixed in with
the default sounds used by the API. It can serve as a quick use directory for loading up sounds and testing them out if you haven't
setup your game fully yet.
CLASS_CONFIG_DIR = "../cfg/class_config/": This is a special directory for storing class config files. I'll touch more on this later but
a quick description is in order. As we build our screen classes we're going to add a way to configure them without editing the code. This is what
a class config file is for. It allows you to load in strings, numbers, etc that drive the game screen so that you can adjust your game by editing a
simple text file.
Well that wraps up this section of the tutorial. As you can see there are a lot of easy to use features that make creating a game simple.
You don't have to really worry about loading resources that is taken care of automatically, if you put your files in the auto loading folders or
the game specific auto loading folder. You also don't have to adjust your game screen once you create it because you can control the images and their
position from the class config file. Pretty neat.
-- 4: Project Files and Folders --
In this section we'll quickly cover the files and folders associated with the Pong Clone project. I mentioned the code
that stores the different directories in the previous section, here we'll take a quick look at those directories in action.
I'll also show you what a class config file looks like. It's not an XML file, it's a simple text file with a few rules
governing how information is read from the file. Let's take a look.
The first directory we'll take a look at is the class config folder.
This folder holds configuration files for some of the game screens. The files including in the base directory are used by the
MmgCore API. The API includes an example executable with some basic functionality so we have some class config files for it.
Notice that the game specific class config files are stored in a folder that has the same name as the game. These files are handled
a little bit differently than the game specific resource directories in that the class config path isn't automatically adjusted when the game
starts. You design your code to point to the folder you want to load class config files for. This gives you more control and allows you
to load up different files as needed. For instance you could load up one class config file for smaller screens and one for larger screen if
you designed your code to handle multiple screens.
An example of a class config file is as follows. An there are some simple rules for them also listed below.
Comments: A commented line, ignored line, must start with the # character. It can't have any preceding
white space so the character must appear at the left most position in the text file.
Key Name: The key name, or the name we use to reference the value after the file is loaded must be followed by a -> if the key is meant
to store a string value, i.e. not a number. If the key is meant to store a number value then a = must follow the key.
#images and text
bmpGameTitle->mpc_game_title.png
bmpGameSubTitle->mpc_game_sub_title.png
bmpMenuItemStartGame1p->start_game_1p.png
bmpMenuItemStartGame2p->start_game_2p.png
bmpMenuItemExitGame->mpc_exit_game.png
bmpFooterUrl->mpc_footer_url.png
version->version:1.0.0
menuTitleScale=1.0
menuTitleOffsetY=-35
menuTitleOffsetX=0
#menuTitlePosY=30
#menuTitlePosX=0
menuSubTitleScale=1.0
menuSubTitleOffsetY=-10
#menuSubTitleOffsetX=0
#menuSubTitlePosY=0
#menuSubTitlePosX=0
menuStartGame1pScale=1.0
menuStartGame1pOffsetY=-15
#menuStartGame1pOffsetX=0
#menuStartGame1pPosY=-10
#menuStartGame1pPosX=0
menuStartGame2pScale=1.0
menuStartGame2pOffsetY=-15
#menuStartGame2pOffsetX=0
#menuStartGame2pPosY=-10
#menuStartGame2pPosX=0
menuExitGameScale=1.0
menuExitGameOffsetY=-15
#menuExitGameOffsetX=0
#menuExitGamePosY=-10
#menuExitGamePosX=0
menuFooterUrlScale=1.0
menuFooterUrlOffsetY=10
#menuFooterUrlOffsetX=0
#menuFooterUrlPosY=25
#menuFooterUrlPosX=0
Notice that there are entries for a couple of images. These images must be already auto loaded, using one of the afore mentioned directories,
to be referenced with just a key. Make sure to notice that the image is referenced by its name, you'll see those images listed when we cover the
drawable files in just a bit. To load a class config file you'll use a command similar to the following one.
You can use the data loaded in many different way. In the above example we have the ability to set the image for certain screen elements, in this
case on the main menu. We can also set a position offset, for adjusting the image position from its default and we can also set the position directly.
Not every entry is used, some are commented out, but all entries should be visible in the class config file so you know what's available to you.
An example of using the class config value to load a specific image file from the auto loaded image cache is as follows.
Let's take a look at the files and folders used for images next. Notice that the files shown are the ones referenced in the class config file we reviewed
previously.
Lastly we have the playable resources, sound files.
Before we move one let's take a look at the actual game screen as it is displayed by the game.
One last thing we want to cover in this section is the loading up of gamepad libraries. These are native libraries used by the
game engine to add support for USB gamepads. Right click the OdroidGameTutorial project and select the run
option on the left hand side menu. Notice that a path is registered here. This tells Java where to look for native libraries
to load at runtime. You'll have to adjust the path to match the location of the project on your computer.
-- 5: Project Classes, MmgPongClone, MainMenu, GamePanel --
In this section we'll take a look at the classes that actually make up the game. Now there are a lot of underlying classes
in the lower level APIs, MmgCore and MmgBase, but I can't cover all of that in this tutorial so we'll gain
some knowledge about those APIs a little at a time. The following image shows the classes involved in the game and how they are
structured, it also describes a little bit about what each one does.
Notice that at the highest level things are really simple. We're only declaring 5 classes to run the game. That's because
we're working on top of the foundation code provided by the lower level APIs. The MmgPongClone class is the static main entry point.
When a project has multiple static main files you may need to select the file, right click on it, and select run file instead of trying
to run the project itself. This is because there can only be one static main per package and it might not be mapped to the class you expect.
The static main entry point takes care of the following responsibilities for us. You can read over the code on your own, I won't cover it line
by line but I'll list the main aspects of the class.
Processes Command Line Arguments: We'll just be using the default values for the Pong Clone game but you can specify
command line arguments to adjust the size of the game, if Open GL should be used, and a few other things.
Loads Engine Config: The engine config file is loaded and any static field values supported by the code are altered to
use the values in the engine config file. We saw earlier how this could be used to change the program resource loading path by
setting the name of the game at runtime.
Loads Native Libraries: Depending on the value of the GameSettings.LOAD_NATIVE_LIBRARIES static field the class
will load the proper native library for USB gamepad support. You can also use this to load up any other other native libraries
you might need.
Initialize the MainFrame and GamePanel: The last thing this class does is initialize the MainFrame, GamePanel and
sets the game's dimensions. This is the handoff from the static main entry point to the drawing routine.
There are a few other things the class takes care of for us so I recommend reading over the file carefully, try to understand everything
that it does. One other aspect of the game engine is that it will automatically scale the game screen to fit inside the GamePanel
dimensions. All integer positions and offsets are run through a helper class method, MmgHelper.ScaleValue. This will ensure that
any values used for positioning are scaled to the game screen. It won't come into play in this game because the game screen dimensions
fit perfectly inside the GamePanel dimensions.
The next class we'll take a look at is the MainFrame class. This is a local, local package, version of the MmgCore class.
There isn't really any new code to add, the class simply extends the MmgCore version and calls its constructors. For more information
on constructors and the use of the super keyword check out the links below.
Let's turn our attention to the GamePanel class. This class is responsible for handling input, game screen events,
and setting which game screen is the current game screen. Think of it as the base of the drawing aspects of the game.
Because we are extending the class from the MmgCore API we get a lot of functionality for free. However, we still
have a few important things we need to control to run the game.
Before I cover the key class methods in the class I'll quickly demonstrate some built in functionality. The splash screen and loading screen
are already defined in the MmgCore API, and properly setup by the MmgCore API's version of the GamePanel class. We are
extending this class so we get some functionality for free. The next two image show the splash screen, and the loading screen. The loading
screen will automatically process the auto loading images and sounds we covered earlier.
Let's take a look at the class fields first. The image below shows the two new fields needed to customize the GamePanel
for running the Pong Clone game. We have a new declaration of the main menu screen, screenMainMenu, so that we can use the customized version for this
game. We also have a game screen, screenGame, that is responsible for drawing and controlling the actual game play.
Next let's take a look at the constructor for this class. Remember this class extends the MmgCore class.
This gives us a lot of functionality built into the class from the MmgCore API. In the constructor we want to
make sure we call the super class's constructor so you'll see a call to super. Next we'll want to initialize the
custom game screens for this class. You'll see screenMainMenu, and screenGame being initialized. Take a
moment to notice that we are resetting the event handlers for the screenSplash and screenLoading class fields
built into the GamePanel super class. Doing this will make sure that any events fired by the screens can be handled here.
The important class methods that we'll take a look at are as follows.
public void SwitchGameState(GameStates g): The SwitchGameState class method
is used to cleanup the current state and switch the game to a new state. Game states are directly tied
to games screens. When we switch to a state, say SCREEN_SPLASH for instance, we change the current
game screen to the splash screen and the GamePanel will ask the splash screen to draw itself
every time it is drawn to the screen.
public void HandleGenericEvent(GenericEventMessage obj): The HandleGenericEvent class method
is used to handle events fired from different game screens. Events are just class method calls that are handled in
a structured way by using Java interfaces. In our example game when the splash screen is done displaying after a few seconds
if fires a generic event to the registered generic event handler. Remember we set this in the class constructor.
This gives us a chance to respond to the event and in this case we cleanup the splash screen and then switch to the
loading screen. A similar event is fired when the loading screen finishes loading the game resources, in this case we
cleanup the loading screen and then switch to the main menu screen.
Let's take a look at how we clean up a game screen.
Notice how we pause the screen and unload any resources it uses, we also make sure it is invisible.
Now let's take a look at how we prepare a game screen to take over, load a new screen.
That's it for this section of the tutorial in the next section we'll take a quick look at the main menu, ScreenMainMenu
used to render the main menu of the game.
-- 6: Project Classes, ScreenMainMenu, ScreenGame --
Load up the ScreenMainMenu class of the com.middlemind.Odroid_Tutorial2_PongClone package.
Notice that the class extends the net.middlemind.MmgGameApiJava.MmgCore.ScreenMainMenu class. That means
we'll have some built in functionality from the MmgCore base class.
There are a few things that this class does. It uses an MmgMenuContainer and MmgMenuItem class to draw the menu and manage input.
We won't cover all the functionality of the super class but we will quickly go over how the class is designed to use a class config file.
Below is a section of code that loads up an image and positions it with default values but then also take into account the class config entries
that can adjust how the screen is drawn.
key = "bmpMenuItemStartGame2p";
if(classConfig.containsKey(key)) {
file = classConfig.get(key).string;
} else {
file = "start_game_2p.png";
}
imgId = file;
lval = MmgHelper.GetBasicCachedBmp(imgId);
menuStartGame2P = lval;
if (menuStartGame2P != null) {
key = "menuStartGame2pScale";
if(classConfig.containsKey(key)) {
scale = classConfig.get(key).number.doubleValue();
if(scale != 1.0) {
menuStartGame2P = MmgBmpScaler.ScaleMmgBmp(menuStartGame2P, scale, false);
}
}
MmgHelper.CenterHor(menuStartGame2P);
menuStartGame2P.GetPosition().SetY(menuStartGame1P.GetY() + menuStartGame1P.GetHeight() + MmgHelper.ScaleValue(10));
key = "menuStartGame2pPosY";
if(classConfig.containsKey(key)) {
tmp = classConfig.get(key).number.intValue();
menuStartGame2P.GetPosition().SetY(GetPosition().GetY() + MmgHelper.ScaleValue(tmp));
}
key = "menuStartGame2pPosX";
if(classConfig.containsKey(key)) {
tmp = classConfig.get(key).number.intValue();
menuStartGame2P.GetPosition().SetX(GetPosition().GetX() + MmgHelper.ScaleValue(tmp));
}
key = "menuStartGame2pOffsetY";
if(classConfig.containsKey(key)) {
tmp = classConfig.get(key).number.intValue();
menuStartGame2P.GetPosition().SetY(menuStartGame2P.GetY() + MmgHelper.ScaleValue(tmp));
}
key = "menuStartGame2pOffsetX";
if(classConfig.containsKey(key)) {
tmp = classConfig.get(key).number.intValue();
menuStartGame2P.GetPosition().SetX(menuStartGame2P.GetX() + MmgHelper.ScaleValue(tmp));
}
}
Notice how we check for the class config key first. If it exists we use it's value if not we use a default value.
The position adjustments are done after the default positioning. Because we designed the class we know what type of data
the class config holds. If it is a number was use classConfig.get(key).number.intValue() or classConfig.get(key).number.doubleValue()
if we are expecting a string we use classConfig.get(key).string.
We'll quickly look at the main menu input handling next.
The input gets passed down from the GamePanel class. The code can be found in the MmgCore API, MmgGameApiJava project, in the GamePanel class.
In this way gamepad input, GPIO gamepad input, mouse input, and keyboard input are passed down to the current game screen.
If you notice in the above image that we have to differentiate the type of input we are receiving.
The next class we'll look at is the ScreenGame class. The is the main game, there is a lot going on it so I'll try to cover it in a concise manner.
We'll cover the class fields, some specific resource loading, game input, and some specifics of the game rendering method. There will be a lot of code I can't
cover here so please take the time to review the class and make sure you understand how it works. Before we continue let's take a look at the game screen.
Take notice of all the visual elements on the screen they will all be controlled by class fields.
There are a few other ways the game screen can render itself, for instance the game win screen, and the single player start screen, also the count down
in between scores. We'll start covering class fields next.
/**
* An enumeration that tracks which number is visible during game start countdown and in-game countdown.
*/
private enum NumberState {
NONE,
NUMBER_1,
NUMBER_2,
NUMBER_3
};
/**
* An enumeration that tracks which state this Screen is currently rendering.
* Allows the Screen to support different views like in-game, countdown, game over, game start, etc.
*/
private enum State {
NONE,
SHOW_GAME,
SHOW_COUNT_DOWN,
SHOW_COUNT_DOWN_IN_GAME,
SHOW_GAME_OVER,
SHOW_GAME_EXIT
};
private GameType gameType = GameType.GAME_ONE_PLAYER;
private State statePrev = State.NONE;
private State state = State.NONE;
private NumberState numberState = NumberState.NONE;
private long timeNumberMs = 0L;
private long timeNumberDisplayMs = 1000;
private long timeTmpMs = 0L;
The NumberState enumeration is used to keep control what number is being shown during the count down.
The count down is shown before the game starts and after each score. The State enumeration is used to control
how the game screen draws itself. You'll see this in the DrawScreen class method. A switch statement will be used
to control how the game is rendered given the current game state. Sometimes there will be work done in the DrawScreen
method, sometimes the screen will just show different MmgObj instances like text etc. and no more work is needed in the
DrawScreen class method.
The gameType class field is used to track if the game was started in one or two player mode. This information is passed in from
the GamePanel class. The class fields statePrev and state are used to track the game's state and the changes to it.
Notice that these are instances of the enumeration. The numberState class field is used to track the numbers being drawn during the count down
part of the game screen. The next three class fields timeNumberMs, timeNumberDisplayMs, and timeTmpMs are used to track how
much time went by for a given number and to change the number after a certain amount of time has passed, in this case it's timeNumberDisplayMs
and it will change the number every one second.
private MmgSound bounceNorm;
private MmgSound bounceSuper;
private MmgBmp bground;
private MmgBmp paddleLeft;
private MmgBmp paddleRight;
private MmgVector2 paddle1Pos;
private MmgVector2 paddle2Pos;
private MmgBmp ball;
private MmgVector2 ballPos;
The next set of class fields are for playing sounds and drawing images. The MmgSound class is used to play different sounds
while the MmgBmp class is used for drawing images to the game screen. The bounceNorm class field is used to hold the normal
ball bounce sound effect. When the ball isn't at full speed this sound will be played when the ball bounce off of a paddle. The bounceSuper
sound if used to play a different sound effect when the ball bounces off of a paddle and is at full speed. The ball's speed will increase
with every paddle bounce until it reaches full speed.
The bground class field is used to draw the game's background. In this case we load up an image that looks like a court of some kind.
It will be drawn as the background of the game. The paddleLeft and paddleRight class fields are images that are created in the game.
Because they are simple rectangles we don't load up an images we create a blank bitmap in code and fill a rectangle on the image.
Position information in our 2D games is tracked by MmgVector2 objects. Because we need to call a method to get the position
of MmgBmp objects we don't want to make this call every game frame if we don't have to. To avoid this we store a reference to key
position information by storing the MmgBmp's position in a local class field. The paddle1Pos, paddle2Pos class fields
are references to the paddleLeft and paddleRight positions. Lastly we have the ball class field which is an image of a blue
ball loaded into an MmgBmp object. Again, we don't want to have to get the position of the ball frequently so we store a reference to the
ball's position so we can save ourself a few method calls.
private int ballMovePerFrameMin = GetSpeedPerFrame(150);
private int ballMovePerFrameMax = GetSpeedPerFrame(425);
private int ballDirX = 1;
private int ballDirY = 1;
private int ballMovePerFrameX = 0;
private int ballMovePerFrameY = 0;
private MmgBmp number1;
private MmgBmp number2;
private MmgBmp number3;
The next set of class fields have to do with the ball's movement and the numbers that are drawn during the count down
screens of the game. The ballMovePerFrameMin is the minimum speed the ball can have. Notice that the speed is converted,
ballMovePerFrameMin = GetSpeedPerFrame(150). We divide the speed by the game's frame rate minus a few frames to take into account any dropped frames. The frame rate
is set in the MmgPongClone class by the static field, FPS. So we convert the movement into a movement per frame. What this does is guarantee
that the movement remains the same if the frame rate changes. For the most part the game will have the same movement at any frame rate, within reason if we divide by too
high a number we'll have a speed per frame of 0.
The next two class fields ballDirX and ballDirY keep track of the direction the ball is moving in.
We don't directly adjust the ball's speed instead we track certain aspects of the ball's movement and then calculate its
next position. These class fields let us multiply the ball's movement by a 1 or a -1 to control how it reacts to screen borders
or paddle. We don't use heavy calculations for the ball's bouncing we use instead simplified calculations and just negate movement in
the X or Y direction depending on what the ball bounces into.
The class fields ballMovePerFrameX and ballMovePerFrameY are used to hold the calculated speed of the ball. These values
are constrained by the ballMovePerFrameMin and ballMovePerFrameMax values. Lastly we have a set of class fields of the MmgBmp
type that are used to draw the number images to the screen during the count down modes of the game screen. These are just like previous MmgBmp
class fields we've seen, they wrap images that have been auto loaded into the image cache and draw them to the game screen during rendering.
private int scoreGameWin = 7;
private MmgFont scoreLeft;
private int scorePlayerRight = 0;
private MmgFont scoreRight;
private int scorePlayerLeft = 0;
private MmgFont exit;
private MmgBmp exitBground;
private int paddle1MovePerFrame = GetSpeedPerFrame(400);
private boolean paddle1MoveUp = false;
private boolean paddle1MoveDown = false;
private int aiMaxSpeed = 425;
private int paddle2MovePerFrame = GetSpeedPerFrame(400);
private boolean paddle2MoveUp = false;
private boolean paddle2MoveDown = false;
The class field scoreGameWin holds the value needed to win the game. Whenever a score is registered this value is checked to see if a player has one.
The scoreLeft class field is an instance of the MmgFont class. Similar to the MmgBmp and MmgSound classes this class is used to load
a font and display text on the screen. The scorePlayerRight class field keeps track of the right-hand side player's actual score as an integer.
The scoreRight class field is another instance of the MmgFont class and is used to draw the right-hand players score on the screen. The
scorePlayerLeft class field tracks the left-hand side player's actual score as an integer.
The exit class field is used to display the game exit message at the center-top of the screen. The next class field exitBground
is an MmgBmp instance and draws a black rectangle behind the exit game text so that it isn't disturbed by the board background image and is easy to read.
The next set of class fields has to do with the movement of paddle one, paddle one is usually drawn on the right-hand side of the screen and belongs to player one.
Notice that we have a normalized speed, speed per frame, in the paddle1MovePerFrame class field. The next two class fields, paddle1MoveUp and
paddle1MoveDown, are used to track the paddle's movement, up or down, based on the input received by input handling methods.
The next block of class fields is almost exactly the same as the previous set so I won't go into too much detail here.
The same speed and movement tracking class fields exist. There is one difference though, we have an aiMaxSpeed class field
which is used to control the speed of the paddle when the computer is playing. Notice that the computer has a slight advantage in that
its paddle can move slightly faster? Since the AI is very simple, the computer will try and match the ball's Y coordinate as it moves across the screen.
This is simple to implement but can lead to an easy opponent so we sped up the computers paddle movement a little to make things for difficult.
private Random rand;
private int ballNewX;
private int ballNewY;
private boolean bounced = false;
private MmgVector2 screenPos;
private int lastX;
private int lastY;
private boolean mousePos = true;
private MmgBmp bgroundPopupSrc;
private Mmg9Slice bgroundPopup;
private MmgFont txtOk;
private MmgFont txtCancel;
The rand class field is used to generate random values when necessary. We don't want to create random number generators
frequently so we'll keep one ready for when we need it. The next two class fields, ballNewX and ballNewY, are used to
store the calculated new X, and Y position of the ball. The bounced class field is used to track if the ball has bounced off of a
paddle during the current game frame, if so a bounce sound is played. The screenPos class field is used to track the position of the screen.
Because there is a development header the screen is drawn at an offset that may need to be taken into account when processing mouse coordinates.
The lastXm lastY, and mousePos class fields are used to process mouse input coordinates for controlling the player two paddle.
The bgroundPopupSrc class field is an image that is used as the background for a popup that verifies if the player does in fact want to leave the game.
It is used to power a the bgroundPopup class field. This is an instance of the Mmg9Slice class which is designed to convert an image into a 9 sliced tile.
This allows the image to be resized with almost no distortion. Lastly we have two more MmgFont object types, txtOk and txtCancel.
These are used to display text on the exit popup that tell the player what button to press to exit the game or what button to press to cancel the exit.
private MmgFont txtGoal;
private MmgFont txtDirecP1;
private MmgFont txtDirecP2;
private MmgFont txtGameOver1;
private MmgFont txtGameOver2;
private int padding = MmgHelper.ScaleValue(4);
private int popupTotalWidth = MmgHelper.ScaleValue(300);
private int popupTotalHeight = MmgHelper.ScaleValue(120);
private boolean infiniteBounce = false;
The last set of class fields is short and sweet. Let's go over them really quickly. The MmgFont instances
txtGoal, txtDirectP1, and txtDirectP2 are all used to display text to the screen during some
modes of the game screen. They display the score needed to win, goal, and direction to control the paddle for player 1
and player 2 if a two player game was chosen.
The next set of MmgFont instances txtGameOver1 and txtGameOver2 are used to draw the game end mode
and show which player has won the game. The padding class field is used to make slight adjustments in drawing different things to the screen
or in collision detection. Again, notice that we always scale the value of position variables because the game engine has the ability to
scale images and screen dynamically we want all drawing position and offset values to be scaled as well. The next two class fields,
popupTotalWidth and popupTotalHeight, are used to control the dimensions of the game exit prompt popup. Lastly the
infiniteBounce class field is a debug variable used to set the ball to bounce infinitely and never register a score.
That wraps up the class fields that are needed to power the game complete with game intro text, directions, and a three second count down.
A count down in between scores, a popup that verifies that you want to exit the game, and a game end screen that shows which player has won the game.
There are a few more key pieces of code I'd like to review before we move on. Make sure you read over the class and understand anything I didn't
cover explicitly. In the next section we'll cover setting up the USB gamepad and the GPIO gamepad in the last section of the tutorial.
//ScreenGame -> LoadResources method
classConfig = MmgHelper.ReadClassConfigFile(GameSettings.CLASS_CONFIG_DIR + GameSettings.NAME + "/screen_game.txt");
if(gameType == GameType.GAME_TWO_PLAYER) {
paddle2MovePerFrame = GetSpeedPerFrame(aiMaxSpeed);
}
SetHeight(MmgScreenData.GetGameHeight());
SetWidth(MmgScreenData.GetGameWidth());
SetPosition(MmgScreenData.GetPosition());
screenPos = GetPosition();
In this code snippet we'll take a look at a few key aspect of using the game engine. The first thing I want to point
out is the loading up of the class config file. Notice that it uses the GameSettings.NAME static class field which
should be data driven by the game engine config XML file. The data loaded from the class config file will be used to customize
and data drive how the game is rendered.
Next take a look at the gameType check. If the game is a two player game we load up a normalized paddle speed for the paddle
that is driven by the AI. Following that is an important block of code. The MmgScreenData class has static fields and methods that
are setup automatically as part of the initialization of the game. It holds information about the dimensions and position of the game window
and the game screen. Notice that we also store the screen's position, screenPos, so that we can quickly reference the position without
having to call the GetPosition() method a bunch of times.
Let's take a look at some of the class config driven loading of resources. We have seen a little bit of this when we were talking about the
ScreenMainMenu class.
//ScreenGame -> LoadResources method
//Load game board config
key = "bmpGameBoard";
if(classConfig.containsKey(key)) {
file = classConfig.get(key).string;
} else {
file = "game_board.png";
}
lval = MmgHelper.GetBasicCachedBmp(file);
bground = lval;
if (bground != null) {
key = "gameBoardScale";
if(classConfig.containsKey(key)) {
scale = classConfig.get(key).number.doubleValue();
if(scale != 1.0) {
bground = MmgBmpScaler.ScaleMmgBmp(bground, scale, false);
} else {
bground = MmgBmpScaler.ScaleMmgBmpToGameScreen(bground, false);
}
}
MmgHelper.CenterHorAndVert(bground);
key = "gameBoardPosY";
if(classConfig.containsKey(key)) {
tmp = classConfig.get(key).number.intValue();
bground.GetPosition().SetY(GetPosition().GetY() + MmgHelper.ScaleValue(tmp));
}
key = "gameBoardPosX";
if(classConfig.containsKey(key)) {
tmp = classConfig.get(key).number.intValue();
bground.GetPosition().SetX(GetPosition().GetX() + MmgHelper.ScaleValue(tmp));
}
key = "gameBoardOffsetY";
if(classConfig.containsKey(key)) {
tmp = classConfig.get(key).number.intValue();
bground.GetPosition().SetY(bground.GetY() + MmgHelper.ScaleValue(tmp));
}
key = "gameBoardOffsetX";
if(classConfig.containsKey(key)) {
tmp = classConfig.get(key).number.intValue();
bground.GetPosition().SetX(bground.GetX() + MmgHelper.ScaleValue(tmp));
}
AddObj(bground);
}
The above code shows the class config data in use with the background of the game screen. Notice that it looks very much
like the example we've looked at from the main menu screen. Also, notice that the name of the file is the reference key into the
cache of auto loaded files. The same thing applies to sounds too. Let's take a look at loading sounds from the sound cache.
//ScreenGame -> LoadResources method
//Load bounce normal sound
key = "soundBounceNorm";
if(classConfig.containsKey(key)) {
file = classConfig.get(key).string;
} else {
file = "jump1.wav";
}
sval = MmgHelper.GetBasicCachedSound(file);
bounceNorm = sval;
In the above code we test the class config rules to see if there is new information for loading a sound file. If not we load the default sound file.
The name of the file is the reference key into the sound cache and is used to load a new MmgSound class instance into the game screen.
Notice that we use the helper methods MmgHelper.GetBasicCachedSound and MmgHelper.GetBasicCachedBmp to load up resources from the cache
created from the auto loading mechanism of the game engine.
I want to briefly review a couple of key class methods at a high level to give you more of an idea of how the class works. You'll
have to do some reviewing on your own because there is a bit too much code to cover in a tutorial. Let's take a look.
private void ResetGame(): This method resets the class fields used to draw the actual game. It does things like make the
exit game, left score, and right score visible. It also centers the ball and resets the paddle positions, etc.
private void SetState(State in): Similar to how the GamePanel class was able to change states and show different screens a screen might
have different modes itself. The SetState method is used to change how the game screen renders itself based on the current mode. For instance
the start game count down, one or two player games, the exit game prompt, etc.
public void DrawScreen(): This is an important class method that is called every time the MmgUpdate method is called. The GamePanel
class takes care of calling the current screen's MmgUpdate method each game frame if the screen is not paused and is visible. On some screens there
is the concept of dirty that will prevent the DrawScreen method from being called because nothing has changed. This comes in handy on screens
like the main menu where you don't need to redo rendering work every game frame. On a screen like the game screen it is less useful because there is movement
every frame so we would always have work to do every game frame. It is important to realize that the game screen is being redrawn every frame.
public void UnloadResources(): This class method takes care of releasing all objects that the screen uses so that the overall memory usage of the game lowers.
We saw this being called in the GamePanel class when cleaning up a state before switching to a new state. The general rule here is to set all objects that the class
initializes in the LoadResources class to null.
That wraps up this section of the tutorial. It would take a lot more text to review some of the methods I wanted to review but you can look them over on your
own and you'll have a working example of the game to guide you. After all it is really only the ScreenGame class that runs the actual game.
-- 7: Setting Up USB Gamepad Input --
NOTE: The lower level native libraries will only work on OSX, Windows, and Linux x86.
The native driver support for USB gamepads should support most gamepads out there. There is a chance that your gamepad will not be recognized on
OSX or Linux. I had one not work properly on OSX but my second attempt worked fine. In order to use the gamepad with your Pong Clone game you'll
need to figure out how input buttons are mapped and what values indicate an on value and an off value. In order to do this we'll use a little helper
program that's part of the MmgGameApiJava project in the net.middlemind.MmgGameApiJava.MmgTestSpace package. Look for the following static
main class and right-click on it and select run with the USB gamepad connected. The native libraries do not support detection of connecting/disconnecting
the game pad so you'll have to have it connected and ready.
Run the program and make sure no gamepad input is activated, the program will scan all the default values for the input. It runs for a few seconds
so you may have to run it a few times. Press the dpad in different directions and the software will print out the index of the input and its value
as well as the value of the input while not pressed.
Because this is your environment feel free to adjust the main GameSettings file shown above. Notice that it tracks a bunch of different options
for a simple USB gamepad. We're only really interested in dpad input and A, B button input. The entries to map the actual button input are pretty self explanatory
but there are a few more global settings I'll quickly review.
GAMEPAD_1_ON: Set this to true if you want to turn on USB gamepad support in the game engine.
GAMEPAD_1_INDEX: The index of the USB gamepad in the list of input devices. You can get this information from the gamepad helper
app shown above.
GAMEPAD_1_THREADED_POLLING: This setting is used to enabled polling the gamepad state from another thread. In this way the gamepad input
data can be updated at a set interval. If this settings is set to false then the gamepad input data is updated every game frame during the update
process.
GAMEPAD_1_POLLING_INTERVAL_MS: If the GAMEPAD_1_THREADED_POLLING setting is set to true then this is the interval that the gamepad
input data will be updated.
Let's take a look at what an input button setup looks like. I'll review the game setting entries for one next.
GAMEPAD_1_UP_INDEX: The index of the up input in the array of inputs for the given gamepad.
GAMEPAD_1_UP_VALUE_ON: The on value of the input, i.e. the value it returns when the input is pressed/activated.
GAMEPAD_1_UP_VALUE_OFF: The off value of the input, i.e. the value it returns when the input is no pressed/activated.
GAMEPAD_1_UP_CHECK_PRESS: Set this game setting to true if you want the code to track the press event for this gamepad input.
GAMEPAD_1_UP_CHECK_RELEASE: Set this game setting to true if you want the code to track the release event for this gamepad input.
GAMEPAD_1_UP_CHECK_CLICK: Set this game setting to true if you want the code to track the click event for this gamepad input.
That's the idea of it, you can control a lot of details about the gamepad input via the GameSettings file. Just map out the
gamepad input's index, on, and off values and what events you want to track.
-- 8: Setting Up GPIO Gamepad Input --
In order to take full advantage of this section you'll need to have completed the first tutorial. Specifically the GPIO setup
section of the tutorial. You're going to have to follow the process for finding a GPIO pin number a second time so that you have two
pins mapped out and both pins are wired up to buttons.
Once you have pin mappings and buttons setup on your breadboard you can adjust the GameSettings file and update the entries that control the
GPIO gamepad input. We'll take a look at the global settings first.
GPIO_GAMEPAD_ON: Set this to true if you want to turn on GPIO gamepad support in the game engine.
GPIO_GAMEPAD_THREADED_POLLING: Set this game setting to true if you want the GPIO gamepad to update its input
at it's own interval. If this is set to false then the GPIO gamepad input data is refreshed each game frame during the update
process. I recommend setting this to true because the GPIO pin state requires reading 1 byte of data from a file. So doing this
every single game frame might be too much, instead poll the pin state every 20 milliseconds.
GPIO_GAMEPAD_POLLING_INTERVAL_MS: The polling interval to use if the GPIO_GAMEPAD_THREADED_POLLING value is set to true.
Next we'll take a look at the entries needed to define one input from the GPIO gamepad.
GPIO_PIN_BTN_UP: The GPIO pin to use for the UP, dpad button.
BTN_UP_CHECK_PRESS: Determines if the engine should track press button events for this GPIO button.
BTN_UP_CHECK_RELEASE: Determines if the engine should track release button events for this GPIO button.
BTN_UP_CHECK_CLICK: Determines if the engine should track click button events for this GPIO button.
With these two new input methods the game will be able to demonstrate keyboard input, mouse input, USB gamepad input,
and GPIO gamepad input. To add an additional button to the GPIO gamepad setup in the first tutorial add a second instance
of the following circuit diagram but use the new GPIO pin you located using the process from the previous tutorial.
NOTE: Make sure you download the scripts file at the top of the tutorial. You'll need to update the script that the
button security service opens up for you. All this is covered in the first tutorial.
All you really have to do is update the btn_prep script with a new entry for the new GPIO pin number. In my case
I used pin 489. So my btn_prep script will have this added segment to open up security for accessing the pin. You'll
need to reboot in order for this to take effect.
TMP=489
sudo echo $TMP > /sys/class/gpio/export
sudo echo in > /sys/class/gpio/gpio$TMP/direction
sudo chmod 777 /sys/class/gpio/gpio$TMP/direction
sudo chmod 777 /sys/class/gpio/gpio$TMP/value
sudo chgrp odroid /sys/class/gpio/
sudo chown odroid /sys/class/gpio/
sudo chmod 777 /sys/class/gpio/export
sudo chmod 777 /sys/class/gpio/unexport
A picture of the GPIO gamepad with two buttons setup for up and down controls of the left-hand side player in a two player game is shown below.
Here is a screen shot of the GPIO gamepad in action actually being used to play the Pong Clone game.
-- 9: Conclusion --
I know that I wasn't able to cover all aspects of the game engine but I hope you got some value from it. If you spend some time
playing the game and looking over the code you'll get even more out of this tutorial and the game engine. Now keep in mind this is a
special game engine designed to teach people how to program and how to make game at the same time. The tool chain for this project is very simple
NetBeans and Gimp if you wanted a simple, free, image editor.
The next tutorial will be about a game called Dungeon Trap. The game isn't finished yet but some of the configuration and setup
is visible in the Netbeans projects included here. I'll cover more about the game engine and underlying APIs and demonstrate an entirely new
game of a totally different genre.
Till next time!!