バーチャル3Dクリエイター神部まゆみです(*^_^*)
この記事はUnityで表情プリセットを作りキーで切り替えるスクリプトについての記事です。
以前似たようなスクリプトを作ったけど、ブレンドシェイプ単体の切り替えだったから、キー一つでまとめて複数のブレンドシェイプを切り替えたいと思った。

Unityでブレンドシェイプを切り替えるスクリプトを動かしたのでメモ。VRMモデルの表情をキーで切り替え
Unityでブレンドシェイプを切り替えるスクリプトを動かしたのでメモです。VRMモデルの表情をキーで切り替えできます。
表情プリセットを登録してキーで切り替える VRMExpressionPresetController.cs
コードは以下です。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
// ==========================================
// 1つのBlendShape設定
// ==========================================
[System.Serializable]
public class BlendShapeValue
{
[Tooltip("ブレンドシェイプ名")]
public string blendShapeName;
[Tooltip("このブレンドシェイプの値")]
[Range(0f, 100f)]
public float weight = 100f;
}
// ==========================================
// 1つの表情プリセット
// ==========================================
[System.Serializable]
public class BlendShapeExpression
{
[Tooltip("表情の名前")]
public string expressionName;
[Tooltip("この表情を呼び出すキー")]
public KeyCode key;
[Tooltip("この表情で使用するBlendShape")]
public List<BlendShapeValue> blendShapes =
new List<BlendShapeValue>();
}
// ==========================================
// メッシュごとの設定
// ==========================================
[System.Serializable]
public class MeshExpressionSet
{
[Tooltip("操作するSkinned Mesh Renderer")]
public SkinnedMeshRenderer meshRenderer;
[Tooltip("表情プリセット")]
public List<BlendShapeExpression> expressions =
new List<BlendShapeExpression>();
}
// ==========================================
// 表情コントローラー
// ==========================================
public class VRMExpressionPresetController : MonoBehaviour
{
[SerializeField]
private List<MeshExpressionSet> meshExpressionSets =
new List<MeshExpressionSet>();
[SerializeField]
[Tooltip("表情切り替えにかかる時間")]
private float transitionDuration = 0.5f;
private Coroutine transitionCoroutine;
// ==========================================
// キー入力
// ==========================================
private void Update()
{
for (int m = 0;
m < meshExpressionSets.Count;
m++)
{
var meshSet = meshExpressionSets[m];
if (meshSet.meshRenderer == null)
continue;
for (int i = 0;
i < meshSet.expressions.Count;
i++)
{
var expression =
meshSet.expressions[i];
if (Input.GetKeyDown(expression.key))
{
StartExpressionTransition(
m,
i
);
}
}
}
}
// ==========================================
// 表情切り替え開始
// ==========================================
private void StartExpressionTransition(
int meshIndex,
int expressionIndex
)
{
if (transitionCoroutine != null)
{
StopCoroutine(transitionCoroutine);
}
transitionCoroutine =
StartCoroutine(
TransitionExpression(
meshIndex,
expressionIndex
)
);
}
// ==========================================
// 表情をフェード切り替え
// ==========================================
private IEnumerator TransitionExpression(
int meshIndex,
int expressionIndex
)
{
var meshSet =
meshExpressionSets[meshIndex];
var renderer =
meshSet.meshRenderer;
if (renderer == null)
yield break;
if (expressionIndex < 0 ||
expressionIndex >=
meshSet.expressions.Count)
{
yield break;
}
var expression =
meshSet.expressions[
expressionIndex
];
var mesh =
renderer.sharedMesh;
if (mesh == null)
yield break;
// ======================================
// 現在のBlendShape値を保存
// ======================================
float[] initialWeights =
new float[mesh.blendShapeCount];
for (int i = 0;
i < mesh.blendShapeCount;
i++)
{
initialWeights[i] =
renderer.GetBlendShapeWeight(i);
}
// ======================================
// 目標値を作る
// ======================================
float[] targetWeights =
new float[mesh.blendShapeCount];
// 最初は全部0
for (int i = 0;
i < targetWeights.Length;
i++)
{
targetWeights[i] = 0f;
}
// ======================================
// プリセットに登録された
// BlendShapeだけ指定値にする
// ======================================
foreach (
var blendShape
in expression.blendShapes
)
{
int index =
mesh.GetBlendShapeIndex(
blendShape.blendShapeName
);
if (index < 0)
{
Debug.LogWarning(
$"BlendShape " +
$"'{blendShape.blendShapeName}' " +
$"が見つかりません。"
);
continue;
}
targetWeights[index] =
Mathf.Clamp(
blendShape.weight,
0f,
100f
);
}
// ======================================
// フェード
// ======================================
float elapsed = 0f;
while (elapsed < transitionDuration)
{
elapsed += Time.deltaTime;
float t =
Mathf.Clamp01(
elapsed /
transitionDuration
);
for (int i = 0;
i < mesh.blendShapeCount;
i++)
{
renderer.SetBlendShapeWeight(
i,
Mathf.Lerp(
initialWeights[i],
targetWeights[i],
t
)
);
}
yield return null;
}
// ======================================
// 最終値
// ======================================
for (int i = 0;
i < mesh.blendShapeCount;
i++)
{
renderer.SetBlendShapeWeight(
i,
targetWeights[i]
);
}
}
}
使い方:モデルにアタッチし、プリセットに登録するブレンドシェイプを追加していく
要素が入れ子構造になっているから最初は戸惑いそうだけど、まぁすぐ理解できたから大丈夫だと思う。


まぁ表情モーキャプで動かしたり手打ちでやったりスクリプトでやったり、方式は色々あるけど、これはスクリプトでやる方式ですね。
動かしながらキーで表情をつけて、VeryAnimationのVA Recorderとかでブレンドシェイプ付きでモーション録画しておくと後で使ったりできる。
おわりに
手軽に作れて良かった。
AIに書いてもらったけど手軽すぎますね…。
また何かあれば追記します(*^_^*)


