Feel free to copy and paste all the code in this section. It is nearly identical to the structure of the Pong game. What will vary is the other classes we will create (Bullet
and Bob
) as well as the way that we handle player input, timing, updating and drawing within the BulletHellGame
class.
As you proceed with this section glance over the code to notice subtle but important differences which I will also point out to you as we proceed.
Paste and examine this code into the BulletHellActivity
class.
import android.app.Activity; import android.graphics.Point; import android.os.Bundle; import android.view.Display; // This class is almost exactly // the same as the Pong project public class BulletHellActivity extends Activity { // An instance of the main class of this project private BulletHellGame mBHGame; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Get the screen resolution Display display = getWindowManager(). getDefaultDisplay(); Point size = new Point(); display.getSize(size); // Call the constructor(initialize) // the BulletHellGame instance mBHGame = new BulletHellGame(this, size.x, size.y); setContentView(mBHGame); } @Override // Start the main game thread // when the game is launched protected void onResume() { super.onResume(); mBHGame.resume(); } @Override // Stop the thread when the player quits protected void onPause() { super.onPause(); mBHGame.pause(); } }
It is very similar to the previous project except for the BulletHellGame
object instead of a PongGame
object declaration. In addition, the BulletHellGame
object (mBHGame
) is therefore used in the call to setContentView
as well as in onResume
and onPause
to start and stop the thread in the BulletHellGame
class which we will code next.
There are lots of errors in Android Studio but only because the code refers to the BulletHellGame
class that we haven't coded yet. We will do that now.
Paste this next code into the BulletHellGame
class. It is exactly the same structure as the previous game. Familiarize yourself with it again by pasting and reviewing it a section at a time. Make note of the code structure and the blank methods that we will soon add new bullet-hell-specific code to.
Add the import
statements and alter the class declaration to handle a thread (implement the Runnable
interface) and extend SurfaceView
.
import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.SoundPool; import android.os.Build; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.io.IOException; class BulletHellGame extends SurfaceView implements Runnable{ }
Add the member variables after the class declaration
class BulletHellGame extends SurfaceView implements Runnable{ // Are we currently debugging boolean mDebugging = true; // Objects for the game loop/thread private Thread mGameThread = null; private volatile boolean mPlaying; private boolean mPaused = true; // Objects for drawing private SurfaceHolder mOurHolder; private Canvas mCanvas; private Paint mPaint; // Keep track of the frame rate private long mFPS; // The number of milliseconds in a second private final int MILLIS_IN_SECOND = 1000; // Holds the resolution of the screen private int mScreenX; private int mScreenY; // How big will the text be? private int mFontSize; private int mFontMargin; // These are for the sound private SoundPool mSP; private int mBeepID = -1; private int mTeleportID = -1; }
All the member variables were also present in the PongGame
class and have exactly the same usage. The previous code includes a few comments as a reminder.
Add the constructor after the members but inside the closing curly brace of the class.
// This is the constructor method that gets called // from BullethellActivity public BulletHellGame(Context context, int x, int y) { super(context); mScreenX = x; mScreenY = y; // Font is 5% of screen width mFontSize = mScreenX / 20; // Margin is 2% of screen width mFontMargin = mScreenX / 50; mOurHolder = getHolder(); mPaint = new Paint(); // Initialize the SoundPool if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { AudioAttributes audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType (AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); mSP = new SoundPool.Builder() .setMaxStreams(5) .setAudioAttributes(audioAttributes) .build(); } else { mSP = new SoundPool(5, AudioManager.STREAM_MUSIC, 0); } try{ AssetManager assetManager = context.getAssets(); AssetFileDescriptor descriptor; descriptor = assetManager.openFd("beep.ogg"); mBeepID = mSP.load(descriptor, 0); descriptor = assetManager.openFd("teleport.ogg"); mTeleportID = mSP.load(descriptor, 0); }catch(IOException e){ Log.e("error", "failed to load sound files"); } startGame(); }
The constructor code we have just seen contains nothing new. The screen resolution is passed in as parameters, the corresponding member variables (mScreenX
and mScreenY
) are initialized and the SoundPool
object is initialized then the sound effects are loaded into memory. Notice that we call the startGame
method at the end.
Add these four empty methods and the identically implemented run
method to handle the game loop.
// Called to start a new game public void startGame(){ } // Spawns ANOTHER bullet private void spawnBullet(){ } // Handles the game loop @Override public void run() { while (mPlaying) { long frameStartTime = System.currentTimeMillis(); if(!mPaused){ update(); // Now all the bullets have been moved // we can detect any collisions detectCollisions(); } draw(); long timeThisFrame = System.currentTimeMillis() - frameStartTime; if (timeThisFrame >= 1) { mFPS = MILLIS_IN_SECOND / timeThisFrame; } } } // Update all the game objects private void update(){ } private void detectCollisions(){ }
The previous methods we have seen before except for one. We have just coded a spawnBullet
method that we can call each time we want to spawn a new bullet. At this point, the method contains no code. We will add code to these methods over this chapter and the next.
Add the draw
and the onTouchEvent
methods that are shown next.
private void draw(){ if (mOurHolder.getSurface().isValid()) { mCanvas = mOurHolder.lockCanvas(); mCanvas.drawColor(Color.argb(255, 243, 111, 36)); mPaint.setColor(Color.argb(255, 255, 255, 255)); // All the drawing code will go here if(mDebugging) { printDebuggingText(); } mOurHolder.unlockCanvasAndPost(mCanvas); } } @Override public boolean onTouchEvent(MotionEvent motionEvent) { return true; }
The draw
method just clears the screen with a plain color and sets the paint color to white ready for drawing the bullets. Note the call to printDebuggingText
when the mDebugging
variable is set to true
. We also added an empty (apart from return statement) method for onTouchEvent
ready to add code to handle the player's input.
These next three methods, just like all the previous methods, serve exactly the same purpose as they did in the Pong project. Add the pause
, resume
, and printDebuggingText
methods.
public void pause() { mPlaying = false; try { mGameThread.join(); } catch (InterruptedException e) { Log.e("Error:", "joining thread"); } } public void resume() { mPlaying = true; mGameThread = new Thread(this); mGameThread.start(); } private void printDebuggingText(){ int debugSize = 35; int debugStart = 150; mPaint.setTextSize(debugSize); mCanvas.drawText("FPS: " + mFPS , 10, debugStart + debugSize, mPaint); }
The printDebuggingText
method simply draws the current value of mFPS
to the screen. This will be interesting to monitor when there are vast numbers of bullets bouncing all over the place. Two new local variables are also declared and initialized (debugSize
and debugStart
) they are used to programmatically position and scale the debugging text as it obviously will vary from the HUD text which will be drawn in the draw
method.
Run the game and you will see we have set up a simple game engine that is virtually identical to the Pong game engine. It doesn't do anything yet except loop around measuring the frames per second, but it is ready to start drawing and updating objects.
Now we can focus on building the new game and learning about Java arrays. Let's start with a class that will represent each one of the huge numbers of bullets we will spawn.
18.225.175.151