Javaで作るブロック崩し④|リファクタリングを行う

AndroidStudio
今まで色々とコードを書いてきましたが、今一度コードの内容を見直してみると、当たり判定の部分などで同じような内容を繰り返していることが分かります。
まだそこまでオブジェクトが多くなく気にならないと思うかもしれませんが、今後さらに増やしていった場合、コードがかなり長く見づらいものになってしまいます。
またきちんと再利用しやすい形をとっておくことで、拡張性も上げることができます。
このように、ゲームの表示や動きといった外部的振る舞いを保ちつつ、理解や修正、拡張が簡単になるように内部構造を改善することをリファクタリングと言います。
ではさっそく当たり判定のメソッドを作り、簡単に呼び出せるようリファクタリングをしていきましょう!

リファクタリングを行う!

完成動画がこちら
(今回はリファクタリングだけなので前回と変わらないです)

当たり判定メソッドを追加

package com.example.myapp_block;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {
    Bar bar;
    Ball ball;
    Block[][] block;
    TextView[] textView;                      
    Intent intent;                             
    Handler handler = new Handler();
    Runnable runnable;
    RelativeLayout relativeLayout;
    RelativeLayout.LayoutParams params;        
    int width, height;
    int tap;
    int dx = 10, dy = 10;
    int colSize, rowSize;
    int breakBlocks = 0;                        
    int WC = ViewGroup.LayoutParams.WRAP_CONTENT;
    boolean isBreak = false;                       //①オブジェクトを壊すかどうかの変数を準備


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
                    ・
                    ・
                    ・

        textView[0].setText("スタート");
    }


    //テキストプロパティ設定メソッド
    public void textViewProperty(int num, int size, int leftMargin, int topMargin) {
        textView[num].setTextSize(size);
        params = new RelativeLayout.LayoutParams(WC, WC);      
        params.addRule(RelativeLayout.ALIGN_PARENT_TOP);                                   
        params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);                                
        params.setMargins(leftMargin, topMargin, 0, 0);                       
        relativeLayout.addView(textView[num], params);                                    
    }


    // タッチイベントメソッド //
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        textView[0].setText("");

                    ・
                    ・
                    ・

        return super.onTouchEvent(event);
    }


    //②当たり判定メソッド(オブジェクト用)
    public void ballHit(int left, int top, int right, int bottom, boolean isBreakObject) {
        //左面
        if ((ball.x + ball.radius <= left + 5) &&
                (ball.x + ball.radius >= left - 5) &&
                (ball.y <= bottom) &&
                (ball.y >= top)) {
            dx = -dx;
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //右面
        if ((ball.x - ball.radius <= right + 5) &&
                (ball.x - ball.radius >= right - 5) &&
                (ball.y <= bottom) &&
                (ball.y >= top)) {
            dx = -dx;
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //下面
        if ((ball.y - ball.radius <= bottom + 5) &&
                (ball.y - ball.radius >= bottom - 5) &&
                (ball.x >= left) &&
                (ball.x <= right)) {
            dy = -dy;
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //上面
        if ((ball.y + ball.radius <= top + 5) &&
                (ball.y + ball.radius >= top - 5) &&
                (ball.x >= left) &&
                (ball.x <= right)) {
            dy = -dy;
            if (isBreakObject) {
                isBreak = true;
            }
        }
        
        double leftSpace = Math.pow(left - ball.x, 2);
        double bottomSpace = Math.pow(bottom - ball.y, 2);
        double rightSpace = Math.pow(right - ball.x, 2);
        double topSpace = Math.pow(top - ball.y, 2);
        //左下角
        if (leftSpace + bottomSpace <= Math.pow(ball.radius, 2)) {
            //ボールが右下斜め方向に進んでいた時
            if (dx > 0 && dy > 0) {
                dx = -dx;
            }
            //ボールが右上斜め方向に進んでいた時
            if (dx > 0 && dy < 0) {
                dx = -dx;
                dy = -dy;
            }
            //ボールが左上斜め方向に進んでいた時
            if (dx < 0 && dy < 0) {
                dy = -dy;
            }
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //左上角
        if (leftSpace + topSpace <= Math.pow(ball.radius, 2)) {
            //ボールが右下斜め方向に進んでいた時
            if (dx > 0 && dy > 0) {
                dx = -dx;
                dy = -dy;
            }
            //ボールが右上斜め方向に進んでいた時
            if (dx > 0 && dy < 0) {
                dx = -dx;
            }
            //ボールが左下斜め方向に進んでいた時
            if (dx < 0 && dy > 0) {
                dy = -dy;
            }
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //右下角
        if (rightSpace + bottomSpace <= Math.pow(ball.radius, 2)) {
            //ボールが左上斜め方向に進んでいた時
            if (dx < 0 && dy < 0) {
                dx = -dx;
                dy = -dy;
            }
            //ボールが右上斜め方向に進んでいた時
            if (dx > 0 && dy < 0) {
                dy = -dy;
            }
            //ボールが左下斜め方向に進んでいた時
            if (dx < 0 && dy > 0) {
                dx = -dx;
            }
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //右上角
        if (rightSpace + topSpace <= Math.pow(ball.radius, 2)) {
            //ボールが左上斜め方向に進んでいた時
            if (dx < 0 && dy < 0) {
                dx = -dx;
            }
            //ボールが右下斜め方向に進んでいた時
            if (dx > 0 && dy > 0) {
                dy = -dy;
            }
            //ボールが左下斜め方向に進んでいた時
            if (dx < 0 && dy > 0) {
                dx = -dx;
                dy = -dy;
            }
            if (isBreakObject) {
                isBreak = true;
            }
        }
    }


    //ブロックを壊すメソッド
    public void breakBlock(int col, int row) {
        block[col][row].top = -50;
        block[col][row].bottom = -50;
        block[col][row].setVisibility(View.GONE);
        breakBlocks++;                                  
    }

}
まずフィールド変数に、オブジェクトを壊すかどうかの変数を準備します。(①)
            
当たり判定メソッドは②のようになります。
引数に、対象のオブジェクトの上下左右の座標、そして先ほど変数として用意したisBreakObjectをとります。
この変数には、対象オブジェクトがボールに当たって壊れるオブジェクトならtrue、壊れないならfalseを渡します。
このメソッドを、今まで書いていたブロックの当たり判定と比較してもらえると、全体的にあまり変わってないことが分かると思います。
変更点として、オブジェクトの座標を引数で渡されたものに変えています。(例:block[col][row].left → left)
また、当たった際にもしブロックのような当たって壊れるようなオブジェクトだった場合は、isBreak=trueとなるようにしています。

当たり判定メソッドを呼び出す

package com.example.myapp_block;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {
    Bar bar;
    Ball ball;
    Block[][] block;
    TextView[] textView;                      
    Intent intent;                             
    Handler handler = new Handler();
    Runnable runnable;
    RelativeLayout relativeLayout;
    RelativeLayout.LayoutParams params;        
    int width, height;
    int tap;
    int dx = 10, dy = 10;
    int colSize, rowSize;
    int breakBlocks = 0;                        
    int WC = ViewGroup.LayoutParams.WRAP_CONTENT;
    boolean isBreak = false;                      


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
                   ・
                   ・
                   ・

        textView[0].setText("スタート");
    }


    //テキストプロパティ設定メソッド
    public void textViewProperty(int num, int size, int leftMargin, int topMargin) {
        textView[num].setTextSize(size);
        params = new RelativeLayout.LayoutParams(WC, WC);      
        params.addRule(RelativeLayout.ALIGN_PARENT_TOP);                                   
        params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);                                
        params.setMargins(leftMargin, topMargin, 0, 0);                       
        relativeLayout.addView(textView[num], params);                                    
    }


    // タッチイベントメソッド //
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        textView[0].setText("");
        
                   ・
                   ・
                   ・

                //画面端                                  //ここから当たり判定
                //画面左端
                if (ball.x <= ball.radius) {
                    dx = -dx;
                    //画面右端
                } else if (ball.x >= width - ball.radius) {
                    dx = -dx;
                    //画面上端
                } else if (ball.y <= ball.radius) {
                    dy = -dy;
                    //画面下端
                } else if (ball.y >= height - ball.radius) {
                    dx = 0;
                    dy = 0;
                }


                //バー
                //当たり判定メソッドを呼び出す
                ballHit(bar.left, bar.top, bar.right, bar.bottom, false);


                //ブロック
                for (int row = 0; row < rowSize; row++) {
                    for (int col = 0; col < colSize; col++) {

                        //①当たり判定メソッドを呼び出す
                        ballHit(block[col][row].left, block[col][row].top, block[col][row].right, block[col][row].bottom, true);

                        //②ボールに当たった場合にブロックを壊す
                        if(isBreak) {
                            breakBlock(col, row);
                            //フラグを元に戻す
                            isBreak = false;
                        }
                    }
                }


                handler.removeCallbacks(runnable);
                relativeLayout.removeView(bar);
                relativeLayout.removeView(ball);
                relativeLayout.addView(ball);
                relativeLayout.addView(bar);
                handler.postDelayed(runnable, 10);
            }
        };
        handler.postDelayed(runnable, 10);

        return super.onTouchEvent(event);
    }


    //当たり判定メソッド(オブジェクト用)
    public void ballHit(int left, int top, int right, int bottom, boolean isBreakObject) {
        //左面
        if ((ball.x + ball.radius <= left + 5) &&
                (ball.x + ball.radius >= left - 5) &&
                (ball.y <= bottom) &&
                (ball.y >= top)) {
            dx = -dx;
            if (isBreakObject) {            //③壊れるオブジェクトならisBreakをtrueに
                isBreak = true;
            }
        }
        //右面
        if ((ball.x - ball.radius <= right + 5) &&
                (ball.x - ball.radius >= right - 5) &&
                (ball.y <= bottom) &&
                (ball.y >= top)) {
            dx = -dx;
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //下面
        if ((ball.y - ball.radius <= bottom + 5) &&
                (ball.y - ball.radius >= bottom - 5) &&
                (ball.x >= left) &&
                (ball.x <= right)) {
            dy = -dy;
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //上面
        if ((ball.y + ball.radius <= top + 5) &&
                (ball.y + ball.radius >= top - 5) &&
                (ball.x >= left) &&
                (ball.x <= right)) {
            dy = -dy;
            if (isBreakObject) {
                isBreak = true;
            }
        }
      
        double leftSpace = Math.pow(left - ball.x, 2);
        double bottomSpace = Math.pow(bottom - ball.y, 2);
        double rightSpace = Math.pow(right - ball.x, 2);
        double topSpace = Math.pow(top - ball.y, 2);
        //左下角
        if (leftSpace + bottomSpace <= Math.pow(ball.radius, 2)) {
            //ボールが右下斜め方向に進んでいた時
            if (dx > 0 && dy > 0) {
                dx = -dx;
            }
            //ボールが右上斜め方向に進んでいた時
            if (dx > 0 && dy < 0) {
                dx = -dx;
                dy = -dy;
            }
            //ボールが左上斜め方向に進んでいた時
            if (dx < 0 && dy < 0) {
                dy = -dy;
            }
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //左上角
        if (leftSpace + topSpace <= Math.pow(ball.radius, 2)) {
            //ボールが右下斜め方向に進んでいた時
            if (dx > 0 && dy > 0) {
                dx = -dx;
                dy = -dy;
            }
            //ボールが右上斜め方向に進んでいた時
            if (dx > 0 && dy < 0) {
                dx = -dx;
            }
            //ボールが左下斜め方向に進んでいた時
            if (dx < 0 && dy > 0) {
                dy = -dy;
            }
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //右下角
        if (rightSpace + bottomSpace <= Math.pow(ball.radius, 2)) {
            //ボールが左上斜め方向に進んでいた時
            if (dx < 0 && dy < 0) {
                dx = -dx;
                dy = -dy;
            }
            //ボールが右上斜め方向に進んでいた時
            if (dx > 0 && dy < 0) {
                dy = -dy;
            }
            //ボールが左下斜め方向に進んでいた時
            if (dx < 0 && dy > 0) {
                dx = -dx;
            }
            if (isBreakObject) {
                isBreak = true;
            }
        }
        //右上角
        if (rightSpace + topSpace <= Math.pow(ball.radius, 2)) {
            //ボールが左上斜め方向に進んでいた時
            if (dx < 0 && dy < 0) {
                dx = -dx;
            }
            //ボールが右下斜め方向に進んでいた時
            if (dx > 0 && dy > 0) {
                dy = -dy;
            }
            //ボールが左下斜め方向に進んでいた時
            if (dx < 0 && dy > 0) {
                dx = -dx;
                dy = -dy;
            }
            if (isBreakObject) {
                isBreak = true;
            }
        }
    }


    //ブロックを壊すメソッド
    public void breakBlock(int col, int row) {
        block[col][row].top = -50;
        block[col][row].bottom = -50;
        block[col][row].setVisibility(View.GONE);
        breakBlocks++;                                  
    }

}
ここで先ほど作成したメソッドを呼び出すことで、各オブジェクトの当たり判定が行われるようになります。
            
バーは元々、上面と左右の当たり判定しか設けていませんでしたが、全面に当たり判定をつけても特に問題がないためそのようにしています。
ブロックは複数あるため、for文を使って全てのブロックに対して当たり判定をつけるようにします。
また、ブロックの場合は、自身が壊れるようにしなくてはならないため、そのための変数としてisBreakObjectとisBreakを設けています。どちらもtrueかfalseを返すboolean型となります。
            
上記の流れとしては、まず当たり判定メソッドを呼び出す際に、引数であるisBreakObjectに自身(オブジェクト)が壊れるべきものであればtrue、そうでなければfalseを渡します。
そして、実際にオブジェクトがボールに当たった場合に、isBreakObjectがtrueならisBreak=true(③)となり、呼び出し側にてブロックを壊す処理(②)が行われます。
この流れを以下の図で示します。

ブロックの当たり判定フロー
図. ブロックの当たり判定フロー
            
以上がリファクタリングとなります。
実際に、リファクタリング後のコードを見てもらえると、かなりスッキリしていることが分かります。
また、今後オブジェクトを増やしたとしても、当たり判定に関してはメソッドを呼ぶだけになるので、コードも短くて済みます。
            
このように、もしコードが長くなってしまったり、同じような内容が繰り返し書かれていたら、リファクタリングをして内部構造を改善していった方が良いでしょう。
そうすることで、コードの理解がしやすいですし、拡張もしやすくなります。
            
次回は、ブロックの配置を変えていきます。
そのために、assetsフォルダというものを作成し、テキストファイルから配置情報をとってこれるようにします。
こうすることで、今後ステージを増やしていく際もかなり楽になります。
少し高度な内容となりますので覚悟しといてくださいね!

コメント

タイトルとURLをコピーしました