Google Sheetを使ったFungusのテキストデータの管理【Fungus】【Unity】【Google Spread Sheet】

概要

私たちが制作した「DreamIsland」(*1)というゲームでは、Fungusを使ってNPCやオブジェクトとの会話イベントを実装しています。その中でFungusのデータ管理(会話テキストやフロー)についていろいろ調査と試作をしていったん解決ができました。

今回は Fungusの会話フローやテキストデータを Google Sheetを使うことによって、開発時の開発フローの改善と生産性向上を行うことができると思っています。

(*1) 宣言もかねて、載せていただきます!是非プレイしてみてください

store.steampowered.com

デモ

デモ動画では、以下の流れで操作を行っています。

  1. Unity画面で会話テキストの表示
  2. Google Sheet側で会話テキストの変更
  3. 変更後の会話テキストの表示確認

リポジトリ

github.com

ターゲット

  • FungusとUnityをある程度使っている人
  • Fungusのデータ管理について困っている人

開発環境

  • Unity 2021.3.4f1
  • Fungus v3.13.8
  • UniTask 2.3.1
  • UniRx 7.1.0

課題と解決方法実装

これまでの課題

Fungusを使うときのデータ管理として、Fungus側は以下が準備されています。

  1. Flowchartにデータをそのまま埋め込む
  2. textデータをimport/exportして管理をする
  3. CSVデータをexportする(importはうまくいなかった)

上記の3つともローカルでは編集できるのですが、チームで開発を行うときにだれが更新するのか・そもそもどのファイルが最新版なのかがわからなくなってしまいます。また テキストデータを編集する人はUnityや実装のことをわかっていないポジションの人かもしれません。

解決方法

概要にもありますが、今回は Google Sheetで会話テキストと会話フローを管理することにしました。また Fungus(Unity)のデータ更新は Unity起動時に 最新版に更新することでシナリオを書く人と実装する人のコミュニケーションコストを無くすようにしました。

以下が大まかな構成図になります。

  1. Google Sheet上で会話テキストや会話フローを編集
  2. GASを使って特定のエンドポイントから最新版の会話データをjsonで取得
  3. Unity起動時に http経由で最新の会話データを取得
  4. Fungusのflowchartにipmortできる形にコンバートを行い、flowchartを最新に更新

実装方法

準備編

いったんいま実装している会話データをGoogle Sheet(CSV)の場合どのような書き方になるのかを確認します。

1 . Fungus(flowchart)で会話を実装

今回は以下のような会話を作りました。

2 . Localizationコンポーネントの追加

以下のMenuからCSVとしてexportできるための機能(Localization) コンポーネントを追加することができます。Tool -> Fungus -> Create -> LocalizationLocalization コンポーネントヒエラルキー上に追加することができます。

3. 会話データをCSVで出力

Localization コンポーネントExport Localization File というボタンがあるので、こちらを押すと 任意のパスに 会話データを CSVファイルとして出力することができます。

Google Sheet側(会話データ側)

1. 外部から会話データを取得できるGASスクリプトの作成

先ほど出力した CSVのフォーマットを そのまま Google Sheetにコピーします。

次にこのデータを Unity側で取得できるように GASを使ってjsonとして渡せるようにします。メニューから 拡張機能 -> App Script を開きます。

開くとスクリプトを書くことができるので、以下のようにして 任意のシート名のデータをjsonとして渡すスクリプトを作成します。

function doGet(e) {
  var sheetName = e.parameter.sheetName;
  var d = {"textData":getData(sheetName)};
  var out = ContentService.createTextOutput();
  out.setMimeType(ContentService.MimeType.JSON);
  out.setContent(JSON.stringify(d));
  return out;
}

function getData(sheetName) {
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  const rows = sheet.getDataRange().getValues();
  const keys = rows.splice(0, 1)[0];
  return rows.map(row => {
    const obj = {};
    row.map((item, index) => {
      obj[String(keys[index])] = String(item);
    });
    return obj;
  });
}

2. GASスクリプトのデプロイ

デプロイから新しいデプロイを選び、ウェブアプリを選択します。ユーザーは 自分を選択します。

このときの表示されるデプロイ URLは使うためどこかに控えておきます

