Javaで作るブロック崩し⑤|assetsフォルダで配置を決める  

AndroidStudio
今回はブロックの配置を変えていこうと思います!
テキストファイルにオブジェクトの配置情報を作っておき、そのファイルから情報を入手するという形をとります。
            
そのためにassetsフォルダというものを用います。
assetsは、画像、String以外のファイルなどをapkに保存して、アプリで読み取るときに使用することができます。
assetsを使用するには、assetsフォルダを作成し、ファイル(今回はテキストファイル)を保存する必要があります。
オブジェクトの配置の仕方は色々あると思うのですが、今後ステージを増やしていくことを考えた場合、メインクラスにその情報を全て書いていくとコードがかなり長くなってしまい、編集しづらくなってしまいます。そのため、テキストファイルに書いてあるステージ情報をメインクラスで読み込む形をとります。
            
ではステージクラスとしてクラスにまとめたらどうかと思うかもしれませんが、その場合もステージクラスをいくつも作る必要があるため、その他のクラスと区別がつきにくくなり、編集しづらくなってしまいます。
また、クラスの場合インスタンスを生成する必要があったりしてかえって面倒になるかと思います。
            
よって、今回はこの方法で進めていきます!
では、テキストファイルを作成していきましょう!

テキストファイルを作成する!

完成動画がこちら
(テキストファイルからブロック配置情報を取り出し描写させる)

assetsフォルダを作成

            
assetsフォルダを作成するには、”app”フォルダ上で右クリック→”New”→”Folder”→”Assets Folder”を押します。(写真1)

assetsフォルダ作成手順①
写真1. assetsフォルダ作成手順1


Target Source Setは”main”にします。(写真2)

assetsフォルダ作成手順②
写真2. assetsフォルダ作成手順2


これで、assetsフォルダが作成されます。

テキストファイルを追加

            
次に、作成したassetsフォルダにテキストファイルを追加します。
テキストファイルを追加するには、”assets”フォルダ上で右クリック→”New”→”File”→ファイル名を記入→Enterを押すことで作成されます。(写真3)

テキストファイル作成手順①
写真3. テキストファイル作成手順1


ファイル名は今回、”stage1″にします。(写真4)

テキストファイル作成手順②
写真4. テキストファイル作成手順2


これで、テキストファイルが作成されました。
            
では、さっそく作成したテキストファイルにブロックの配置情報を書いていきたいと思います!

テキストファイルにブロック配置情報を書く!

//ステージ1
    //ブロック設計
    colSize = "10"              //ブロックの数
    rowSize = "5"
    margin = "10"               //ブロックの隙間
    blockType = '0011111100'    //ブロックの配置を決める(0:ブロックなし 1:ブロックあり)
                '0000000000'
                '0011111100'
                '0000000000'
                '0011111100'
上からブロックの行数、列数、マージン(ブロック同士の隙間)を決めていきます。これらの値は”(ダブルクォーテーション)で囲みます。
blockTypeはブロックのタイプを指定するために用意した変数になります。0がブロックなし、1がブロックありになります。
これでブロックの配置をしやすくしています。また、ここでは値を’(シングルクォーテーション)で囲んでいます。
            
これらに関しては、後ほどメインクラスでこのファイルを呼び出す際に詳しく説明します。

