イクチオカップ

志井.park

https://github.com/orgs/shii-park/repositories

Next.js

Go

React

Unity

Firestore

君のコメントが配信者の力に! 24時間配信生活!!

林壱仁_++志井++

こなた

山田優吾

推しアイデア

UnityとWebの奇跡の融合!? Web側で送信されたコメントがUnityで弾として発射されちゃう! 君のコメントで推しをゲームクリアに導こう。

作った背景

配信者になりたいなって思った瞬間、ないですか? でも視聴してくれてもコメントが盛り上がらないんじゃ... そんな不安を一気に解消、コメントが絶対に盛り上がるゲームを作っちゃえばいいんでは??

推し技術

UnityとWebの接続をWebSocketで実現しています(予定)。それによりコメントが来たタイミングでリアルタイムで反映されます(予定)。 Unity側にはゲームが重くならないようにいろんなデザインパターンを取り入れています。

プロジェクト詳細

2025_HACKZ_ICHTHYO_CUP

ゲームについて

タイトル

ストリーマーシューター

概要

配信弾幕ゲーム

このゲームにおけるプレイヤーは、シューティングゲームをプレイする配信者側と、その配信者を応援するためにコメントを打つ視聴者側の2種類に別れる。

配信者側はシューティングをプレイし、敵の弾を避けるながら自らが放つ弾を当てなければならないが、この弾を放つという仕組みはプレイヤーに委ねられているわけではなく、視聴者側の入力するコメントから行われている。

視聴者側がコメントを打ち込むことにより、それが配信者側のゲームに反映され、弾が放たれるのだ。

そう、このゲームは推しをゲームクリアに導くため、オタクたちが立ち上がる配信協力型ゲームなのである

構成等

ゲームとWebの関係は以下の通り。 image

TODO : 技術構成図も作ろう。

使用技術

ゲーム

Flyweightパターン

一言で言えば、一つのインスタンスを使い回すデザインパターン。 インスタンスを生成する、削除するという行為そのものは決して重くはないが、これが何度も続くようであれば話は変わってくる。

今回のゲームにおいては「弾幕シューティング」と銘打っているため、敵もプレイヤーもたくさん弾を撃つ。 このような仕組みで生成、削除を何度も使うとメモリも食えば、処理もだんだん重くなって処理落ち、ということになりかねない。

ということで、一つのインスタンスに構成要素を定義し、再定義して仕舞えば再利用できるような形にした。

もし現在利用可能なインスタンスがあればそれを利用し、なければ生成する、というような形。

具体的には以下のような感じになる。 image

ここで暗くなっているのはUnity上では非表示になっており、ゲームプレイ中は消えたように感じるが、実際には画面上に残っており、また別のところで発射の処理を行う時に位置の移動、発射処理等を行うことで、さも今生成したかのように振る舞うことができる。

今回のゲームにおいては、プレイヤーの弾、エネミーの弾、エネミーそのものなどのゲーム中に大量に生成されるものにこの処理が適応されている。

MVPパターン

(M)Model (V)View (P)Presenter で構成されるオブジェクト

わかりやすいコードはこちら BulletModel.cs BulletView.cs BulletPresenter.cs

Modelにはそのオブジェクトのデータの実態を持つ。 BulletModelにおいては、弾のコメントデータや、そのオブジェクトが利用可能かどうかを示すboolなどを持っている。

ViewにはそのオブジェクトのUIに関する処理を持つ。 BulletViewにおいては、弾の表示を行うコンポーネントに対してコメントの注入を行ったり、表示非表示を管理する。

PresenterにはそのオブジェクトのModelとViewの繋ぎこみを行う。 コードを参照して貰えばわかるが、ModelとViewはそれぞれの存在を知らない。 そのため、それぞれをつなげる役割であるPresenterが必要である。BulletPresenterの理解に関しては後述のR3を参考にしてほしい。

あくまで行っているのはPresenterがModelの状態を観測し、変更があればViewのメソッドを発火させるという処理。 すなわち、Modelに何かしら変更があってもViewには影響がないし、Viewに変更があってもModelには影響がない。 別のViewを挟もうが、別のModelの情報を取ってこようが、極論問題ないのである。ModelとViewはお互いを知らないので。

これにより責務分離、拡張性の高いコード等々良いことが多くあります。

今回のゲームでは表示機能を持つオブジェクトに関しては概ねMVPパターンの実装を行っている。(Model、Viewと直接書いていないものもある)

Unity 6

