LoginSignup
5
1

バーチャルキャストでモーションをキャプチャーしてUnity上でEasyMotionRecorderを使ってanimationファイルにする。

Last updated at Posted at 2023-12-11

目的

 動く人形のVCIをみなさんにも作ってもらってバーチャルキャストをにぎやかにしたい。

モーションキャプチャーVCI

2023120112550457.jpg
URL=https://virtualcast.jp/products/7c988b3f9dcae3a0c7561953ca0c1342b08094805ace8a3a0d14bbbb6ee6f246
 扱い方はVCIの説明欄に書いてありますのでそちらをご覧ください。

モーションデータ取得の説明

1)モーションデータとは対象アバターのボーンの位置と回転データです。ただし位置はHipボーンのみです。
2)vci.studio.GetAvatars()を用いて対象アバターを選択します。
3)updateAll()の中で指定したキャプチャー間隔(100msまたは200msに1回)でGetBoneTransform(ボーン名)をして、ボーン名.positionとボーン名.rotationを用いてボーンの位置と回転データを取得します。VCIの仕様上ワールド座標しか取得できません。
4)配列に文字列として取得したデータを入れて、[書出]キューブをグリップすることによりprint命令でデータを送ります。
5)書き出す前にaisot様が作られたvci-webmetryをブラウザ(Chromeとedgeは使えると思います)で表示しておきます。
URLは、https://aisot.github.io/vci-webmetry/
ですのでブラウザでURLを開けばバーチャルキャストからのプリント文を拾ってくれますのでデータを下の画像の一番下の行にある[Copy]を押してとりあえずメモ帳に貼り付けて保存してください。[書出]をグリップする前に一番下の行にある[Clear]を押しておかないとデータが前のやつに追加されてしまいます。
Calendar.png

LUAコード

main.lua
vci.ResetRootTransform()
local avatar = vci.studio.GetAvatars()
local avatar_no = 1
local hyouji_panel = vci.assets.GetTransform("hyouji_panel")
local old_time = vci.me.Time.TotalMilliseconds
local _time = 0
local total_time = 0
local rec_frame_kankaku = 200 --キャプチャー間隔ミリ秒
local max_koma = 2000
local koma_no = 0
local koma_data = {}
local first_flg = true
local cap_no = 1
local pose_string = ""
local data_head = ""
local trace_flg = false
local avatar_name = ""
local Obj2 = {
        "Hips",--1
        "Head",--2
        "Neck",--3
        "Chest",--4
        "UpperChest",--5
        "Spine",--6
        "LeftShoulder",--7
        "LeftUpperArm",--8
        "LeftLowerArm",--9
        "LeftHand",--10
        "RightShoulder",--11
        "RightUpperArm",--12
        "RightLowerArm",--13
        "RightHand",--14
        "LeftUpperLeg",--15
        "LeftLowerLeg",--16
        "LeftFoot",--17
        "LeftToes",--18
        "RightUpperLeg",--19
        "RightLowerLeg",--20
        "RightFoot",--21
        "RightToes",--22
        "LeftEye",--23
        "RightEye",--24
        "LeftIndexProximal",--25
        "LeftIndexIntermediate",--26
        "LeftIndexDistal",--27
        "LeftMiddleProximal",--28
        "LeftMiddleIntermediate",--29
        "LeftMiddleDistal",--30
        "LeftRingProximal",--31
        "LeftRingIntermediate",--32
        "LeftRingDistal",--33
        "LeftLittleProximal",--34
        "LeftLittleIntermediate",--35
        "LeftLittleDistal",--36
        "LeftThumbProximal",--37
        "LeftThumbIntermediate",--38
        "LeftThumbDistal",--39
        "RightIndexProximal",--40
        "RightIndexIntermediate",--41
        "RightIndexDistal",--42
        "RightMiddleProximal",--43
        "RightMiddleIntermediate",--44
        "RightMiddleDistal",--45
        "RightRingProximal",--46
        "RightRingIntermediate",--47
        "RightRingDistal",--48
        "RightLittleProximal",--49
        "RightLittleIntermediate",--50
        "RightLittleDistal",--51
        "RightThumbProximal",--52
        "RightThumbIntermediate",--53
        "RightThumbDistal"--54
}

