サーバー権限の技術

*このコンテンツは、ベータ版の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の割り当てと調整を自動的に処理します。

インスタンスがDataModelに親子関係を持つ前に、NameSizeParentなどの非シミュレーションアクセスプロパティを自由に設定できます。

シミュレーション(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 -- Partは今やDataModelに存在します。非シミュレーションアクセスの変化はこれ以降エラーになります
-- Partはクライアントに即座に存在し、サーバーと調整されます
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 Workspace = game:GetService("Workspace")
-- スムーズに追跡するオブジェクト
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を返します。これを使用するのは、探している特定のアニメーションがわかっているときです。
  • 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 Workspace = game:GetService("Workspace")
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は各クライアントの入力を他のすべてのクライアントに転送しません。これがゲームに適しているかどうかは、ゲームのデザインによります:

  • 基本的なヒューマノイドの移動に関しては、デフォルトの動作は、他のプレイヤーキャラクターの動きが権限のあるサーバーの状態から外挿されないことを意味し、その結果、他のプレイヤーキャラクターは誤予測されることなく、わずかに過去にレンダリングされます。
  • 一方で、レースゲームでは、デフォルトの動作により、クライアントは他のプレイヤーがスロットルや他の入力を適用しているかどうかを知ることができません。そのため、他の車両はローカルプレイヤーの後ろに表示される可能性がありますが、実際には前にいるかもしれません。この問題を緩和するために、サーバー上で属性にプレイヤーの入力を保存し、以下のコードサンプルやRacingテンプレートで示したように、クライアント側でそれらの同期された属性を使用して操作します。このアプローチにより、属性をシミュレーションへの入力として使用し、完全に複製されたプレイヤー入力を持つことができます。
属性にプレイヤー入力を保存する(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は、米国並びにその他の国における登録商標および非登録商標です。