ビンゴマシーンをつくろう

[Unreal Engine 4 (UE4) Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ue4)
19日目の記事です

クリスマスが目前に迫ったとある家庭でのお話。

###水曜日 深夜
　何かと慌ただしい年の暮れ、夜遅く疲れて帰宅すると、眠そうな目で起きてきた妻に、次の土曜に子供がクラスの友達を連れてきて家でパーティをすることになったと聞かされ、そうかそうかそれは良かったと適当に返事しながら風呂に入ろうとしたところ、「実はビンゴをやることになったんだけど、抽選するマシーンみたいなのが無いのよ」それなら百均とかアプリストアにあるだろう、とパンツを脱ぎながら言うと「それがダメなのよ。パパはゲーム会社に勤めてるからサクッと作ってくれるんだって言っちゃったみたいで・・・」いやちょっと待てそんな時間はないぞ「来てくれる子たちの中に好きな子もいるらしくてものすごく気合はいっちゃってて」しばらく全裸で妻と見つめあっていたらくしゃみが出た。「じゃ伝えたからね、おやすみなさい」といって妻は寝室に戻って行った。

　土曜ってあさってじゃないか！サクッとなんてできるか！しかもプログラマじゃないし！ソファで髪をガシガシと拭きながら考えてみる。
とりあえずUE4で作れそうだけど、とにかくヤバイ。時間もヤバイが見た目もヤバイことになりそうだ。うちPhotoshopとか画像編集ソフトとか入れてないしなあ。絵素材とかなかったらショボイよな・・・
　結局そのまま眠ってしまい明け方に布団に潜ったが仕事に行く時間まで大して眠れなかった。


###木曜日 夜
　そして日付が変わる前になんとか帰宅。さっそくUE4のランチャーを起動する。テスト用のプロジェクトが開くまでの間の時間でビンゴについてWikipediaに教えを乞う。カードはすでに百均で買ってきたらしい。会社のイベントでもよく見かける5x5のマスに1～75の数字がランダムに選ばれて並んでいるやつだ。要は1～75の数字をランダムに抽選して表示すればいいだけだろう。ブラウザにはマシーンの画像。Wiiパーティにもあったな。さすがにこれを再現するのは時間が無いし、そもそもサクッと作れるほど技術も無い。
　とりあえずUMGでデッカイ数字出しときゃインパクト出るっしょ。VFXも簡単なやつ入れて、あとストアのアプリも参考にしてポイのを作れば、相手は子供だなんとかなるなる。


起動したので、さっそく Widgetを作って EventGraphをいじりながら仕組みを考える。

ただランダムで抽選すると数字の被りが出るから、
![fig00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/e8d4c4d4-2b40-f5ac-b64c-23e61ffddc29.png)
75個の配列に数字を入れてシャッフルして、そこから減らす方が良さそう。

あと抽選の残り数が少なくなると、ランダムだと同じ数字が出て数字が動かないのもカッコ悪いので、ランダム抽選はやめよう。配列とポインター的な変数を用意して、関数を作る。
![fig01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/bc7dde6d-e04e-dcf0-c72a-fe8ce1bf6b20.png)
この関数をつないで、
![fig02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/0b49a42c-e055-14b9-9dd9-09fb706dcb3a.png)
表示確認だ。とりあえず数字が見たい。
![fig03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/0904c4af-50bb-3694-b2b4-638f52ec2fd2.png)
よしデカイ！インパクト大！

抽選する関数はこんな感じかな。
![fig04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/71f76fd7-7490-bde5-0b76-df53dc640ba8.png)

ルーレットの処理と、そこで確定したら配列から引き抜く処理を作らないと。
ここはドキドキワクワクの瞬間だからちゃんと作っておかないと。


###木曜 深夜
家族が寝静まって無情に時が進む。なんとかできたぞ。
ルーレット部分のカスタムイベント。
![fig05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/f33d9642-3cae-2c63-b8ee-05615af3b316.png)
**setTimerByEvent** 使って、インターバルタイムを引き伸ばしていけばあのデジタルな感じのルーレットが再現できた。条件を満たすまでは何度でもイベントを呼ぶしくみ。
ルーレットが止まるまで 変数Interval を初期化できないので、もう一つカスタムイベントを用意。
![fig08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/fc0766a4-52a4-6b43-1d6d-a2c402b9d754.png)
このイベント経由でルーレットを回す。なので外からこのイベントを呼ぶ。

確定した時の処理。
![fig06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/9c3da7ad-5507-5328-961f-6c9761512dd0.png)

ルーレットはゲームパッドとかから入力を受けてスタートするようにしたいので、**ProjectSettings** の **Input** にトリガーとしての入力を受け付けるイベントのマッピングを追加しておいた。
![fig07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/6afdee77-db9d-e3f7-08f1-cffe606817b7.png)

テストはレベルブループリントからWidgetをViewportに追加して再生。
![fig09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/4bcc343e-a9d4-66c0-c651-b752ebf028e0.png)
Inputに設定したイベント名がノードとして取り出せるので、それとルーレット開始イベントをつなげば、入力を拾ってイベントを呼び出してくれる。

![sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/e5e33c34-f081-d031-f146-2a3ad4bb8937.gif)

できた！けど地味だ！色が無い！
こういう時は丸いものだ！なんか丸いものを作ろう。マテリアルでできたな。
![fig10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/4970c479-54e3-9a90-b958-ee2bb3b57751.png)
Imageパーツにこのマテリアルをセットしてキャンバスに追加すれば、ほら！
![fig11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/15d4fdeb-6491-1287-5048-39d24326896e.png)
・・・
なんか間に合わせられそうな気がしてきたけど、
とりあえず今日はこの辺にしておこう。明日も会社行かなきゃ


できたもの

- ナンバーの表示
- 入力受付
- ルーレットの動き
- ナンバーの抽選

 
次つくるもの

- ルーレット中は入力を無視
- ナンバー確定をわかりやすく
- ナンバー確定履歴
- ブラシュアップ



###金曜日 夜
なんとか少し早めに帰宅できたので、晩ご飯を温めることもせずかき込んで続きを作りにかかる。
レベルBPでキー入力の受付をとりあえずやってて、そこからWidgetのイベントを呼び出してる。ルーレットが回ってナンバーが確定するとこまではWidget側でやってる。ルーレット中は入力をキャンセルしないといけないからブーリアンでフラグを作ろう。ちゃんと作るならステートの管理をするんだろうけど、とりあえず動けばいいレベルだから、Widget側で管理してしまおう。ボタン入力してもWidget側ではじく！
ついでに確定した時にカラーを変えてみよう。

まずはここから
![fig12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/73243a55-70e5-b15a-f2fa-b99bf63738c8.png)
この isReady が準備OKかどうかの状態を示す。最初は trueで。
レベルBPからルーレットを回せと言ってきやがったら
![fig13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/4ab637be-6535-e0e8-5f28-878f9d28e369.png)
ここで無視するか決める。
最後までルーレットが回り切ったら、フラグを立てる。
![fig14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/f9b36c49-a216-334a-a7cb-f3ae867ff991.png)

![sample2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/965db086-27f6-bfbd-45a9-b681384d0745.gif)


あとは確定履歴表示だけど、この流れで先に「ルーレット回していいよ」表示を足しておこう。
![sample3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/8902153f-10bc-cad0-2145-8c97f2d499ab.gif)
フレームインとフレームアウトの２つのアニメーションを用意して、フラグをセットしているところで再生する。ルーレットが回っている間だけ引っ込んでいるようにする。



###金曜 深夜
いつの間にか日付が変わっている。今日のお昼に間に合えばいいと言っていた。大丈夫！まだ時間はある！
機能的に残るはナンバー確定履歴だ。ここをしっかり作らないと、ゲーム進行時にグダってしまう。
帰りの電車の中で構造のプランを考えておいたのでさっそく試す。
キモはVerticalBox。
![fig15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/80856365-0d56-c311-ac59-ca70455b0524.png)
最初に全部 Collapse（非表示）にしておいて、あとから順番に Visible（表示）を増やしていくようにすれば、順番がゴチャらなくていいと思う。見た目にソートしてるように見えると思う。


で、まずは新しく中に入れる子Widgetを用意する。ナンバーが表示できればいい。
![fig16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/9bc34fc0-838a-59c6-a145-98c6ca50123f.png)

Expose on Spawnで数字を書き換えられるようにしておいて、あとはたくさん並ぶ中で目立たせた方が良さそうだからアニメーション入れておこう。
![sample4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/2d0e40eb-b9a5-a439-99e9-748eaaaed78d.gif)
Visibility を Visible（表示状態）にしたときに一緒に再生できるようにカスタムイベント作っておくくらいでいいだろう。
![fig17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/e1fc3af0-7a66-fc7e-ea7d-20905c1568db.png)


次はVerticalBoxのWidgetだ。
![fig18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/4213e598-2c6f-bb71-4bbc-86fd798a970d.png)

ヘッダー部分にTextBlockを一つ。これは並べた時に、「B」「I」「N」「G」「O」 の5文字を入れる予定。
すぐ下にVerticalBoxを配置。中身はブループリントから追加する。
![fig19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/9212928f-389a-1936-abf3-9a23a4bb9ab7.png)
ビンゴカードに並んでいるナンバーは、列ごとに配置される範囲が固定されている。開始ナンバーを受け取ってForLoopのIndexに加算することで、使いまわしのできるWidgetになる。

数字が確定したらVerticalBoxの中のナンバー用Widget を Visible にするカスタムイベント。
![fig20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/d1788ed7-0aed-ef63-9971-a1f02ce01d02.png)
GetChildAt を使うと VerticalBoxの子要素に対してIndex番号でアクセスできて便利なんだけど、キャスト（型変換）してやれば子要素のWidgetが持つ関数を呼び出せる。


