目的
動く人形のVCIをみなさんにも作ってもらってバーチャルキャストをにぎやかにしたい。
モーションキャプチャーVCI
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]を押しておかないとデータが前のやつに追加されてしまいます。
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
2)UniVRMをインポートします。VRM 0.xの方をお使いください。
URL=https://github.com/vrm-c/UniVRM/releases
3)EasyMotionRecorderをインポートします。
URL=https://github.com/neon-izm/EasyMotionRecorder/releases
4)モーションさせたいVRMファイルをAssets欄にドラッグ&ドロップするとファイルが展開されます。展開されない場合は、UniVRMのインポートがうまくできていない可能性があります。
5)展開されたPrefabファイルをHierarchyにドラッグ&ドロップします。
6)EasyMotionRecorderのPrefabsフォルダーにあるファイルをHierarchyにドラッグ&ドロップします。
7)HierarchyにあるEasyMotionRecorderを選択し、表示されたInspector欄の2か所にVRMアバター(この場合はaiai11)をドラッグ&ドロップします。Target FPSは初期値が60ですが、多すぎるので10にしています。
8)地面がどこかわからないのでPlaneを設置します。TransformのPositionはXYZすべて0にしてください。
9)アバターを動かすプログラムを組み込みますので、Hierarchyの下にある+をクリックしてCreate Emptyしてください。名前をcontrollerにします。
10)Assets欄にC#のファイルを作りますので、Assets欄で右クリックをしてC# Scriptを押してCreateしてください。
11)作成されたファイルの名前をRigControl_ILtanに変更し、ダブルクリックすると設定している人はエディターが開きますので下のソースを貼り付けてください。エディターが開かない方はメモ帳に貼り付けてRigControl_ILtan.csで保存してください。(拡張子は.txtではなく.cs)にしてください。作ったファイルをAssets欄にドラッグ&ドロップしてください。
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にしてください。名前が違うと動きません。ダブルクリックでエディターを開いて下のコードを貼り付けてください。
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倍になります。
14)Assets欄にあるRigControl_ILtanのファイルをHierarchyのcontrollerにドラッグ&ドロップしてInspectorのHumanoidにアバター(この場合はaiai11)をドラッグ&ドロップしてください。Now_motionにモーションNo.を入れてください。二次元配列の左側の数字です。
15)Main Cameraの位置を調整してアバターが映るようにします。
16)HierarchyのEasyMotionRecorderを選択し再生ボタンを押します。動き始める前にキーボードの[R]キーを1回押して下さい。Frame Indexの数字が増えていくとキャプチャーされています。動作が止まったら[X]キーを押してください。UnityエディターをSeaneにしていると[X]キーを押してもUnityにキー操作を取られて停止しない場合があります。停止しないとファイルが作成されません。
17)Assets欄にResourcesというフォルダーが作成されて、その中にRecordMotion・・・というファイルができていると思います。そのファイルをクリックするとちょっと読込時間がかかってInspectorにデータが表示されると思います。そうしたら、Inspectorのタブの下のRecordMotionの辺りを右クリックしてください。メニューが出たらExport as Generic animation clipsをクリックしてください。するとResourcesフォルダーにもう一つファイルが作成されます。
18)作成されたファイルを選択して、InspectorタブのそのInspectorという文字を右クリックするとメニューが表示されますので、Debugをクリックしてください。
19)Legacyにチェックを入れてください。これを忘れると動きません。タグがInspectorからDebugに変わってますが、これを右クリックしてNormalをクリックしてもとに戻しておいてください。これでモーションの作成は完了です。
アニメーションが動くか簡易テスト
1)シーンを新しく作ってアバターのPrefabをHierarchyにドラッグ&ドロップする。右クリックでメニューを開きUnpackをクリックする。
2)InspectorのTransform以外の項目をRemove Componentで全部消す。
3)Add ComponentでAnimationを追加する。
4)ResourcesフォルダーにあるアニメーションファイルをAnimationsの文字の上にドラッグ&ドロップする。そして表示されたファイル名をダブルクリックする。
5)Hierarchy欄でアバターが選択されているのを確認した上で、再生ボタンをクリックするとアバターが動きます。
最後に
アドベントカレンダーに参加しようかどうか迷っていたのですが、自分が日頃やっているキャラクターをプログラムで動かすということが説明できてよかったです。今回は表情が動かないアニメーションになりましたが、アニメーションデータに表情の項目を追加して手打ちで動かすか、最近バーチャルキャストではLUAでブレンドシェイプが制御できるようになったのでそちらでやるのか、という感じです。
それはさておき、今回このような機会を与えていただき誠にありがとうございました。また来年できたらいいな。