テキストファイルから配置情報を入手する!

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 breakBlocks = 0;
    int WC = ViewGroup.LayoutParams.WRAP_CONTENT;
    boolean isBreak = false;                              

    //①assetsフォルダ読み込み
    InputStream inputStream;
    BufferedReader bufferedReader;
    StringBuilder stringBuilder;
    String str;
    Pattern pattern1, pattern2;        //③
    Matcher matcher1, matcher2;
    List<String> list1, list2;

    //②ステージデータ
    //(ブロック)
    int colSize, rowSize;
    int margin;
    String blockType;                   


    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        relativeLayout = new RelativeLayout(this);
        relativeLayout.setBackgroundColor(Color.BLACK);
        setContentView(relativeLayout);

        WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        Point point = new Point();
        display.getSize(point);
        width = point.x;
        height = point.y;

        //④assetsファイルからテキストファイルの文字列データを入手
        inputStream = null;        
        bufferedReader = null;
        stringBuilder = null;
        try {
            //読み込み開始
            inputStream = getAssets().open("stage1");       
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));   
            stringBuilder = new StringBuilder();           
            while ((str = bufferedReader.readLine()) != null) {     //⑤   
                stringBuilder.append(str);
            }

        } catch (Exception e) {        //例外をキャッチした際の処理
            e.printStackTrace();      
        } finally {
            try {
                //読み込み終了
                if (inputStream != null) inputStream.close();
                if (bufferedReader != null) bufferedReader.close();
            } catch (Exception e) {        //例外をキャッチした際の処理
                e.printStackTrace();
            }
        }


        //⑥読み込んだ文字列から条件にマッチするものを取り出す
        if (stringBuilder != null) {

            // ""(ダブルクォーテーション)で挟まれた文字列を抽出する
            pattern1 = Pattern.compile("\".+?\"");
            matcher1 = pattern1.matcher(stringBuilder.toString());
            list1 = new ArrayList<String>();
            while (matcher1.find()) {
                list1.add(matcher1.group());
            }

            // ''(シングルクォーテーション)で挟まれた文字列を抽出する
            pattern2 = Pattern.compile("\'.+?\'");
            matcher2 = pattern2.matcher(stringBuilder.toString());
            list2 = new ArrayList<String>();
            while (matcher2.find()) {
                list2.add(matcher2.group());
            }

            //⑦ブロックの変数に格納
            colSize = Integer.parseInt(list1.get(0).substring(1, list1.get(0).length()-1));
            rowSize = Integer.parseInt(list1.get(1).substring(1, list1.get(1).length()-1));
            margin = Integer.parseInt(list1.get(2).substring(1, list1.get(2).length()-1));
            blockType = String.join("", list2).replace("'", "");     
        }

        //ボール生成
        ball = new Ball(MainActivity.this);
        ball.x = width / 2;
        ball.y = height / 2;
        relativeLayout.addView(ball);

        //バー生成
        bar = new Bar(MainActivity.this);
        bar.left = width / 3;
        bar.right = width / 3 * 2;
        bar.top = height / 5 * 4;
        bar.bottom = height / 5 * 4 + 20;
        relativeLayout.addView(bar);

        //⑧ブロック生成
        block = new Block[colSize][rowSize];
        int order = 0;       //文字列から1文字ずつ切り出すための変数
        for (int row = 0; row < rowSize; row++) {
            for (int col = 0; col < colSize; col++) {
                block[col][row] = new Block(MainActivity.this);
                block[col][row].left = (width - (colSize - 1) * margin) / colSize * col + col * margin;
                block[col][row].right = (width - (colSize - 1) * margin) / colSize * (col + 1) + col * margin;
                block[col][row].top = height / 36 * row + row * margin;      //⑨
                block[col][row].bottom = height / 36 * (row + 1) + row * margin;
                relativeLayout.addView(block[col][row]);

                //ブロックの配置する
                String type = blockType.substring(order, order+1);      //⑩
                if (type.equals("0")) {
                    block[col][row].top = -50;
                    block[col][row].bottom = -50;
                    block[col][row].setVisibility(View.GONE);       
                    breakBlocks++;
                }

                order++;
            }
        }

        //テキスト生成
        textView = new TextView[6];
        for (int num = 0; num < 6; num++) {
            textView[num] = new TextView(this);
            textView[num].setTextColor(Color.WHITE);
            textView[num].setText("");
            switch (num) {
                case 0: textViewProperty(num, 64, 270, 700);
                    break;
                case 1: textViewProperty(num, 64, 80, 700);
                    break;
                case 2: textViewProperty(num, 28, 80, 1100);
                    break;
                case 3: textViewProperty(num, 28, 610, 1100);
                    break;
                case 4: textViewProperty(num, 28, 650, 1100);
                    break;
                case 5: textViewProperty(num, 64, 130, 700);
                    break;
            }
        }

        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) &&

                   ・
                   ・
                   ・

                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++;                                  
    }

}
フィールド変数には、assetsフォルダを読み込むための変数①と、実際に取り出したデータを格納するための変数②を用意します。
③のPatternクラスはimport(Alt+enterで自動importが可能)をする際に2つ選択肢が表示されますが、”java.util.regex”の方を選択してください。(写真5)

Patternクラスのimport
写真5. import文の追加
            
④では、作成したassetsフォルダからテキストファイルの文字列データを入手しています。
初めに用いる変数にnullを格納しています。これはデータ格納前に変数の中身を空にするために行います。
実際に読み込む処理を行う際は、例外が発生する可能性があるためtry文を使用します。
finallyは、tryの中で例外が発生してもしなくても必ず実行されるものになります。

ではそれぞれの構文の中身を説明していきたいと思います。
            
