Javaで作るブロック崩し⑥|アニメーションを実装する 

AndroidStudio
今回はアニメーションを入れていこうと思います!
テキスト表示、オブジェクト画面in、オブジェクト画面outに関してアニメーションを入れます。
また、アニメーションの最中は画面操作ができないようにします。
            
ではさっそく始めていきましょう!
完成動画、完成コードは以下のようになります。

アニメーションを実装する!

完成動画がこちら
(アニメーション最中は画面タッチ無効化)
            
完成コードは以下のようになります。

package com.example.myapp_block;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Build;
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.Window;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class MainActivity extends AppCompatActivity {
    Bar bar;
    Ball ball;
    Block[][] block;
    TextView[] textView;
    Intent intent;
    Window window;                                  
    Handler handler, handler1, handler2;            
    Runnable runnable;
    RelativeLayout relativeLayout;
    RelativeLayout.LayoutParams params;
    TranslateAnimation translateAnimation;
    AlphaAnimation alphaAnimation;
    int width, height;
    int tap;
    int dx = 10, dy = 10;
    int breakBlocks = 0;
    int WC = ViewGroup.LayoutParams.WRAP_CONTENT;
    boolean isBreak = false;                              
    boolean gameGamenFlag = 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;       
        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[7];
        for (int num = 0; num < 7; 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;
                case 6: textViewProperty(num, 64, 210, 700);
                    break;
            }
        }

        //スタートプロセス
        gameProcess('s');
    }


//テキストプロパティ設定メソッド
    public void textViewProperty(int num, int size, int leftMargin, int topMargin) {
        textView[num].setTextSize(size);
        //レイアウトの属性を設定(LayoutParamsクラス)
        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);
    }


