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>
画面内にボールを落とす穴を設置
次に MainActivity で Hole を実体化します。穴の位置は乱数を使ってランダムに発生させます。このとき画面の端っこに穴を設置してしまうとゲームの難易度が下がってしまうため、画面の端っこからは離して設置します。 下記のように乱数の発生範囲を限定しています。
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 が上に重なるので Hole → Ball の順に 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 のメニューを使ってゲームのリトライ処理を実装します。


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