よし、これをルーレットのWidgetに配置しよう。
![fig21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/5b68bcd1-f911-9e0d-9346-73f4ef843ec8.png)
Expose on Spawn してるやつには Defaultの値として渡す。
B列は 1～15
![fig22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/ff9287c4-79a7-ee40-a49c-a7c1ce6fcc34.png)
I列は 16～30
![fig23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/616e7b17-6e08-2707-3249-e30e870db4d8.png)
N列は 31～45
![fig24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/2d8133e7-1f2d-921f-0864-d2837191689c.png)
G列は 46～60
![fig25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/12fac695-3cd4-7ae5-6115-ca6f607a808e.png)
O列は 61～75
![fig26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/dbb52b7b-b467-eb0c-8469-53f45ebd0428.png)


この並べたVerticalBoxの列Widgetをいったん配列に登録しておく。
![fig28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/6754ac03-93d8-12b7-0a66-3ddc6df5acce.png)

確定したナンバーをいろいろ計算したら、この配列を通して該当するナンバーを表示させる。
![fig27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/0caeffab-5c33-ea82-9ee4-1b16ad5bfd1d.png)

![fig29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/fd685265-e9cf-6ce2-f00f-c57d25e74a39.png)

できた。
![fig31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/630afe6a-d2ac-fe9a-55b9-f7c5d469db96.png)

あ、一応リセットボタン作っておこう。
列用Widgetにイベントを用意。
![fig30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/1afb0a4b-e176-3c9f-cbd5-fb2c827f24b0.png)
これで、ナンバーのお団子をすべて非表示に戻す。
これを親のWidgetでイベント作って呼び出すだけ。その際にイロイロなかったことにする。
![fig32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/c0211ef9-f100-9e45-e24e-a407a4051464.png)


リセットボタンは、ルーレット開始ボタン同様に ProjectSettings の Inputから追加する。
子供がうっかりボタンを押してしまった時のためにダイアログを作ろうと思ったけど、時間が無いのでちょっと細工する。
ついでにシャッフルボタンも足しておこう。
![fig33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/6c28259a-fb21-e8c9-7781-f177ddaf5e77.png)
レベルBPでブーリアンの変数を追加して同時押し判定に使う。初期値を false にしておいて。
![fig34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/0b030225-e754-fabc-e85c-49b27727d465.png)

これで、キーボードなら Shiftキー押しながら R、ゲームパッドなら Backボタン押しながらStartボタンでリセットだ。
シャッフルは、配列の中身を入れ替えるだけなのでカスタムイベントで簡単。
![fig35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/141281f4-29ca-a82a-6d84-7fd5ead53b70.png)
ナンバーの順番は最初の初期化時にシャッフルしているだけなので、一回のプレイでは固定されている。ほとんど気にならないと思うが、ある程度終盤になると次の番号が予測できてしまう。相手が酒飲みの大人じゃないので一応のシャッフル機能だ。

ふと時計を見ると3時を回っていた。やばい！なんか眠い！


###当日 未明
熱いコーヒーをすすりながらビジュアルプランを考える。
ここからはラッシュアップのターンだ。
もっとナイアガラ触っておけばよかったと悔やみつつ何ができるかいじっていく。

とりあえずフォントは変えてポップな感じにして。
背景はマテリアルアニメーションをシリンダーに貼って謎空間を演出してみた。
![ss.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/0bafea58-1424-0b0c-89f1-57dc456adf95.jpeg)
無意味にスピード感が出たぞ。

もうちょっと豪華さが欲しい。
あの舞台のピンスポぐるぐるするやつ。
まずは丸いライト用のWidgetを作って楕円軌道を動くようにしてみよう。
![fig36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/7cd62f3f-f575-2b01-0671-3e8b8e112841.png)
マテリアルは最初の方で作った円を複製してブレンドモードを Additive（加算）にする。
![fig37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/5908f487-634a-cdde-205c-d7406985ee12.png)
なんとかいい感じの動きができた。
カラーと角度を初期設定できるようにExpose on Spawnしておく。
これを親のキャンバスに３つほど配置すれば・・・うんいいね。あとテキストも固いから変更しておこう。
![ss2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212880/5c409c88-ba46-8c1e-1f11-7064a21f3d72.jpeg)

できた！
けど、もうちょっと何かこう・・・
お、こんなところに[素材](https://twitter.com/i/events/992226594621079552)発見。PNGだから落とせば使えそうだ。
よしよしこれをこうやって、こうして、つないで・・・できた！


いつのまにか外が少し明るくなっている。
仕事でもないのに朝チュンしてしまった。
これで悔いはない。あとは見守るだけだ。ハッピークリスマス！

サンプルの動画 → https://youtu.be/FR1AZbfpt-U


###あとは寝るだけ
というわけで、ちょっとボリュームが出てしまいましたが、いかがだったでしょうか。
ナレッジ的なお役立ち情報とかではないのですが、アドベントカレンダーということで遊べるものを作ろうと思って、ビンゴマシーンをネタにしてみました。グラフィック素材はGrayちゃんとフォント以外は現地調達です。
できることがあらかじめ分かっているので、アレンジもしやすいし、UE4の手習いにちょうど良い教材だと勝手に思ってます。
ハンズオンとかで皆で作って遊ぶとか。それぞれのセンスやこだわりが思い思いに表現されて楽しそうです。
この時期、時間の惜しい中最後まで読んでくださってありがとうございます。

みなさまよいクリスマスを過ごせますように。