こちらを開くと実際に遊べます。
https://reversi-d1kqojbar.now.sh/
現時点のソースコードはこちらにあります。
1. 石をひっくり返す時のアニメーション
まず、石を置いた時には StoreクラスのsetStone()メソッドが呼ばれるようになっているので、その中で20msの間隔を空けて20回の「EV_BOARD_FLIPPING」イベントを発生するようにしました。
このイベントをBoardコンポーネントで受け取って、各セルを描画する際に、「もしひっくり返しアニメーションの実行中で、かつひっくり返し対象のセルであれば、Cellコンポーネントにflippingプロパティを通してその旨を伝える」ということを行っています。
Cellコンポーネントでは、flippingプロパティがnullでない場合は、
flipping.count / flipping.total
で現在のアニメーションの進行率が分かるので、それに応じて円を描画するときの横幅を変化させています。
さらに、アニメーションの進行率が 50% を越えた時点で石の色を反転させるようになっています。
これで石をひっくり返すアニメーションは上手く行きました。
石をひっくり返すアニメーション
---
ちなみに、CSSトランジションを使えば自分で石の横幅を変化させなくても、開始状態と終了状態を指定するだけでブラウザ側でアニメーションさせることが出来ます。その方法を使った方がパフォーマンス的には良かったかも知れません。
2. 画面遷移時のアニメーション
今回のアプリケーションでは、
タイトル画面 → 設定画面 → ゲーム画面
という流れで画面が切り替わるようになっています。この切り替えのタイミングで、次に進む場合は右から左へ、前の画面に戻る場合は左から右へとアニメーションする処理を入れました。
タイトル画面 → 設定画面 → ゲーム画面の遷移アニメーション
---
この部分は、CSSトランジションを使っています。
Reactでは 「React Transition Group」というライブラリを使うのが半ば公式に推奨されているみたいです。
https://github.com/reactjs/react-transition-group
これを使うのも悪くないのですが、今回は勉強のために全て自前で実装してみました。その結果、かなり試行錯誤して時間がかかりましたが、分かってみればそんなに難しくはないので、これぐらいの画面遷移であれば自前でやってしまうのもありだと思います。
基本的には、CSSで表示状態のスタイルと非表示状態のスタイルを定義しておいて、各画面のコンポーネントでCSSクラスを切り替えるだけです。
非表示状態の画面は透明(opacity:0)でかつ表示位置が画面の範囲外になるようにしていますが、それだけだとドキュメント全体のサイズが、表示されていない部分まで含んで認識されてしまって、余計な横スクロールが出来てしまう状態になったので、そこは一工夫が必要でした。
これに対処するために、一段上のAppコンポーネントのスタイルで、
.App {
width: 100vw;
height: 100vh;
overflow: hidden;
}
とすることで、画面外の部分はドキュメント全体のサイズに影響をおよぼさないようになり、余計な横スクロールを抑止することが出来ました。
3. スマホ対応
AndroidやiOSの実機で動作確認していると、色々と気になる点が出てきました。
スマホ向けにPWAなどでゲーム的なアプリケーションを作る場合には常に出てくる問題だと思いますが、以下の3つの問題があります。
1. ダブルタップ問題
2. ピンチズーム問題
3. Pull to Refresh問題
これらになんとか対処するために色々と調べて、以下の変更を行いました。どれがどの問題への対策だったかよく覚えていないのですが、とりあえず下記の対策をしておけば良いのではないかと思います。
htmlタグに属性を追加
style="overflow-y: hidden;"
viewportメタタグの設定
meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no"
モバイルアプリ用メタタグの追加
meta name="apple-mobile-web-app-capable" content="yes" meta name="mobile-web-app-capable" content="yes"
これで少なくとも手元のAndroid端末(Android 9.0)ではほぼ完璧になりました。
Chromeブラウザでページを開いてから、「ホーム画面に追加」を選んでホーム画面から起動すると、ブラウザのアドレスバーも無くなり、ダブルタップしようが上下にスワイプしようがびくともしません。ほぼネイティブアプリの感覚ですね。
手元のiOS端末(iPhone 5s / iOS 12.1.3)では、Pull to Refresh問題は解消しましたが、ダブルタップやピンチズームは反応しなかったり何かのタイミングで急に出来てしまったり、よくわからない感じです。(笑)
4. 終わりに
さて、ここまでで今回の「React/TypeScriptでリバーシゲームを作る」シリーズは無事終了です! 10月から3月まで、ほぼ半年もかかってしまいました。その間にGoogle発のクロスプラットフォーム開発環境であるFlutterが正式リリースされて、最近はかなり人気も高まってきているようです。
今度はFlutterでまたリバーシゲームを作ってみようかなあと思っているところです。
React/TypeScriptでリバーシゲームを作る
(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html
(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html
今度はFlutterでまたリバーシゲームを作ってみようかなあと思っているところです。
React/TypeScriptでリバーシゲームを作る
(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html
(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html
(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html
(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html