まず、try構文では、getAssets().open(“stage1”)で先ほど作成したstage1ファイルを指定して読み込み開始します。
bufferedReaderはバッファリング(データを一定数溜めてから入力処理)するためのクラス変数です。
そして、stringBuilderのインスタンスを生成し、そこに文字列を追加していきます。
⑤では、1行ずつ読み込んで文字列を追加しています。
次に、catch構文に例外が発生した際の処理を書きます。
ここでは、引数として例外インスタンスを受け取ります。例外が発生した場合に、変数eの中にエラーメッセージやスタックトレースといった情報が例外インスタンスとして格納されます。
e.printStackTrace()がスタックトレースの内容を画面に出力する処理になります。
finally構文では、読み込み終了の際の処理を書きます。
ここで、finallyを用いているのは、ファイルを開いて読み込んだりする場合は必ず、最後にファイルを閉じて処理を終わらせなければならないからです。
ここでも例外が発生する可能性があるためtry文を使用します。
⑥では、読み込んだ文字列から条件にマッチするものを抽出しています。stringBuilderの中に読み込んだ文字列を格納していますが、これがnullでない場合に実行されるようにします。

pattern1では、”(ダブルクォーテーション)で挟まれた文字列を指定しています。ここでは正規表現というものを用いています。
正規表現は文字列の分割、置換え、抽出、検索する際によく用いられるものです。

matcher1では、matcherメソッドの引数に渡した文字列からpattern1にマッチしたものを抽出します。引数にはstringBuilderを文字列に変換したものを渡します。

次にこれらをArrayListを用いて格納していきます。matcher1.find()は、マッチ条件を満たすものがある場合はtrueを返すというものです。これを用いて、”(ダブルクォーテーション)で挟まれたすべての文字を順番に追加していきます。

pattern2についても同様に書いていきます。pattern2では、’(シングルクォーテーション)で挟まれた文字列を指定しています。以上のここまでがブロック生成に必要な文字列の抽出となります。
続いて、この取り出したものをブロックの変数に入れます。(⑦)
ただ、このまま文字列を整数型に変換して入れようとしても、文字列に”(ダブルクォーテーション)や’(シングルクォーテーション)が含まれているせいでうまくいきません。
したがってこれらを取り除いてから変数に入れます。

各要素をget()で取り出した後、substringメソッドによって文字列の抽出を行います。第一引数には抜き出し開始の位置、第二引数には抜き出し終了位置(※この文字は含まない)を指定します。
今回は1とlist1.get(0).length()-1を渡すことで、最初と最後の文字を除く文字列を取り出しています。
blockTypeの場合は、一度すべての要素をjoinメソッドを使ってつなぎ合わせた後、’(シングルクォーテーション)を除外しています。
joinメソッドは、指定された区切り文字で、文字列群を結合するためのメソッドです。
ここでは区切り文字は空白としています。また、replaceメソッドを使って、シングルクォーテーションを空白に置き換えることで除外しています。
ここで、joinメソッドにエラーが出ているかと思いますが、Alt+enterを押すことで以下のようにアノテーション(Annotation)の追加が出てくると思いますので選択しましょう。これでエラーが消えるかと思います。(写真6)

アノテーション(Annotation)の追加
写真6. アノテーションの追加
最後に、ブロック生成の部分を修正していきます。(⑧)
今まで、top、bottomを任意の位置から描き始める形をとっていましたが、これからは画面上端から描き始めるようにします。(⑨)
これで画面全体を使って位置の指定ができます。

例えば1、3、5段目にブロックを描く場合には、テキストファイル内にあるblockType1、3、5行目の任意の箇所に1を入れて、間の2、4行目の値はすべて0にします。(”stage1″ファイル参照)

このようにかなり柔軟な配置が可能になります。
ブロックの配置を決めるためには、blockTypeの値を各ブロックに割り当てなければなりません。そのためにorderの変数を用います。
これは文字列の取り出す位置を指定するものになります。基本的に一文字ずつ順番に取り出していくようにします。

取り出すやり方は先ほども出てきたsubstringメソッドを用います。(⑩)
orderの初期値を0にすることで最初の文字から取り出すことができます。一文字ずつ取り出したいので、引数はorder,order+1とします。
またfor文の最後でorder++とすることで、要素が変わるごとに文字を順番に取り出せるようになります。
これで、ブロックごとに0、1を割り振ることができました。

値が0のブロックは非表示にします。また、クリア条件をbreakBlocks=colSize×rowSizeとしているため、breakBlocks++も忘れずにしておきます。
以上で、テキストファイルから配置情報を入手することができるようになりました。お疲れ様です!
次回はこれを用いていくつかステージを作り、画面遷移できるようにしていきたいと思います!

コメント

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