Notionのタスクのステータス変更をDiscordでメッセージを送る【Discord.py,Notion API,GitHub Actions,cron-job】

はじめに

チーム開発を行っているときのタスク管理はいろいろ方法があるかと思います。
私が参加しているインディーズゲームのチームの一つでは、Notionでのタスク管理を行っています。 このときにタスクのステータス(対応中・確認中、担当者へのFB)などが変わった際に、チャットで送るのはわりと手間になります。
今回はこちらの作業を自動化してチームの生産性をあげていこうと考えました

完成物

Discord側の通知メッセージ

  1. タスクステータス変更時

  2. タスク期限が迫っている時

Repositoryは以下で 公開しています。

github.com

やりたいこと

  1. Notionの特定のタスク状態が変更されたときに担当者及び確認者 Discordに通知を送る

  2. 一日一回タスクの期限が3日以内のタスクをDiscordで担当者及び確認者に知らせる

  3. タスクの変更状態をなるべくリアルタイムで監視する

(4. お金をかけずに無料で行う(インディーズゲームのため) )

前提として、Notionのタスクボードは以下の感じにわけられています。

準備

まずは今回使っていくAPI等の作成や登録準備を行います。

Notionの インテグレーション(API)の作成と登録

以下のサイトで Notionのアプリを作成します。

www.notion.so

タスク監視用に作成した インテグレーション情報は以下です。内部で使うだけであれば、 インテグレーションの種類は、内部インテグレーション で大丈夫です。

このときシークレットトークンは後ほど使うので、控えておいてください。

次に Notionのタスクページの share (日本語の場合は 共有 ) に先ほど作成した インテグレーションを以下のように追加します。

DiscordのWebHookの作成

今回は Discordのメッセージを送るだけなので、Botではなく 準備・導入が簡単な WebHookを使っています。

サーバー設定 → 連携サービス → ウェブフック から以下のように作成します。

WebHookのURLは後ほど使うため、控えておいてください。

GitHub のpersonal tokenの作成

Setting → Developer settings → personal access tokesから以下のように workflow にチェックを入れて アクセストークンを作成します。

自動化内容と実装

1. Notionの特定のタスク状態が変更されたときに担当者及び確認者 Discordに通知を送る

2. 一日一回タスクの期限が3日以内のタスクをDiscordで担当者及び確認者に知らせる

基本的に以下の記事とRepositoryをそのまま使っています。 細かい実装等や Notion APIについては、以下の記事でも解説していますのでご覧ください

ayousanz.hatenadiary.jp

github.com

3. タスクの変更状態をなるべくリアルタイムで監視する

実行環境の構築周辺について

今回 上記の二つの処理を定期的に実行するために、お金をかけたくない・なるべつ楽をしたい(開発・運用)ということでいろいろ調べました。 方法としては、だいたい以下があるではないかと考えました。 1. IaaS(EC2やGCE) で環境構築をして、cronコマンドで定期時的に実行する。 2. FaaS(lambdaやCloud Functions) + 監視系(Cloud Watchや schedule、AWS Batch) 3. Google Script Appでの実行 4. GitHub Actionsでの定期実行(cron) 5. その他 cronサービス

結果から言うと今回は、4 + 5の組み合わせで構築を行いました。 インディーズゲームでは、運用メンバーが技術に詳しいわけではない可能性が高いことや将来的にほかのサービスに移行する際に version管理されていてDockerなどにも載せやすいように考えています。

構築図は以下のようになっています。

Notionのタスク変更履歴の取得

まずNotionのタスク状態を監視するには、変更履歴を取得する必要があります。

そこで、NotionAPIのSearchを使って変更された内容を取得します。

Queryの書き方は以下のようになります。→ GitHubのソースコード

        db = self.notion.search(
            **{
                "sort": {
                    "direction": "descending",
                    "timestamp": "last_edited_time"
                },
                "filter": {
                    "value": "page",
                    "property": "object"
                },
                "page_size": 100,
            }
        )

