このBeatSaberプラグインは、アバターやセイバー等のオブジェクトの動きを曲の時間に合わせて記録するツールです。
ChroMapper-CameraMovementで記録したファイルを読み込んで再生することができます。
以下のmodの記録はデフォルトで設定してあります。
- アバター
- セイバー
- Saber Factory
- CustomSabersLite ※バージョンによって
CustomSaber
の設定で上手くいく場合があります - CustomSaber
- NalulunaSaber
※設定ファイルを作成すれば、任意のオブジェクトも記録可能です。
- リリースページから最新のMovementRecorderのリリースをダウンロードします。
- ダウンロードしたzipファイルを
Beat Saber
フォルダに解凍して、Plugin
フォルダにMovementRecorder.dll
ファイルをコピーします。 - 依存modは
SiraUtil
,BSML
,SongCore
の3つです。基本modなので既に入っているはずです。
左のMODSタブにMOVEMENT RECORDER
が追加されます。
Movement Recorder Enabled
: 本modの機能を有効にします。WIP Map Only
: WIP譜面でのみ記録します。Avatar Movement
: アバター表示で使用しているmodを選択します。Saber Movement
: セイバー表示で使用しているmodを選択します。Other Movement1~3
: その他のオブジェクトを記録します。(要設定作成)Record Frame Rate(fps)
: 動きを記録する間隔を設定します。Movement Research
: 動きのあるオブジェクトを調査します。Research Check Song Sec(s)
: 調査する曲の経過時間を選択します。Not Dispose Memory
: 同一条件での記録時に記録メモリを開放せずに使いまわします。Min Memory Allocation Time (min)
:Not Dispose Memory
有効時の記録メモリの最低記録確保時間です。===Movement Recorder Log===
: 記録した結果のログを表示します。Initialize Process Time
: ゲーム開始時の初期化処理時間です。Capture Object Count
: 記録するオブジェクトの数です。Record Array Size
: 記録用に確保した配列のサイズです。Allocated Memory Size
: 記録用に確保した配列のメモリ消費用です。Record Count
: 記録したフレーム数です。Last Song Time
: 最後の記録フレームの曲時間です。Save Process Time
: 保存処理時間です。Save File Size
: 保存したファイルのサイズです。One Movement Recording Time
: 1フレームの記録処理時間の平均,最大,最小です。
※!Warning! Array size extended during play due to unexpected circumstances:* Count
と表示される場合は、プレイ中に記録用配列が不足して拡張処理を行っています。通常は曲の長さで予め確保するため起きないはずですが、もし発生する場合は連絡を下さい。
-
AvatarとSaberの設定を使用しているmodに合わせて選択します
-
主にカメラスクリプト作成を想定しているので、WIPに対象譜面を入れて記録機能を有効にしてプレイします。
-
WIP譜面の場合は譜面フォルダに
MovementRecorder
フォルダが作成されて、そこに記録ファイルが保存されます。通常譜面は
UserData/MovementRecorder
に保存されます。 -
CameraMovementで読み込んで再生します。※使用したアバターやセイバーは記録時と同じファイルを読み込んで下さい。
動きデータはプレイ中は予め確保したメモリ(配列)に保存だけして極力プレイに負荷がかからないようになっていますが、プレイ終了後にバックグラウンドで保存するため負荷が高くなります。次のプレイ開始までに保存が完了しないと開始できないため、開始時に画面が暗転した状態で固まる場合があります。特にゲーム中に中断してリプレイした場合は保存する時間が無いため顕著に固まりますので注意して下さい。
記録ファイルはかなりサイズが大きくなります。アバターやセイバーの構成しているオブジェクトの数や、記録時間、fpsによりますが数十MBから100MB以上になることもあります。なので普段の記録に使用する場合は容量に注意してください。
fpsを落としてもCameraMovementで再生時に中間を補間して表示しますので、表示はなめらか※になります。用途に合わせて設定して下さい。
※線形補間しているためセイバーなど動きの激しい部分は、等速直線運動以外(円運動・斬り返しなど)に一部破綻が出てきます)
デフォルト設定ではアバターやセイバーを構成するオブジェクトの全てを記録しています。これはモデル毎にオブジェクト構成が違うため汎用的にするための措置です。
実際に記録が必要なオブジェクトはもっと少なくできます。特に揺れものはCameraMovementで再生時にVRMならSpring Boneで、CustomAvatarならDynamicBone※で揺らせますので、記録から除外することが可能です。そのためには、モデルごとにオブジェクトの構成を調査して設定ファイルの記録対象や除外対象を作成する必要があります。
※DynamicBoneをChroMapperで動作させるには、BeatSaberのゲームフォルダからDynamicBone.dll
をコピーしてPluginフォルダに入れて下さい。
記録オブジェクト数(Capture Object Count) × 記録秒数(s) × fps × 7(座標3,回転4) × 4(Byte:float) ≒ 必要メモリ数[Byte]
上記が記録に必要なメモリサイズで、ゲームプレイの最初にメモリが確保されます。90秒の曲でオブジェクト数180で30fpsだと13MByteぐらい(他にヘッダ情報で+数10kByte必要)です。記録ファイルも同じサイズが必要です。
Movement Research
を有効にして、動き調査ファイルMotion_Research_Data.json
を作成します。- 調査結果を見ながら不要なオブジェクトを探します。
- 使用する設定ファイルの
searchSettings
の項目をコピーして、exclusionStrings
項目を追加して調査結果で不要なオブジェクトを追加していきます。設定は正規表現になるので、()などのメタ文字はエスケープが必要です。正規表現がわかる人は、子オブジェクト全てなどは/.+などを指定して記載を省略できます。 - 作成した設定に変更して記録します。
UserData/MovementRecorder.json
にmodの設定ファイルが保存されます。
設定ファイルのうちsearchSettings
を独自に作成することで、任意のオブジェクトを記録することができます。
"searchSettings": [
{
"name": "CustomAvatar",
"type": "Avatar",
"topObjectStrings": [
"^.+/Avatar Container/SpawnedAvatar[^/]+/"
],
"rescaleString": "^.+/Avatar Container/SpawnedAvatar[^/]+$",
"searchStirngs": [
"^.+/Avatar Container/SpawnedAvatar[^/]+(/.+)?"
],
"exclusionStrings": [
"^.+/Avatar Container/SpawnedAvatar[^/]+/Head(/.+)?",
"^.+/Avatar Container/SpawnedAvatar[^/]+/LeftHand(/.+)?",
"^.+/Avatar Container/SpawnedAvatar[^/]+/LeftLeg(/.+)?",
"^.+/Avatar Container/SpawnedAvatar[^/]+/Pelvis(/.+)?",
"^.+/Avatar Container/SpawnedAvatar[^/]+/RightHand(/.+)?",
"^.+/Avatar Container/SpawnedAvatar[^/]+/RightLeg(/.+)?",
"^.+/Avatar Container/SpawnedAvatar[^/]+/Body(/.+)?"
]
}
]
name
: 設定の名前です。メニューで選択するときに表示されますtype
: 設定の種類です。Avatar
,Saber
,Other
から選択します。topObjectStrings
: オブジェクトのパスのうち不要な先頭部分を正規表現で設定します。読み込み時に、この設定に一致した部分を削除します。rescaleString
: オブジェクトのスケールを設定しているパスを正規表現で指定します。searchStirngs
: 記録するオブジェクトのパスを正規表現で指定します。exclusionStrings
: 除外するオブジェクトのパスを正規表現で指定します。
topObjectStrings
とrescaleString
は記録データの読み取り用の設定で、本modの動作や記録対象には一切影響を与えません。
記録対象はsearchStirngs
に一致して、exclusionStrings
に一致しないオブジェクトをsearchStirngs
の設定毎に記録します。そのため、searchStirngs
で重複するオブジェクトがある場合は重複して記録されますので注意して下さい。
オブジェクト検索は曲がスタートするタイミング(曲時間0秒)時点で存在するオブジェクトから検索します。名前で検索するため、フルパスで全く同一のオブジェクトは区別できないため正しく記録できません。
Movement Research
を有効時にプレイするとMovementRecorder
フォルダにMotion_Research_Data.json
が保存されます。
これは、曲開始時間から指定した経過時間(デフォルトは1秒後)で座標や角度が変化したオブジェクトの一覧が記録されます。
内容は現在設定での記録対象、ワールド座標・角度で変化のあったもの、ローカル座標・角度で変化のあったのも、スケールが1倍以外のオブジェクト一覧です。
記録対象の設定ファイルを作成するときに使えると思います。
{
"recordObjectNames": [現在の設定で記録対象のオブジェクトのリスト],
"motionLocalEnabled": [ローカル座標で変化のあったオブジェクトのリスト],
"motionWorldEnabled": [ワールド座標で変化のあったオブジェクトのリスト],
"otherOneScales": [スケールが1倍以外のオブジェクトのリスト
{
"objectName": "オブジェクト名",
"scale": "xスケール yスケール zスケール"
}
]
}
mvrec データ構造 ※(string)(float)の部分がデータ
meta_json(string)
meta_json.recordCount.loop {
SongTime(float)
meta_json.objectCount.loop {
position.x(float)
position.y(float)
position.z(float)
rotation.x(float)
rotation.y(float)
rotation.z(float)
rotation.w(float)
}
}
- meta_json ・・・ 記録データのJSON形式のメタ情報(下記参照)
- SongTIme ・・・ フレーム記録した曲時間
- position ・・・ 対象オブジェクトの座標(Vector3)
- rotation ・・・ 対象オブジェクトの回転(Quaternion)
- meta_json.objectCountの並びはmeta_json.objectNamesと一致
- (string) ・・・ 可変長 BinaryWriter.Write(String)
- (float) ・・・ 4Byte BinaryWriter.Write(Single)
- loop ・・・ カウント数でループする
meta_json構造
{
"objectCount":記録オブジェクト数
"recordCount":記録レコード数
"levelID":"譜面のlevelID"
"songName":"譜面の曲名"
"serializedName":"譜面のモード"
"difficulty":"譜面の難易度"
"Settings":[記録対象の設定リスト
{
"name":"設定名"
"type":"設定タイプ"
"topObjectStrings":[オブジェクトのパスの不要な先頭部分のリスト]
"rescaleString":"オブジェクトのスケールを設定しているパス"
"searchStirngs":[記録するオブジェクトのパスのリスト]
"exclusionStrings":[除外するオブジェクトのパスのリスト]
}
]
"recordFrameRate":記録フレームレート
"objectNames":[記録対象オブジェクトのパスのリスト]
"objectScales":[記録対象オブジェクトのスケールのリスト]
"recordNullObjects":[記録対象オブジェクトが途中でNULLになったリスト
{
"songTime":曲時間
"objIndex":対象オブジェクトのインデックス
}
]
}
meta_jsonはC#の場合は、BinaryReader.ReadString()で読み取れますので、JSONパースしてrecordCountとobjectCountを取り出します。 recordCountとobjectCountでループを回しながらBinaryReader.ReadSingle()でSongTime,position,rotationを読み取って下さい。
C#以外の場合は、meta_jsonは先頭に長さを示す可変長のプレフィックスがありますので、まずはそれを読み取ります。 プレフィックスのバイト数分UTF-8で読み取り、JSONパースしてreadCountとobjectCountで回しながら、4バイトずつ読み取ってfloat値として下さい。 バイトオーダーはリトルエンディアンになります。C#以外は結構面倒なので、読み取り部だけでもC#で書いた方が楽です。
https://learn.microsoft.com/ja-jp/dotnet/api/system.io.binarywriter.write7bitencodedint?view=net-7.0
- 1バイトの先頭の1ビットが後続データがあるかどうかを示しています。残りの7ビットが長さを示すint値です。
- 長さが1(0x01)~127(0x7f)Byte以内はプレフィックスは1バイトのみです。
- 長さが128(0x80)Byte以上はプレフィックスは2バイト以上になります。
(例) 長さ 447Byteの場合のプレフィックスは、1バイト目0xBF 2バイト目0x03です。
- 447 → 0x1bf → 0b110111111
- 0xBF 0b10111111 -> 0111111 ※先頭1ビット目が1なのでプレフィックスは後続がある
- 0x03 0b00000011 -> 0000011 ※先頭1ビット目が0なんでプレフィックスはここまで
リトルエンディアンなので
- 0000011+0111111 = 0b110111111 → 447