Among UsのMod開発で対NPCモードを作ってみる 再
#amongus #mod #開発 #bepinex
元々やろうとしていたのはこちら
Among UsのMod開発で対NPCモードを作ってみる (停止)
通信とか無視で値を書き換えることだけに注力していたせいで、
NPCの移動周りで困ってしまった。
なので、通信を理解しながら一つずつ進めることにする。
NPCの人数設定
NPCをロビーにスポーンさせる
他のプレイヤーにも表示される
あとから参加したプレイヤーにも表示される
会議で投票するようにする
とりあえずランダムで投票
タスクに向かって移動するようにする
壁があったら移動できないようにする
扉が閉まっていたら移動できないようにする
簡単な強化学習でも組んで、タスクに向かって障害物を避けながら進めるようにする
タスクの優先度、緊急タスクの優先度を管理できるようにする
生きているか、幽霊かで壁を超えられるかを判定する
幽霊の場合はタスクまでの最短距離を進めばいいので、複雑な移動経路の計算は不要
NPCがタスクを完了できるようにする
実際にタスクはできないので、一定時間タスク可能範囲で停止していたらタスクを完了したことにするのが現実的か
インポスターはキルするするようにする
クールタイム、距離を加味してキルするようにする
NPCが独自に行動経路などを保持し、他プレイヤーのグレースケールを保持するようにする
会議でNPCの持っているグレースケールの平均を表示するようにする
見分けがつきやすいように、ランダムでハットやスキンを装備するようにする
その先未定
Among UsのMod開発のために通信を確認する と並行して進める。
NPCの人数設定
まずNPCの人数設定やけど、カスタマイズからできるようにしたい。
https://scrapbox.io/files/61b02766b84d03001fe3e3fe.jpg
カスタム設定の末尾に項目を追加するなり、別のページを作るなりを考えて、
ここでの設定が他プレイヤーにも通知されることを目指す。
ただし、この設定が他プレイヤーに通知されることはあまり重要じゃなくて、
通信に乗せることでNPCをスポーンさせる際の通信の下準備とか、
サーバサイドでの処理とかのトリガーにできるんじゃないかって考え。
まずはカスタム設定の各項目がどこにあるかを掴む。
GameSettingMenuがそれっぽい。
AllItemsの中身が並んでる感じかな。
ControllerSelectable にあるのが各ボタンの操作による設定変更か。
上部のタブの切り替えでは、RegularGameSettings と RoleSettings の表示非表示を切り替えていそう。
GameSettingMenu.ToggleRoles() で RoleSettingsの表示非表示を切り替えてる感じ。
なので、上部のタブを選択したら切り替わるっぽくみえるけど、実際は上部をクリックしたら反応という感じ。
ひとまずぱっと動きを見た感じはこんな感じか。
やることは、
RegularGameSettingsに行を追加する
プラス・マイナスのボタンを設置し、数値が変わるようにする
設定がサーバに送信されるようにする
こんなかんじ。
もし難しければ、
タブを追加する
タブを切り替えられるようにする
タブに行を追加する
プラス・マイナスのボタンを設置し、数値が変わるようにする
設定がサーバに送信されるようにする
かな。
前者はRegularGameSettingsに手を入れるので、他への影響を懸念する必要がある。
設定データの送信も同時に行うことを目指すと思うから、ややこしそう。
後者は独自の通信を新たに追加する必要があるけど、他のデザインくずれとか通信破壊は懸念しなくていい。
UIに関係するので、これまでみたいに値変えておけばいいやーってもんじゃなく、
どのグループの配下のオブジェクトなのかーってのもやってあげなくちゃいけなさそう。
Game Settings > GameGroup > SliderInner の子要素にしてあげないといけないっぽい。
じゃないと、相対距離がおかしくなって、ちゃんと並ばなさそう。
あと、Scroller でスクロール可能な高さを変えてあげないと、追加した要素が見えない。
ってことで、この辺を対応したらこんな感じ。
https://scrapbox.io/files/61b07ed38abaf10023319133.jpg
一番下にNPCの人数を設定する項目が追加された。
ショートタスクを複製したから、プラス・マイナスのボタンもあって、
押すことで数値が更新される。
次は、プラス・マイナスのボタンに追加の処理を入れないといけない。
マイナスでNPCをデスポーンして、プラスでNPCをスポーンさせる。
とりあえず、プラスとマイナスにイベントを追加することを考える。
といっても、PassiveButtonにOnClickがあるから、ここにListenerを追加すればOKそう。
こんな感じ。
code:.cs
var plus = newItem.FindChild("Plus_TMP")?.GetComponent<PassiveButton>();
if (plus != null)
{
plus.OnClick.AddListener((Action)(() =>
{
Plugin.Logger.LogInfo("clicked plus button");
}));
}
これで最低限は出来たかな?
と思ったけど、推奨設定をチェックすると、コピー元のショートタスクと同じ表示になってしまった。
どっかにデフォルト値をもってるのかも。探す。
StringNames 型の OptionBehavior.Title を変更した状態で推奨設定に戻したら名前が変わった。
独自のStringNamesの代わりになるのを作ってそれを持たせておいて、
名前に変換するところに処理噛ませて、独自のStringNamesの代わりなのが届いたら独自変換した結果を返し、
それ以外なら通常処理を通す的なのを作る必要がある。
これさえやれば、推奨に戻されても問題ない!!
OnValueChanged って値かわっても動かないのね。
プラス・マイナスボタンのOnClickイベントで明示的に叩くようにした。
NPCをロビーにスポーンさせる
カスタム設定のNPC人数が変えられたことをトリガーに、NPCを追加、削除する。
ホストにだけ見える状態でNPCを追加、削除する
NPCの人数がマイナスや14人より多いといったことがないようにする
通信を正しく行うようにし、ホスト以外のプレイヤーにもNPCが見えるようにする
ホストにだけ見える状態でのNPC追加は以前にやったので、先に通信がどうなのかをみる。
InnerNetServer と、 InnerNetClient を見る感じ。
2人目のJoin時の流れ
InnerNetServer.JoinGame
InnerNetServer.HandleNewGameJoin
InnerNetServer.WriteJoinedMessage
InnerNetServer.Broadcast
InnerNetClient.GetOrCreateClient
InnerNetClient.GetClient
以下まだまだ続く
上では端折ったけど、頻繁に叩かれてるのが
InnerNetClient.OnDataReceived
InnerNetClient.HandleMessage
InnerNetClient.Broadcast
この辺りはメッセージを変換したり、メッセージから呼び出す処理を割り振ったりするところやと思う。
とりあえずサーバー側が接続を検知して、色々動き出す。
もちろん動き出すにはクライアントからのリクエストが必要。
ってことで、クライアントの動きをみる。
InnerNetClient.Connect
InnerNetClient.WaitForConnectionOrFail
InnerNetClient.JoinGame
InnerNetClient.WaitWithTimeout
InnerNetClient.GetOrCreateClient
InnerNetClient.GetClient
InnerNetClient.SetActivePodType
InnerNetClient.FindClientById
かな。
NPC分のConnectを偽装すれば、サーバは複数プレイヤーがほんとに存在すると信じるんじゃないかな。
InnerNetClientをNPC分複製して、そこからConnectを叩いて接続しに行くって感じがイメージできる。
ってことで、InnerNetClient自体を複製するか、
通信のところだけを新たに生成してConnectからJoinGameあたりを叩くことを目標にやってみる。
InnerNetClientを継承してるのはAmongUsClient。
AmongUsClientはNetworkManagerの中にあるComponent。
とりあえずAmongUsClientを複製して、Connectしようとすると、
新しいConnectionが出来たといってホスト側も切断されてしまう。
内部的に同じConnectionを使おうとしてそう。そりゃそうよな、複数作る必要ないし。
Connectionを管理してるものを見つけ出して複製したいけど、staticよなー。
ってことで、丁寧にAmongUsの中にあるHazelを使ってConnectionを作ってみるか。
code:connection
var endpoint = new IPEndPoint(IPAddress.Parse(myClient.networkAddress), myClient.networkPort);
_connection = new UnityUdpClientConnection(endpoint);
_connection.DataReceived = (Il2CppSystem.Action<DataReceivedEventArgs>)OnDataReceive;
_connection.Disconnected = (Il2CppSystem.EventHandler<DisconnectedEventArgs>)OnDisconnect;
_connection.ConnectAsync();
code:error
Error :Unhollower Exception in IL2CPP-to-Managed trampoline, not passing it to il2cpp: System.ArgumentException: Delegate has parameter of type Hazel.DataReceivedEventArgs (non-blittable struct) which is not supported
at UnhollowerRuntimeLib.DelegateSupport.ConvertDelegateTIl2Cpp (System.Delegate delegate) 0x000ae in <139c2f4042e14f759ab7fb771030183f>:0
at Il2CppSystem.Action1[T].op_Implicit (System.Action1T ) 0x00000 in <5a8d6b3fcc1e432f878b18ceba43f31a>:0
at NpcMode.Npc..ctor () 0x0003e in <9579c73dac4a4c70b132b2403d10e09c>:0
at NpcMode.NumNpcSetting.OnValueChanged (OptionBehaviour op) 0x00018 in <9579c73dac4a4c70b132b2403d10e09c>:0
at NpcMode.NumNpcSetting+<>c.<CreateNumNpcSetting>b__4_0 () 0x00001 in <9579c73dac4a4c70b132b2403d10e09c>:0
at (wrapper dynamic-method) UnhollowerRuntimeLib.DelegateSupport.(il2cpp delegate trampoline) System.Void(intptr,UnhollowerBaseLib.Runtime.Il2CppMethodInfo*)
OnDataReceive がだめっぽい。IDEではエラーが出ないから型間違いはないと思うけど。
Hazel.DataReceivedEventArgs がcastできないのが問題っぽい。
unhollowerをみると、
For IL2CPP delegate types, use the implicit conversion from System.Action or System.Func, like this: UnityAction a = new Action(() => {}) or var x = (UnityAction) new Action(() => {})
knah/Il2CppAssemblyUnhollower: A tool to generate Managed->IL2CPP proxy assemblies
とのこと。
Action<T> の変換が上手くいかない。
通信の問題の解決が難しいので、いったんストップ!
#2021/12/05週
更新履歴
#2021/12/11 解決できない問題がありいったんストップ
#2021/12/08 カスタム設定に項目を追加(見た目だけ)
#2021/12/08 かきはじめ