function updateAll()
    if first_flg == true and Obj2 ~= nil and avatar[1].GetId() ~= nil then
        avatar_name = avatar[avatar_no].GetName()
        vci.assets._ALL_SetText("Text",avatar_name.."\n"..tostring(koma_no).."/"..tostring(max_koma))
        first_flg = false
    end
    if vci.assets.IsMine and first_flg == false and trace_flg == true then
        local now_time = vci.me.Time.TotalMilliseconds
        _time =  now_time - old_time
        total_time = total_time + _time
        old_time = now_time
        if total_time > rec_frame_kankaku then
            pose_string = ""
            for i = 1, #Obj2 do
                local bone = avatar[avatar_no].GetBoneTransform(Obj2[i])
                if bone ~= nil then
                    local local_rot = bone.rotation
                    if i==1 then
                        local kosi_pos = bone.position
                        local st1 = string.format("%0.3f,%0.3f,%0.3f,%0.3f,%0.3f,%0.3f,%0.3f,",kosi_pos.x,kosi_pos.y,kosi_pos.z,local_rot.x,local_rot.y,local_rot.z,local_rot.w)
                        pose_string = pose_string..i..","..st1
                    else
                        local st2 = string.format("%0.3f,%0.3f,%0.3f,%0.3f,",local_rot.x,local_rot.y,local_rot.z,local_rot.w)
                        pose_string = pose_string..i..","..st2
                    end
                end
            end
            pose_string = pose_string:gsub("0.000", "0")
            pose_string = pose_string:gsub("-0.000", "0")
            pose_string = pose_string:gsub("1.000", "1")
            koma_no = koma_no + 1
            if koma_no > max_koma then
                trace_flg = false
                return
            end
            koma_data[koma_no] = pose_string
            vci.assets._ALL_SetText("Text",avatar_name.."\n"..tostring(koma_no).."/"..tostring(max_koma))
            total_time = 0
        end
    end
end

function kakidasi()
    data_head = "cap_data["..tostring(cap_no).."][0] = \""
    data_head = data_head.."1 P"..tostring(rec_frame_kankaku)..","
    data_head = data_head.."\";\r"
    print(data_head)
    vci.StartCoroutine(
    coroutine.create(
        function()
            for i=1,#koma_data do
                local cap_d = "cap_data["..tostring(cap_no).."]["..i.."] = \""..koma_data[i].."\";\r"
                print(cap_d)
                sleep(0.05)
            end
        end
    )
)
end

function sleep(sec)
    local t0 = os.time() + sec
    while os.time() < t0 do
        -- コルーチンの実行を中断する
        coroutine.yield()
    end
end

function onUse(use)
    if use == "hyouji_panel" then
        avatar_no = avatar_no + 1
        if avatar_no > #avatar then
            avatar_no = 1
        end
        avatar_name = avatar[avatar_no].GetName()
        vci.assets._ALL_SetText("Text",avatar_name.."\n"..tostring(koma_no).."/"..tostring(max_koma))
    end
    if use == "rec_cube" then
        if trace_flg == true then
            trace_flg = false
        else
            trace_flg = true
            koma_no = 0
            koma_data = {}
            total_time = 0
            old_time = vci.me.Time.TotalMilliseconds
        end
    end
    if use == "write_cube" then
        trace_flg = false
        kakidasi()
    end
    if use == "frame_cube" then
        if rec_frame_kankaku == 200 then
            rec_frame_kankaku = 100
        elseif rec_frame_kankaku == 100 then
            rec_frame_kankaku = 200
        end
        vci.assets._ALL_SetText("Text4","キャプチャ\n間隔\n"..tostring(rec_frame_kankaku).."ms")
    end
    if use == "no_cube" then
        cap_no = cap_no + 1
        vci.assets._ALL_SetText("Text5","キャプチャ\nNo="..tostring(cap_no))
    end
end

Unity

1)レイアウトはTallを使ってます。
1.png