今回使用したUnity 6は、Unityの最新バージョンです。 去年出たばかり、去年使った時はプレビュー版だったのにLTSになってびっくり。

今までと何が違うの? 第一にレンダリングパフォーマンス。 HD レンダーパイプラインに大きくパフォーマンス強化が入ったことで、CPU フレーム時間を最大 50%も削減できる。

第二にライティングの強化。 一言で言えば、ライティング周りで機能拡張があったことによって昼夜の切り替えだったり照明周りのライティングがしやすくなったことで細かい表現の調整ができるようになったとのこと。

そして個人的に面白いなと思っているのがマルチプラットフォームの充実。 元々Unity自体はいろんなプラットフォームに対してビルドが可能だったのがさらに便利に。 特にAndroid、iOSはWeb上でアプリを動かすことができるようになっため、ウェブビューにアプリを乗せて実行する、なんてこともできるようです。

R3

イベント管理を行うことのできるライブラリです。 元々UnityにおいてRx系といえばUnirxというくらいには蔓延っていましたが、2020年を最後にUniRxの更新がストップ。それからしばらく音沙汰がありませんでしたが、今年に入ってからUniRxの製作者neuecさん主導でCysharpが新しいライブラリR3を開発しました。 その目的は既存のRxの再定義、再実装。現在、非同期処理のベストプラクティスはasync/awaitですが、それに比べるとRxは価値が低下。実装自体も古いC#で実装していたため、機能面でもかなり劣っていました。async/awaitとの共存や、最新のC#の環境に合わせるためにR3は作られました。

例えばどんなことが変わったの? 1番面白い変更はObservableの定義について。 元々、Observable はSystem定義のインタフェースに依存していましたが、R3ではSystem定義のインタフェースに依存していない独自の抽象クラスObservableとして定義されています。

RxとR3のObservableを比較すると、変更されているのはOnErrorとOnCompletedの挙動です。 元々Rxでは例外が発生した時はOnErrorを発行し、そのままObservableは動作を停止するように作られていました。

(たかだか)例外一個出ただけでイベントが止まると困る場面が出てきます。破棄されたObservableを再構築するのもパフォーマンス的にはいかがなものかと、そういった問題を解決するために、OnErrorで動作を停止するのはなく、例外を通知しObservable自体の寿命は関係しないOnErrorResumeが追加されました。

また、既存のOnErrorとOnCompletedは統合されてOnCompletedになり、停止理由が送ることが可能になっています。

個人的にこのことを表す1番好きな一文が**「すべてのObservableは「完了」する」**元々の実装を考えるとかなり大胆な変更がされていることがよくわかりますね。

これ以外にも今までを振り返ってよりよく実装され直している部分が多々あります。 使用感的にはあまりUniRxとは違いは大きく感じませんが、より深いところを見てみるとなかなかに面白いです。

R3なし

クラスAは自身の変数を変更したときにクラスBを参照して、クラスBのメソッドを発火する必要があります。 この時クラスBはクラスAのことを知る必要はありません。

image

R3あり

クラスAが自身の変数を変更したという通知をクラスBが受け取ることでクラスBが自身の処理を発火させます。 この時クラスAはクラスBのことを知る必要はありません。

image

例の場合、変更を行う、変更から処理を行うの関係が1 : 1の関係なので、いまいちピンと来ないかも知れませんが、変更から処理を行うクラスがもしも複数個あった場合、変更を行うクラスはそれだけたくさんのクラスを参照することになります。

image

依存性の逆転を行うことで、それぞれの変更から処理を行うクラスは変更を行うクラス1つを参照すれば良いため、擬似的に1 : 1の関係を生み出すことができます。

image

今回は、データとUIを完全にわけ、データ間でのやりとりやUIの表示を行いやすくしました

DoTween

高品質なアニメーションを行うことのできるライブラリです。 例えば三角形を上に移動させたい時は以下のコードを1行書くだけで動画のように動きます。

this.gameObject.transform.DOMove(Vector3.up * 2, 3);

image

動きながら回転は以下のように

this.gameObject.transform.DOMove(Vector3.up * 2, 3); this.gameObject.transform.DORotate(new Vector3(0, 0, 360), 3, RotateMode.FastBeyond360);

image

ちょっと力を貯めて

this.gameObject.transform.DOMove(Vector3.up * 2, 3).SetEase(Ease.InBack); this.gameObject.transform.DORotate(new Vector3(0, 0, 360), 3, RotateMode.FastBeyond360).SetEase(Ease.InBack);

image

苦労したところ

メンバー

林壱仁_++志井++

@ITI0820