次にその中から 変更されたタスクを探すのですが、一つ問題があります。定期実行では厳密にはリアルタイムでの監視ではないため、処理中などにタスクが移動された場合は監視できなくなります。

そこで、Notion側に タスクの移動前に状態を持たせて変数の代わりにしました。Propertyの preStatus という名前で隠しパラメータを作成しています。

移動前のパラメータがあるので、こちらを使ってフィルターをしていきます。

    def is_task_status_doing_from_confirm(self, result) -> bool:
        status = result['properties']['ステータス']['select']['name']
        pre_status = result['properties']['preStatus']['select']['name']
        if status == '対応中' and pre_status == '確認依頼':
            self.update_task_preStatus(result['id'], status)
            return True
        else:
            return False

    def is_task_status_confirm_from_doing(self, result) -> bool:
        status = result['properties']['ステータス']['select']['name']
        if status == '確認依頼':
            self.update_task_preStatus(result['id'], status)
            return True
        else:
            return False

これで変更されたタスクのみが取得できます。

定期実行サービスから GitHub Actionsを実行する

GitHub Actionsを手動実行するだけであれば、on: workflow_dispatch: をymlファイルに入れれば Actions画面から実行することができます。 しかし、これをほかのサービスから呼び出したい場合は少し設定する必要があります。

まずは workflowのidを取得する必要があるので、

curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/org-name/repository-name/actions/workflows 

を叩いて情報を取得します。

以下のような情報が取得できるかと思います。

{
    "total_count": 3,
    "workflows": [
        {
            "id": 25677508,
            "node_id": "W_kwDOHTP_Hc4Bh87E",
            "name": "CodeQL",
            "path": ".github/workflows/codeql-analysis.yml",
            "state": "active",
            "created_at": "2022-05-08T13:11:13.000Z",
            "updated_at": "2022-05-08T13:11:18.000Z",
            "url": "https://api.github.com/repos/astilbestudio/Notion-Watch-Task-Notification-discord/actions/workflows/25677508",
            "html_url": "https://github.com/astilbestudio/Notion-Watch-Task-Notification-discord/blob/main/.github/workflows/codeql-analysis.yml",
            "badge_url": "https://github.com/astilbestudio/Notion-Watch-Task-Notification-discord/workflows/CodeQL/badge.svg"
        },
        {
            "id": 25677510,
            "node_id": "W_kwDOHTP_Hc4Bh87G",
            "name": "watch-deadline-task",
            "path": ".github/workflows/watch-deadline-task.yml",
            "state": "active",
            "created_at": "2022-05-08T13:11:13.000Z",
            "updated_at": "2022-05-08T13:11:21.000Z",
            "url": "https://api.github.com/repos/astilbestudio/Notion-Watch-Task-Notification-discord/actions/workflows/25677510",
            "html_url": "https://github.com/astilbestudio/Notion-Watch-Task-Notification-discord/blob/main/.github/workflows/watch-deadline-task.yml",
            "badge_url": "https://github.com/astilbestudio/Notion-Watch-Task-Notification-discord/workflows/watch-deadline-task/badge.svg"
        },
        {
            "id": 25782832,
            "node_id": "W_kwDOHTP_Hc4BiWow",
            "name": "watch-task-status",
            "path": ".github/workflows/watch-task-status.yml",
            "state": "active",
            "created_at": "2022-05-10T04:35:43.000Z",
            "updated_at": "2022-05-10T04:35:43.000Z",
            "url": "https://api.github.com/repos/astilbestudio/Notion-Watch-Task-Notification-discord/actions/workflows/25782832",
            "html_url": "https://github.com/astilbestudio/Notion-Watch-Task-Notification-discord/blob/main/.github/workflows/watch-task-status.yml",
            "badge_url": "https://github.com/astilbestudio/Notion-Watch-Task-Notification-discord/workflows/watch-task-status/badge.svg"
        }
    ]
}

これで 実行したい workflowのIDを取得できました。