2)UniVRMをインポートします。VRM 0.xの方をお使いください。
URL=https://github.com/vrm-c/UniVRM/releases
2.png

3.png

4.png

3)EasyMotionRecorderをインポートします。
URL=https://github.com/neon-izm/EasyMotionRecorder/releases
5.png
6.png

4)モーションさせたいVRMファイルをAssets欄にドラッグ&ドロップするとファイルが展開されます。展開されない場合は、UniVRMのインポートがうまくできていない可能性があります。
7.png

5)展開されたPrefabファイルをHierarchyにドラッグ&ドロップします。
8.png

6)EasyMotionRecorderのPrefabsフォルダーにあるファイルをHierarchyにドラッグ&ドロップします。
9.png

7)HierarchyにあるEasyMotionRecorderを選択し、表示されたInspector欄の2か所にVRMアバター(この場合はaiai11)をドラッグ&ドロップします。Target FPSは初期値が60ですが、多すぎるので10にしています。
10.png

8)地面がどこかわからないのでPlaneを設置します。TransformのPositionはXYZすべて0にしてください。
11.png

9)アバターを動かすプログラムを組み込みますので、Hierarchyの下にある+をクリックしてCreate Emptyしてください。名前をcontrollerにします。
12.png
13.png

10)Assets欄にC#のファイルを作りますので、Assets欄で右クリックをしてC# Scriptを押してCreateしてください。
14.png

11)作成されたファイルの名前をRigControl_ILtanに変更し、ダブルクリックすると設定している人はエディターが開きますので下のソースを貼り付けてください。エディターが開かない方はメモ帳に貼り付けてRigControl_ILtan.csで保存してください。(拡張子は.txtではなく.cs)にしてください。作ったファイルをAssets欄にドラッグ&ドロップしてください。
15.png

RigControl_ILtan.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System;
using VRM;