//③プロセス移行メソッド
    @RequiresApi(api = Build.VERSION_CODES.O)
    public void gameProcess(char s){
        switch (s){
            //スタート処理
            case 's': {
                //繰り返し処理の準備
                handler = new Handler();
                handler1 = new Handler(getMainLooper());
                handler2 = new Handler(getMainLooper());

                //タッチイベント無効化するクラスを呼び出す
                window = getWindow();
                window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);

                textView[6].setText("ステージ1");

                //初期アニメーション
                objectsAppear(1);             //オブジェクト画面IN
                handler1.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        startAnimation(textView[6], height, 2);     //消すアニメーション
                    }
                }, 2000);
                handler2.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //タッチイベント無効化解除
                        window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                        //ゲーム画面フラグをON
                        gameGamenFlag = true;
                        textView[0].setText("スタート");
                        startAnimation(textView[0], height, 3);    //強調アニメーション
                    }
                }, 5000);
            }
            break;


            //ゲームオーバー処理
            case 'o': {
                //ゲーム画面フラグをOFF
                gameGamenFlag = false;

                //タッチイベント無効化
                window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                handler2.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //タッチイベント無効化解除
                        window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                    }
                }, 5000);

                //オブジェクトを非表示
                runnable = null;                    //描画を停止
                relativeLayout.removeAllViews();    //handlerがダブってしまうので前の描画を削除

                //オブジェクト画面OUT
                objectsAppear(0);

                //テキストを再表示
                relativeLayout.addView(textView[1]);
                relativeLayout.addView(textView[2]);
                relativeLayout.addView(textView[3]);
                textView[1].setText("ゲームオーバー");
                textView[2].setText("ステージ選択");
                textView[3].setText("もう一度トライ");
                //テキストにListenerを設定
                textView[3].setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        intent = new Intent(MainActivity.this, MainActivity.class);
                        startActivity(intent);
                    }
                });

                //テキストをアニメーション表示
                startAnimation(textView[1], height, 1);     //テキスト画面IN
                startAnimation(textView[2], height, 1);
                startAnimation(textView[3], height, 1);
            }
            break;


            //ゲームクリア処理
            case 'c': {
                //ゲーム画面フラグをOFF
                gameGamenFlag = false;

                //タッチイベント無効化
                window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                handler2.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //タッチイベント無効化解除
                        window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                    }
                }, 5000);

                //オブジェクトを非表示
                runnable = null;                    //描画を停止
                relativeLayout.removeAllViews();    //handlerがダブってしまうので前の描画を削除

                //オブジェクト画面OUT
                objectsAppear(0);

                //テキストを再表示
                relativeLayout.addView(textView[2]);
                relativeLayout.addView(textView[4]);
                relativeLayout.addView(textView[5]);
                textView[2].setText("ステージ選択");
                textView[4].setText("次のステージ");
                textView[5].setText("ゲームクリア");
                //テキストにListenerを設定
                textView[4].setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        intent = new Intent(MainActivity.this, MainActivity.class);
                        startActivity(intent);
                    }
                });

                //テキストをアニメーション表示
                startAnimation(textView[2], height, 1);     //テキスト画面IN
                startAnimation(textView[4], height, 1);
                startAnimation(textView[5], height, 1);
            }
            break;
        }
    }


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

        tap = (int) event.getX();

        runnable = new Runnable() {
            @RequiresApi(api = Build.VERSION_CODES.O)
            @Override
            public void run() {
                //ゲーム進行
                if (gameGamenFlag) {
                    //ここから動きの部分
                    bar.left = tap - (width / 6);
                    bar.right = tap + (width / 6);

                    ball.x += dx;
                    ball.y += dy;


                    //ブロックを全て壊したらゲームクリア
                    if (breakBlocks == rowSize * colSize) {
                        //ゲームクリアプロセス
                        gameProcess('c');
                    }

                    //ボールが画面下に出たら、ゲームオーバー
                    if (dx == 0 && breakBlocks != rowSize * colSize){
                        //ゲームオーバープロセス
                        gameProcess('o');
                    }


                    //画面端                                  //ここから当たり判定
                    //画面左端
                    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;
            }
        }
        //右面
        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;
            }
        }
        //角にボールが当たった時の当たり判定
        //Math.pow:2乗
        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++;                                  
    }

    //②アニメーション切り替えメソッド
    public void objectsAppear(int status){
        switch (status){
            case 0: {       //画面OUT
                //ボール
                startAnimation(ball, height, 0);
                //ブロック
                for (int row = 0; row < rowSize; row++) {
                    for (int col = 0; col < colSize; col++) {
                        startAnimation(block[col][row], height, 0);
                    }
                }
            }
            break;
            case 1: {       //画面IN
                //ボール
                startAnimation(ball, height, 1);
                //ブロック
                for (int row = 0; row < rowSize; row++) {
                    for (int col = 0; col < colSize; col++) {
                        startAnimation(block[col][row], height, 1);
                    }
                }
            }
            break;
        }
    }


    //①アニメーション実装メソッド
    private void startAnimation(View view, int height, int type) {
        switch (type) {
            case 0: {       //画面OUT
                translateAnimation = new TranslateAnimation(
                        Animation.ABSOLUTE, 0.0f,
                        Animation.ABSOLUTE, 0.0f,
                        Animation.ABSOLUTE, 0.0f,
                        Animation.ABSOLUTE, height);
                translateAnimation.setDuration(4000);
                translateAnimation.setRepeatCount(0);
                translateAnimation.setFillAfter(true);
                view.startAnimation(translateAnimation);
            }
            break;
            case 1: {       //画面IN
                translateAnimation = new TranslateAnimation(
                        Animation.ABSOLUTE, 0.0f,
                        Animation.ABSOLUTE, 0.0f,
                        Animation.ABSOLUTE, -height,
                        Animation.ABSOLUTE, 0.0f);
                translateAnimation.setDuration(4000);
                translateAnimation.setRepeatCount(0);
                translateAnimation.setFillAfter(true);
                view.startAnimation(translateAnimation);
            }
            break;
            case 2: {       //テキストフェードアウト
                alphaAnimation = new AlphaAnimation(1.0f, 0.0f);        //透明度を1から0に変化
                alphaAnimation.setRepeatCount(0);
                alphaAnimation.setDuration(700);
                alphaAnimation.setFillAfter(true);
                view.startAnimation(alphaAnimation);
            }
            break;
            case 3: {       //テキスト強調
                alphaAnimation = new AlphaAnimation(0.3f, 0.8f);        //透明度を0.3から0.8に変化
                alphaAnimation.setRepeatCount(-1);                      //繰り返し回数=リピート
                alphaAnimation.setDuration(700);
                alphaAnimation.setRepeatMode(2);                        //繰り返しモード:reverse
                alphaAnimation.setFillAfter(true);
                view.startAnimation(alphaAnimation);
            }
            break;
        }
    }
}

