まずGameOverActivityは、元のMyGameOverクラスそのままです。また最初に起動されるMainActivityも、以下のようにボタン表示するだけのめちゃ簡単版。
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Space Trip Game - Start");
Button button = new Button(this);
button.setText("Start Game");
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, GameActivity.class);
startActivity(intent);
}
});
setContentView(button);
}
}
画面いっぱいのボタンという、清清しいまでの手抜きな開始画面となっちょりますw
さて、今回のキモとなるGameActivityをみていきましょう。まずはクラス定義と、entire lifetime 用の関数定義です。
public class GameActivity extends Activity {
GameView view;
Thread mainThread;
@Override protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Space Trip");
view = new GameView(this);
setContentView(view);
}
@Override protected void onDestroy() {
super.onDestroy();
if (mainThread != null && mainThread.isAlive()) {
mainThread.interrupt();
}
}
}
まあ定番の処理ですよね。ゲームの主要部分はGameViewクラスにあることがわかります。
続いて foreground lifetime 用の関数も定義しておきます。GameViewクラスのisRunningメンバを設定して、ゲーム内時間を止めたりしているだけですね。
protected void onResume() {
super.onResume();
view.isRunning = true;
}
protected void onPause() {
super.onPause();
view.isRunning = false;
}
ここまではガチ定番処理な感じですねぇ。
さて、これからいよいよGameViewクラスの定義にはいります。GameActivityの内部クラスとして定義されています。まずはクラス定義とコンストラクタ。
class GameView extends SurfaceView implements Runnable {
Context context;
Paint paint = new Paint();
Boolean isRunning;
GameScreen s;
List<GameObject> rocks = new ArrayList<GameObject>();
GameBitmapObject ship;
long score, loopCount;
public GameView(Context c) {
super(c);
context = c;
mainThread = new Thread(this);
mainThread.start();
}
さて、これからいよいよGameViewクラスの定義にはいります。GameActivityの内部クラスとして定義されています。まずはクラス定義とコンストラクタ。
class GameView extends SurfaceView implements Runnable {
Context context;
Paint paint = new Paint();
Boolean isRunning;
GameScreen s;
List<GameObject> rocks = new ArrayList<GameObject>();
GameBitmapObject ship;
long score, loopCount;
public GameView(Context c) {
super(c);
context = c;
mainThread = new Thread(this);
mainThread.start();
}
}
ゲーム自体の初期化は別にあるので、コンストラクタはわりと定番っぽいコードですね。ゲームの本体はスレッドで実行されるrun()関数にあります。まずは擬似コードで表現してみましょう。
public void run() {
public void run() {
画面表示ができるまで待つ
ゲームの初期化
while (true) { // メインループ
if (isRunning) {
自機や岩を動かす
doDraw(); // 自機や岩など画面表示
衝突チェック ⇒ 衝突でgameover()
スコアを計算
ループ回数に応じて岩を追加 ⇒ addRock()
}
while (true) { // メインループ
if (isRunning) {
自機や岩を動かす
doDraw(); // 自機や岩など画面表示
衝突チェック ⇒ 衝突でgameover()
スコアを計算
ループ回数に応じて岩を追加 ⇒ addRock()
}
}
}
さて実際のコードを見てみましょう。まずは「画面表示ができるまで待つ」の部分。Canvasをロックできるまでループして待つロジックになっています。
Canvas canvas;
do {
sleep(100);
canvas = getHolder().lockCanvas();
} while (canvas == null);
s = GameScreen.factory().init(canvas);
getHolder().unlockCanvasAndPost(canvas);
Bitmap b = BitmapFactory.decodeResource(context.getResources(),R.drawable.droid);
ship = new GameBitmapObject(s.w(0.1F), s.h(0.1F), b);
init();
自機のサイズは縦横共に画面の10%に設定しています。現時点ではアスペクト比などまったく考慮していないです。なので横長の画面だと、かなりデブった感じのAndroid君が表示されることになりますね。
オブジェクト生成以外の初期化処理は、ゲームオーバー後にも使用するのでinit()にまとめています。
void init() {
paint.setColor(Color.WHITE); // スコアの表示色
score = loopCount = 0;
s.setup(ship);
s.center(ship); // 自機を中心に戻す
ship.dx = ship.dy = 0; // 自機の速度を0に戻す
ship.rpw = s.w(0.01F); // 自機の当たり判定を画像より狭める
ship.rph = s.h(0.01F);
rocks.clear(); // 岩を全て取り除く
addRock(); // ゲーム開始時には3つ岩が追加されている
addRock();
addRock();
}
自機は最初は速度がゼロで、画面中心に配置されます。当たり判定のPadding幅(rpw, rph)を1%にしていますから、それぞれ中心から8%ぐらいが当たり判定のあるエリアになります。
さて続いてはメインループの中を記述しましょう。「自機や岩を動かす」部分は以下のようになっています。
ship.m(s).calc();
for (GameObject rock : rocks) {
rock.m(s).calc();
}
GameObject の関数 m() の説明がまだですね。これは以下のように定義された便利関数です。
public GameObject m(GameScreen s) {
move();
s.warp(this);
s.transform(this);
return this;
}
doDraw()はGameObjectのdraw()を利用しているので、以下のようにシンプルです。
void doDraw() {
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.BLACK);
String sc = "0000000000" + score;
canvas.drawText(sc.substring(sc.length() - 10), 0, paint.getTextSize(), paint);
ship.draw(canvas);
for (GameObject rock : rocks) {
rock.draw(canvas);
}
getHolder().unlockCanvasAndPost(canvas);
}
}
addRock()は少し長いですが、GameBitmapObjectを定義してランダムに配置しているだけです。
void addRock() {
Bitmap b;
if (rocks.size() > 0)
b = ((GameBitmapObject)rocks.get(0)).bitmap;
else
b = BitmapFactory.decodeResource(context.getResources(), R.drawable.redrock0);
GameBitmapObject o = new GameBitmapObject(s.w(0.01F * s.i(10, 5)), s.h(0.01F * s.i(10, 5)), b);
o.rpw = s.w(0.01F);
o.rph = s.h(0.01F);
s.setup(o);
do {
s.random(o);
o.calc();
} while (ship.collision(o));
o.dx = s.w(0.001F * s.i(80, -39));
o.dy = s.h(0.001F * s.i(80, -39));
rocks.add(o);
}
さて実際のコードを見てみましょう。まずは「画面表示ができるまで待つ」の部分。Canvasをロックできるまでループして待つロジックになっています。
Canvas canvas;
do {
sleep(100);
canvas = getHolder().lockCanvas();
} while (canvas == null);
次は「ゲームの初期化」部分。
getHolder().unlockCanvasAndPost(canvas);
Bitmap b = BitmapFactory.decodeResource(context.getResources(),R.drawable.droid);
ship = new GameBitmapObject(s.w(0.1F), s.h(0.1F), b);
init();
自機のサイズは縦横共に画面の10%に設定しています。現時点ではアスペクト比などまったく考慮していないです。なので横長の画面だと、かなりデブった感じのAndroid君が表示されることになりますね。
オブジェクト生成以外の初期化処理は、ゲームオーバー後にも使用するのでinit()にまとめています。
void init() {
paint.setColor(Color.WHITE); // スコアの表示色
score = loopCount = 0;
s.setup(ship);
s.center(ship); // 自機を中心に戻す
ship.dx = ship.dy = 0; // 自機の速度を0に戻す
ship.rpw = s.w(0.01F); // 自機の当たり判定を画像より狭める
ship.rph = s.h(0.01F);
rocks.clear(); // 岩を全て取り除く
addRock(); // ゲーム開始時には3つ岩が追加されている
addRock();
addRock();
}
さて続いてはメインループの中を記述しましょう。「自機や岩を動かす」部分は以下のようになっています。
ship.m(s).calc();
for (GameObject rock : rocks) {
rock.m(s).calc();
}
public GameObject m(GameScreen s) {
move();
s.warp(this);
s.transform(this);
return this;
}
「衝突チェック」は以下のようなコードです。説明の必要がないぐらいシンプルですねw
for (GameObject rock : rocks) {
if (ship.collision(rock)) {
gameover();
break;
}
}
「スコアを計算」「ループ回数に応じて岩を追加」も以下のような単純なコードです。速度が速いほど点数が増えやすくなっています。また10秒ごとに岩が増えていくのがわかります。
score += Math.abs(s.scaleX(ship.dx)) + Math.abs(s.scaleX(ship.dy));
if (rocks.size() -3 < ++loopCount / 100)
addRock();
さて、これでメイン部分のコードは終わりです。後は呼び出される3つの関数(doDraw, addRock, gameover)が残っているだけになりました。
doDraw()はGameObjectのdraw()を利用しているので、以下のようにシンプルです。
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.BLACK);
String sc = "0000000000" + score;
canvas.drawText(sc.substring(sc.length() - 10), 0, paint.getTextSize(), paint);
ship.draw(canvas);
for (GameObject rock : rocks) {
rock.draw(canvas);
}
getHolder().unlockCanvasAndPost(canvas);
}
}
void addRock() {
Bitmap b;
if (rocks.size() > 0)
b = ((GameBitmapObject)rocks.get(0)).bitmap;
else
b = BitmapFactory.decodeResource(context.getResources(), R.drawable.redrock0);
GameBitmapObject o = new GameBitmapObject(s.w(0.01F * s.i(10, 5)), s.h(0.01F * s.i(10, 5)), b);
o.rpw = s.w(0.01F);
o.rph = s.h(0.01F);
s.setup(o);
do {
s.random(o);
o.calc();
} while (ship.collision(o));
o.dx = s.w(0.001F * s.i(80, -39));
o.dy = s.h(0.001F * s.i(80, -39));
rocks.add(o);
}
岩の画像は毎回生成せずに、2個目からは最初の岩の画像を使いまわすようにしています。サイズは縦横それぞれ、画面の5~15%ってとこでしょうか。自機と同様に画像より小さめの当たり判定を設定しています。
初期位置ですが、random()で配置した後に、collision()で自機との当たり判定をチェックしているあたり、前回と同じですね。岩追加と同時にゲームオーバーになるのを防いでいます。
初期位置ですが、random()で配置した後に、collision()で自機との当たり判定をチェックしているあたり、前回と同じですね。岩追加と同時にゲームオーバーになるのを防いでいます。
最後はgameover()ですが、これは前回のものとほぼ同じでしょうかね。
public void gameover() {
isRunning = false;
Intent intent = new Intent(GameActivity.this, GameOverActivity.class);
intent.putExtra("SCORE", score);
startActivity(intent);
init();
}
さて、これでコードが揃いました。実行してみましょう。
ほら、前のと同じゲームがプレイできるようになりました。仮想画面を使っているせいか、前より動きがなめらかな気がします。また岩が画像になっただけで、雰囲気が良くなりましたねぇ。
ただ衝突判定をきちんとした結果、ゲーム自体は前より難しくなったみたいですw
岩は時間で増えるので、最初におもいきって速度出して点数を稼いでおくのがコツかなぁ。現時点でも、そこそこ面白いですw
とりあえず動作するので、パッケージを公開してみます。興味ある方はご参照ください ⇒ SpaceTrip_20110810.zip
ただ衝突判定をきちんとした結果、ゲーム自体は前より難しくなったみたいですw
岩は時間で増えるので、最初におもいきって速度出して点数を稼いでおくのがコツかなぁ。現時点でも、そこそこ面白いですw
とりあえず動作するので、パッケージを公開してみます。興味ある方はご参照ください ⇒ SpaceTrip_20110810.zip
0 件のコメント:
コメントを投稿