
前回は、背景とボールを画像にしてみました。今回はその他の要素も入れて、一応ゲーム仕立てにしてみました。
http://jp.youtube.com/watch?v=solyzt9hVMA に動画をアップロードしてあります。アプリケーション選択画面にもちゃんとアイコンを表示してます!
ソースコードを固めた zip ファイルをここに置いておきます。参考までに
です。
↓zip ファイルの中身です。なお、本ゲームはある程度まとまった形になったので、修正 BSD ライセンス(新 BSD ライセンスが正しいかも)で配布することにしました。各ソースコード上部に記載しています。英文が有効ですが、Open Source Group Japan による日本語参考訳はこちらです。
あなたが宇宙船に乗って宇宙を旅していると 未知の星の重力圏に捕まってしまい、脱出できなくなってしまいました… 地上を探索していると、突如上空から自機と同じくらい巨大なボールが! ぶつかってしまったら最後です。 集中力が続く限り、避けて避けて避け続けてください!
…結局脱出できないのか。何て救いの無いストーリーだろう…
操作方法は↓の通りです。
| ↑キー | ゲーム開始/ポーズ/ゲーム再開の切り替え |
| ←キー | 進行方向を左に |
| →キー | 進行方向を右に |
| スペースキー | 重力加速度を振り切って上昇 |
宇宙船と書いておきながら画像もクラス名も UFO ですが… UFO はゲーム開始直後は左右方向には停止しており、自由落下を始めます。スペースキーを押している間上昇を続けます。また、一度左右いずれかのキーを押すとその方向に移動を開始し、以後は左右の切り替えはできますが停止しません。
画面上端あるいは下端に衝突するか、上空から落下してくるボールと衝突したらゲームオーバーです。
画面サイズには依存していない(背景画像は引き伸ばされる)ですが、背景画像を 320x240 で作っていますので、エミュレータを起動する際はデフォルトのスキンか -skin QVGA-L (320x240) で起動することをお勧めします。-skin HVGA-L (480x320) だと、PC によってはひどく重くなってしまいます。-skin QVGA-P (240x320) は、背景画像がいびつにはなりますがゲーム的には結構面白いかもしれません。
以下の画像ファイルを使います。
背景として使います。

空からこのボールが落ちてきます。

プレイヤーが乗ったUFOです。

UFO がボールにぶつかったり、画面の上端あるいは下端にぶつかると爆発します。