アニメーション実装メソッドを作成

            
網掛け①では、アニメーション実装メソッドを書いています。case0ではオブジェクト画面out用のアニメーション、case1ではオブジェクト画面in用のアニメーション、case2ではテキストが徐々に消えていくアニメーション、case3ではテキストの透明度が薄くなったり濃くなったり繰り返すアニメーションを定義しています。
移動させる場合はtranslateAnimation、透明度を変える場合はalphaAnimationを用います。
translateAnimationを生成する際には第1、第2引数にx軸の始点、終点を、第3、第4引数にはy軸の始点、終点を設定します。画面outの場合は画面内から画面下に垂直に降りていってほしいため、第3引数は0、第4引数にはheightを与えます。画面inの場合はこれとは逆に第3引数は-height、第4引数には0を与えます。
setDurationはアニメーションにかかる時間、setRepeatCountは繰り返すかどうかを設定できます。繰り返す場合は-1をそうでない場合は0を与えます。
setFillAfterではアニメーション後にオブジェクトをそのままにするか元の位置に戻すかを設定します。trueにすることでアニメーション後に位置や状態がそのままになります。
alphaAnimationを生成する際には第1引数に開始時の透明度、第2引数に終了時の透明度を設定します。
setRepeatModeは、繰り返しモードの設定になります。2を与えることでリバースモードとなります。これは終了後すぐ最初から繰り返すのではなく、終了後の状態からアニメーションをスタートさせるやり方です。(今回だと0.3f→0.8fの後、0.3f→0.8fと繰り返すのでなく、0.8f→0.3fと繰り返します)

アニメーション切り替えメソッドを作成

            
網掛け②では、アニメーション切り替えメソッドを書いています。ここで、オブジェクトの画面in、outのアニメーションを行うメソッドを定義します。先ほど作成したメソッドを呼び出すことでオブジェクトにアニメーションを行わせます。

プロセス移行メソッドを作成

            
網掛け③では、プロセス移行メソッドを書いています。これは、スタート、ゲームオーバー、ゲームクリアの場合の処理をまとめたメソッドなります。スタートの場合はcase’s’、ゲームオーバーの場合はcase’o’、ゲームクリアの場合はcase’c’としています。ここでは、各状態になった際のテキスト、オブジェクト表示、アニメーションの処理を書いています。
window.addFlagsメソッドを用いて、アニメーション表示の際に画面タッチができないようにしています。そして、handler.postDelayedを使うことで、アニメーション終了時に画面タッチができるよう戻します。また、これを用いることでアニメーションのタイミングもずらしています。
ゲームクリアやゲームオーバーの際には、今まで繰り返し描画していたものを止める必要があるため、runnable=nullにします。また、その際handlerがダブってしまい、前の描画が残ってしまうため全ての描画をremoveAllViews()で削除します。その後テキストに関してはアニメーション表示させる必要があるため再描画させます。
ゲーム終了後には画面タッチによってオブジェクト等を描画させる必要がないため、gameGamenFlagという変数を用意して、この値がtrueの場合のみ描画させ、ゲームクリアやゲームオーバーの際にはこの値をfalseにして描画させないようにします。
以上でアニメーションの実装は終了です。
お疲れ様でした!

コメント

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