【Roblox】CFrameのRotationだけを補間する
1. はじめに
前回の記事で、ModelのScaleをTweenで補間する方法をご紹介しました。
まだ前回の記事を読んでいない方は、先にこちらをご覧ください。
さて、前回に引き続き補間し辛いもの補間する方法についてなのですが、今回はCFrameのRotationです。
「CFrameって普通にTweenも使えるし補間できるんじゃ?」と思われるかもしれませんが、Tweenを適用できるのはあくまで「CFrameのプロパティ」であるため、PositionとRotationをそれぞれ独立させて補間することができません。
そのため、座標の移動を行いながら、それとは関係無く回転を補間させようとしても、Tweenによって座標まで上書きされてしまいます。
また、BasePart.Rotationに対して適用した場合、WeldやMotor6D、WeldConstraintなどで接続している物があると、Weld側のプロパティも一緒に変更されて絶対的な位置を維持するようになっているため、接続している物に付いてきて欲しい場合には使えません。
というわけで今回は、CFrameのRotationだけを補間する方法についてご紹介します。
バージョン:0.670.0.6700713
2. Tweenを適用する
さて、Tweenを適用する方法ですが、CFrameValueを利用します!
CFrameValueとは、特定の型の値を保持するだけの機能をもつValue系オブジェクトの一種で、CFrame型の値を保持するためのInstanceです。
あれ、どっかで聞いた説明ですね?
そうです、CFrameValueはValue系オブジェクトの一種なので、前回使ったNumberValueとは保持する型が変わっただけで、あとは全部同じと思っていただいて大丈夫です。
つまり、Tweenを適用する方法もほぼ一緒で、CFrameValue.Valueに対して補間を行い、その結果のRotationを対象のCFrameに反映する形で行います。
今回は、移動と回転が独立していることを確認するためにPlayerCharacterに適用します。
StarterCharacterScriptsの子にCFrameValueを、その子にLocalScriptを追加してください。
名前はそれぞれ「RotationValue」「RotationTweenScript」としておきます。
そして、RotationTweenScriptに以下のコードを追加してください。
--!strict
local rotationValue_ = script.Parent
local pvInstance_: PVInstance?
-- 対象PVInstanceを設定する関数.
local function setPVInstance(parent: any)
if not parent or not parent:IsA("PVInstance") then
pvInstance_ = nil
return
end
-- 現在のRotationで初期化.
rotationValue_.Value = parent:GetPivot().Rotation
-- PVInstanceを設定.
pvInstance_ = parent
end
-- RotationValueの親が変わったら対象Modelを設定しなおす.
rotationValue_:GetPropertyChangedSignal("Parent"):Connect(function()
setPVInstance(rotationValue_.Parent)
end)
-- 対象Modelを設定.
setPVInstance(rotationValue_.Parent)
-- Changedイベントに接続.
rotationValue_.Changed:Connect(function(value: CFrame)
if pvInstance_ then
-- RotationはRotationValueの値を、Positionは現在の値を用いる.
pvInstance_:PivotTo(value.Rotation + pvInstance_:GetPivot().Position)
end
end)
-- 以下、動作確認用.
-- Tweenを作成.
local TweenService = game:GetService("TweenService")
local tween0 = TweenService:Create(rotationValue_, TweenInfo.new(1), {Value = CFrame.fromEulerAngles(0, math.rad(90), 0)})
local tween1 = TweenService:Create(rotationValue_, TweenInfo.new(1), {Value = CFrame.fromEulerAngles(0, math.rad(-90), 0)})
-- Completedイベントを使って、二つのTweenを交互に実行するようにする.
tween0.Completed:Connect(function(playbackState: Enum.PlaybackState)
tween1:Play()
end)
tween1.Completed:Connect(function(playbackState: Enum.PlaybackState)
tween0:Play()
end)
-- 再生.
tween0:Play()
ここで対象にしているPVInstanceとは「Position Velocity Instance」の略で、CFrameとPivotOffsetの形で現在位置などの情報を持つ、ModelやBasePartのBaseClassです。
対象をPVInstanceとして扱えば、対象がModelでもBasePartでもこのScriptが使えます。
コードは型が変わっただけでほぼ前回そのまんまですが、補間したRotationを反映するところで、補間されたRotationと現在のPositionを合わせたCFrameを作成し設定している所だけ注意が必要です。
動作確認用には、1秒ごとに交互に右左に向きを変えるようにしてみました。
それではさっそく実行して確認してみましょう。
CFrameのRotationだけを補間することができました!
ちなみに、実際に利用する場合は、開始時にCFrame.RotationとRotationValue.Valueが一致しているとは限らないので、ValueにRotationを代入してからTweenを再生しましょう。
さて、これで問題解決!……ではないかもしれません。
3. Tweenを使わずにRotationだけを補間する
例えば、「常にPlayerCharacterの方を向こうとするオブジェクト」が作りたいとします。
ただし、常に完全にPlayerCharacterを見ているのではなく、少し遅れて、現在の方向とPlayerCharacterの方向のなす角度が大きいほど素早く振り向くイメージです。
これをTweenで作ろうとした場合、時間はどのように設定して、いつ開始すればよいのでしょう?
結局定期的に振り向きのTweenを更新して再生する感じになると思いますが、それだと更新タイミング毎に振り向く感じになってしまい、意図通りの挙動にならない場合もあると思います。
できれば毎フレーム、適切に補間した方向に向けてやりたいですよね。
しかし、CFrameのRotationを手動で補間するのは非常に手間がかかってしまいます。
そんな時に利用できる、指定した比率で簡単に補間を行ってくれるメソッドがちゃんと用意されています。
Lerp
それがCFrame:Lerpです。Lerpとは「Linear interpolation(線形補間)」の略で、始点の値のメソッドとして使用し、第一引数に終点の値、第二引数に0~1で比率を指定すれば、そのまんま線形補間した値を返してくれるというものです。
比率が0だと始点の値、1だと終点の値を返し、0.5だと丁度中間の値を返す、という感じです。
Lerpメソッド自体はCFrameに限ったものではなく、Vector3などにも存在するのですが、手動で補間し辛いCFrameでは特によく利用することになるかと思います。
ちなみに、手動で補間するのが大変な理由を説明しておくと、CFrameのRotationは内部では3×3の回転行列で保持されており、その補間を行うには一度四元数(Quaternion)に変換して補間を行ってからCFrameに戻す必要があるのですが、Robloxでは四元数を扱うAPIは公開されておらず、自分で四元数をきちんと理解して1から実装する必要があるためです。
四元数についてはRobloxで直接扱うことは無いものの、プログラムでより正確な回転情報を扱うためには必ず必要なもの、ということは知っておきましょう。
実際に使ってみる
それでは、Lerpを使って、常にPlayerCharacterの方を向こうとするオブジェクトを実際に作ってみましょう。
リグビルダーでWorkspace上にRigを生成したら、その子にScriptを2つ追加してください。
名前は「Setup」と「RotationLerp」としておきます。
RotationLerpの方は、RunContextをClientに設定してください。
そして、各Scriptに以下のコードを追加してください。
--!strict
-- 最初に接続したPlayerをNetworkOwnerにする.
local model_ = script.Parent
local player_ = game:GetService("Players").PlayerAdded:Wait()
local target_ = player_.Character or player_.CharacterAdded:Wait()
for _, v in ipairs(model_:GetDescendants()) do
if v:IsA("BasePart") then
v:SetNetworkOwner(player_)
end
end
-- 以下、動作確認用の移動処理.
-- Humanoid:MoveToではCFrameに代入した際に自動的に停止してしまうのでLinearVelocityを使う.
local rootPart = model_:WaitForChild("HumanoidRootPart")::Part
local LinearVelocity = Instance.new("LinearVelocity", rootPart)
LinearVelocity.Attachment0 = Instance.new("Attachment", rootPart)
LinearVelocity.ForceLimitsEnabled = false
LinearVelocity.VectorVelocity = Vector3.new(10, 0, 0)
-- 2秒毎に左右に移動させる.
while true do
task.wait(2)
LinearVelocity.VectorVelocity *= -1
end
--!strict
local model_ = script.Parent
local player_ = game:GetService("Players").LocalPlayer
local target_ = player_.Character or player_.CharacterAdded:Wait()
-- Steppedのコールバック関数.
local function onStepped(time: number, deltaTime: number)
if not target_ then
return
end
local pivot = model_:GetPivot()
local pos = pivot.Position
local targetPos = target_:GetPivot().Position
-- 回転だけを適用するためのCFrameを作成.
targetPos = Vector3.new(targetPos.X, pos.Y, targetPos.Z) -- 高さを同じにすることで、Y軸だけの回転にする.
local goalRotation = CFrame.lookAt(pos, targetPos).Rotation
local rotation = pivot.Rotation:Lerp(goalRotation, 0.05) -- 5%補間する.
model_:PivotTo(rotation + pos)
end
-- イベントを接続.
game:GetService("RunService").Stepped:Connect(onStepped)
Setupは、NetworkOwnerを最初に接続したPlayerにした上で、動作確認用のLinearVelocityを付けています。
RotationLerpはRunService.Steppedで向きを補間する処理を毎フレーム行っています。
クライアント側で動かしている理由は、サーバー側で動かすと通信のラグの分、少しガクガクして見えてしまうためです。
今回は、スムーズに補間できている事を確認するため、最初に接続したクライアントで動かしています。
Lerp関数では、5%分だけ目標のRotationへ補間しています。
毎フレーム行われる処理なので、これくらいの小さな値で丁度よくなります。
それではさっそく実行して確認してみましょう。
しっかり、少し遅れてスムーズに振り向いています。
Lerpを使って、CFrameのRotationだけを補間することができました!
4. まとめ
- CFrameで一つのプロパティのため、PositionとRotationを個別にTweenを適用できない
- CFrameValueは、CFrame型の値を保持するためだけのオブジェクト
- CFrameValueにCFrameを複製し、そちらにTweenを適用してからRotationを反映することで、RotationだけにTweenを適用できる
- CFrame.Rotationを手動で補間するには、四元数の計算を自分で行う必要があり、手間がかかる
- CFrame:Lerpを使えば、指定した比率で補間した値を取得できる
今回は、CFrameのRotationだけを補間する方法をご紹介しました。
今後もTweenが適用し辛いもの、補間し辛いものがあれば、その対応策や代替手段をご紹介していければと思います!
最後までお読みいただき、ありがとうございました!
5. 参考

当社ではRobloxを活用したゲームの開発、 また企業の商品やサービスの認知度拡大に寄与する3Dワールドの制作など、 Robloxにおける様々な活用支援を行っております。 Robloxのコンテンツ開発をご検討されている企業様は、お気軽にご相談ください。 landho.co.jp/
Discussion