public class RigControl_ILtan : MonoBehaviour
{
    public GameObject humanoid;
    private Vector3 bodyRotation = new Vector3(0, 0, 0);
    private Vector3 bodyPosition = new Vector3(0, 0, 0);
    Animator animator;
    List<HumanBodyBones> BoneList;
    private float countup;
    private float timeLimit = 0.2f;
    private bool anime_flg = false;
    Data_file_ILtan d_file_ILtan;
    private string[] pose_data;
    private float[] now_koma;
    private float[] next_koma;
    private Quaternion[] now_angle = new Quaternion[60];
    private Quaternion[] next_angle = new Quaternion[60];
    private Vector3 now_pos;
    private Vector3 next_pos;
    [SerializeField] int now_motion = 1;
    private int koma_no = 1;
    private int koma_suu;
    private float idou_pos_x = 0.0f;
    private float idou_pos_y = 0.0f;
    private float idou_pos_z = 0.0f;
    private float idou_rot_x = 0.0f;
    private float idou_rot_y = 0.0f;
    private float idou_rot_z = 0.0f;
    private int[] speed;
    private BlendShapePreset now_face;
    private BlendShapePreset[] currentFace = { BlendShapePreset.Neutral, BlendShapePreset.Neutral, BlendShapePreset.Blink, BlendShapePreset.Angry, BlendShapePreset.Joy, BlendShapePreset.Sorrow, BlendShapePreset.Fun, BlendShapePreset.A, BlendShapePreset.I, BlendShapePreset.U, BlendShapePreset.E, BlendShapePreset.O };
    VRMBlendShapeProxy proxy;
    void OnEnable()
    {
        BoneList = new List<HumanBodyBones>();
        BoneList.Add(HumanBodyBones.Hips); //1
        BoneList.Add(HumanBodyBones.Head); //2
        BoneList.Add(HumanBodyBones.Neck); //3
        BoneList.Add(HumanBodyBones.Chest); //4
        BoneList.Add(HumanBodyBones.UpperChest); //5
        BoneList.Add(HumanBodyBones.Spine); //6
        BoneList.Add(HumanBodyBones.LeftShoulder); //7
        BoneList.Add(HumanBodyBones.LeftUpperArm); //8
        BoneList.Add(HumanBodyBones.LeftLowerArm); //9
        BoneList.Add(HumanBodyBones.LeftHand); //10
        BoneList.Add(HumanBodyBones.RightShoulder); //11
        BoneList.Add(HumanBodyBones.RightUpperArm); //12
        BoneList.Add(HumanBodyBones.RightLowerArm); //13
        BoneList.Add(HumanBodyBones.RightHand); //14
        BoneList.Add(HumanBodyBones.LeftUpperLeg); //15
        BoneList.Add(HumanBodyBones.LeftLowerLeg); //16
        BoneList.Add(HumanBodyBones.LeftFoot); //17
        BoneList.Add(HumanBodyBones.LeftToes); //18
        BoneList.Add(HumanBodyBones.RightUpperLeg); //19
        BoneList.Add(HumanBodyBones.RightLowerLeg); //20
        BoneList.Add(HumanBodyBones.RightFoot); //21
        BoneList.Add(HumanBodyBones.RightToes); //22
        BoneList.Add(HumanBodyBones.LeftEye); //23
        BoneList.Add(HumanBodyBones.RightEye); //24
        BoneList.Add(HumanBodyBones.LeftIndexProximal); //25
        BoneList.Add(HumanBodyBones.LeftIndexIntermediate); //26
        BoneList.Add(HumanBodyBones.LeftIndexDistal); //27
        BoneList.Add(HumanBodyBones.LeftMiddleProximal); //28
        BoneList.Add(HumanBodyBones.LeftMiddleIntermediate); //29
        BoneList.Add(HumanBodyBones.LeftMiddleDistal); //30
        BoneList.Add(HumanBodyBones.LeftRingProximal); //31
        BoneList.Add(HumanBodyBones.LeftRingIntermediate); //32
        BoneList.Add(HumanBodyBones.LeftRingDistal); //33
        BoneList.Add(HumanBodyBones.LeftLittleProximal); //34
        BoneList.Add(HumanBodyBones.LeftLittleIntermediate); //35
        BoneList.Add(HumanBodyBones.LeftLittleDistal); //36
        BoneList.Add(HumanBodyBones.LeftThumbProximal); //37
        BoneList.Add(HumanBodyBones.LeftThumbIntermediate); //38
        BoneList.Add(HumanBodyBones.LeftThumbDistal); //39
        BoneList.Add(HumanBodyBones.RightIndexProximal); //40
        BoneList.Add(HumanBodyBones.RightIndexIntermediate); //41
        BoneList.Add(HumanBodyBones.RightIndexDistal); //42
        BoneList.Add(HumanBodyBones.RightMiddleProximal); //43
        BoneList.Add(HumanBodyBones.RightMiddleIntermediate); //44
        BoneList.Add(HumanBodyBones.RightMiddleDistal); //45
        BoneList.Add(HumanBodyBones.RightRingProximal); //46
        BoneList.Add(HumanBodyBones.RightRingIntermediate); //47
        BoneList.Add(HumanBodyBones.RightRingDistal); //48
        BoneList.Add(HumanBodyBones.RightLittleProximal); //49
        BoneList.Add(HumanBodyBones.RightLittleIntermediate); //50
        BoneList.Add(HumanBodyBones.RightLittleDistal); //51
        BoneList.Add(HumanBodyBones.RightThumbProximal); //52
        BoneList.Add(HumanBodyBones.RightThumbIntermediate); //53
        BoneList.Add(HumanBodyBones.RightThumbDistal); //54
    }
    void Start()
    {
        animator = humanoid.GetComponent<Animator>();
        motion_start(now_motion);
    }
    void Update()
    {
        if (anime_flg == true)
        {
            countup += Time.deltaTime;
            if (countup >= timeLimit)
            {
                if (pose_data[koma_no] != null)
                {
                    countup = 0.0f;
                    koma_no++;
                    if (koma_no == koma_suu)
                    {
                        anime_flg = false;
                        return;
                    }
                    now_koma = load_pose(pose_data[koma_no]);
                    next_koma = load_pose(pose_data[koma_no + 1]);
                    if (speed[koma_no] != 0)
                    {
                        timeLimit = (float)speed[koma_no] / 1000;
                    }
                    koma_yomikomi();
                }
            }
            else
            {
                float num = countup / timeLimit;

                Vector3 iti = Vector3.Lerp(now_pos, next_pos, num);
                for (int j = 0; j < BoneList.Count; j++)
                {
                    int bone_no = (int)now_koma[0];
                    if (j > 0)
                    {
                        bone_no = (int)now_koma[j * 5 + 3];
                    }
                    if (bone_no == 0) break;
                    Quaternion kakudo = Quaternion.Lerp(now_angle[bone_no], next_angle[bone_no], num);
                    if (animator.GetBoneTransform(BoneList[bone_no - 1]) != null)
                    {
                        animator.GetBoneTransform(BoneList[bone_no - 1]).rotation = kakudo;
                    }
                    if (bone_no == 1)
                    {
                        animator.GetBoneTransform(BoneList[bone_no - 1]).position = iti + bodyPosition;
                    }
                }
            }
        }
    }

