サーバー権限テクニック

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

このガイドでは、サーバー権限モデルを使用して高品質でスムーズなマルチプレイヤーゲームを作成するためのさまざまなテクニックを概説します。

予測インスタンス作成(インスタンスステッチ)

インスタンス ステッチング により、クライアントスクリプトは RunService:BindToSimulation() コールバック内で予測的に Instances を作成できます。クライアントはサーバーの往復を待たずにすぐに Instance を作成し、サーバーの権限のあるコピーが到着すると、クライアントで作成されたインスタンスとサーバーの権限のあるコピーが1つに統合されます。スクリプトの観点から見ると、Instance は即座に存在し、サーバーと一致しています。

インスタンスステッチングは、インスタンスができるだけ早くクライアントで表示およびアクティブである必要がある場合に役立ちます。サーバーは最終的にクライアントが必要とするインスタンスをレプリケートしますが、サーバーとの通信による遅延により、少なくとも1回の往復遅延が発生します。例としては、ロケットランチャーの発射や物理的制約の作成が挙げられます — ステッチングなしでは、クライアントはロケットが遠くからポップインするのを見たり、新しい制約が彼らに複製されるときにジッターを体験します。

技術的動作

インスタンスステッチングは、クライアントとサーバーの両方で同じ決定論的GUIDを生成することによって機能します。GUIDは、作成されるInstanceのタイプ、ソースのアイデンティティ(下記参照)、現在のシミュレーションフレーム、およびフレームごとにリセットされるスクリプトのコールカウンターの4つの入力から導き出されます。

  • Instance.new() の場合 — ソースはスクリプト自体です(同じテキストの2つのスクリプトは異なると見なされます)。
  • Instance.fromExisting() の場合 — ソースは、Instance.fromExisting() を呼び出している Instance です。
  • Instance:Clone() の場合 — 各クローンインスタンスは、ソースインスタンスのGUIDをコンテキストシードとして使用します。

クライアントとサーバーが入力に同意すると、一致するGUIDが生成され、ステッチが成功します。

実装

インスタンスステッチングを利用するには、ModuleScriptからクライアント および サーバーで要求される RunService:BindToSimulation() コールバック内で Instance.new()Instance:Clone() または Instance.fromExisting() を呼び出します。あなたの側で必要なのはこれだけです; システムはGUIDの割り当てと調整を自動的に処理します。

非-シミュレーションアクセス プロパティ(NameSize、または Parent など)をインスタンスに自由に設定できますが、それが DataModel に親子付けされる前に行ってください。

シミュレーション(ModuleScript) - BindToSimulation() コールバックでインスタンスを作成
local RunService = game:GetService("RunService")
local Simulation = {}
Simulation.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
local part = Instance.new("Part")
part.Name = "PredictedPart"
part.Size = Vector3.new(2, 2, 2)
part.Parent = workspace -- パーツはデータモデルに存在する; この後の非シミュレーションアクセスの変更はエラーになります
-- パーツは即座にクライアント上に存在し、サーバーと調整されます
end)
end
return Simulation

Instance:Clone()Instance.fromExisting() は、ソースインスタンスがクライアントとサーバーの両方にレプリケートされている場合、正しくステッチされます。両方の側が一致するソースGUIDからクローンし、一致する予測GUIDを生成します。

シミュレーション(ModuleScript) - BindToSimulation() コールバックでインスタンスをクローン
local RunService = game:GetService("RunService")
local Simulation = {}
local sourceTemplate -- 複製されたインスタンス
Simulation.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
local cloned = sourceTemplate:Clone()
cloned.Parent = workspace
-- クローンされた階層は、サーバーの権限のあるコピーとステッチされます
end)
end
return Simulation

位置のスムージング

予測された動的オブジェクトの位置を視覚的にスムーズにするためには、シミュレーションされている他のオブジェクトとは異なるオブジェクトをレンダリングすることができます。

  1. シミュレーションされたオブジェクトを非表示にします。
  2. シミュレーションされたオブジェクトを追跡するための無質量の衝突しない視覚専用のクローンとしてレンダラーオブジェクトを作成します。
  3. レンダラーオブジェクトにスクリプトを添付し、不可視のシミュレーションされたオブジェクトの位置をスムーズに追従させます。このレンダリングとシミュレーションの分離により、レンダラーオブジェクトの位置を変更して視覚的にスムーズな体験を作成することができます。

以下のサンプル Script では、レンダリングされたオブジェクト(親)がシミュレーションされたオブジェクトをスムーズに追跡します。レンダリングされたオブジェクトは、通常シミュレーションされたオブジェクトの「後ろ」にわずかにあるため、通常は問題ありませんが、特定の状況では望ましくない場合があります。