Unity側(ゲーム側)

会話データ用をロードするためのクラスを以下のように作成します。

[Serializable]
    public class TextData
    {
        public string Key;
        public string Description;
        public string Standard;
    }

    [Serializable]
    public class TextDataList
    {
        public List<TextData> textData;
    }

次に GAS(http)経由で Google Sheetのデータを取得します。またFungusに沿ったテキストフォーマットに変更する必要があるため、二つの関数を用意しておきます。

/// <summary>
        /// ゲーム情報をスプレッドシートから取得
        /// </summary>
        /// <returns></returns>
        public static async UniTask<T> GetGameInfo<T>()
        {
            var request = UnityWebRequest.Get($"{url}?sheetName={sheetName}");
            await request.SendWebRequest();
            if (request.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError or UnityWebRequest.Result.DataProcessingError)
            {
                Debug.Log("fail to get card info from google sheet");
            }
            else
            {
                var json = request.downloadHandler.text;
                var data = JsonUtility.FromJson<T>(json);
                return data;
            }

            return default;
        }

        public static string ConvertFungusTextFormat(TextDataList data)
        {
            var fungusText = "";
            foreach (var info in data.textData)
            {
                fungusText += $"#{info.Key}\n";
                fungusText += $"{info.Standard}\n";
                fungusText += "\n";
            }

            return fungusText;
        }

最後にこれらのスクリプトを呼び出し、Fungus側に起動時に反映すれば終了です

public class UpdateFungusText : MonoBehaviour
    {
        private Localization _localization;
        [SerializeField] private GameStarted gameStarted;

        private void Awake()
        {
            _localization = GetComponent<Localization>();
        }

        private async void Start()
        {
            var data = await LoadTextData.GetGameInfo<TextDataList>();
            var fungusText = LoadTextData.ConvertFungusTextFormat(data);
            _localization.SetStandardText(fungusText);
            gameStarted.enabled = true;
        }
    }

省略している部分や少し変更されている部分もありますので、最新版はRepositoryをご確認ください

終わりに

FungusのデータをGoogle Sheetで管理することによって、テキストのローカライズの対応がより容易、テキストの確認作業の高速化、エンジニア側とシナリオ側のコミュニケーションの削減など多くのメリットがあります。

またFungusを使ったときの会話テストをEditor Windowのみで完結させる拡張機能は以下で紹介していますので、ご覧ください

ayousanz.hatenadiary.jp

OSSをForkしてUPM・OpenUPMの登録を行う【Unity】【upm】【OpenUPM】

はじめに

個人製作でいろいろやっているとOSSを使うことがありますが、最終更新日が数年前のものがよくあります。しかし、ライブラリとしては使いたいけどいろいろいまのversionとはあっていないものがあるので、今回はその辺の更新とUPM、OpenUPMの登録をする方法をメモとして置いておきます。

みなさんも古いものは更新してどんどんほかの人(自分が)使えるようにしていきましょう!

対象者

  • Unityで UPM,OpenUPMを登録したい人(今回はUnityで限定させていただきます)
  • ライブラリを自分用にカスタマイズしたい人

ライブラリのfork

まずは自分がメンテナンスしたいライブラリがないと始まらないため、今回は 以下のライブラリをメンテナンスして UPMとOpenUPMで使えるようにしていきます。

github.com

ボタンを押すといろいろ設定ができますが、そのまま作成します。

(以下の画像は別のリポジトリのforkなので名前等は自分のものにしてください)

まずはこちらから fork を押して自分のリポジトリにforkしていきます

github.com

(今回は すでにforkされている方がいたので、fork されていたものをforkしています。ほぼ同じなので好きなところからforkしてください)

upmの登録

基本的には以下の記事を参考に進めていきます。

qiita.com

upmとして公開するには、以下が必要になってきます。もともとのライブラリによってすでに作成済みになっている場合があるので適宜スキップしてください。

  1. ライブラリのフォルダの作成
  2. package.json の作成
  3. package.json の内容を適切に記述

フォルダの作成

上の画像のように ライブラリ用のフォルダを作成していきます。名前は適切なものにして、ほかの人が見たときにわかるようにしておきましょう。