    public void motion_start(int data_no)
    {
        System.Threading.Thread.Sleep(1000);

        speed = new int[2002];
        d_file_ILtan = new Data_file_ILtan();
        pose_data = d_file_ILtan.cap_data_load(data_no);
        koma_suu = d_file_ILtan.check_cap(data_no);
        string[] seigyo_data = pose_data[0].Split(',');
        for (int i = 0; i < seigyo_data.Length - 1; i++)
        {
            string[] koma_seigyo = seigyo_data[i].Split(' ');
            int koma_seigyo_gyou = int.Parse(koma_seigyo[0]);
            for (int j = 1; j < koma_seigyo.Length; j++)
            {
                if (koma_seigyo[j].Substring(0, 1) == "X") //X軸移動
                {
                    idou_pos_x = float.Parse(koma_seigyo[j].Substring(1));
                }
                else if (koma_seigyo[j].Substring(0, 1) == "Y") //Y軸移動
                {
                    idou_pos_y = float.Parse(koma_seigyo[j].Substring(1));
                }
                else if (koma_seigyo[j].Substring(0, 1) == "Z") //Z軸移動
                {
                    idou_pos_z = float.Parse(koma_seigyo[j].Substring(1));
                }
                else if (koma_seigyo[j].Substring(0, 1) == "P") //スピード変更
                {
                    speed[koma_seigyo_gyou] = int.Parse(koma_seigyo[j].Substring(1));
                }
                bodyPosition = new Vector3(idou_pos_x, idou_pos_y, idou_pos_z);
                humanoid.transform.position = bodyPosition;
            }
        }
        now_koma = load_pose(pose_data[1]);
        next_koma = load_pose(pose_data[2]);
        if (speed[koma_no] != 0)
        {
            timeLimit = (float)speed[koma_no] / 1000;
        }
        koma_yomikomi();
        countup = 0.0f;
        anime_flg = true;
    }
    public void koma_yomikomi()
    {
        for (int j = 0; j < BoneList.Count; j++)
        {
            var bone_no = (int)now_koma[0];
            if (j > 0)
            {
                bone_no = (int)now_koma[j * 5 + 3];
            }
            if (bone_no == 0)
            {
                break;
            }

            var now_rote_x = now_koma[j * 5 + 4];
            var now_rote_y = now_koma[j * 5 + 5];
            var now_rote_z = now_koma[j * 5 + 6];
            var now_rote_w = now_koma[j * 5 + 7];
            var next_rote_x = next_koma[j * 5 + 4];
            var next_rote_y = next_koma[j * 5 + 5];
            var next_rote_z = next_koma[j * 5 + 6];
            var next_rote_w = next_koma[j * 5 + 7];
            now_angle[bone_no] = new Quaternion(now_rote_x, now_rote_y, now_rote_z, now_rote_w);
            next_angle[bone_no] = new Quaternion(next_rote_x, next_rote_y, next_rote_z, next_rote_w);
            Quaternion kakudo = Quaternion.Lerp(now_angle[bone_no], next_angle[bone_no], 0);
            if (animator.GetBoneTransform(BoneList[bone_no - 1]) != null && now_angle != null && next_angle != null)
            {
                if (bone_no == 1)
                {
                    var now_pos_x = now_koma[j * 8 + 1];
                    var now_pos_y = now_koma[j * 8 + 2];
                    var now_pos_z = now_koma[j * 8 + 3];

                    var next_pos_x = next_koma[j * 8 + 1];
                    var next_pos_y = next_koma[j * 8 + 2];
                    var next_pos_z = next_koma[j * 8 + 3];
                    now_pos = new Vector3(now_pos_x, now_pos_y, now_pos_z);
                    next_pos = new Vector3(next_pos_x, next_pos_y, next_pos_z);
                    Vector3 iti = Vector3.Lerp(now_pos, next_pos, 0);
                    animator.GetBoneTransform(BoneList[bone_no - 1]).position = iti + bodyPosition;
                }
                animator.GetBoneTransform(BoneList[bone_no - 1]).rotation = kakudo;
            }
        }
    }

