2015-08-09

Android Studio でボール転がしゲームの作成

スポンサーリンク

前回は加速度センサを使うことでユーザによるボールの操作が可能になりました。最初に比べると随分ゲームっぽさが増しましたが、ボールを転がしているだけではゲームとは言えません。今回はボールを転がす目標(ゴール)を作りゲームらしく仕上げていきます。

Hole クラスの作成


ボールのゴールとなるホールを作成します。要するに玉を転がして穴に落とそうというゲームです。Hole クラスは前回作成した Ball クラスとほぼ同じです。画面内での位置と半径を示すメンバ変数を持っています。
  • "Hole.java"
public class Hole extends View{
    int x, y, r;
    Paint p;

    public Hole(Context context){
        super(context);
        x = y = 0;
        r = 40;
        p = new Paint();
        p.setColor(Color.BLACK);
        p.setStyle(Paint.Style.FILL);
        p.setAntiAlias(true);
    }
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(x, y, r, p);
    }
}

横画面固定にする


前回は縦画面固定に設定していましたが、実機に表示されるボールを操作しようとするとスマートフォンを両手に持つほうが操作しやすかったので、横画面固定に変更します。
  • "AndroidManifest.xml"
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android=
    "http://schemas.android.com/apk/res/android"
    package="com.example.ball" >
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="landscape" >
            <intent-filter>
                <action android:name=
                    "android.intent.action.MAIN" />
                <category android:name=
                    "android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

画面内にボールを落とす穴を設置


次に MainActivityHole を実体化します。穴の位置は乱数を使ってランダムに発生させます。このとき画面の端っこに穴を設置してしまうとゲームの難易度が下がってしまうため、画面の端っこからは離して設置します。 下記のように乱数の発生範囲を限定しています。
Hole hole = new Hole(this);
Random rnd = new Random();
hole.x = rnd.nextInt(width - (2 * (hole.r + ball.radius)))
                     + hole.r + ball.radius;
hole.y = rnd.nextInt(height - (2 * (hole.r + ball.radius)))
                     + hole.r + ball.radius;

画面内に複数の図形を表示するため FrameLayout を使っています。FrameLayout は後に追加した View が上に重なるので HoleBall の順に addView() を実行します。
FrameLayout framelayout = new FrameLayout(this);
framelayout.setBackgroundColor(Color.GREEN);
setContentView(framelayout);

framelayout.addView(hole);
framelayout.addView(ball);

run() メソッド内に、ボールが穴の上を通ったらボールを穴の中心に固定するコードを記述します。
if ((hole.x - hole.r < ball.x && ball.x < hole.x + hole.r) &&
    (hole.y - hole.r < ball.y && ball.y < hole.y + hole.r)) {
    ball.x = hole.x;
    ball.y = hole.y;
    ball.vx = ball.vy = 0;
    ball.invalidate();
}

上記を踏まえ "MainActivity.java" を下記の通り書き換えます。
  • "MainActivity.java"
public class MainActivity extends Activity implements Runnable, SensorEventListener {
    SensorManager manager;
    Ball ball;
    Hole hole;
    Handler handler;
    int width, height, time;
    float gx, gy, dpi;
    FrameLayout framelayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        framelayout = new FrameLayout(this);
        framelayout.setBackgroundColor(Color.GREEN);
        setContentView(framelayout);

        time = 10;
        handler = new Handler();
        handler.postDelayed(this, 3000);

        WindowManager windowManager =
            (WindowManager) getSystemService(WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        width = display.getWidth();
        height = display.getHeight();
        dpi = getResources().getDisplayMetrics().densityDpi;

        ball = new Ball(this);
        ball.x = width / 2;
        ball.y = height / 2;

        hole = new Hole(this);
        Random rnd = new Random();
        hole.x = rnd.nextInt(
            width - (2 * (hole.r + ball.radius)))
            + hole.r + ball.radius;
        hole.y = rnd.nextInt(
            height - (2 * (hole.r + ball.radius)))
            + hole.r + ball.radius;

        framelayout.addView(hole);
        framelayout.addView(ball);
    }
    @Override
    public void run() {
        ball.vx += (float) (gx * time / 1000);
        ball.vy += (float) (gy * time / 1000);
        ball.x += dpi * ball.vx * time / 25.4;
        ball.y += dpi * ball.vy * time / 25.4;

        if (ball.x <= ball.radius) {
            ball.x = ball.radius;
            ball.vx = -ball.vx / 3;
        } else if (ball.x >= width - ball.radius) {
            ball.x = width - ball.radius;
            ball.vx = -ball.vx / 3;
        }

        if (ball.y <= ball.radius) {
            ball.y = ball.radius;
            ball.vy = -ball.vy / 3;
        } else if (ball.y >= height - ball.radius) {
            ball.y = height - ball.radius;
            ball.vy = -ball.vy / 3;
        }

        if ((hole.x - hole.r < ball.x &&
             ball.x < hole.x + hole.r) &&
            (hole.y - hole.r < ball.y &&
             ball.y < hole.y + hole.r)) {
            ball.x = hole.x;
            ball.y = hole.y;
            ball.vx = ball.vy = 0;
            ball.invalidate();
        } else {
            ball.invalidate();
            handler.postDelayed(this, time);
        }
    }
    public void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(this);
    }
    @Override
    protected void onResume() {
        super.onResume();
        manager = (SensorManager)getSystemService(
                SENSOR_SERVICE);
        List<Sensor> sensors =
                manager.getSensorList(
                        Sensor.TYPE_ACCELEROMETER);
        if (0 < sensors.size()) {
            manager.registerListener(
                    this, sensors.get(0),
                    SensorManager.SENSOR_DELAY_NORMAL);
        }
    }
    @Override
    protected void onPause() {
        super.onPause();
        manager.unregisterListener(this);
    }
    @Override
    public void onSensorChanged(SensorEvent event) {
        gy = event.values[0];
        gx = event.values[1];
    }
    @Override
    public void onAccuracyChanged(
            Sensor sensor, int accuracy) {
    }
}

実機にインストールして実行すると、ボールが穴の上を通るとボールが穴の中心に固定されてゲームが終了する様子が確認できます。



以上でなんとか『ユーザが操作をして目標を達成する』というゲームとしての体裁が整いました。次は ActionBar のメニューを使ってゲームのリトライ処理を実装します。
スポンサーリンク

1 件のコメント:

  1. HoleもBallと同じように傾けたら動くようにしたいのですが、Runメソッドの中身をHole内の変数にあうように複製したりしてもBallだけが動きます。 両方動くようにするためにはどうすればいいでしょうか?

    返信削除