package.json の作成と記述

フォルダの中に package.json を作成します。

次に記述を以下のようにします。

{
  "name": "com.ayutaz.uigradients" ,
  "displayName": "UI Gradients" ,
  "version": "1.0.1" ,
  "unity": "2021.3" ,
  "description": "A small collections of scripts to add gradient effects to UGUI elements." ,
  "keywords": [
    "Gradient" ,
    "UI"
  ]
}

確認作業

  1. 新規のプロジェクトを作成して、PackageManagerを開きます。

  2. Add Package from Git URL から自分のURLを追加します。

私の場合は、https://github.com/ayutaz/Unity-UIGradient.git?path=Assets/UIGradients になります。

これで以下のようにライブラリが問題なく追加できれば upmの対応は終了です。

遭遇するかもしれないエラーと対応方法

~ is not valid JSON: Unexpected token in JSON at position 0

UnityのPackge Managerから登録した際に 上記のエラーが表示されることがあります。こちらは、BOM が入っていることがあるのでこちらを取り除いていきます。

Editorによって少し操作が異なってきますが、以下のようなメニューがあるので エンコードを指定して開きなおす などを押して適切なエンコード (だいたいは utf-8 ) で開いて保存すれば直ります。

更新したときの作業

修正点が見つかって、更新したときには package.jsonversion を更新しておきましょう。

こちらを更新しないと 追加したときのversionの数字が更新されません。

OpenUPMの登録

Open UPM( packge.json からインストールする) ためには登録する必要があります。

openupm.com

こちらから以下のように申請していきましょう

  1. リポジトリの登録

  1. 詳細の設定

Defaultのブランチや ライセンスなどを記載していきます。特にこだわらなければ、リポジトリと同じものにしましょう

  1. PRの提出とデプロイ待ち

あとは上記のものを PRを出して、テストが通れば数時間くらいでデプロイが完了します。

このときに package.jsonのversionと同じgit tagを設定しないとPackage Managerから見てない ことに注意してください

PRがマージされると package.jsonに記載すると PackageManagerに表示されるようになります!

OpenUPM のサイトに登録されていれば今後自分が登録したものが使えるようになります!

  1. 登録の確認 以下登録した終わった今回のライブラリ

openupm.com

GitHub Actionsでビルドが終了時に DiscordでartifactのダウンロードページURLを受け取る【GitHub Actions,Discord,Python】

はじめに

GitHub ActionでCI/CD環境を組んでいて、ビルドができても完了通知やビルドデータのダウンロードをするためには毎回 GitHubを開く必要があります。

かなり手間になっていたので、ビルドが環境するとアーティファクトがあるページのURLをDiscordに送信するシステムを作りました。

(デモリポジトリはUnity用に作成していますが、ほかのものでも代用できると思います)

public Repositoryではないと動かないことが判明したため、調査中です

成果物

以下のように Discordにビルドで作成されたアーティファクトがあるページのURLが、 discordでビルド環境時にメッセージとして送られてきます。

Repository

github.com

動作環境

使い方

1. Actions secretsの登録

まずは今回は GitHub APIと discordのwebHookを使っているため、以下の画像のように GitHub環境変数WebHookURLGitHub Personal Token 登録をします。

tokenは最低限のものを許可していれば大丈夫です。

この際に GitHub上の設定で、Setting → Actions → General → Workflow permissionsRead and write permissions になっていることを確認してください。 動かない可能性があります。

2. GitHub Actionのymlの作成

ymlの実行したい箇所に以下を追加します

      - name: Set up Python 3.9
        uses: actions/setup-python@v3.1.2
        with:
          python-version: 3.9
      - name: Install dependencies
        run: |
          pip install -r .github/send_download_artifact_url/requirements.txt
      - name: send download artifact page url
        env:
          GITHUB_REPOSITORY: ${{ github.repository }}
        run: |
          python .github/send_download_artifact_url/send-download-artifact-page-url.py

3. スクリプトの作成と配置

実行するスクリプト( send-download-artifact-page.py ) と requirements.txt を以下のように .github/send_download_artifact_url 以下に配置します。

