【マインスイーパー開発 #10】ゲームオーバーをバイブレーションで知らせる|Android & Kotlinアプリ開発

Android & Kotlin

Android & Kotlinの環境でマインスイーパーを開発する方法を説明します。

今回は、地雷を開いた時にバイブレータを作動させてゲームオーバーを分かりやすく知らせる工夫をします。

ソースコード

開発環境は次の通りです。

PCMacBook Pro(2016年モデル)
IDEAndroid Studio 4.0.1
Android SDKminSdkVersion 21
targetSdkVersion 30
言語Kotlin 1.3.72
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sosotata.minesweeper">

    <uses-permission android:name="android.permission.VIBRATE" />
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:configChanges="orientation|screenSize"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
package com.sosotata.minesweeper.ui.main

import android.content.Context
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.ScaleAnimation
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import com.sosotata.minesweeper.R
import com.sosotata.minesweeper.model.TileController
import com.sosotata.minesweeper.model.TileType
import com.sosotata.minesweeper.ui.widgets.PlayTimeCounter
import kotlinx.android.synthetic.main.game_fragment.*

class GameFragment : Fragment() {
    companion object {
        fun newInstance() = GameFragment()
    }

    private lateinit var viewModel: GameViewModel
    private var playTimeCounter: PlayTimeCounter? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View {
        return inflater.inflate(R.layout.game_fragment, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)

        viewModel.tile = TileController.create(TileType.Square)
        viewModel.tile.startedGame = {
            playTimeCounter?.start()
        }

        viewModel.tile.gameOver = {
            playTimeCounter?.stop()
            animateButton {
                resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_gameover))
            }
            vibrate()
        }

        viewModel.tile.clearedGame = {
            playTimeCounter?.stop()
            animateButton {
                resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_victory))
            }
        }

        viewModel.tile.updateBombCount = {count ->
            bombCountText.text = "%03d".format(count)
        }

        viewModel.tile.initialize(requireContext())

        playTimeCounter = PlayTimeCounter(playTimer)
        squareView.initialize(viewModel.tile)

        resetButton.setOnClickListener {
            animateButton {
                viewModel.tile.initialize(requireContext())
                squareView.initialize(viewModel.tile)
                resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_normal))
                playTimeCounter?.stop()
                playTimeCounter?.reset()
            }
        }
    }

    override fun onResume() {
        playTimeCounter?.resume()
        super.onResume()
    }

    override fun onPause() {
        playTimeCounter?.pause()
        super.onPause()
    }

    private fun animateButton(Animated: () -> Unit)
    {
        val btnEffect = ScaleAnimation(
            1.0f, 1.3f, 1.0f, 1.3f,
            Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)
        btnEffect.duration = 100
        btnEffect.setAnimationListener(object : Animation.AnimationListener {
            override fun onAnimationStart(animation: Animation) {}
            override fun onAnimationRepeat(animation: Animation) {}
            override fun onAnimationEnd(animation: Animation) {
                Animated()
            }
        })
        resetButton.startAnimation(btnEffect)
    }

    private fun vibrate() {
        val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
        vibrator.let {
            if (it.hasVibrator()) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    it.vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE))
                } else {
                    it.vibrate(150)
                }
            }
        }
    }
}

解説

バイブレーターを使用するには、マニフェストに次のパーミッションを宣言する必要があります。

<uses-permission android:name="android.permission.VIBRATE" />

Android SDKのVibratorクラスのvibrateメソッドは、API Level 26でメソッドが新たに追加されて、古いものは全て非推奨(Deprecated)になっています。

そのため、動作環境がAPI Level 26以上かをチェックして使用するメソッドを呼び分けます。

private fun vibrate() {
    val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
    vibrator.let {
        if (it.hasVibrator()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                it.vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE))
            } else {
                it.vibrate(150)
            }
        }
    }
}
そそたた
そそたた

開発中のマインスイーパー は、minSdkVersionが21のためこのように呼び分けていますが、minSdkVersionがそもそも26以上の場合は不要です。

最後にゲームオーバーの通知を受けた時に自作したvibrateメソッドを実行すれば完成です。

viewModel.tile.gameOver = {
    playTimeCounter?.stop()
    animateButton {
        resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_gameover))
    }
    vibrate()
}

コメント

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