以下、ソースコードを掲載します。前回 までの内容を前提として説明を書きます。Updatable.java と UpdateHandler.java については、ゲームへの第一歩。キーパッドでボールを動かしてみます。をご覧下さい。
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (c) 2007, Ryo Ichinose
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the auther nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="rio1218.dodgeballs">
<application android:icon="@drawable/ufo" android:label="DodgeBalls">
<activity class="DodgeBalls">
<intent-filter>
<action android:value="android.intent.action.MAIN" />
<category android:value="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activityCreator でプロジェクトを作成すると、自動生成してくれます。ただしそのままではアプリケーション選択画面にデフォルトのアイコンしか表示されないので、application タグの android:icon 属性に表示画像を指定します。指定の仕方はこれまで同様、res/drawable 以下に用意した画像ファイル名称を使って "@drawable/ファイル名称(拡張子を除く)" とします。
<?xml version="1.0" encoding="utf-8"?> <!-- * Copyright (c) 2007, Ryo Ichinose * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the auther nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> <resources> <string name="mode_ready">\n\n*** Dodge Balls ***\nPress Up To Play</string> <string name="mode_pause">\n\nPause\nPress Up To Go Back</string> <string name="mode_end_1">\n\nGame Over\nYou survived </string> <string name="mode_end_2">sec.\nPress Up To Play Again</string> </resources>
画面表示用の文字列リソースです。ゲーム内容に合わせた文字列を用意しています。
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (c) 2007, Ryo Ichinose
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the auther nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<rio1218.dodgeballs.GameView
id="@+id/game"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/wallpaper"
/>
<TextView
id="@+id/message"
android:text="@string/mode_ready"
android:visibility="visible"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAlign="center"
android:textColor="#ff0000ff"
android:textSize="24sp"
android:layout_weight="1"
/>
</FrameLayout>
今回の画面構成は
としています。GameView の背景には wallpaper.png を指定しています。また、TextView の android:textColor を "#ff0000ff" としてメッセージ表示色を青にしました。
/*
* Copyright (c) 2007, Ryo Ichinose
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the auther nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package rio1218.dodgeballs;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.widget.TextView;
public class DodgeBalls extends Activity
{
private static final String BUNDLE_KEY = "GameView";
private GameView gameView;
@Override
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
TextView messageView = (TextView)findViewById(R.id.message);
gameView = (GameView)findViewById(R.id.game);
gameView.setTextView(messageView);
if (null != icicle) {
Bundle map = icicle.getBundle(BUNDLE_KEY);
if (null != map) {
gameView.restore(map);
gameView.update();
return;
}
}
gameView.setMode(GameView.MODE_READY);
gameView.update();
}
@Override
protected void onFreeze(Bundle outState)
{
super.onFreeze(outState);
outState.putBundle(BUNDLE_KEY, gameView.save());
}
@Override
protected void onPause()
{
super.onPause();
gameView.setMode(GameView.MODE_PAUSE);
}
}
名前が DodgeBalls.java になったのと、扱う View が BallView ではなく GameView になった以外は 前回 の BallActivity.java と同じです。
/*
* Copyright (c) 2007, Ryo Ichinose
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the auther nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package rio1218.dodgeballs;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.content.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
/**
* Main view.
*/
public class GameView extends View implements Updatable
{
// Modes of this view.
public static final int MODE_READY = 0;
public static final int MODE_RUNNING = 1;
public static final int MODE_PAUSE = 2;
public static final int MODE_OVER = 3;
// Frames per second, and the time between frames.
private static final int FPS = 10;
private static final long TIME_TO_SLEEP = (long)(1000.0/FPS);
// The number of balls.
private static final int NUM_OF_BALLS = 5;
// The maximum elapsed time.
private static final int MAX_ELAPSED_TIME = 999999;
// Handler for periodic update of screen.
private UpdateHandler updateHandler = new UpdateHandler(this);
// Resource manager.
private Resources res = getContext().getResources();
// Text area to show messages.
private TextView messageView;
// Current mode of this view.
private int mode = GameView.MODE_READY;
// Time related variables.
private long lastTime;
private long elapsedTime;
// Player.
private Ufo ufo = new Ufo();
// Balls.
private List<Ball> balls = new ArrayList<Ball>();
private int[] launchFrames = {12, 30, 20, 3, 27, 19};
private int launched;
private int frames;
/**
* Constructor of this view class.
*/
public GameView(Context context, AttributeSet attrs, Map inflateParams)
{
super(context, attrs, inflateParams);
for (int i = 0; i < GameView.NUM_OF_BALLS; i ++) {
balls.add(new Ball(i, GameView.NUM_OF_BALLS));
}
init();
}
public GameView(Context context, AttributeSet attrs, Map inflateParams, int defStyle)
{
super(context, attrs, inflateParams, defStyle);
for (int i = 0; i < GameView.NUM_OF_BALLS; i ++) {
balls.add(new Ball(i, GameView.NUM_OF_BALLS));
}
init();
}
private void init()
{
setFocusable(true);
launched = 0;
frames = 0;
elapsedTime = 0;
ufo.setDrawable(res.getDrawable(R.drawable.ufo),
res.getDrawable(R.drawable.explosion));
ufo.init();
for (Ball ball : balls) {
ball.setDrawable(res.getDrawable(R.drawable.ball));
ball.init();
}
}
/**
* Sets view mode to the specified one.
*/
public void setMode(int newMode)
{
mode = newMode;
CharSequence str = null;
switch (mode) {
case GameView.MODE_READY:
messageView.setVisibility(View.VISIBLE);
break;
case GameView.MODE_PAUSE:
str = res.getText(R.string.mode_pause);
messageView.setText(str);
messageView.setVisibility(View.VISIBLE);
break;
case GameView.MODE_RUNNING:
messageView.setVisibility(View.INVISIBLE);
lastTime = System.currentTimeMillis();
break;
case GameView.MODE_OVER:
String time = Long.toString(elapsedTime / 1000);
str = res.getText(R.string.mode_end_1);
str = str + " " + time + " " + res.getText(R.string.mode_end_2);
messageView.setText(str);
messageView.setVisibility(View.VISIBLE);
break;
}
}
public void update()
{
if (mode == GameView.MODE_RUNNING) {
updateBalls();
updateUfo();
launchBalls();
updateTime();
fireDrawBalls();
invalidate(ufo.getDirtyRect());
}
updateHandler.sleep(TIME_TO_SLEEP);
}
private void updateBalls()
{
for (Ball ball : balls) {
if (ball.isAlive()) {
if (ufo.getRect().intersect(ball.getRect())) {
ufo.explode();
}
ball.update();
}
}
}
private void updateUfo()
{
ufo.update();
if (! ufo.isAlive()) {
setMode(GameView.MODE_OVER);
}
}
private void launchBalls()
{
if (launched == GameView.NUM_OF_BALLS) {
return;
}
for (int i = 0; i < GameView.NUM_OF_BALLS; i ++) {
if (launchFrames[i] == frames) {
balls.get(i).fall();
launched ++;
if (launched == GameView.NUM_OF_BALLS) {
return;
}
}
}
frames ++;
}
private void updateTime()
{
if (elapsedTime > GameView.MAX_ELAPSED_TIME) {
elapsedTime = GameView.MAX_ELAPSED_TIME;
return;
}
long now = System.currentTimeMillis();
elapsedTime += now - lastTime;
lastTime = now;
}
private void fireDrawBalls()
{
for (Ball ball : balls) {
if (ball.isAlive()) {
invalidate(ball.getDirtyRect());
}
}
}
public Bundle save()
{
Bundle map = new Bundle();
map.putInteger("mode", mode);
map.putInteger("launched", launched);
map.putInteger("frames", frames);
map.putLong("lastTime", lastTime);
map.putLong("elapsedTime", elapsedTime);
ufo.save(map);
for (Ball ball : balls) {
ball.save(map);
}
return map;
}
public void restore(Bundle map)
{
setMode(GameView.MODE_PAUSE);
mode = map.getInteger("mode");
launched = map.getInteger("launched");
frames = map.getInteger("frames");
lastTime = map.getLong("lastTime");
elapsedTime = map.getLong("elapsedTime");
ufo.restore(map);
for (Ball ball : balls) {
ball.restore(map);
}
}
public void setTextView(TextView messageView)
{
this.messageView = messageView;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
ufo.setScreenSize(w, h);
for (Ball ball : balls) {
ball.setScreenSize(w, h);
}
if (mode == GameView.MODE_READY) {
init();
}
}
@Override
public void onDraw(Canvas canvas)
{
super.onDraw(canvas);
for (Ball ball : balls) {
ball.draw(canvas);
}
ufo.draw(canvas);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
switch (mode) {
case GameView.MODE_READY:
setMode(GameView.MODE_RUNNING);
init();
update();
break;
case GameView.MODE_RUNNING:
setMode(GameView.MODE_PAUSE);
update();
break;
case GameView.MODE_PAUSE:
setMode(GameView.MODE_RUNNING);
update();
break;
case GameView.MODE_OVER:
setMode(GameView.MODE_RUNNING);
init();
invalidate();
update();
break;
}
return true;
}
if (mode == GameView.MODE_RUNNING) {
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
ufo.setDirection(Ufo.RIGHT);
return true;
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
ufo.setDirection(Ufo.LEFT);
return true;
}
if (keyCode == KeyEvent.KEYCODE_SPACE) {
ufo.startFire();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_SPACE) {
ufo.stopFire();
return true;
}
return super.onKeyUp(keyCode, event);
}
}
前回 までの BallView.java を基本として、ゲーム性をもたせるべく改造しています。まず、名前が BallView.java から GameView.java になっています^^;
前回 までは(最大)30 FPS としていたのですが、今回(最大)10 FPS まで落としました。というのも、ちょっと古い PC でエミュレータを動かしていると背景+UFO+ボールx5 の再描画が遅くて…10 くらいなら結構大丈夫そうなので、これで調整しました。なお、今時の PC なら 30 FPS でも余裕みたいです。実機なら全然違ってくるんでしょうね〜。
ゲーム性の一環として、開始してから爆発してしまうまでの経過時間(ポーズ中は除く)を計測することにしました。このための変数が lastTime/elapsedTime です。これらは、ゲームオーバー時に表示されます(常時表示してると重いから…)
前回 まではボールを操作しましたが、今回はボールは空から降ってくるだけになり、自分で操作するのは UFO になりました!というわけで、新たに Ufo クラスを導入しました(変数名 ufo)。
NUM_OF_BALLS で定義した数のボールが、空から落ちてきます。List
ボールの生成処理が追加になりました。
増えた変数分だけ初期化処理が追加になりました。Java5 以降だと for (Ball ball : balls){} とループが組めるので重宝してます。
MODE_OVER の設定でゲームオーバー状態に移行する際に、生存時間を文字列に変換してメッセージ領域に設定する様にしました。
MODE_RUNNING 中の処理が増えたので、
に分けました。
ボールの移動処理を行います。各ボールと UFO の衝突判定を行い、衝突している場合は UFO を爆発させます(Ufo#explode())。
UFO の移動処理を行います。
ボールの落下開始処理を行います。前述の launchFrames の内容に従って、最大 NUM_OF_BALLS のボールを落下開始させます。一度落下状態になったボールは、以後画面下端から上端に自動的に戻って再度落下し始めるので、NUM_OF_BALLS のボールを落下開始させた後は何もしません。
経過時間を保存します。update() から MODE_RUNNING の間だけ実行されるため、結果として UFO の生存中だけ更新されます。
各ボールの移動範囲の再描画を指示します。
増えた変数分の保存/回復処理が追加になりました。
Ufo とボール全てに画面サイズを通知します。これにより、画面サイズが変わっても(一応)コードの変更無しでゲームを実行できます。
Ufo とボール全てを描画します。爆発状態になった時に、爆発イメージがボールの上に重なる様に表示したいので、ボールの方を先に描画します。
ゲームの状態変更をキーパッドの↑キーで行います。後は左右で UFO の左右移動切り替え、スペースキーで UFO の上昇となります。実機の場合だと違うキー割り当ての方がいいかもしれませんが、エミュレータだとこの割り当てでやりやすいと思います。
新しく追加しました。スペースキーを話したら、UFO の上昇を停止して自由落下させます。
/*
* Copyright (c) 2007, Ryo Ichinose
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the auther nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package rio1218.dodgeballs;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
/**
* This is the class represents a single ball.
*/
public class Ball
{
// Ball related constants.
private static final int FRAMEOUT_MARGIN = 5;
private static final int RECT_MARGIN = 2;
private static final int RADIUS = 12;
private static final int SIZE = (Ball.RADIUS * 2);
private static final int VY = 8;
private int screenWidth;
private int screenHeight;
private boolean alive;
private int id;
private int numBalls;
private int x;
private int y;
private Bitmap bitmap;
public Ball(int id, int numBalls)
{
this.id = id;
this.numBalls = numBalls;
}
public void setDrawable(Drawable drawable)
{
bitmap = Bitmap.createBitmap(Ball.SIZE, Ball.SIZE, true);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, Ball.SIZE, Ball.SIZE);
drawable.draw(canvas);
}
public void setScreenSize(int w, int h)
{
screenWidth = w;
screenHeight = h;
}
public Rect getDirtyRect()
{
return new Rect(
x - Ball.RADIUS,
y - Ball.RADIUS - Ball.VY,
x + Ball.RADIUS,
y + Ball.RADIUS + Ball.VY
);
}
public Rect getRect()
{
return new Rect(x - Ball.RADIUS + Ball.RECT_MARGIN,
y - Ball.RADIUS + Ball.RECT_MARGIN,
x + Ball.RADIUS - Ball.RECT_MARGIN,
y + Ball.RADIUS - Ball.RECT_MARGIN);
}
public void init()
{
alive = false;
int step = (numBalls == 0) ? 0 : screenWidth / numBalls;
this.x = step * id + step / 2;
y = - Ball.RADIUS;
}
public void fall()
{
alive = true;
y = - Ball.RADIUS;
}
public boolean isAlive()
{
return alive;
}
public void update()
{
y += VY;
if (y > screenHeight + Ball.RADIUS + Ball.FRAMEOUT_MARGIN) {
y = - Ball.RADIUS;
}
}
private final Paint paint = new Paint();
public void draw(Canvas canvas)
{
canvas.drawBitmap(bitmap, x - Ball.RADIUS, y - Ball.RADIUS, paint);
}
public void save(Bundle map)
{
String suffix = Integer.toString(id);
map.putInteger("screenWidth" + suffix, screenWidth);
map.putInteger("screenHeight" + suffix, screenHeight);
map.putBoolean("alive" + suffix, alive);
map.putInteger("numBalls" + suffix, numBalls);
map.putInteger("x" + suffix, x);
map.putInteger("y" + suffix, y);
}
public void restore(Bundle map)
{
String suffix = Integer.toString(id);
screenWidth = map.getInteger("screenWidth" + suffix);
screenHeight = map.getInteger("screenHeight" + suffix);
alive = map.getBoolean("alive" + suffix);
numBalls = map.getInteger("numBalls" + suffix);
x = map.getInteger("x" + suffix);
y = map.getInteger("y" + suffix);
}
}
前回 までの Ball.java との大きな違いは
という所でしょうか。
なお、getRect() は UFO との衝突判定用の矩形を作るためのメソッドです。画像より一周り小さいくらいの範囲にしています。
/*
* Copyright (c) 2007, Ryo Ichinose
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the auther nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package rio1218.dodgeballs;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
/**
* This is the class represents the UFO.
*/
public class Ufo
{
// Key direction.
public static final int STOP = 0;
public static final int RIGHT = 1;
public static final int LEFT = 2;
// Speed parameters.
private static final int VX = 8;
private static final int ACC = -6;
private static final int G = 3;
private static final int MIN_VY = -10;
private static final int MAX_VY = 6;
// Size constants.
private static final int RECT_MARGIN = 2;
private static final int WIDTH = 32;
private static final int HEIGHT = 24;
private static final int DIRTY_X = Ufo.VX;
private static final int DIRTY_Y = -Ufo.MIN_VY;
// Explosion parameters.
private static final int EXPLOSION_WIDTH = 32;
private static final int EXPLOSION_HEIGHT = 32;
private static final int FRAMES_EXPLOSION = 15;
private int screenWidth;
private int screenHeight;
private int frames;
private boolean alive = true;
private boolean exploding;
private boolean onFire;
private int direction = Ufo.STOP;
private int x;
private int y;
private int vy;
private Bitmap bUfo;
private Bitmap bExplosion;
public void setDrawable(Drawable dUfo, Drawable dExplosion)
{
bUfo = Bitmap.createBitmap(Ufo.WIDTH, Ufo.HEIGHT, true);
Canvas ufoCanvas = new Canvas(bUfo);
dUfo.setBounds(0, 0, Ufo.WIDTH, Ufo.HEIGHT);
dUfo.draw(ufoCanvas);
bExplosion = Bitmap.createBitmap(Ufo.EXPLOSION_WIDTH, Ufo.EXPLOSION_HEIGHT, true);
Canvas expCanvas = new Canvas(bExplosion);
dExplosion.setBounds(0, 0, Ufo.EXPLOSION_WIDTH, Ufo.EXPLOSION_HEIGHT);
dExplosion.draw(expCanvas);
}
public void setDirection(int direction)
{
switch (direction) {
case Ufo.STOP:
case Ufo.RIGHT:
case Ufo.LEFT:
this.direction = direction;
}
}
public void setScreenSize(int w, int h)
{
screenWidth = w;
screenHeight = h;
}
public void startFire()
{
onFire = true;
}
public void stopFire()
{
onFire = false;
}
public void explode()
{
if (! exploding) {
exploding = true;
frames = 0;
}
}
public Rect getDirtyRect()
{
return new Rect(
x - Ufo.DIRTY_X,
y - Ufo.DIRTY_Y,
x + Ufo.WIDTH + Ufo.DIRTY_X,
y + Ufo.HEIGHT + Ufo.DIRTY_Y
);
}
public Rect getRect()
{
return new Rect(x + Ufo.RECT_MARGIN,
y + Ufo.RECT_MARGIN,
x + Ufo.WIDTH - Ufo.RECT_MARGIN,
y + Ufo.HEIGHT - Ufo.RECT_MARGIN);
}
public void init()
{
setDirection(Ufo.STOP);
frames = 0;
alive = true;
exploding = false;
onFire = false;
x = screenWidth / 2 - Ufo.WIDTH / 2;
y = screenHeight / 2;
vy = 0;
}
public boolean isAlive()
{
return alive;
}
public void update()
{
if (exploding) {
frames ++;
if (frames >= Ufo.FRAMES_EXPLOSION) {
frames = 0;
alive = false;
}
return;
}
switch (direction) {
case RIGHT:
x += VX;
if (x > screenWidth - Ufo.WIDTH) {
x = screenWidth - Ufo.WIDTH;
direction = LEFT;
}
break;
case LEFT:
x -= VX;
if (x < 0) {
x = 0;
direction = RIGHT;
}
break;
}
vy += onFire ? G + ACC : G;
vy = (vy > MAX_VY) ? MAX_VY : vy;
vy = (vy < MIN_VY) ? MIN_VY : vy;
y += vy;
if (y <= 0 || y >= screenHeight - Ufo.HEIGHT) {
exploding = true;
frames = 0;
}
}
private final Paint paint = new Paint();
public void draw(Canvas canvas)
{
if (exploding) {
canvas.drawBitmap(bExplosion, x, y, paint);
}
else {
canvas.drawBitmap(bUfo, x, y, paint);
}
}
public void save(Bundle map)
{
map.putInteger("screenWidth", screenWidth);
map.putInteger("screenHeight", screenHeight);
map.putInteger("frames", frames);
map.putInteger("direction", direction);
map.putBoolean("alive", alive);
map.putBoolean("exploding", exploding);
map.putBoolean("onFire", onFire);
map.putInteger("x", x);
map.putInteger("y", y);
map.putInteger("vy", vy);
}
public void restore(Bundle map)
{
screenWidth = map.getInteger("screenWidth");
screenHeight = map.getInteger("screenHeight");
frames = map.getInteger("frames");
direction = map.getInteger("direction");
alive = map.getBoolean("alive");
exploding = map.getBoolean("exploding");
onFire = map.getBoolean("onFire");
x = map.getInteger("x");
y = map.getInteger("y");
vy = map.getInteger("vy");
}
}
前回 までの Ball.java をベースに、上下方向には自由には移動できない様に修正しました。
通常の UFO 表示と爆発時に爆発イメージ表示のために、それぞれ用の画像リソースをもらって Bitmap として保存しておきます。
キー操作で自由に操作できるのは左右のみとなりました。
startFire() により、上昇加速度の発生状態となります。この間は、重力加速度よりも上昇加速度の方が大きいため、Ufo は上昇します。stopFire() により上昇加速度が 0 になるため、重力加速度により自由落下します。
ボールとの衝突時に GameView によって実行されます。実行すると爆発中となり、経過フレーム数の計測を開始します。
再描画用の矩形を生成します。
ボールとの衝突判定用の矩形を生成します。
爆発中の場合は、経過フレーム数を計測します。経過フレーム数が FRAMES_EXPLOSION に到達した時点で、alive = false としてゲームオーバーとなります。
左右の進行状態に応じて、x 座標を更新します。また、重力加速度と上昇加速度の兼ね合いで y 座標を更新します。画面上端あるいは下端に到達した場合は、爆発中に移行します。
爆発中の場合は爆発イメージを、そうでない場合は UFO を描画します。
増えた分の変数の処理が追加になっています。
サンプルコードの説明終わり。自分で作った割には、いい暇つぶしになるなあなどと思ってます…
FC2 Blog Ranking に参加してます。クリックよろしくお願いします!
<<B'z 「ACTION」 感想 1-10曲分をお送りします! | ホーム | ガンダム占い(Yahoo!占い)>>
Author:いちのせ りょう
1974年北海道生まれ、育ちは沖縄/宮崎、で現在は東京在住のSEです。社会人10年目、未だにばりばり作ってます!Rio's Laboratory もよろしく…
性別:男性
車:MAZDA DEMIO
好きな音楽:B'z、The Yellow Monkey、Bon Jovi
好きなゲーム:MGS、Bio Hazard、Zeldaとか
やりたい事:F1観に行きたい、沖縄の海に潜りたい、空を飛びたい