次にIDを使って外部から Actionsを実行します。

curl -XPOST -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/org-name/repository-name/actions/workflows/workflow-id/dispatches -d '{"ref": branchName"}' 

後からこれを任意の定期実行サービスで実行すれば定期的にタスクのステータス変更を監視することができます!

参考にさせていただいたサイト

swfz.hatenablog.com

サイドビューゲームでのURPでのライト表現とマルチシーン・DIコンテナを使ったInputSytemの実装【Unity,URP】

はじめに

こちらを作った際のいろいろな技術検証メモです

成果物

youtu.be

UniryRoomにも公開しています。

unityroom.com

やったこと

  • URPを使った ライト表現
  • マルチシーン及びDIコンテナを使った際のInputSystemのプレイヤー操作
  • sprite shapeを使ったマップ作製

使用したアセット

マップ・キャラクターには以下のアセットを使わせていただいています。

assetstore.unity.com

assetstore.unity.com

内容

URPを使ったライト表現

こちらのライト表現の実装方法です。

以下のように Unity URP 2DのSpot Lightを使っています。

マルチシーン及びDIコンテナを使った際のInputSystemのプレイヤー操作

シーン構成は、以下のようになっています。

また以下のような流れでシーンをロードしています。 1. Playerシーンをロード 2. Townシーンをロード 3. ワープポイントに移動すると Caveシーンをロード 4. 2 ~3のループ

シーンのロードはマルチシーンを導入しているため、 LoadSceneMode.Additive で行っています。 また自然な位置での遷移移動を実現したかったので、各シーンのワープポイントは以下ようにほぼ同じ位置に設定しています

このときに各シーンので InputSytemのAction Mapa及びシーン遷移は以下のように実装しました。

        public static void SwitchStage(SideGameInputActions sideGameInputActions)
        {
            var currentStageCount = SceneManager.sceneCount;
            for (var sceneIndex = 0; sceneIndex < currentStageCount; sceneIndex++)
            {
                var currentStage = SceneManager.GetSceneAt(sceneIndex);
                switch (currentStage.name)
                {
                    case "Town":
                        SceneManager.LoadSceneAsync("Cave", LoadSceneMode.Additive);
                        SceneManager.UnloadSceneAsync("Town");
                        sideGameInputActions.Cave.Enable();
                        sideGameInputActions.Town.Disable();
                        break;
                    case "Cave":
                        SceneManager.LoadSceneAsync("Town", LoadSceneMode.Additive);
                        SceneManager.UnloadSceneAsync("Cave");
                        sideGameInputActions.Town.Enable();
                        sideGameInputActions.Cave.Disable();
                        break;
                }
            }
        }

またワープポイントが同じ位置にあり、加算シーンロードを行っています。そのため同一シーンの多重ロードが起きる問題が起きたため、ロード処理は UniRxの ThrottleFirst を使用して一定秒間シーン処理をされないように制限をかけています。

            _playerMove.Collider2D.OnTriggerEnter2DAsObservable()
                .Where(tag => tag.CompareTag("Respawn"))
                .ThrottleFirst(TimeSpan.FromSeconds(5f))
                .Subscribe(_ => { LoadScene.SwitchStage(_inputActions); }).AddTo(_disposables);

sprite shapeを使ったマップ作製

マップにおける変形した画像の実装には、Sprite Shapeを使っています。

当たり判定は、Colliderの追加と以下の設定でやってくれるみたいです。
しかも、コライダー追加後に Spriteを変形させても自動的に変更してくれる!

blog.unity.com

gamedev65535.com

VContainerでRootLifetimeScopeを使うときの設定【Unity,VContainer,DI】

はじめに

Unityでゲームを作る際に シーン間での値共有の方法がいくつかあると思います。
それらの方法を調査している中 (*1) で、VContainerの RootLifetimeScope を使う方法があります。
RootLifetimeScope を設定する際に少し手間取ったので、メモしておきます。