    public float[] load_pose(String pose_data)
    {
        float bone_no = 0;
        float p_x = 0f;
        float p_y = 0f;
        float p_z = 0f;
        float r_x = 0f;
        float r_y = 0f;
        float r_z = 0f;
        float r_w = 0f;
        int ind1 = 0;
        int ind2 = 0;
        float[] p_data = new float[2002];
        int count = 0;
        if (pose_data != null)
        {
            ind2 = pose_data.IndexOf(",", ind1);
            bone_no = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
            ind1 = ind2 + 1;
            ind2 = pose_data.IndexOf(",", ind1);
            p_x = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
            ind1 = ind2 + 1;
            ind2 = pose_data.IndexOf(",", ind1);
            p_y = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
            ind1 = ind2 + 1;
            ind2 = pose_data.IndexOf(",", ind1);
            p_z = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
            ind1 = ind2 + 1;
            ind2 = pose_data.IndexOf(",", ind1);
            r_x = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
            ind1 = ind2 + 1;
            ind2 = pose_data.IndexOf(",", ind1);
            r_y = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
            ind1 = ind2 + 1;
            ind2 = pose_data.IndexOf(",", ind1);
            r_z = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
            ind1 = ind2 + 1;
            ind2 = pose_data.IndexOf(",", ind1);
            r_w = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
            ind1 = ind2 + 1;
            p_data[0] = bone_no;
            p_data[1] = p_x;
            p_data[2] = p_y;
            p_data[3] = p_z;
            p_data[4] = r_x;
            p_data[5] = r_y;
            p_data[6] = r_z;
            p_data[7] = r_w;
            count++;
            while (ind1 < pose_data.Length)
            {
                ind2 = pose_data.IndexOf(",", ind1);
                bone_no = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
                ind1 = ind2 + 1;
                ind2 = pose_data.IndexOf(",", ind1);
                r_x = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
                ind1 = ind2 + 1;
                ind2 = pose_data.IndexOf(",", ind1);
                r_y = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
                ind1 = ind2 + 1;
                ind2 = pose_data.IndexOf(",", ind1);
                r_z = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
                ind1 = ind2 + 1;
                ind2 = pose_data.IndexOf(",", ind1);
                r_w = float.Parse(pose_data.Substring(ind1, ind2 - ind1));
                ind1 = ind2 + 1;
                p_data[count * 5 + 3] = bone_no;
                p_data[count * 5 + 4] = r_x;
                p_data[count * 5 + 5] = r_y;
                p_data[count * 5 + 6] = r_z;
                p_data[count * 5 + 7] = r_w;
                count++;
            }
            return p_data;
        }
        else
        {
            return null;
        }
    }
}

12)さっきの要領でAssets欄にC#のファイルをもう一つ作ります。今回は名前をdata_file_ILtanにしてください。名前が違うと動きません。ダブルクリックでエディターを開いて下のコードを貼り付けてください。

16.png

data_file_ILtan.cs
using UnityEngine;

public class Data_file_ILtan
{
    public string[][] cap_data = new string[50][];
    public Data_file_ILtan()
    {
        for (int j = 0; j < 50; j++)
        {
            cap_data[j] = new string[2002];
        }

    //ここにデータを貼る。

    }