レンダラーパーツで BasePart の位置をスムーズに追跡
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
-- スムーズに追跡するオブジェクト
local smoothTarget:BasePart = workspace.SimulatedPart
-- スムーズにする視覚オブジェクト
local renderer:BasePart = script.Parent
-- スムーズ化する時間; 小さいほど速い
local smoothTime = 0.07
-- スムーズな位置を計算するために必要なデータを保存
local smoothVelocity = Vector3.new()
-- レンダラーオブジェクトの物理を無効にする
renderer.Massless = true
renderer.Anchored = true
renderer.CanCollide = false
RunService.RenderStepped:Connect(function(deltaTime: number)
-- 目標オブジェクトをスムーズに追跡
local smoothPosition, smoothVelocity = TweenService:SmoothDamp(
renderer.Position,
smoothTarget.Position,
smoothVelocity,
smoothTime,
math.huge,
deltaTime)
renderer.Position = smoothPosition
end)

サッカー の例ゲームは、このテクニックのバリエーションを使用して、サッカーボールの位置スムージングをよりインテリジェントにオンオフします。特に、サッカーボールは、シミュレーションされたボールがレンダリングされたボールから「十分に遠くに跳ねた」場合にのみ位置をスムーズにします。このアプローチは、通常の条件下では視覚的な遅延がないサッカーボールを持ち、シミュレーションされたボールが予期しない新しい位置にジャンプした場合にのみスムーズに位置を補間することを可能にします。これは、たぶん、ネットワークアーティファクトやサーバーサイドの変更によるものです。

アニメーションコードの作成

サーバー権限の下で、サーバーがミスを修正すると、クライアントのシミュレーションはロールバックして再シミュレーションされる可能性があります。ロールバック中は、アニメーション状態が巻き戻されるため、以前のフレームでキャッシュされた AnimationTrack が無効になる可能性があります。

アニメーションロジックのミラーリング

コアゲームプレイロジックと同様に、アニメーションの制御ロジックは、サーバーとクライアント間で同期されていなければならず、そうでないとミス予測やジッターのある動作が発生します。シミュレーション同期 を参照して、クライアントとサーバーの両方で初期化される ModuleScript 内で RunService:BindToSimulation() を通じて関数を結びつけるパターンを確認してください。

トラックキャッシングを避ける

サーバー権限スクリプトの一般的なパターンは、読み込み時に AnimationTrack オブジェクトをキャッシュし、無期限に再利用することです。このパターンは、サーバーがミスを修正し、クライアントが修正されたデータでシミュレーションを巻き戻して再生すると、サーバー権限のあるゲームでは失敗します。スクリプトが停止または置き換えられたトラックへの参照を保持していると、AdjustWeight()AdjustSpeed() のような呼び出しは、もはや視覚的に表現されないトラックで操作されます。

クライアントでトラックをキャッシュする(信頼性が低い)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
-- アニメーショントラックをキャッシュ
local tracks = {}
tracks["WalkForward"] = animator:LoadAnimation(walkForwardAnim)
RunService:BindToSimulation(function(dt: number)
tracks["WalkForward"]:AdjustSpeed(1 + math.cos(time()))
end)

トラックオブジェクトに保持するのではなく、アニメーションID(または Animation インスタンス)を保存し、必要なときに Animator に対してライブトラックを照会します。これには以下の2つのAPIがあります。

  • Animator:GetTrackByAnimationId() — 特定のアニメーションIDに対する現在のアクティブトラックを返す。アクティブなアニメーションがない場合は nil を返します。このAPIは、特定のアニメーションを探している場合に使用します。
  • Animator:GetPlayingAnimationTracks() — すべてのアクティブなトラック(再生中、フェードアウト中、または一時停止中)を返します。すべてのアクティブなもの(例えば、すべてのアニメーションを停止したり、何らかの基準でトラックを見つけたりするため)を繰り返す必要がある場合に使用します。

ModuleScript 名称 CustomAnimateReplicatedStorage内にあります:

CustomAnimate
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local CustomAnimate = {}
-- アニメーションリファレンスを保存(ロードされたトラックではない)
local animations = {
WalkForward = ReplicatedStorage.Animations.WalkForward,
}
local function getOrLoadTrack(animator: Animator, animation: Animation): AnimationTrack
local track = animator:GetTrackByAnimationId(animation.AnimationId)
if not track then
track = animator:LoadAnimation(animation)
end
return track
end
CustomAnimate.SyncAnimations = function(character)
local humanoid = character:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
RunService:BindToSimulation(function(dt: number)
local walkTrack = getOrLoadTrack(animator, animations.WalkForward)
if not walkTrack.isPlaying then
walkTrack.Looped = true
walkTrack.Priority = Enum.AnimationPriority.Core
walkTrack:Play()
end
walkTrack:AdjustSpeed(1 + math.cos(time()))
end)
end
return CustomAnimate

