はじめに

本課題は[Roblox ムービー演出の作り方(3) Scriptでの処理〜準備編〜]の続きとして、各ターゲットのCFrameのデータをフレーム単位に分解する処理を解説します。
MoonAnimator2でのアニメーションの作成方法等については、[Roblox ムービー演出の作り方(1) MoonAnimator2][Roblox ムービー演出の作り方(2) データ構造とエクスポート]をご確認ください。

下準備

今回は各ターゲット用のアニメーションのキーフレームのCFrameのデータを、1フレーム毎に分解するCreateAnimationFrames関数を用意します。
関数名については別名でも構いませんが、前回の記事[Roblox ムービー演出の作り方(3) Scriptでの処理〜準備編〜]で用意したLocalScriptのSetup関数内で呼び出されるので、LocalScript内のSetup関数より前に定義する必要があります。
パラメータとしては、キーフレーム情報が入った配列と、アニメーションの総フレーム数を受け取る形になります。

各ターゲットの位置データをフレーム単位に分解

まずパラメータで受け取ったキーフレーム情報が入った配列から、キーフレームでのターゲットのCFrameのデータを取得し、ソート用の配列に込めていきます。

今回のサンプルの場合、以下の画像にある様にRigに対応した1フォルダ配下のCFrameフォルダ内にキーフレームをあらわす数字のフォルダ(0、180、90)が複数存在し、その最深部にある0というCFrameオブジェクトに、このキーフレームでのRigのCFrameのデータが入っています。

この最深部にあるキーフレーム毎のCFrameのデータを元に1フレーム単位のCFrameのデータを計算していくのですが、CFrameフォルダからGetChildren関数でキーフレームフォルダを取得すると0、180、90の順番で取得されてしまうため、まずはキーフレームの順番にソートします。①②

次にソートされたキーフレーム毎のCFrameのデータを元に、キーフレーム同士の間の1フレーム毎のCFrameのデータを計算します。
CFrameの要素としては位置情報の他にターゲットの向いている方向も含まれているので、その要素毎に計算する必要があります。③

残りの作業として、最後のキーフレームとアニメーション全体の総フレーム数が異なる場合、その間を1フレームずつ補完する必要があります。
この作業は、ターゲットによってフレームの数が異なるとアニメーションを再生した際に終了を判断するために処理を入れなければいけなくなるため、アニメーション全体のフレーム数を合わせるためにも総フレーム数分はデータを作成します。
方法としては既存の最後の1フレームのCFrameのデータを、総フレーム数になるまでコピーしてあげるだけです。④

ここまでの手順のサンプルを以下に用意したので、コメントも含めて参考にしてみてください。

StartPlayer/StarterCharacterScripts/LocalScript

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
-- アニメーションのフレーム毎のデータを作成する関数
function CreateAnimationFrames(frameData, frameLength)
    local frameArray = {}
 
    -- キーフレーム毎のCFrameのデータをソートするための配列
    local sortFrames = {}
    -- ①キーフレーム毎にCFrameのデータを取得
    for i, data in pairs(frameData.CFrame:GetChildren()) do
        if tonumber(data.Name) ~= nil then
            local keyFrames = data.Values:GetChildren()
            sortFrames[#sortFrames + 1] = {
                order = tonumber(data.Name),
                cFrameValue = keyFrames[#keyFrames]
            }
        end
    end
 
    -- ②order順にソート
    table.sort(sortFrames, function(a, b)
        return a.order < b.order
    end)
 
    -- ③1フレーム毎のCFrameのデータを作成
    local frameIndex = 1
    for i = 1, #sortFrames do
        if sortFrames[i]["order"] == 0 then
            frameArray[0] = sortFrames[i]["cFrameValue"].Value
        end
 
        if i < #sortFrames then
 
            local currentFrameData = sortFrames[i]["cFrameValue"].Value
            local nextFrameData = sortFrames[i + 1]["cFrameValue"].Value
 
            -- 次のキーフレームとの差分を要素毎に取得
            local diff = {
                pos = nextFrameData.Position - currentFrameData.Position,
                xv = nextFrameData.XVector - currentFrameData.XVector,
                yv = nextFrameData.YVector - currentFrameData.YVector,
                zv = nextFrameData.ZVector - currentFrameData.ZVector,
            }
 
            local currentIndex = 1
            -- フレーム毎のCFrameを計算
            while true do
                -- 次のフレームまでの割合を計算
                local coefficient = currentIndex / (sortFrames[i + 1]["order"] - sortFrames[i]["order"])
                -- 現在のフレームのCFrameのデータを差分に対する割合で計算
                frameArray[frameIndex] = CFrame.fromMatrix(
                    currentFrameData.Position + diff["pos"] * coefficient,
                    currentFrameData.XVector + diff["xv"] * coefficient,
                    currentFrameData.YVector + diff["yv"] * coefficient,
                    currentFrameData.ZVector + diff["zv"] * coefficient
                )
 
                -- 現在のフレームが次のキーフレームと同じならループから抜ける
                if frameIndex == sortFrames[i + 1]["order"] then
                    frameIndex = frameIndex + 1
                    break
                end
 
                currentIndex = currentIndex + 1
                frameIndex = frameIndex + 1
            end
        end
    end
 
    -- ④最後のキーフレームが総フレーム数により小さい場合は最後のフレーム情報で補完
    if frameIndex < frameLength then
        local lastFrame = frameArray[#frameArray]
        for i = frameIndex, frameLength do
            frameArray[i] = lastFrame
        end
    end
 
    return frameArray
end

次の手順

次回の記事では、今回作成した1フレーム毎のデータを元にターゲットを移動する処理を解説します。