    public int check_cap(int no)
    {
        int i = 1;
        while (cap_data[no][i] != null)
        {
            i++;
        }
        return i - 1;
    }

    public int motion_suu()
    {
        int i = 1;
        while (cap_data[i][1] != null)
        {
            i++;
        }
        return i - 1;
    }

    public string[] cap_data_load(int no)
    {
        string[] ans = new string[2002];
        int i = 0;
        while (cap_data[no][i] != null)
        {
            ans[i] = cap_data[no][i];
            i++;
        }
        return ans;
    }
}

13)モーションキャプチャーで作成したデータを「//ここにデータを貼る。」の位置に貼り付けて保存してください。エディターが開かない方はメモ帳に貼り付けてdata_file_ILtan.csで保存してください。(拡張子は.txtではなく.cs)にしてください。作ったファイルをAssets欄にドラッグ&ドロップしてください。データの1行目を「cap_data[1][0] = "1 P200 X0.5 Y1 Z-1.5,";」に変更すると、X軸に0.5m、Y軸に1m、Z軸に-1.5m位置を移動できます。P200は200ミリ秒後に次のデータに移ることを意味してますので、P100にするとモーションの速度が2倍になります。
17.png

14)Assets欄にあるRigControl_ILtanのファイルをHierarchyのcontrollerにドラッグ&ドロップしてInspectorのHumanoidにアバター(この場合はaiai11)をドラッグ&ドロップしてください。Now_motionにモーションNo.を入れてください。二次元配列の左側の数字です。
18.png

15)Main Cameraの位置を調整してアバターが映るようにします。
19.png

16)HierarchyのEasyMotionRecorderを選択し再生ボタンを押します。動き始める前にキーボードの[R]キーを1回押して下さい。Frame Indexの数字が増えていくとキャプチャーされています。動作が止まったら[X]キーを押してください。UnityエディターをSeaneにしていると[X]キーを押してもUnityにキー操作を取られて停止しない場合があります。停止しないとファイルが作成されません。
20.png

17)Assets欄にResourcesというフォルダーが作成されて、その中にRecordMotion・・・というファイルができていると思います。そのファイルをクリックするとちょっと読込時間がかかってInspectorにデータが表示されると思います。そうしたら、Inspectorのタブの下のRecordMotionの辺りを右クリックしてください。メニューが出たらExport as Generic animation clipsをクリックしてください。するとResourcesフォルダーにもう一つファイルが作成されます。
21.png

18)作成されたファイルを選択して、InspectorタブのそのInspectorという文字を右クリックするとメニューが表示されますので、Debugをクリックしてください。
22.png

19)Legacyにチェックを入れてください。これを忘れると動きません。タグがInspectorからDebugに変わってますが、これを右クリックしてNormalをクリックしてもとに戻しておいてください。これでモーションの作成は完了です。
23.png

アニメーションが動くか簡易テスト

1)シーンを新しく作ってアバターのPrefabをHierarchyにドラッグ&ドロップする。右クリックでメニューを開きUnpackをクリックする。
25.png

2)InspectorのTransform以外の項目をRemove Componentで全部消す。
26.png

3)Add ComponentでAnimationを追加する。
27.png

4)ResourcesフォルダーにあるアニメーションファイルをAnimationsの文字の上にドラッグ&ドロップする。そして表示されたファイル名をダブルクリックする。
28.png

5)Hierarchy欄でアバターが選択されているのを確認した上で、再生ボタンをクリックするとアバターが動きます。
29.png

最後に

 アドベントカレンダーに参加しようかどうか迷っていたのですが、自分が日頃やっているキャラクターをプログラムで動かすということが説明できてよかったです。今回は表情が動かないアニメーションになりましたが、アニメーションデータに表情の項目を追加して手打ちで動かすか、最近バーチャルキャストではLUAでブレンドシェイプが制御できるようになったのでそちらでやるのか、という感じです。
 それはさておき、今回このような機会を与えていただき誠にありがとうございました。また来年できたらいいな。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1