ファイル名や配置パスは、ymlを書き換えれば任意のものに変更しても大丈夫です。

discordに送信するスクリプトは以下になっています。

import os
import requests

from dotenv import load_dotenv

load_dotenv()

headers = {
    "Accept": "application/vnd.github.v3+json",
    "Authorization": os.getenv("PERSONAL_ACCESS_TOKEN"),
}

req = requests.get(f" https://api.github.com/repos/{os.getenv('GITHUB_REPOSITORY')}/actions/artifacts",
                   headers=headers).json()


def get_download_url(content):
    for artifact in content["artifacts"]:
        if artifact["name"] == "Build-StandaloneWindows64":
            run_id = artifact["workflow_run"]["id"]
            url = f"https://github.com/{os.getenv('GITHUB_REPOSITORY')}/actions/runs/{run_id}"
            return url
    return None


def message(url):
    content = {
        "username": "ビルドダウンロードページ",
        "content": "ビルドが終了しました。"
                   + f"\nダウンロードページ: {url}"
    }
    return content


requests.post(os.getenv("DISCORD_WEBHOOK_URL"), message(get_download_url(req)))

実装の詳細

最新のビルドデータを取得

https://api.github.com/repos/OWNER/REPO/actions/artifacts を使い、Repositoryのartifactのリストを取得しています。その中から ビルドデータ (今回の場合は、Build-StandaloneWindows64 ) に一致するもので最新のものを取得しています。

https://docs.github.com/ja/rest/actions/artifacts#list-artifacts-for-a-repository

実際にAPIを叩いてみると、以下のようなデータが返ってくることが分かります。

artifactのあるページのURL作成

GitHub上でartifactがあるページは https://github.com/owner_name/repository_name/actions/runs/run_id となっています。こちらは直接取得できないため、↑のでビルドデータの中に run_id が含まれているので、そこからidを取得して URLを生成しています。

(ブックマークは気にしないでください)

リポジトリのownerとRepositoryNameを取得

Repositoryに依存しない実装にしたかったので、Repository名やowner名をスクリプト上に記載したくはなかったので この二つを 環境変数として取得しています。

${{ github.repository }} で取得できるため、 ymlファイルでは以下のように設定をして Python上で環境変数を読んでいます。

        env:
          GITHUB_REPOSITORY: ${{ github.repository }}

参考サイト

zenn.dev

終わりに

本来は直接artifactをダウンロードできるURLが欲しかったのですが、以下のようにダウンロードURLは発行できるものの1分しか持たないみたいです。

github.com

そのため、今回は妥協してダウンロードできるページのURLを送信するようにしました。

Unityで3D音源の距離と音量からAudioVisualiserを作成する【Unity】

はじめに

前回以下のような 距離から音量を取得する機能を実装しました。こちらをせっかくなら いい感じの見た目にしようと思い、Audio Visualiser の作成を行いました

成果物

動作環境

  • Unity 2021.3.4f1

実装

各音源から距離と音量を取得・表示する

以前に実装について書いたので以下をご覧ください。

ayousanz.hatenadiary.jp

Visualiser UIの作成

今回のAudio Visualiserは 16等分割した円の画像を使って向きと大きさを表現しています。

まずはこちらのUIを作る作業からやっていきます

(完成UIの拡大版)

(完成したUIのパーツUI : 白で塗りつぶししているため、Web上だと見えないため画像をダウンロードして使用してください)

画像作成アプリは、なんでもいいですが 今回は firealpaca を使っていきます。

放射線上の下書きと円形の下書きを書いて、1/16の円を作っていきます。

次に Unity上で、以下の感じで Image の Fill Amountを変更してUIに変化を持たせたいため、画像を横向きに変更します (画像作成時に横向きにしておくとこの作業がスキップできます)

画像の回転は Windows標準機能のフォトアプリの編集機能が優秀だったため、こちらを使っていきます。 回転する角度は 11.25度(360 / 32) ですが、小数点以下は指定できないみたいなので11度回転させてあげます

できた画像を Unity上にimportして、Texture Typeを Spriteにしておきます。 そのあとに 円の中心に回転したいため Sprite Editorを使って Pivotを円の中心に変更します