(*1) ほかの方法については以下の記事にいろいろ記載がありますので、こちらを参考にしました。

qiita.com

やりたいこと

  1. Loading シーンで Google SpreadSheetから取得したデータを 保存したい
  2. 1で取得したデータをゲーム内で使用したい
  3. (*1) 中にもあるようになるべく綺麗な実装にしたい

成果物

Loadingシーンでデバッグ表示されている リスト内データ数と 、Mainシーンでデバッグ表示されているリスト内データ数が同じことが以下の動画からわかります。
(わかりにくい場合は、cloneしてお試しください)

RootLifetimeScopeの作り方及び使い方

こちらは公式サイトほかのサイトがあるので、そちらを読めばわかる方もいるかと思います。
(私はわからずつよつよエンジニアに聞きました)

RootLifetimeScopeを作成

まずは ゲーム全体の Scopeで使える RootLifetimeScope を作ります。

今回は以下のような RootLifetimeScope クラスを作成しました。

using _Project;
using VContainer;
using VContainer.Unity;

public class RootLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        builder.Register<CharacterDataModel>(Lifetime.Singleton);
        builder.Register<InputSystemTest>(Lifetime.Singleton);
    }
}

次に 上記のクラスを GameObjectにアタッチをして、 Prefab化します。

最後に ProjectSettings > Player > Optimization > Preloaded Assetsに VContainerSettings がセットされていることを確認してください。
特にないもしなければ、生成時に入るかと思います。

これで RootLifetimeScope の作成は終了です。

各クラスから RootLifetimeScopeに注入されているクラスを使う

(Unity内では Loadingシーンと Mainシーンのどちらでも Rootにあるものを使っているのですが、説明上 Mainのみとさせて頂きます。
詳細はRepositoryからcloneしてご確認ください。 )

まずは シーン用の LifetimeScope を作成します。

using VContainer;
using VContainer.Unity;

namespace _Project.Main
{
    public class MainLifetimeScope : LifetimeScope
    {
        protected override void Configure(IContainerBuilder builder)
        {
            builder.RegisterEntryPoint<MainManager>();
        }
    }
}

これを 適当なオブジェクトにアタッチします。
このとき Parentは None のままで大丈夫です

以上で RootLifetimeScope が使えるようになっているかと思います。

Unityでゲーム起動時に スプレッドシートから データを取得する方法

こちらの機能のみを試したコードは以下のRepositoryにありますので、詳細は以下をご確認ください。

github.com

Notionのタスク 一覧でタスクがDoneになったときに完了日付を自動入力する【Notion,Python,GitHub Actions】

はじめに

最近 Notionを使ってタスク管理を再度始めました(昔やっていたのですが、飽きてしまっていて)
しかし タスクがいつ完了したものなのか後から確認するためには、日付を入力してカレンダーから見る必要があります。
この日付入力が少しめんどくさいので、自動化しました。

成果物

コードや 実際に動いている スクリプト :

github.com

実装簡易説明

細かい部分の詳細はRepositoryもしくは以下の参考にしたサイトを確認してください。   実装時に時間がかかった部分のみ記載しておきます。

Notion DB 取得時に query Filter

Notion のDBからデータを取得する際にすべて取得してもいいのですが、重たくなる・一括で取得できる数に制限という問題のため Queryでフィルターをかけてなるべく必要なデータだけ取得していきます。

公式 API DOcumentに フィルターの書き方があるので、こちらを参考にしています。

developers.notion.com

フィルターを書く際に 期限は select 型にしているため、一致しているかどうか( equals )で判別することができます。 しかし、日付( 'date' )は nullなどがないため is_empty で判定をしています。

                "filter": {
                    "and": [
                        {
                            "property": "期限",
                            "select": {
                                "equals": 'Done'
                            }
                        },
                        {
                            "property": "日付",
                            "date": {
                                "is_empty": True
                            }
                        }

                    ]
                }

フィルターはほかにも書き方があるため、必要に応じて変更していくといいかと思います。

developers.notion.com

