The HUD in this game is no more complex than the previous game. We will define some Rect
instances to draw the controls on the screen, we will rely on GameState
to provide the time and fastest times for each level and we will make the button Rect ArrayList
available so that GameEngine
can pass then them to our two classes that require them to handle the player's input.
Get started by adding a new class called HUD
and add the following members and constructor method.
import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import java.util.ArrayList; class HUD { private Bitmap mMenuBitmap; private int mTextFormatting; private int mScreenHeight; private int mScreenWidth; final float ONE_THIRD = .33f; final float TWO_THIRDS = .66f; private ArrayList<Rect> mControls; static int LEFT = 0; static int RIGHT = 1; static int JUMP = 2; HUD(Context context, Point size){ mScreenHeight = size.y; mScreenWidth = size.x; mTextFormatting = size.x / 25; prepareControls(); // Create and scale the bitmaps mMenuBitmap = BitmapFactory .decodeResource(context.getResources(), R.drawable.menu); mMenuBitmap = Bitmap .createScaledBitmap(mMenuBitmap, size.x, size.y, false); } }
The class starts off with five members that we will use to control position and formatting of the parts of the HUD. Next there is an ArrayList
for our Rect
buttons and next there is the static
variables LEFT
, RIGHT
and JUMP
which the UIController
(that we code next) and PlayerInputComponent
(that we code next chapter) can use to identify what the player is trying to do.
In the constructor we initialize some of our formatting variables using the passed in screen resolution, call the prepareControls
method and load and scale the Bitmap
that is used for the menu background.
Now we can code the prepareControls
method that we just called.
private void prepareControls(){ int buttonWidth = mScreenWidth / 14; int buttonHeight = mScreenHeight / 12; int buttonPadding = mScreenWidth / 90; Rect left = new Rect( buttonPadding, mScreenHeight - buttonHeight - buttonPadding, buttonWidth + buttonPadding, mScreenHeight - buttonPadding); Rect right = new Rect( (buttonPadding * 2) + buttonWidth, mScreenHeight - buttonHeight - buttonPadding, (buttonPadding * 2) + (buttonWidth * 2), mScreenHeight - buttonPadding); Rect jump = new Rect(mScreenWidth - buttonPadding - buttonWidth, mScreenHeight - buttonHeight - buttonPadding, mScreenWidth - buttonPadding, mScreenHeight - buttonPadding); mControls = new ArrayList<>(); mControls.add(LEFT,left); mControls.add(RIGHT,right); mControls.add(JUMP, jump); }
In the previous code we initialize our remaining formatting members relative to the screen resolution in pixels. We then use them to position our three Rect
objects that represent the buttons and then add them to the mControls ArrayList
.
Next add the draw method which just like the HUD in the previous project will be called each frame of the game to draw the HUD. Notice the usual suspects are passed in as parameters to enable the method to do its job.
void draw(Canvas c, Paint p, GameState gs){
if(gs.getGameOver()){
// Draw the mMenuBitmap screen
c.drawBitmap(mMenuBitmap, 0,0, p);
// draw a rectangle to highlight the text
p.setColor(Color.argb (100, 26, 128, 182));
c.drawRect(0,0, mScreenWidth,
mTextFormatting * 4, p);
// Draw the level names
p.setColor(Color.argb(255, 255, 255, 255));
p.setTextSize(mTextFormatting);
c.drawText("Underground",
mTextFormatting,
mTextFormatting * 2,
p);
c.drawText("Mountains",
mScreenWidth * ONE_THIRD
+ (mTextFormatting),
mTextFormatting * 2,
p);
c.drawText("City",
mScreenWidth * TWO_THIRDS
+ (mTextFormatting),
mTextFormatting * 2,
p);
// Draw the fastest times
p.setTextSize(mTextFormatting/1.8f);
c.drawText("BEST:" + gs.getFastestUnderground()
+" seconds",
mTextFormatting,
mTextFormatting*3,
p);
c.drawText("BEST:" + gs.getFastestMountains()
+" seconds", mScreenWidth * ONE_THIRD
+ mTextFormatting,
mTextFormatting * 3,
p);
c.drawText("BEST:" + gs.getFastestCity()
+ " seconds",
mScreenWidth * TWO_THIRDS + mTextFormatting,
mTextFormatting * 3,
p);
// draw a rectangle to highlight the large text
p.setColor(Color.argb (100, 26, 128, 182));
c.drawRect(0,mScreenHeight - mTextFormatting * 2,
mScreenWidth,
mScreenHeight,
p);
p.setColor(Color.argb(255, 255, 255, 255));
p.setTextSize(mTextFormatting * 1.5f);
c.drawText("DOUBLE TAP A LEVEL TO PLAY",
ONE_THIRD + mTextFormatting * 2,
mScreenHeight - mTextFormatting/2,
p);
}
// else block follows next
}
The method is long and might seem complicated at first glance but there is nothing we haven't seen before. There is one thing to note, however. All the code is wrapped in an if
block with a condition of gs.getGameOver
. So, all the code we just added runs when the game is over. We will add the else
block which follows this if
block in a moment.
The code inside the if
block draws the background, level names and fastest times as well as the message to tell the player how to start the game. Clearly, we don't want these things on the screen while the game is being played.
Still inside the draw
method add the else
block that follows the if
block which will execute while the game is being played.
// else block follows next
else {
// draw a rectangle to highlight the text
p.setColor(Color.argb (100, 0, 0, 0));
c.drawRect(0,0, mScreenWidth,
mTextFormatting,
p);
// Draw the HUD text
p.setTextSize(mTextFormatting/1.5f);
p.setColor(Color.argb(255, 255, 255, 255));
c.drawText("Time:" + gs.getCurrentTime()
+ "+" + gs.getCoinsRemaining() * 10,
mTextFormatting / 4,
mTextFormatting / 1.5f,
p);
drawControls(c, p);
}
In the else
block we draw a transparent rectangle across the top of the screen which has the effect of highlighting the text that is drawn on top of it. Then we draw the current time. The slightly convoluted formula (gs.getCoinsRemaining() * 10
) has the effect of calculating (and displaying) the time penalty based on how many coins the player still needs to collect. The final line of code in the draw
method (but still inside the else
block) calls the drawControls
method. This is separated out purely to stop the method getting any longer than it already is.
Here are the final two methods of the HUD class. Add the drawControls
and getControls
methods.
private void drawControls(Canvas c, Paint p){ p.setColor(Color.argb(100,255,255,255)); for(Rect r : mControls){ c.drawRect(r.left, r.top, r.right, r.bottom, p); } // Set the colors back p.setColor(Color.argb(255,255,255,255)); } ArrayList<Rect> getControls(){ return mControls; }
The drawControls
method loops through the mControls ArrayList
and draws each button as a transparent rectangle. The getControls
method simply returns a reference to mControls
. GameEngine
will use this method to pass mControls
to the other classes that need it.
3.19.59.60