シーン上に配置すると、以下のようにImageが円の中心を軸にきれいに回転するようになっています。

Audioデータから Visualiser UIをリアルタイで更新する

今回は Visualiserは以下のように要件定義して実装をしていきます

  1. 聞こえている音とVisualiser上の色を同じ色にする(見やすくするため)
  2. 聞こえている方向を 360度の円形上で表現する
  3. 聞こえている音量を 一つの軸の高さで表現する

上を満たすための AudioData は以下のようにしました。

    public class VisualizerAudioData
    {
        public float AudioValue;
        public Vector2 AudioVector;
        public Color32 AudioColor;
    }

1 はほぼ何もしていないので、詳細は省略します。

2.3については以下のようにしています。

簡単に説明すると Visualiserの画像数(表現できる音の種類 = 16個) の配列に その方向の音量で一番大きいデータを入れる 処理をしています。

まずは Player と Audioの位置から 角度を以下のように計算します

        public static float Vector2ToAngle(Vector2 vector2)
        {
            var angle = Mathf.Atan2(vector2.y, vector2.x) * Mathf.Rad2Deg;
            if (0f <= angle) return angle;
            return 360f + angle;
        }

次に上で計算した角度がどの向きグループに属するのかを計算します

例えば 角度が 0度の場合は 配列[0]に属する、角度が 120度の場合は 120 / (360/16) = 5.33 ≒ 6なので 配列[6] に属する。

        private int VisualizerDataIndex(float angle)
        {
            return Mathf.CeilToInt(angle / (360f / _visualiserData.Length)) - 1;
        }

すでに入っているAudioDataの音量よりも大きいかどうかを比較して、大きい場合のみ更新します。

        public void UpdateVisualizerData(VisualizerAudioData data)
        {
            if (data.AudioValue <= _minValue) return;
            var angle = Vector2ToAngle(data.AudioVector);

            var oldValue = _visualiserData[VisualizerDataIndex(angle)].AudioValue;
            if (oldValue < data.AudioValue)
            {
                _visualiserData[VisualizerDataIndex(angle)] = data;
            }
        }

これで データクラスの更新が終わったので、配列データを View側に 渡して Visualiser UIを更新していきます。

MVP の設計にしているため、 PresenterがModelのデータ配列をもらって View側(MonoBehaviour) を更新します

サンプルプロジェクトのクラス図

まとめ

ModelとViewを分割しているため、3D Visualiserとかにも応用できそう??

HoloLensやOculusQuestとかで周りの環境音を取得して、表示したら面白そうですね!(どこかでやりたい)

Unity WebGLビルド後 特定のブランチ GitHubに自動デプロイする【Unity,WebGL】

はじめに

UnityでWebGL開発を行っているときにビルド後デプロイしたい時があります。OSSとして公開しているプロジェクトの場合、デプロイ先としてGitHub Pageを選んだとしてもアセットをpublicにすることができません。 今回は、ローカルでビルド後 WebGLデプロイ用のブランチに自動で pushする機能を作りました。

成果物

WebGL Deploy URL

ayutaz.github.io

プロジェクトリポジトリ

github.com

サンプルプロジェクトの成果物として、InputSystemをいろいろ使っているプロジェクトです

動作環境

  • Window10
  • Unity 2021.3.3f1
  • Git,GitHubが使えるPC

実装

Build後に特定の処理を行う

ビルド処理が終わった後に処理を挟みたい場合は、IPostprocessBuildWithReport を継承したスクリプトを使用します。

2019.1以前は IPostprocessBuild がありましたが、こちらは現時点では非推奨になっています。 docs.unity3d.com

githubへのcommit/pushは Deploy.bat にまとめています。

外部のファイルを実行する際に process.StartInfo.FileName に実行するファイル名を設定します。
また process.StartInfo.CreateNoWindow を trueにすることで、cmdが立ち上がらずにバックグラウンド処理になります。