GitHub Actionsから環境変数を読み込む

GitHub Actionsをpublic Repositoryで呼び出しているため、スクリプトにハードコーディングすることができません。 そのため、環境変数を使っていきたいと思います。

yml ファイルに以下のように設定すると スクリプト側から 呼ぶことができます。

      - name: Run script
        env:
          NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
          PRIVATE_DB: ${{ secrets.PRIVATE_DB }}
          WORK_DB: ${{ secrets.WORK_DB }}

この際に GitHub 側に sercret keyで事前に登録しておく必要があります。

参考にさせていただいたサイト qiita.com

Notionのpage Property(date) の更新

地味に詰まったところで、data を更新するときには object 型を入れないといけないみたいです。(公式ドキュメントより)

今回は starttime_zone をオブジェクト側で指定していきます。 data 型のオブジェクト内容は PostManなどで以下のように確認できます。

                    "date": {
                        "start": "2022-05-02",
                        "end": null,
                        "time_zone": null
                    }

その他参考サイト

developers.notion.com

Fungusを使ったゲーム制作におけるテスト作成と自動テスト環境の作成及び構築【Unity,Fungus】

はじめに

2021年5月から複数人で3D オープンワールドゲーム( DreamIsland ) [PVは以下動画]を制作しています。 ゲーム内では、ゲームでよくなる NPCとの会話やセーブ・ロード機能、メニュなどいろいろな機能があります。
制作をしていく中で、これらを実装していくといくつかバグが出てきますが毎回どこが原因がデバッグをするのはとても手間だと感じていました。 特に NPCとの会話は 数十体いるNPCと会話するのは、数時間に及ぶものになっています。
そこで本チームではNPCとの会話におけるデバッグの一部のテスト環境の構築と自動化できる環境を作成を行いました。

www.youtube.com

制作環境

(内容に関わるもののみ記載)

  • Unity 2021.3.0f1

  • Fungus v3.1.9

  • UniTask
  • UniRx

対象者

  • UnityでFungusを使った大きめの(*1) ゲーム制作をしている方
  • テストコードやC#をある程度理解している方

(*1) : 人によって感覚が違いますが、ここではテストコードを書いたほうがプロジェクトの生産性が上がる規模としています。

本プロジェクトにおいての会話実装及び仕様説明

本プロジェクトでは、NPCとの会話に 会話アセットして有名な Fungus を使用して NPCとの会話機能を実装しています。 Fungusは、以下の感じで Insptorから会話内容を確認できたり、ノードベースで会話内容の分岐やループ、終了を作成することができます。

f:id:ayousanz:20220416112737p:plain

会話パートの説明

NPCとの会話では以下の順序で処理を行っています。

  1. プレイヤーが NPCに近づき 特定のキーを押して話しかけるトリガーをONにする。
  2. 目の前にいる NPCから 会話用 Interfaceを GetComponentして、会話処理を開始する(NPC側の会話処理を始める)
  3. NPC側にある会話処理が終了するまで、ほかの処理を待機する。
  4. 会話処理が終了するとプレイヤー側に処理を戻して、ほかの処理を開始する。

制作時に起きた問題点

Fungusを使用して会話を実装する場合、以下のような運用ルールが必ず必要になると思います。

1. flowchartのMessageとスクリプト側のFlowchart用 Message Stringが一致しない問題

Fungus側のMessgeを呼ぶときには、FlowChart.SendFungusMessage(message); を呼び出す必要があります。
このとき message が 呼び出すオブジェクトにアタッチされている Flowchart内の Messageと一致していないと呼び出すことができません

f:id:ayousanz:20220416114116p:plain

こちらのstring値は、作業者が設定するためどうしてもミスが起きてしまうことがあります。

2. Fungus内の変数とスクリプトから呼び際に変数名の一致

Fungusにおいて、会話内の分岐や変数の扱いは variables という flowchart内変数を用いて実装します。

f:id:ayousanz:20220416123928p:plain

