Besiege Mod ネットワーク実装理論(というClaudeがつくったdocのver1)
Besiegeのデコンパイルコード(NetworkBlock, NetworkEntity, NetworkController, NetworkInterpolation,
NetworkCompression, ServerMachine, TransportNetworkManager等)を読んで理解した上でまとめた実装ガイド。
このコードは実装サンプルであり、参照・解説を目的とする。
関連ドキュメント: lag-theory.md(通信理論全般)
目次
1. Besiegeの通信基盤の実態 #1-besiege%E3%81%AE%E9%80%9A%E4%BF%A1%E5%9F%BA%E7%9B%A4%E3%81%AE%E5%AE%9F%E6%85%8B
2. Modブロックへの自動適用 #2-mod%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%B8%E3%81%AE%E8%87%AA%E5%8B%95%E9%81%A9%E7%94%A8
3. Mod固有オブジェクト(非ブロック)の実装方針 #3-mod%E5%9B%BA%E6%9C%89%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E9%9D%9E%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AE%E5%AE%9F%E8%A3%85%E6%96%B9%E9%87%9D
4. サンプルコード A: Modブロックの正しい実装 #4-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89-a-mod%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AE%E6%AD%A3%E3%81%97%E3%81%84%E5%AE%9F%E8%A3%85
5. サンプルコード B: 非ブロックオブジェクトの手動同期 #5-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89-b-%E9%9D%9E%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E6%89%8B%E5%8B%95%E5%90%8C%E6%9C%9F
6. 将来のBesiegeブロック通信改善アプローチ #6-%E5%B0%86%E6%9D%A5%E3%81%AEbesiege%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E9%80%9A%E4%BF%A1%E6%94%B9%E5%96%84%E3%82%A2%E3%83%97%E3%83%AD%E3%83%BC%E3%83%81
7. 制約と注意事項まとめ #7-%E5%88%B6%E7%B4%84%E3%81%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85%E3%81%BE%E3%81%A8%E3%82%81
1. Besiegeの通信基盤の実態
全体フロー
code:_
【ホスト側】
BlockBehaviour.transform(Rigidbody物理確定値)
│
↓ ServerMachine.PollObjects() → NetworkBlock.PollObject()
posTracker.WithinThreshold() で変化を検出
│ 変化あり
↓ NetworkCompression.CompressPosition() → 6バイト
NetworkCompression.CompressRotation() → 7バイト
│
↓ NetworkController.PollObjectsRelative() でバッファに集積
│
↓ TransportNetworkManager → levelChannel (Unreliable) でブロードキャスト
ヘッダ: frame(4) + session(1) + current(2) = 7バイト
【クライアント側】
TransportNetworkManager ← levelChannel 受信
│
↓ NetworkController.ReadBufferRelative()
↓ NetworkEntity.SetData(frame, data, offset, hasPos, hasRot)
→ if (frame >= lastPosFrame) // 古いフレームは破棄
posTracker.Set(decompressedPos) // 補間開始
│
↓ NetworkController.UpdateEntities(delta) 毎フレーム
↓ NetworkEntity.UpdateEntity(delta)
posTracker.Update(delta)
trackTransform.position = posTracker.Vector // Transformに適用
座標チャネルは Unreliable
Besiegeは座標データを Unreliable チャネル で送信する。つまりパケットロスを許容している。
その代わり NetworkInterpolation の補間 + 予測(Dead Reckoning)でカバーする。
重要なイベント(破壊・点火・爆発など)は Reliable チャネル(AllCostDelivery) で送信する。
table:_
チャネル QoS 用途
levelChannel Unreliable ブロック座標(全ブロック分を一括)
logicChannel Reliable ロジック状態(キー入力など)
serverChannel AllCostDelivery ゲーム状態メッセージ
inputChannel AllCostDelivery プレイヤー入力
通信品質を保証する仕組み
table:_
仕組み 実装場所 内容
フレーム番号 NetworkEntity.SetData() frame >= lastPosFrame チェック。古いフレームを破棄
Dead Zone フィルタ NetworkEntity.PollObject() posTracker.WithinThreshold() 。変化なしはスキップ
補間 NetworkInterpolation.Update() 2点間を SLERP/LERP で補間
Dead Reckoning NetworkInterpolation maxAlpha=1.5f まで外挿。パケットが届かなくても動き続ける
送信間引き NetworkBlock.SkipFrame() skipChildCount に応じて子ブロックを間引き
緊急イベント NetworkEntity.EntityEvent / NetBlock.Event() Reliable で状態変更を通知
座標圧縮
code:_
位置: 6バイト
- X, Z: 17ビットずつ(ワールド範囲内で量子化)
- Y: 14ビット(対数スケール、高さ方向を効率よく表現)
回転: 7バイト
- Quaternion の最大成分を1バイトで識別
- 残り3成分を各16ビット(計6バイト)
計: 1ブロックあたり最大13バイト(変化した成分のみ送信)
NetworkCompression は public static クラスで、Mod から全メソッドが使用可能。
ベースブロックと子ブロックの区別
ベースブロックは ワールド絶対座標 を送信する。
子ブロックは ベースブロックのローカル空間での相対座標 を送信する。
これにより帯域を節約しつつ、マシン全体の位置をネットワークで同期する。
クライアント側でブロックの絶対座標を復元するには:
code:_
world_pos = baseNetworkBlock.transformMatrix.MultiplyPoint3x4(localPos)
2. Modブロックへの自動適用
重要: Modカスタムブロックの座標同期はBesiegeが自動で行う
ModBlockBehaviour を継承してBesiegeに登録したカスタムブロックは、
シミュレーション開始時に ServerMachine.InitSimBlock() が呼ばれ、
NetworkBlock コンポーネントが自動的に設定される。
code:_
ServerMachine.InitSimBlock(block, ...)
↓
NetworkBlock netBlock = block.NetBlock; // BasicInfo.NetBlock は public フィールド
netBlock.Init(index, networkController, baseEnt, SimPhysics);
networkController.Add(netBlock);
Modが何もしなくても、カスタムブロックの座標は以下の流れで同期される:
ホスト: SimulateUpdateHost() / SimulateFixedUpdateHost() でRigidbodyを動かす → Besiegeが自動でTransformを読み取り、変化があれば圧縮・送信
クライアント: Besiegeが受信データを NetworkInterpolation で補間し、trackTransform.position に適用 → ブロックのTransformが自動で動く
BlockBehaviour.NetBlock へのアクセス
BasicInfo.NetBlock は public フィールドなので、Modからアクセスできる。
code:csharp
// BlockBehaviour.NetBlock → NetworkBlock インスタンス
NetworkBlock netBlock = BlockBehaviour.NetBlock;
// null チェック必須(シミュ前はnull、ローカルプレイでもnull)
if (netBlock != null)
{
// 現在の補間済み位置を取得
Vector3 pos = netBlock.Position; // posTracker.lastVec
Quaternion rot = netBlock.Rotation; // rotTracker.lastRot
Vector3 vel = netBlock.Velocity; // NormalizedDeltaVector (base block基準)
float angVel = netBlock.AngularVelocityMag;
// イベント送信(Reliable チャネル)
netBlock.Event(NetworkEntity.EntityEvent.Break);
netBlock.Event(NetworkEntity.EntityEvent.Ignite);
// ... EntityEvent の全種別は NetworkEntity.EntityEvent enum を参照
}
クライアント側でTransformを動かしてはいけない
クライアント側では NetworkEntity.UpdateEntity() が trackTransform.position を毎フレーム上書きする。
SimulateUpdateClient() で同じTransformを動かすと競合が起きる。
code:csharp
public override void SimulateUpdateClient()
{
// ❌ やってはいけない: Besiegeが既に動かしているTransformを再度動かす
// BlockBehaviour.transform.position = somePos;
// ✅ OK: 表示のみの処理(Transformを動かさない)
// BlockBehaviour.Visual.SetActive(someCondition);
}
3. Mod固有オブジェクト(非ブロック)の実装方針
なぜ Besiege の通信系に直接乗せられないか
code:csharp
// ServerMachine.cs より
protected NetworkController networkController; // protected → Mod からアクセス不可
// (System.Reflection も禁止のため Reflection 経由でも不可)
NetworkController は ServerMachine の protected フィールド。
Besiegeのサンドボックスにより System.Reflection も禁止されているため、
カスタムオブジェクトを NetworkController に手動登録することはできない。
ModNetworking API で代替する
ModNetworking を使い、ホストがフレームごとに座標を全クライアントへ送信する。
Besiegeの NetworkInterpolation クラス(public)を補間に再利用することで、
Besiegeと同等の補間品質を実現できる。
NetworkCompression も public static クラスなので圧縮/解凍に再利用できる。
設計上の留意点
送信頻度を Besiegeに合わせる:
Besiegeの ServerSettings.sendRate は約 0.1秒 (10Hz)、skipChildCount で子ブロックはさらに間引く。
Mod固有オブジェクトも同程度の頻度(10〜20Hz)を目安にする。
フレーム番号を付けて古いパケットを捨てる:
ModNetworking は Unreliable 相当の送受信をするため、パケットの順序は保証されない。
Besiegeと同様にフレーム番号を送り、古いフレームは捨てる。
送信側(ホスト)自身にはメッセージが届かない:
ModNetworking.SendToAll() は呼び出し側本人には届かない(ModNetworking.cs 参照)。
ホスト側の処理はローカルで直接実行する。
4. サンプルコード A: Modブロックの正しい実装
Mod カスタムブロック(ModBlockBehaviour 継承)での通信実装例。
座標同期はBesiegeが自動で行うため、Modは イベント送信 に専念する。
code:csharp
using Modding;
using UnityEngine;
// Mod カスタムブロック: 簡単なシューティングブロックの例
public class CustomCannonBlock : ModBlockBehaviour
{
private MKey _fireKey;
// ---- イベントメッセージ定義(OnLoadで設定)----
// 発射イベント: 発射位置 + 発射方向
public static MessageType MSG_FIRE;
// ---- ライフサイクル ----
public override void SafeAwake()
{
_fireKey = AddKey("発射", "fire", KeyCode.Space);
}
// ホスト専用: 物理処理を行う
// Besiegeがこのブロックの Transform を自動でクライアントへ同期する
public override void SimulateFixedUpdateHost()
{
if (_fireKey.IsPressed)
{
Fire();
}
}
// クライアント専用: Transform は Besiege が自動更新するため触らない
// エフェクト処理などを行う
public override void SimulateUpdateClient()
{
// Transform を動かしてはいけない(NetworkBlock が毎フレーム上書きする)
// エフェクト、サウンドなどのビジュアル処理のみ行う
}
// ---- 発射処理 ----
private void Fire()
{
if (!StatMaster.isMP || !ModNetworking.IsNetworkingReady)
{
// シングルプレイまたはネットワーク未準備: ローカルのみ
SpawnProjectileLocally(BlockBehaviour.transform.position, BlockBehaviour.transform.forward);
return;
}
Vector3 pos = BlockBehaviour.transform.position;
Vector3 dir = BlockBehaviour.transform.forward;
// 全クライアントへ発射イベントを送信(Reliable 相当)
Message msg = MSG_FIRE.CreateMessage(pos, dir);
ModNetworking.SendToAll(msg);
// SendToAll は自分には届かないため、ホスト自身はローカルで実行
SpawnProjectileLocally(pos, dir);
}
private void SpawnProjectileLocally(Vector3 pos, Vector3 dir)
{
// 弾の生成処理(省略)
// ...
}
// ---- ネットワーク状態の確認 ----
public void OnFireReceived(Message msg)
{
Vector3 pos = (Vector3)msg.GetData(0);
Vector3 dir = (Vector3)msg.GetData(1);
SpawnProjectileLocally(pos, dir);
}
// ---- BlockBehaviour.NetBlock の活用例 ----
// NetBlock から補間済み速度を取得する例(HUD表示などに使える)
public Vector3 GetNetworkVelocity()
{
NetworkBlock netBlock = BlockBehaviour.NetBlock;
if (netBlock == null)
return Vector3.zero;
return netBlock.Velocity; // posTracker.NormalizedDeltaVector
}
}
OnLoad での設定
code:csharp
public class MyMod : ModEntryPoint
{
public override void OnLoad()
{
CustomCannonBlock.MSG_FIRE = ModNetworking.CreateMessageType(
DataType.Vector3, // 0: 発射位置
DataType.Vector3 // 1: 発射方向
);
ModNetworking.CallbacksCustomCannonBlock.MSG_FIRE += OnFireMessage;
}
private void OnFireMessage(Message msg)
{
// 受信時: どのブロックが発射したか特定が必要
// msg.Sender で送信プレイヤーは取得できる
Vector3 pos = (Vector3)msg.GetData(0);
Vector3 dir = (Vector3)msg.GetData(1);
// 弾を生成
}
}
5. サンプルコード B: 非ブロックオブジェクトの手動同期
Besiegeのブロックシステム外の独立したオブジェクト(弾丸・エフェクトオブジェクトなど)を
ModNetworking + NetworkInterpolation で同期する例。
設計
code:_
ホスト:
SimulateUpdateHost() → 変化検出 → 20Hz で SendToAll
クライアント:
MSG受信 → NetworkInterpolation.Set() → UpdateClient() で Update() → Transform に適用
フレーム番号チェックで古いパケットを捨てる
同期オブジェクト管理クラス
code:csharp
using Modding;
using UnityEngine;
using System.Collections.Generic;
// Mod 独自オブジェクト 1 個の同期状態を管理する
// NetworkInterpolation は Besiege 本体の public クラスを再利用する
public class SyncedModObject
{
public int ObjectId;
public Transform Transform;
// Besiege 本体の NetworkInterpolation を補間に再利用
private NetworkInterpolation _posInterp = new NetworkInterpolation();
private NetworkInterpolation _rotInterp = new NetworkInterpolation();
// 最後に受信したフレーム番号(古いパケットを捨てるため)
private uint _lastPosFrame;
private uint _lastRotFrame;
// ホスト側: Dead Zone 検出用
private Vector3 _lastSentPos;
private Quaternion _lastSentRot;
private const float POS_DEAD_ZONE_SQ = 0.0001f; // 0.01m 以下は送信スキップ
private const float ROT_DOT_THRESHOLD = 0.99999f;
public SyncedModObject(int id, Transform transform)
{
ObjectId = id;
Transform = transform;
// Besiege の ServerSettings に合わせて補間間隔を設定
// sendRate は約 0.1f (10Hz)
float interval = NetworkScene.ServerSettings.sendRate;
_posInterp.OverrideInterval(interval);
_rotInterp.OverrideInterval(interval);
}
// ---- ホスト側: 変化チェックと送信データ準備 ----
// 変化があれば true を返す(Dead Zone フィルタ)
public bool IsDirty()
{
Vector3 pos = Transform.position;
Quaternion rot = Transform.rotation;
// 位置の変化量チェック
float dx = pos.x - _lastSentPos.x;
float dy = pos.y - _lastSentPos.y;
float dz = pos.z - _lastSentPos.z;
bool posChanged = (dx * dx + dy * dy + dz * dz) > POS_DEAD_ZONE_SQ;
// 回転の変化量チェック(内積で近似)
float dot = Quaternion.Dot(rot, _lastSentRot);
bool rotChanged = (dot < 0f ? -dot : dot) < ROT_DOT_THRESHOLD;
return posChanged || rotChanged;
}
public void RecordSentState()
{
_lastSentPos = Transform.position;
_lastSentRot = Transform.rotation;
}
// ---- クライアント側: フレーム番号チェックと補間開始 ----
// フレーム番号付きで位置データを受信
public void ReceivePosition(uint frame, Vector3 pos)
{
if (frame < _lastPosFrame)
return; // 古いフレームは無視
_lastPosFrame = frame;
_posInterp.Set(pos);
}
// フレーム番号付きで回転データを受信
public void ReceiveRotation(uint frame, Quaternion rot)
{
if (frame < _lastRotFrame)
return;
_lastRotFrame = frame;
_rotInterp.Set(rot);
}
// 毎フレーム補間を進める(クライアント側で呼ぶ)
public void UpdateInterpolation(float delta)
{
if (_posInterp.isActive)
{
_posInterp.Update(delta);
Transform.position = _posInterp.Vector;
}
if (_rotInterp.isActive)
{
_rotInterp.Update(delta);
Transform.rotation = _rotInterp.Rotation;
}
}
// ---- 強制同期(再接続・初期化時)----
public void SnapTo(Vector3 pos, Quaternion rot)
{
Transform.position = pos;
Transform.rotation = rot;
// 補間のベースを現在値でリセット
_posInterp.SetData(NetworkScene.ServerSettings.sendRate, pos);
_rotInterp.SetData(NetworkScene.ServerSettings.sendRate, rot);
_lastPosFrame = 0;
_lastRotFrame = 0;
}
}
ホスト側: 定期送信マネージャ
code:csharp
public class SyncedObjectManager : ModBlockBehaviour
{
// --- メッセージ型 ---
public static MessageType MSG_STATE_UPDATE; // 定期更新
public static MessageType MSG_FULL_STATE; // フルステート(再接続時)
private List<SyncedModObject> _objects = new List<SyncedModObject>();
// 送信用フレームカウンタ
private uint _frameCounter;
// 送信間隔(Besiegeの sendRate に合わせる)
private float _sendTimer;
public override void OnSimulateStart()
{
_frameCounter = 0;
_sendTimer = 0f;
if (StatMaster.isMP && StatMaster.isHosting)
{
// プレイヤー参加時にフルステートを送る
Events.OnPlayerJoin += OnPlayerJoin;
}
}
public override void OnSimulateStop()
{
Events.OnPlayerJoin -= OnPlayerJoin;
}
public override void SimulateUpdateHost()
{
if (!ModNetworking.IsNetworkingReady)
return;
// ServerSettings.sendRate に合わせた間隔で送信
_sendTimer += Time.deltaTime;
float interval = NetworkScene.ServerSettings.sendRate;
if (_sendTimer < interval)
return;
_sendTimer -= interval;
_frameCounter++;
SendStateUpdate(_frameCounter);
}
public override void SimulateUpdateClient()
{
// クライアント側: 毎フレーム補間を進める
float delta = Time.deltaTime;
for (int i = 0; i < _objects.Count; i++)
{
_objectsi.UpdateInterpolation(delta);
}
}
private void SendStateUpdate(uint frame)
{
for (int i = 0; i < _objects.Count; i++)
{
SyncedModObject obj = _objectsi;
if (!obj.IsDirty())
continue; // Dead Zone: 変化なしはスキップ
obj.RecordSentState();
// フレーム番号を Integer で送る
// Besiege は uint だが DataType に uint がないため int で代用
// フレーム番号が int.MaxValue を超えたら 0 に戻す(実用上問題なし)
Vector3 pos = obj.Transform.position;
Vector3 euler = obj.Transform.rotation.eulerAngles;
// Vector3 として euler を送る(DataType に Quaternion なし)
Message msg = MSG_STATE_UPDATE.CreateMessage(
(int)frame,
obj.ObjectId,
pos,
euler
);
ModNetworking.SendToAll(msg);
// SendToAll は自分には届かないためホスト自身には不要(すでに Transform を持っている)
}
}
// --- 受信ハンドラ(OnLoad で登録)---
public static void OnStateUpdateReceived(Message msg)
{
int frame = (int)msg.GetData(0);
int objectId = (int)msg.GetData(1);
Vector3 pos = (Vector3)msg.GetData(2);
Vector3 euler = (Vector3)msg.GetData(3);
uint uframe = (uint)((frame >= 0) ? frame : 0);
Quaternion rot = Quaternion.Euler(euler);
// objectId でオブジェクトを特定(省略)
// SyncedModObject target = FindObject(objectId);
// if (target != null)
// {
// target.ReceivePosition(uframe, pos);
// target.ReceiveRotation(uframe, rot);
// }
}
// --- フルステート送信(再接続対応)---
private void OnPlayerJoin(Player player)
{
if (!StatMaster.isHosting || !ModNetworking.IsNetworkingReady)
return;
for (int i = 0; i < _objects.Count; i++)
{
SyncedModObject obj = _objectsi;
Message msg = MSG_FULL_STATE.CreateMessage(
obj.ObjectId,
obj.Transform.position,
obj.Transform.rotation.eulerAngles
);
ModNetworking.SendTo(player, msg);
}
}
public static void OnFullStateReceived(Message msg)
{
int objectId = (int)msg.GetData(0);
Vector3 pos = (Vector3)msg.GetData(1);
Vector3 euler = (Vector3)msg.GetData(2);
Quaternion rot = Quaternion.Euler(euler);
// objectId でオブジェクトを特定してスナップ(省略)
// SyncedModObject target = FindObject(objectId);
// if (target != null) target.SnapTo(pos, rot);
}
}
OnLoad でのメッセージ登録
code:csharp
public class MyMod : ModEntryPoint
{
public override void OnLoad()
{
SyncedObjectManager.MSG_STATE_UPDATE = ModNetworking.CreateMessageType(
DataType.Integer, // 0: frameCounter
DataType.Integer, // 1: objectId
DataType.Vector3, // 2: position
DataType.Vector3 // 3: rotation (EulerAngles)
);
SyncedObjectManager.MSG_FULL_STATE = ModNetworking.CreateMessageType(
DataType.Integer, // 0: objectId
DataType.Vector3, // 1: position
DataType.Vector3 // 2: rotation (EulerAngles)
);
ModNetworking.CallbacksSyncedObjectManager.MSG_STATE_UPDATE +=
SyncedObjectManager.OnStateUpdateReceived;
ModNetworking.CallbacksSyncedObjectManager.MSG_FULL_STATE +=
SyncedObjectManager.OnFullStateReceived;
Events.OnPlayerJoin += OnPlayerJoin;
}
private void OnPlayerJoin(Player player)
{
// プレイヤー参加時: フルステート送信は SyncedObjectManager に委譲
}
}
6. 将来のBesiegeブロック通信改善アプローチ
Besiegeブロックの通信改善を行う場合、以下のアプローチが現実的。
A: ServerSettings の調整(全体影響)
NetworkScene.ServerSettings は public static フィールドなので Mod から変更できる。
ただし全プレイヤーの全ブロックに影響するため、慎重に行う。
code:csharp
// 参考: ServerSettings の変更例(MP接続後、ホスト側のみで行う)
// sendRate: 送信間隔(秒)。小さいほど高頻度。デフォルト約0.1f
// skipChildCount: 子ブロックの間引き数。0=毎回, 1=2回に1回, 2=3回に1回
// smoothness: 補間の滑らかさ (0〜1)
if (StatMaster.isHosting)
{
// 例: 20Hz送信にしたい場合(デフォルトは10Hz程度)
// NetworkScene.ServerSettings.sendRate = 0.05f;
// ※ ServerSettings の書き換えは全体影響するため要注意
}
注意: ServerSettings を Mod から書き換えると、全ブロックの送信頻度・補間品質が変わる。
意図しない副作用が出る可能性があるため、本当に必要な場合のみ行うこと。
B: 個別ブロックの NetworkInterpolation 調整
BlockBehaviour.NetBlock → NetworkBlock → posTracker, rotTracker の順でアクセスできる。
ただし posTracker, rotTracker は protected フィールドなので直接参照はできない。
アクセス可能な public API:
code:csharp
NetworkBlock netBlock = BlockBehaviour.NetBlock;
if (netBlock != null)
{
// 補間済み座標は public プロパティで取得できる
Vector3 interpolatedPos = netBlock.Position; // posTracker.lastVec
Quaternion interpolatedRot = netBlock.Rotation; // rotTracker.lastRot
Vector3 velocity = netBlock.Velocity; // NormalizedDeltaVector
float angVelMag = netBlock.AngularVelocityMag;
// pollTransform は public フィールド → 特定ブロックの送信を止めることができる
// netBlock.pollTransform = false; // ⚠ 慎重に。座標同期が完全に止まる
// baseInterval は protected → 直接変更不可
}
C: 補間品質の改善(クライアント側のみ)
Besiegeブロックのクライアント側補間は NetworkInterpolation.Update() が行う。
NetworkInterpolation は static メソッド AdjustThreshold() を持ち、
閾値を変更できる(これは全インスタンス共通)。
code:csharp
// 位置・回転の更新閾値を調整する
// ※ これは全 NetworkInterpolation インスタンスに影響する
NetworkInterpolation.AdjustThreshold(
vecThreshold: 0.5f, // 0〜1 の範囲。小さいほど敏感(0.001f〜0.1f の間で量子化)
rotThreshold: 0.5f // 同上
);
7. 制約と注意事項まとめ
mod.json の必須設定
code:json
{
"MultiplayerCompatible": true
}
アクセス可否まとめ
table:_
クラス / フィールド アクセス 用途
BlockBehaviour.NetBlock ✅ public フィールド NetworkBlock への参照
NetworkBlock.Position / Rotation / Velocity ✅ public プロパティ 補間済み座標の取得
NetworkBlock.Event(EntityEvent) ✅ public メソッド 状態変更イベント送信
NetworkBlock.pollTransform ✅ public フィールド (注意) 送信の有効/無効
NetworkInterpolation ✅ インスタンス生成可能 補間処理の再利用
NetworkInterpolation.AdjustThreshold() ✅ public static 閾値の全体調整
NetworkCompression.* ✅ public static 座標圧縮/解凍の再利用
NetworkScene.ServerSettings ✅ public static (要慎重) 全体設定の変更
ServerMachine.networkController ❌ protected カスタムオブジェクトの登録不可
NetworkEntity.posTracker / rotTracker ❌ protected 補間パラメータ直接変更不可
System.Reflection ❌ Besiege禁止 回避策にも使えない
MP 判定ガード
code:csharp
// 送受信前に必ず確認
if (!StatMaster.isMP)
return; // シングルプレイなら不要
if (!ModNetworking.IsNetworkingReady)
return; // MP接続・ゲーム状態受信完了まで使えない
// ホスト専用処理
if (StatMaster.isHosting) { ... }
// クライアント専用処理
if (StatMaster.isClient) { ... }
SendToAll の仕様
ModNetworking.SendToAll() は 送信者自身には届かない(ModNetworking.cs L228 で自分への送信をスキップ)。
ホストが SendToAll したイベントは、ホスト自身でもローカル処理が必要。
code:csharp
// ホスト: イベント処理
void TriggerEvent()
{
Message msg = MSG_EVENT.CreateMessage(...);
ModNetworking.SendToAll(msg); // クライアントへ送信
ProcessEventLocally(...); // ホスト自身でも実行(自分には届かないため)
}