public class WebGLDeploy : IPostprocessBuildWithReport
    {
        public int callbackOrder { get; }
        private string _batPath;

        public void OnPostprocessBuild(BuildReport report)
        {
            _batPath = Path.Combine(GetBuildFolderPath(), "Build");
            RunCommand(_batPath);
        }

        private static string GetBuildFolderPath()
        {
            return Directory.GetParent(Application.dataPath)?.FullName;
        }

        private static void RunCommand(string buildFolderPath)
        {
            var process = new Process();
            process.StartInfo.WorkingDirectory = buildFolderPath;
            process.StartInfo.FileName = Path.Combine(buildFolderPath, "Deploy.bat");
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardInput = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.CreateNoWindow = true;
            process.Start();
            process.WaitForExit();
        }
    }

BuildデータをGitHubスクリプト上から Commit/Pushする

事前に Deploy.bat を Buildフォルダに入れておきます。

Batファイルは、以下のように定義しています。

git add *
git commit -m "Deploy"
git push origin webgl_build

Fungusの会話テストができるEditorWindow機能【Unity,Fungus,Editor拡張】

はじめに

私が作っているゲーム (DreamIsland) Unityでの会話機能を作る際に OSSで公開されている Fungusを使用しています。

しかし、どのゲームでもそうかもしれませんが各NPCとの会話はNPCの前まで移動して会話テストする必要があります。このテストがかなり時間がかかっているため、今回は 場所を移動せずとも会話テストができる Editor拡張機能の作成を行いました。

GitHub ActionsでのCI/CD環境で Fungusのテストについては、以下の記事で書いていますので自動テストは以下の記事をご覧ください。

ayousanz.hatenadiary.jp

成果物

  • Editor実行時にシーン上にあるNPC(FungusがアタッチされているNPC)一覧の取得とリスト表示(NPC Numberにソート表示)
  • 会話テストしたいNPCを選択時に選択したNPCのボタンの色を変更
  • EditorWindowからNPC情報の確認と Phaseの変更、会話テスト実行機能

(注) * 内部で GetComponent をしているため、Editor実行したときしか使用することができません。

環境とゲーム内の会話処理周りの情報

環境

  • Unity 2021.3.0f1
  • Fungus v3.13.7
  • UniTask

会話処理周り

Fungusの会話処理は NPCController 内に実装が書かれていて、テスト用の Interfaceをして IDebugNPC を作成しています。

その他のクラス図は以下のようになっています。

機能詳細と実装

今回実装した機能は大きく分けると以下になります。

  1. NPC一覧の取得とソートでの一覧表示
  2. 選択したNPC Buttonのハイライトと情報の表示
  3. fungusのphaseを指定した会話テストの実行

なお、IDebugNPC は以下のように定義しています。

using Cysharp.Threading.Tasks;

namespace _DreamIsland.Card
{
    public interface IDebugNpc
    {
        string GetNpcName();
        string GetNpcObjectName();
        UniTask Talk();
        void SetTalkPhase(int phase);
        int GetTalkPhase();
    }
}

1. NPC一覧の取得とソートでの一覧表示

まず NPCの一覧取得には、FindGameObjectWithTag を使っています。 その後、名前の中から数字情報をとってきてソートをかけています。

        private static int NPCNumber(string npcObjectName)
        {
            if (npcObjectName.Contains("NPC"))
            {
                return Convert.ToInt32(npcObjectName.Substring(2, npcObjectName.IndexOf("NPC", StringComparison.Ordinal) - 2));
            }

            return 100;
        }

        private static List<IDebugNpc> GetNpcList()
        {
            var npcList = GameObject.FindGameObjectsWithTag("NPC");
            var conversationNpcList = new List<IDebugNpc>();
            foreach (var npc in npcList)
            {
                if (!npc.TryGetComponent<IDebugNpc>(out var debugNpc)) continue;
                conversationNpcList.Add(debugNpc);
            }

            conversationNpcList.Sort((a, b) => NPCNumber(a.GetNpcObjectName()) - NPCNumber(b.GetNpcObjectName()));
            return conversationNpcList;
        }

2. 選択したNPC Buttonのハイライトと情報の表示

NPC一覧表示と選択時の色変更は、以下のようにしています。

ボタンの背景は、OnEnable 時に色の取得をして保存しています。

