後編です!
前編の記事を読んでいない方は、まず下記を読まれることをおすすめします。
fanbox post: creator/53638632/post/2493113
なお、この記事のコードでは、極力説明を省略するため、効率的でなかったり、あまり褒められた書き方ではなかったりする部分が見受けられると思いますが、大目に見ていただけるとありがたいです。
前回までのサンプルコードはこちらです。今回はこの途中まで作ったサンプルコードを利用する手順から説明していきます。
ダウンロードしたZIPファイルを適当なところに展開し、NotesDecal.slnをダブルクリックして開いてください。
このようなメッセージが表示される場合がありますが、OKを押して大丈夫です。
プロジェクトを新規作成したときと同じように、ソリューションの下のプロジェクト名を右クリックして、「Beat Saber Modding Tools」→「Set Beat Saber Directory...」と選択します。
Visual Studio 2019を一旦終了させて、再度起動し、最近開いた項目(R)のリストからNotesDecal.slnをクリックして開きます。
右のファイル一覧から「NotesDecalController.cs」をダブルクリックして開き、左のエディタ内の「Monobehaviour Messages」という行の左にある[+]をクリックします。これで前回終了時の状態になります。
このプロジェクトファイルには画像ファイルが入っていないので、前回、画像ファイルを導入していない方は、下記のZIPファイルをダウンロードして、中身の画像ファイルをビートセイバーのフォルダにコピーしてください。
ビートセイバーのフォルダは、Steam版のデフォルトインストール先の場合は
C:\Program Files (x86)\Steam\steamapps\common\Beat Saber
となりますが、環境により異なります。
ここまでできたら、Visual Studioの画面上のメニューから「ビルド」→「ソリューションのビルド」を選択してビルドしてみましょう。ビルドが成功すると自動的にビートセイバーのPluginsフォルダにmodのdllがコピーされます。
ビートセイバーを実行すると、このように前回の内容が入ったmodが動きます。
前回は、ノーツに見立てたキューブを作成して、そこに画像を張り付ける処理を書きましたが、今回はこの部分は不要なので削除してしまいます。
private void Start() の { } 内で
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
の行から
newImage.SetActive(true);
の行までを削除してください。下の画面のようになります。
ここまでは、メニュー画面中かゲームプレイ中か、に関係なく処理を書いていましたが、今回はノーツに画像を張り付けたいので、まず「ゲーム(曲)が始まったとき」の状況で処理を行えるようにする必要があります。末尾 (image.SetActive(false)) の下に、以下を入力(コピー&ペースト)してください。
----
SceneManager.activeSceneChanged += (Scene _, Scene next) =>
{
if (next.name == "GameCore")
{
Plugin.Log.Debug("GameCore Scene Started");
}
};
----
SceneManagerにエラーを表す赤波線がつくので、SceneManagerを右クリックして「クイック アクションとリファクタリング...」を選択、次に表示されるメニューで「using UnityEngine.SceneManagement;」を選択すると、エラーが消えます。(Sceneの赤波線も一緒に消えます)
ここでは、ゲームシーンに移行(曲が開始)したときに、「GameCore Scene Started」というログを追記するよう記述しています。このログはビートセイバーのフォルダの中にある以下のフォルダに格納されています。
C:\Program Files (x86)\Steam\steamapps\common\Beat Saber\Logs\NotesDecal
(Steam版デフォルトインストール先の場合)
このフォルダの中の、_latest.logがビートセイバー前回起動時のログになります。ビルドを行って、ビートセイバーを起動し、ゲームプレイ(曲)を3回、開始終了を繰り返してから、この_latest.logを開くと、次のようになっています。
----
[INFO @ 06:17:58 | NotesDecal] NotesDecal initialized.
[DEBUG @ 06:18:01 | NotesDecal] OnApplicationStart
[DEBUG @ 06:18:01 | NotesDecal] NotesDecalController: Awake()
[DEBUG @ 06:18:20 | NotesDecal] GameCore Scene Started
[DEBUG @ 06:18:27 | NotesDecal] GameCore Scene Started
[DEBUG @ 06:18:31 | NotesDecal] GameCore Scene Started
[DEBUG @ 06:18:39 | NotesDecal] OnApplicationQuit
[DEBUG @ 06:18:40 | NotesDecal] NotesDecalController: OnDestroy()
----
中央に「GameCore Scene Started」が3回出力されているのがわかると思います。ちゃんとゲーム開始時の動作が動いているようですね。(他のログは、このプロジェクトのテンプレートによって出力されたものです)
このように、ログをうまく使用すると、プログラムが正常に動作しているのか、動作しなかったときは何が起こっているのか、を把握することができます。modの作者が、不具合の報告と一緒にログファイルを送って、と言うのは、この情報が欲しいからなのです。
ちなみにmod作者がログファイルを求めるときは、mod単位のログではなく全体のログが必要なので、
C:\Program Files (x86)\Steam\steamapps\common\Beat Saber\Logs\_latest.log
(Steam版デフォルトインストール先の場合)
を送ってあげてください。
次は「ノーツが出現したとき」の状況で処理を行えるようにします。ノーツが出現したときに画像を張り付ける処理を書けば、望みの動作が実現できそうですね。ただ、一気にたくさん処理を書くと、動かなかったときにどこが間違っていたのか見つけるのに苦労するので、ひとつひとつ順に作って、動作を確認していきましょう。
さきほど書いた Plugin.Log.Debug("GameCore Scene Started"); のすぐ下、} の手前に挿入する形で以下を入力してください。
----
StartCoroutine(GameCoreCoroutine());
IEnumerator GameCoreCoroutine()
{
while (true)
{
PauseController pauseController = Resources.FindObjectsOfTypeAll().FirstOrDefault(x => x.isActiveAndEnabled);
BeatmapObjectManager beatmapObjectManager = pauseController?.GetField("_beatmapObjectManager");
if (beatmapObjectManager != null)
{
beatmapObjectManager.noteWasSpawnedEvent += (NoteController noteController) =>
{
Plugin.Log.Debug("Note Spawned");
};
break;
}
yield return null;
}
}
----
入力する場所は間違っていませんか?このようになるはずです。
GetField...にエラーを表す赤波線がつくので、右クリックして「クイック アクションとリファクタリング...」を選択、次に表示されるメニューで「using IPA.Utilities;」を選択すると、エラーが消えます。
この部分は、詳細を理解する必要ありませんが、いちおう簡単に説明しておきます。「ノーツが出現したとき」に反応させるためにはBeatmapObjectManagerが必要になるのですが、これはゲーム(曲)を開始した瞬間には存在しないので処理待ちを行っています。またBeatmapObjectManagerは直接取ってくることができないため、PauseControllerを介して取得しています。
このあたり、Zenjectというものを使用するともうちょっとスマートに書くことができますが、プロジェクトの根幹から変えないといけないので、今回は触れません。
ここまでできたら再びビルドを行って、ビートセイバーを起動します。ゲーム(曲)を開始していくつかノーツを出現させてから終了し、さきほどのログファイルを確認してみましょう。
----
[INFO @ 06:59:58 | NotesDecal] NotesDecal initialized.
[DEBUG @ 07:00:01 | NotesDecal] OnApplicationStart
[DEBUG @ 07:00:01 | NotesDecal] NotesDecalController: Awake()
[DEBUG @ 07:00:25 | NotesDecal] GameCore Scene Started
[DEBUG @ 07:00:27 | NotesDecal] Note Spawned
[DEBUG @ 07:00:28 | NotesDecal] Note Spawned
[DEBUG @ 07:00:29 | NotesDecal] Note Spawned
[DEBUG @ 07:00:30 | NotesDecal] Note Spawned
[DEBUG @ 07:00:31 | NotesDecal] Note Spawned
[DEBUG @ 07:00:41 | NotesDecal] OnApplicationQuit
[DEBUG @ 07:00:43 | NotesDecal] NotesDecalController: OnDestroy()
----
このように、ノーツが出現した数だけ「Note Spawned」の行が出力されるようになっています。ちゃんと動いているようですね。
お待たせしました。やっと本丸に到達です。出現したノーツに画像を張り付ける処理を書きましょう。
さきほど書いたコード中から、
Plugin.Log.Debug("Note Spawned");
を置き換える形で、以下を入力してください。
----
if (noteController.noteTransform.transform.Find("NotesDecal") == null)
{
noteController.gameObject.AddComponent();
noteController.gameObject.AddComponent().SetRadius(0);
GameObject newImage = Instantiate(image);
newImage.name = "NotesDecal";
newImage.transform.SetParent(noteController.noteTransform);
newImage.transform.localPosition = new Vector3(0, 0, -0.27f);
newImage.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
newImage.GetComponent().material.renderQueue = 3100;
newImage.SetActive(true);
}
----
さきほどログで確認したように、この部分は新しいノーツが出現するたびに呼び出されます。出現したノーツはnoteControllerからアクセスできるようになっています。
if (noteController.noteTransform.transform.Find("NotesDecal") == null)
この部分は、既に画像が張り付けられているかどうかをチェックし、まだ画像がない場合のみ処理するようにしています。ノーツは再利用されるので、新しく出現したノーツでも、既に画像が追加されている場合があり、このチェックが必要です。
その次のCanvas関連は、画像を表示するための準備。
GameObject newImage = Instantiate(image);
ここからは、メニュー画面で作成しておいた画像から複製を作成して、新しいノーツに追加し、位置やサイズを設定しています。前回、予行演習でやった内容ですね。
newImage.GetComponent().material.renderQueue = 3100;
これは、ノーツのBloom(発光)が画像を透過してしまうので、それを避けるために描画順を操作しています。
ここまでで、private void Start() の { } の中は、下のようになっていると思います。
これをビルドして実行すると…おつかれさまです。完成です!
余裕があればカスタマイズも行ってみましょう。別の画像を使ってみます。ビートセイバーフォルダのNotesDecal.pngを、下のZIPファイルに入っている絵文字画像に差し替えてみてください。
この絵文字画像を使う場合、ボムに画像が付くとわかりにくくなるのと、ドット(矢印なし)ノーツの見分けがつかなくなるので、これらでは画像を非表示にするようにしたいと思います。
上記の「画像を張り付ける」で書いた部分
if (noteController.noteTransform.transform.Find("NotesDecal") == null) の行から
} の行までを、まるごと以下に差し替えてください。
----
GameObject noteImage = noteController.noteTransform.transform.Find("NotesDecal")?.gameObject;
if (noteImage == null)
{
noteController.gameObject.AddComponent();
noteController.gameObject.AddComponent().SetRadius(0);
noteImage = Instantiate(image);
noteImage.name = "NotesDecal";
noteImage.transform.SetParent(noteController.noteTransform);
noteImage.transform.localPosition = new Vector3(0, 0, -0.27f);
noteImage.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
noteImage.GetComponent().material.renderQueue = 3100;
}
if (noteController.noteData.colorType == ColorType.None ||
noteController.noteData.cutDirection == NoteCutDirection.Any)
{
noteImage.SetActive(false);
}
else
{
noteImage.SetActive(true);
}
----
noteDataにエラーを表す赤波線がつくので、右クリックして「クイック アクションとリファクタリング...」を選択、次に表示されるメニューで「参照を 'Beatmap Core, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' に追加します。」を選択すると、エラーが消えます。
(もし上記で消えない場合は、前回やったように、右のファイル一覧の上にある「参照」を右クリックして「参照の追加(R)...」を選択し、
C:\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\BeatmapCore.dll
を手動で追加してください)
これで、ボムやドットノーツには画像が付かなくなります。また、localScaleを変更すると画像のサイズを、localPositionを変更すると画像の位置を変更することができます。使用する画像やノーツに合わせて調整してみてもいいでしょう。
ビートセイバーフォルダに置いた画像ファイルNotesDecal.pngを別名に変更するなどして、読み込まれないようにすると、このmodも、メニュー画面での画像読み込みの部分で止まるので、動作しなくなります。
設定画面が欲しい?やっぱりそう思いますよね。ただ、設定画面の作り方は、長くなる割にあまりおもしろい内容でもないので、説明は割愛させてください。代わりに下記のサンプルコードで、設定画面まで実装しておきました。
このままで問題なく動くのですが、もしこのmodを配布したい場合は、modに作者名やバージョンなどの情報を入れておきましょう。
右のファイル一覧から manifest.json をダブルクリックして開きます。
基本的に : の右の "" の中に入力していきます。例えば、authorの欄が作者名なので、"author": "Nalulululuna", のように入力します。
author: 作者名
version: modのバージョン
description: modの説明
gameVersion: どのビートセイバーバージョンを対象としているか (一致していなくても、ログに警告が出るだけで、ここだけで動かなくなることはありません)
dependsOn: 他に必要とする依存mod
features: これは現在使われていないので削除してかまいません。削除する場合、上の行末尾の , も一緒に削除してください
こちらのサンプルコードは自由に改変してかまいませんし、もちろん完成したmodも自由に配布可能です。
サンプルコード (プロジェクト)
NotesDecal.dll
https://drive.google.com/file/d/1_RPbk-mvCQZVztMlGfDbAevoAS2KVAfF
NotesDecal.png
https://drive.google.com/file/d/1EXfrPlPDpLvTFiOJH1xy-d1CY29CwvTs
今回、プログラムのコードにして、実質たったの50行程度しか書いていません。それでも、このような効果を実現できるのはおもしろいと思いませんか?アイデア次第で目を引くような演出ができるかもしれません。なにか楽しいものができたら、教えていただけるとうれしいです!
JAN
2021-07-18 11:34:37 +0000 UTCなるるるるな / NALULUNA
2021-07-18 09:26:49 +0000 UTCJAN
2021-07-18 09:16:38 +0000 UTCなるるるるな / NALULUNA
2021-07-18 07:26:38 +0000 UTCJAN
2021-07-18 06:53:55 +0000 UTC