音や視覚効果の再生

予測されたシミュレーションにおいて、クライアントが発生すると思っていたがサーバーで実際には発生しなかったイベントのために効果や音をトリガーすることが可能です。レンダリングシステムは、誤って予測された効果を「取り消す」準備をしておくべきです。たとえば、クライアントが手榴弾が爆発することを予測し、パーティクル効果をトリガーしたが、他のプレイヤーが手榴弾を解除した場合、クライアントはパーティクル効果を非表示にする必要があります。

予測されたシミュレーションをレンダリングするための良い戦略は、シミュレーションループ内に状態マシンパターンを同期させ、レンダーステップ関数で状態の変化をレンダリングすることです。以下の例では、手榴弾を状態マシンパターンでシミュレートします。

手榴弾を追跡するためのシンプルな状態マシン(ModuleScript)
local module = {}
module.GrenadeStates = {
Idle = 0,
Lit = 1,
Exploded = 2,
Defused = 3,
}
module.GrenadeExplodeTime = 3.0
module.Initialize = function(grenade)
RunService:BindToSimulation(function(deltaTime)
-- 空の手榴弾状態を初期化
local grenadeState = grenade:GetAttribute("State")
if grenadeState == nil then
grenadeState = module.GrenadeStates.Idle
grenade:SetAttribute("State", grenadeState)
grenade:SetAttribute("Timer", 0.0)
end
-- 手榴弾タイマーを増加させる
local timer = grenade:GetAttribute("Timer")
timer = timer + deltaTime
grenade:SetAttribute("Timer", timer)
-- 点火された手榴弾を爆発させる
if grenadeState == module.GrenadeStates.Lit then
if timer >= module.GrenadeExplodeTime then
grenadeState = module.GrenadeStates.Exploded
grenade:SetAttribute("State", grenadeState)
grenade:SetAttribute("Timer", 0.0)
end
end
end)
end
return module

前述の状態マシンを使用すると、同期された手榴弾の状態に基づいて別のスクリプト内で RunService.RenderStepped 接続に効果をレンダリングできます。

同期された手榴弾の状態に基づいたパーティクルと音のレンダリング
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Simulation = require(ReplicatedStorage.Simulation)
local grenade = script.Parent
local previousGrenadeState = nil
-- 手榴弾の状態を示すインスタンスをハイライト
local highlight = Instance.new("Highlight")
highlight.Parent = grenade
highlight.FillTransparency = 1
highlight.OutlineTransparency = 1
highlight.DepthMode = Enum.HighlightDepthMode.Occluded
RunService.RenderStepped:Connect(function(deltaTime: number)
local grenadeState = grenade:GetAttribute("State")
local grenadeTimer = grenade:GetAttribute("Timer")
-- 手榴弾が点火されている場合は点火パーティクルを発生させる
grenade.LitEmitter.Enabled = grenadeState == Simulation.GrenadeStates.Lit
-- 手榴弾がちょうど爆発した場合は爆発エミッターを再生
if previousGrenadeState ~= grenadeState then
if grenadeState == Simulation.GrenadeStates.Exploded and grenadeTimer < 0.2 then
grenade.ExplosionEmitter:Emit(100)
grenade.ExplosionSound:Play()
end
previousGrenadeState = grenadeState
end
-- 状態および時間に基づいて手榴弾のハイライトカラーを変更
if grenadeState == Simulation.GrenadeStates.Lit then
highlight.FillColor = Color3.fromRGB(255, 0, 0)
highlight.FillTransparency = 1 - (grenadeTimer / Simulation.GrenadeExplodeTime)
elseif grenadeState == Simulation.GrenadeStates.Idle then
highlight.FillTransparency = 1
elseif grenadeState == Simulation.GrenadeStates.Exploded then
highlight.FillTransparency = 1
elseif grenadeState == Simulation.GrenadeStates.Defused then
highlight.FillColor = Color3.fromRGB(0, 255, 125)
highlight.FillTransparency = 0.5
end
end)

ネットワーク遅延に関する設計

特定のゲームプレイメカニックは、他のメカニックよりもネットワークマルチプレイヤーに適しています。プレイヤーは、他のプレイヤーがアクションを実行してから、そのプレイヤーの入力を受け取るまでに常に遅延が発生します。非常にスムーズなマルチプレイヤーゲームを作成する最良の方法は、これらの制限を念頭に置いてゲームを設計することです。