また 内部で GetComponent を使用しているため、エディタ実行中のみ (EditorApplication.isPlaying == true )処理されるようにしています。

                using (new EditorGUILayout.VerticalScope(GUILayout.Width(200f)))
                {
                    _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
                    {
                        foreach (var npc in _npcList.Where(_ => EditorApplication.isPlaying))
                        {
                            var npcObjectName = npc.GetNpcObjectName();
                            GUI.backgroundColor = _selectNpcGameObjectName == npcObjectName ? Color.green : _editorBackgroundColor;

                            if (GUILayout.Button($"{npcObjectName}"))
                            {
                                _selectedNpc = npc;
                                _selectNpcGameObjectName = npc.GetNpcObjectName();
                                _selectNpcName = npc.GetNpcName();
                            }
                        }
                    }
                    EditorGUILayout.EndScrollView();
                }

参考にさせていただいたサイトは以下です。

www.hanachiru-blog.com

zenn.dev

www.codetd.com

3. fungusのphaseを指定した会話テストの実行

  1. SetPhase(int phase) で任意の会話フェーズを指定する
  2. 会話テストを開始(内部で Fungus ≒ Flowchartの SendFungusMessage を呼んでいる)
  3. phaseを初期状態に戻す ( set phaseしたデータが残ってしまうため)

会話テスト開始ボタンを押した後のコードは以下のようになっています。

                        var prePhase = _selectedNpc.GetTalkPhase();
                        _selectedNpc.SetTalkPhase(_selectNpcTalkPhase);
                        await _selectedNpc.Talk();
                        _selectedNpc.SetTalkPhase(prePhase);

3D音源の距離減衰で各サウンド音量の取得【Unity】

成果物

  • サウンド(色がついているオブジェクト)ごとに距離に応じた再生音量を取得

Repository

github.com

Demo(Unity Room)

unityroom.com

実装

AudioSouceとListenerの挙動

Unityで3D音源を鳴らすときには、基本的に AudioSourceのAudioClipに任意のサウンドデータをアタッチしてサウンドを鳴らすようになっています。

AudioSourceを設定した後に AudioListenerがアタッチされているオブジェクトを移動させると以下のようにAudio SourceのInspctorで現在のListenerと3D Sound設定した関数の交わっている個所を見ることができます。

Audio Sourceの距離減衰のAnimationCurveの取得

設定したAnimationCurveを取得するには、AudioSource.GetCustomCurve から取得します。

docs.unity3d.com

しかし、LogarithmicやLinearの場合は、こちらの関数を使うことができません(仕様?)。そのため、今回は以下のようにして Linerと Customを判別して AnimationCurveを返す関数を作成しました。

        private AnimationCurve GetAnimationCurve()
        {
            return animationCurve = _audioSource.rolloffMode switch
            {
                AudioRolloffMode.Linear => AnimationCurve.Linear(_audioSource.minDistance, 1, 1, 0),
                AudioRolloffMode.Custom => _audioSource.GetCustomCurve(AudioSourceCurveType.CustomRolloff),
                _ => animationCurve
            };
        }

ここの注意なのですが、Customで返ってくるAnimatinCurveは0-1のサイズに取得称されるため、計算する際に0-1の範囲で計算する必要があります。

プレイヤーと発生しているサウンドの距離からなっている音量を取得する

先ほど取得した Audio SourceのAnimation Curveから任意の点の音量値を取得するには、AnimationCurve.Evaluate を使います。

docs.unity3d.com

この関数は、引数に time(今回の場合は、なっているAudioSource Object とプレイヤーの距離) が必要になるため Vector3.Distance(AudioSource.position,Player.position) で 距離を計算します。

その後、取得した Animation Curveが0-1に丸め込みされるため 距離 / MaxDistanceで 0-1の範囲に収まるように変換します。

        public float GetAudiValue(Vector3 playerPosition)
        {
            var distance = Vector3.Distance(transform.position, playerPosition);
            return animationCurve.Evaluate(distance / _maxDistance);
        }

このときの MaxDistance は 、Audio Sourceの Max Distanceを _audioSource.maxDistance から取得してきた値になります。

docs.unity3d.com

後は 適当にViewスクリプトを作って取得した各サウンドの音量値を表示させてあげれば、以下のようにできます。