この変数をスクリプト側から参照したり変更したりするときは、スクリプト側から変数名を string値で指定する必要性があります。

(以下例 : 常に変数を監視して、会話処理を行っている)

        private void ObservationTalk()
        {
            FlowChart.ObserveEveryValueChanged(value => value.GetBooleanVariable(TalkEventValueName))
                .Skip(1)
                .Where(_ => IsMissionEvent())
                .Subscribe(isTalk => { IsTalk = isTalk; }).AddTo(this);
        }

このときにスクリプト側の変数名と flowchart内の変数名が違っている場合は、処理ができずバグの原因になってしまいます。

こちらも作業者がスクリプト側及びflowchart内のどちらとも設定するため、ミスが起きることが考えられます。

Flowchartでのテストについて

上記の問題点について以下のテストコードで自動化を行いました。
また PRを出した際に GitHub Actionsを用いて CI で自動テストをすることにより、プロジェクトの品質を保つことができています。

(前提として、本プロジェクトでは NPC周りは NPCのBaseクラスを各NPCで使用していたり継承したりしています。以下NPC周りのクラス図)

f:id:ayousanz:20220416125114p:plain

またテストを実行する際に 会話処理がシーンごとにテストを行っています。    テストを実行する際には、以下のコードのようにテスト実行後シーン遷移が終わったのちにヒエラルキー上に存在するNPCを取得して、各テストの引数に渡しています。

        [UnityTest]
        [Category("FungusValuesTest")]
        public IEnumerator NPCFungusValuesInGame()
        {
            SceneManager.LoadSceneAsync("_DreamIsland/_Scenes/NPCs").completed += _ => { NPCFlowchartValueTest(); };
            yield return null;
        }

flowchart内の必須変数の存在テスト

スクリプト側で設定したflowchart内の変数が存在しているかどうかについては、Fungus/Flowchart にある HasVariable を使用しています。

        private static void NPCControllerFlowchartValueTest(IEnumerable<GameObject> npcList)
        {
            foreach (var npc in npcList)
            {
                if (!npc.TryGetComponent<NPCController>(out _)) continue;
                if (!npc.TryGetComponent<Flowchart>(out var flowchart)) continue;
                var isTalkValue = flowchart.HasVariable("isTalk");
                var isPhaseValue = flowchart.HasVariable("phase");
                Assert.True(isTalkValue);
                Assert.True(isPhaseValue);
            }
        }

スクリプトのmessageとflowchart側のmessageの文字列の一致テスト

スクリプト側から FlowchartのMessageBlockを呼び出す際の文字一致に関しては、Fungus/MessageReceivedGetSummary を使用しています。

using System.Linq;
using Fungus;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;

namespace _DreamIsland.Editor
{
    public class SetFlowchartMessage : EditorWindow
    {
        [MenuItem("DreamIsland/SetFlowchartMessage")]
        private static void SetMessage()
        {
            var allObject = Resources.FindObjectsOfTypeAll(typeof(GameObject))
                .Select(c => c as GameObject)
                .Where(c => GetFullPath(c).Contains("NPCs"));
            foreach (var obj in allObject)
            {
                if (!obj.TryGetComponent<Flowchart>(out _)) continue;
                var message = obj.GetComponent<MessageReceived>();
                if (!message.GetSummary().Equals(obj.name))
                {
                    Debug.Log($"{obj.name}のflowchartのmessageを{message.GetSummary()}から{obj.name}に変更します");
                    message.SetMessage(obj.name);
                }
            }
        }

        private static string GetFullPath(GameObject obj)
        {
            return GetFullPath(obj.transform);
        }

        private static string GetFullPath(Transform t)
        {
            var path = t.name;
            var parent = t.parent;
            while (parent)
            {
                path = $"{parent.name}/{path}";
                parent = parent.parent;
            }

            return path;
        }
    }
}
#endif

また、このテストで失敗した際に 以下のような Editor拡張を使用して一括で プロジェクト内にある NPC Prefabを一括で正しいもの変更するようにしています。