たとえば、プレイヤー移動の加速度が遅いゲームは、加速度が高いゲームよりもスムーズに見えます。その理由は、プレイヤーの入力によるネットワーク遅延によって引き起こされる位置の違いが、高い加速度を持つゲームよりも小さいためです。

別の例として、プレイヤーが入力を押すことで瞬時に大きな爆発を引き起こすゲームプレイメカニックは、入力の後遅れ爆発する場合よりも多くのネットワークアーティファクトを生じるでしょう。これは、爆発ではなく導火線効果に再シミュレーションを置くことであり、ネットワークアーティファクトが目立たないようになります。

他のプレイヤーの入力を予測する

デフォルトでは、Robloxは各クライアントから他のすべてのクライアントへの入力を転送しません。これがゲームの設計に合致しているかどうかは、その設計次第です。

  • 基本的なヒューマノイドの移動に関して、デフォルトの動作は、他のプレイヤーキャラクターの動きが権限のあるサーバーステートから外挿されないことを意味します。その結果、他のプレイヤーキャラクターは誤予測を発生させず、わずかに過去にレンダリングされます。
  • レーシングゲームでは対照的に、デフォルトの動作はクライアントが他のプレイヤーがスロットルやその他の入力を適用しているかどうかを把握できないことを意味し、そのため他の車両がローカルプレイヤーよりも後ろに見えることがあります(実際には前にいる場合でも)。これを軽減するために、サーバー上の属性 にプレイヤー入力を保存し、クライアント側で RunService:BindToSimulation() を使用してそれらの同期属性に対して操作することができます。次のコードサンプルやレーシングテンプレートで示されているように、このアプローチを使用すると、シミュレーションへの入力として属性を使用し、プレイヤーの入力を完全に複製することができます。
属性にプレイヤー入力を保存する(ModuleScript)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local module = {}
module.storePlayerInput = function(player:Player, humanoidRootPart:BasePart)
local inputContext:InputContext = player.PlayerGui.InputContext
local throttle = inputContext.DefuseAction:GetState()
humanoidRootPart:SetAttribute("Throttle", throttle)
-- 他の入力を属性に書き込む...
end
module.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
if RunService:IsServer() then
-- サーバーからすべてのクライアントに入力を転送
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
else
-- ローカルプレイヤー入力を属性として書き込む
local player = Players.LocalPlayer
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
-- ゲームへの入力として属性を使用
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local throttle = humanoidRootPart:GetAttribute("Throttle")
if throttle then
-- プレイヤーの車両にスロットルを適用
end
end
end)
end)
return module

デバッグ

サーバー権限ゲームをデバッグするために使用できる新しいツールと技術があります。

サーバー権限ビジュアライザー

CtrlShiftF6(Windows)または ShiftF6(Mac)を押すと、スタジオのサーバー権限ビジュアライザーが開き、いくつかの重要な情報が表示されます。

詳細説明
インスタンス予測成功率過去8秒間の正しく予測されたインスタンスの割合。
入力受け入れ率サーバーにタイムリーに到着したすべてのプレイヤーの入力の割合。遅れた入力はこの数値を下げます。
クライアント-サーバーステップデルタクライアントとサーバー間のフレーム数で、クライアントの参加時間を含みます。この数値の安定性は、サーバーへの接続の安定性を表します。
RCCハートビートFPSサーバー上のシミュレーションのフレームレート。この数値が59未満になると、サーバーはシミュレーションに追いつけなくなり、ゲームの品質が低下します。
予測インスタンス数クライアントが予測しているインスタンスの数。
入力ドロップ理由数

サーバーが各理由で入力をドロップした回数:

  • [x] あまりにも古い — 入力が遅れて到着したため、ネットワークが悪化したか、クライアントがシミュレーションに追いつけなかったことを意味します。
  • [x] 順序が乱れた — ネットワークバグが発生し、入力が並べ替えられて破棄されたことを意味します。
  • [x] バッファがいっぱい — サーバーが入力をバッファリングできなかった。ネットワークが突然改善したか、サーバーがシミュレーションに追いつけなかったことを意味します。

シミュレーション半径

自動予測 (Enum.PredictionMode.Automatic) に依存している場合、スタジオの設定で領域が有効にされた状態で、プレイヤーキャラクターの周囲の予測半径を可視化できます(Windowsでは AltS、Macでは S)。緑の円柱は、インスタンスが予測される範囲を示し、その半径はデバイスのパフォーマンス特性に応じて拡大および縮小します。

サーバー権限が実行されているプレイヤーキャラクターの周囲のシミュレーション半径
©2026 Roblox Corporation。Roblox(ロブロックス)、RobloxロゴおよびPowering Imaginationは、米国並びにその他の国における登録商標および非登録商標です。