f:id:ayousanz:20220416133202p:plain

        private static void IsFlowchartMessageMatchNPCNameTest(IEnumerable<GameObject> npcList)
        {
            foreach (var npc in npcList)
            {
                if (!npc.TryGetComponent<Flowchart>(out _)) continue;
                var messageReceived = npc.GetComponent<MessageReceived>();
                Assert.AreEqual(npc.name, messageReceived.GetSummary());
            }
        }

GitHub Actionsを用いた CI/CD環境構築方法

ayousanz.hatenadiary.jp

GitHub ActionsのTest Reuslt画面では以下のように確認できます。

f:id:ayousanz:20220416132043p:plain

まとめ

テスト環境を作成したこととテストを自動化することによって、リリース前にバグを心配する部分をだいぶ減らすことができました。
また日々の開発でも会話周りの原因をテストができる環境により、原因の解明を迅速の行うことができています。

みなさん テストを頑張って書きましょう! (*注)

(*注) : 個人開発の場合、テストを書くコストに見合わない場合も結構あります。。。

DreamIslandが近々 SteamにReleaseされるため、宣伝及びプレイをお願い致します!

f:id:ayousanz:20220416133559p:plain

MagicOnionでローカルでのゲームっぽいものを作る【Unity,gRPC,MagicOnion】

仕事でgRPCを触る機会があったので、せっかくだったら個人でもゲームっぽいものを作りたいということで n回目のMagicOnionに挑戦しました
(過去に二回ほど環境構築やらでつまづいています)

記事を書いていて特に書くことがないので、メモ程度になりました

Demo

ローカルPC内で複数のアプリから任意のルームへの参加して移動およびチャット機能の実装をしたデモ動画になります。
有料セットなどを抜いたRepositoryはこちらになるので、コードはRepositoryからご確認ください。

youtu.be

実装部分

環境構築やサーバーとクライアントとの基本のデータ送信部分は参考サイトをそのままなので、サイトを見てください。

任意のPrefabを生成する

参考サイトでは基本オブジェクトの生成のみしかなかったので、せっかくなので好きなアバターを生成したいので簡単に以下のように変更しています。

  1. ゲームシーンに切り替わるまでサーバーとの通信を待機して、切り替わったら接続と同時にPrefab情報を登録。(*1)
  2. ClientHubからで登録されたPrefabを使ってプレイヤーモデルを生成
  3. サーバーにはPrefabのposition,rotationのみを送信(今回はモデルの種類は一つのため)

(*1)

_gamingHubClient = new GamingHubClient
{
    CharacterGameObject = otherPlayerPrefab
};
await _gamingHubClient.ConnectAsync(_channel);

(*2)

        void IGamingHubReceiver.OnJoin(Player player, string playerName)
        {
            var playerObject = Object.Instantiate(CharacterGameObject);

            if (player.Name == playerName)
            {
                _myObject = playerObject;
            }

            playerObject.GetComponent<CharacterHandler>().SetName(player.Name);

            playerObject.name = player.Name;
            playerObject.transform.SetPositionAndRotation(player.Position, player.Rotation);
            _players[player.Name] = playerObject;
        }

参考サイト

qiita.com

qiita.com

zenn.dev

zenn.dev

UnityでのURLのPingの非同期確認方法【Unity,C#】

はじめに

特定のIPアドレスがとっているかどうかを接続する前に、確認したいときがあります。 C# での同期的な処理やUnityのPingを使った方法(timeout時間は指定できない?)は、以下のようにできるみたいでした。

santerabyte.com

qiita.com

非同期でtimeout時間を指定した場合がすぐにわからなったのでまとめておきます

実装

参考サイト docs.microsoft.com

Ping には Unityのクラスを使用せずに、System.Net.NetworkInformation.Ping を使用しています。    Unity側のPingで timeoutを指定して、非同期にする方法がわからず...

var ping = new Ping();
response = await ping.SendPingAsync(Url, TimeOut);