Files
Master-Arbeit-Tom-Hempel/Unity-Master/Assets/Scripts/RPMFacialAnimator.cs
2025-09-21 22:42:26 +02:00

580 lines
20 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class FacialExpression
{
public string name;
public BlendShapeWeight[] blendShapeWeights;
[Range(0.1f, 5f)]
public float duration = 1f;
[Range(0f, 2f)]
public float holdTime = 0.5f;
}
[System.Serializable]
public class BlendShapeWeight
{
public string blendShapeName;
[Range(0f, 1f)]
public float weight = 0.5f;
}
public class RPMFacialAnimator : MonoBehaviour
{
[Header("Avatar Configuration")]
[SerializeField] private Transform avatarRoot;
[SerializeField] private string headRendererName = "Renderer_Head";
[Header("Animation Settings")]
[SerializeField] private bool autoPlay = true;
[SerializeField] private bool loopAnimations = true;
[SerializeField] private float timeBetweenExpressions = 2f;
[SerializeField] private bool smoothTransitions = true;
[SerializeField] private AnimationCurve transitionCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[Header("Predefined Expressions")]
[SerializeField] private FacialExpression[] facialExpressions;
[Header("Manual Controls")]
[SerializeField] private bool enableManualControls = true;
[Range(0f, 1f)] public float mouthSmile = 0f;
[Range(0f, 1f)] public float mouthOpen = 0f;
[Range(0f, 1f)] public float eyeBlinkLeft = 0f;
[Range(0f, 1f)] public float eyeBlinkRight = 0f;
[Range(0f, 1f)] public float browInnerUp = 0f;
[Range(0f, 1f)] public float cheekPuff = 0f;
[Range(0f, 1f)] public float jawOpen = 0f;
[Header("Auto Blink Settings")]
[SerializeField] private bool enableAutoBlink = true;
[SerializeField] private float blinkInterval = 3f;
[SerializeField] private float blinkDuration = 0.15f;
[SerializeField] private float blinkRandomness = 1f;
[Header("Debug")]
[SerializeField] private bool showDebugInfo = false;
[SerializeField] private bool listAllBlendShapes = false;
private SkinnedMeshRenderer headRenderer;
private Dictionary<string, int> blendShapeIndexMap;
private Coroutine expressionCoroutine;
private Coroutine blinkCoroutine;
private bool isAnimating = false;
// RPM ARKit Blendshape names for reference
private readonly string[] arkitBlendShapes = {
// Eyes
"eyeBlinkLeft", "eyeBlinkRight", "eyeLookDownLeft", "eyeLookInLeft", "eyeLookOutLeft", "eyeLookUpLeft",
"eyeSquintLeft", "eyeWideLeft", "eyeLookDownRight", "eyeLookInRight", "eyeLookOutRight", "eyeLookUpRight",
"eyeSquintRight", "eyeWideRight",
// Jaw
"jawForward", "jawLeft", "jawRight", "jawOpen",
// Mouth
"mouthClose", "mouthFunnel", "mouthPucker", "mouthLeft", "mouthRight", "mouthSmileLeft", "mouthSmileRight",
"mouthFrownLeft", "mouthFrownRight", "mouthDimpleLeft", "mouthDimpleRight", "mouthStretchLeft", "mouthStretchRight",
"mouthRollLower", "mouthRollUpper", "mouthShrugLower", "mouthShrugUpper", "mouthPressLeft", "mouthPressRight",
"mouthLowerDownLeft", "mouthLowerDownRight", "mouthUpperUpLeft", "mouthUpperUpRight",
// Brows
"browDownLeft", "browDownRight", "browInnerUp", "browOuterUpLeft", "browOuterUpRight",
// Cheeks
"cheekPuff", "cheekSquintLeft", "cheekSquintRight",
// Nose
"noseSneerLeft", "noseSneerRight",
// Tongue
"tongueOut",
// Additional
"mouthOpen", "mouthSmile", "eyesClosed", "eyesLookUp", "eyesLookDown"
};
void Start()
{
InitializeAnimator();
SetupDefaultExpressions();
if (autoPlay && !enableManualControls)
{
StartExpressionAnimation();
}
if (enableAutoBlink)
{
StartAutoBlinking();
}
}
void InitializeAnimator()
{
// Find avatar root if not assigned
if (avatarRoot == null)
{
avatarRoot = transform;
}
// Find the head renderer
Transform headRendererTransform = FindChildRecursive(avatarRoot, headRendererName);
if (headRendererTransform == null)
{
Debug.LogError($"RPMFacialAnimator: Could not find child object with name '{headRendererName}' in avatar hierarchy.");
return;
}
headRenderer = headRendererTransform.GetComponent<SkinnedMeshRenderer>();
if (headRenderer == null)
{
Debug.LogError($"RPMFacialAnimator: '{headRendererName}' does not have a SkinnedMeshRenderer component.");
return;
}
// Build blend shape index map
BuildBlendShapeIndexMap();
if (showDebugInfo)
{
Debug.Log($"RPMFacialAnimator: Initialized with {blendShapeIndexMap.Count} blend shapes on {headRendererName}");
}
if (listAllBlendShapes)
{
ListAllBlendShapes();
}
}
void BuildBlendShapeIndexMap()
{
blendShapeIndexMap = new Dictionary<string, int>();
if (headRenderer?.sharedMesh == null) return;
for (int i = 0; i < headRenderer.sharedMesh.blendShapeCount; i++)
{
string blendShapeName = headRenderer.sharedMesh.GetBlendShapeName(i);
blendShapeIndexMap[blendShapeName] = i;
}
}
void ListAllBlendShapes()
{
if (headRenderer?.sharedMesh == null) return;
Debug.Log("=== RPM Avatar Blend Shapes ===");
for (int i = 0; i < headRenderer.sharedMesh.blendShapeCount; i++)
{
string name = headRenderer.sharedMesh.GetBlendShapeName(i);
bool isARKit = Array.Exists(arkitBlendShapes, bs => bs == name);
Debug.Log($"Blend Shape {i}: {name} {(isARKit ? "(ARKit)" : "(Custom)")}");
}
}
Transform FindChildRecursive(Transform parent, string childName)
{
foreach (Transform child in parent)
{
if (child.name == childName)
return child;
Transform found = FindChildRecursive(child, childName);
if (found != null)
return found;
}
return null;
}
void SetupDefaultExpressions()
{
if (facialExpressions == null || facialExpressions.Length == 0)
{
facialExpressions = new FacialExpression[]
{
// Happy Expression
new FacialExpression
{
name = "Happy",
duration = 1.5f,
holdTime = 1f,
blendShapeWeights = new BlendShapeWeight[]
{
new BlendShapeWeight { blendShapeName = "mouthSmile", weight = 0.9f },
new BlendShapeWeight { blendShapeName = "mouthSmileLeft", weight = 0.8f },
new BlendShapeWeight { blendShapeName = "mouthSmileRight", weight = 0.8f },
new BlendShapeWeight { blendShapeName = "cheekSquintLeft", weight = 0.5f },
new BlendShapeWeight { blendShapeName = "cheekSquintRight", weight = 0.5f }
}
},
// Surprised Expression
new FacialExpression
{
name = "Surprised",
duration = 1f,
holdTime = 0.8f,
blendShapeWeights = new BlendShapeWeight[]
{
new BlendShapeWeight { blendShapeName = "mouthOpen", weight = 0.8f },
new BlendShapeWeight { blendShapeName = "jawOpen", weight = 0.6f },
new BlendShapeWeight { blendShapeName = "eyeWideLeft", weight = 1.0f },
new BlendShapeWeight { blendShapeName = "eyeWideRight", weight = 1.0f },
new BlendShapeWeight { blendShapeName = "browInnerUp", weight = 0.9f },
new BlendShapeWeight { blendShapeName = "browOuterUpLeft", weight = 0.7f },
new BlendShapeWeight { blendShapeName = "browOuterUpRight", weight = 0.7f }
}
},
// Sad Expression
new FacialExpression
{
name = "Sad",
duration = 2f,
holdTime = 1.5f,
blendShapeWeights = new BlendShapeWeight[]
{
new BlendShapeWeight { blendShapeName = "mouthFrownLeft", weight = 0.8f },
new BlendShapeWeight { blendShapeName = "mouthFrownRight", weight = 0.8f },
new BlendShapeWeight { blendShapeName = "browDownLeft", weight = 0.6f },
new BlendShapeWeight { blendShapeName = "browDownRight", weight = 0.6f },
new BlendShapeWeight { blendShapeName = "browInnerUp", weight = 0.5f }
}
},
// Angry Expression
new FacialExpression
{
name = "Angry",
duration = 1.2f,
holdTime = 1f,
blendShapeWeights = new BlendShapeWeight[]
{
new BlendShapeWeight { blendShapeName = "browDownLeft", weight = 1.0f },
new BlendShapeWeight { blendShapeName = "browDownRight", weight = 1.0f },
new BlendShapeWeight { blendShapeName = "eyeSquintLeft", weight = 0.7f },
new BlendShapeWeight { blendShapeName = "eyeSquintRight", weight = 0.7f },
new BlendShapeWeight { blendShapeName = "mouthFrownLeft", weight = 0.6f },
new BlendShapeWeight { blendShapeName = "mouthFrownRight", weight = 0.6f },
new BlendShapeWeight { blendShapeName = "noseSneerLeft", weight = 0.5f },
new BlendShapeWeight { blendShapeName = "noseSneerRight", weight = 0.5f }
}
},
// Thinking Expression
new FacialExpression
{
name = "Thinking",
duration = 1.8f,
holdTime = 2f,
blendShapeWeights = new BlendShapeWeight[]
{
new BlendShapeWeight { blendShapeName = "browInnerUp", weight = 0.6f },
new BlendShapeWeight { blendShapeName = "mouthPucker", weight = 0.5f },
new BlendShapeWeight { blendShapeName = "eyeLookUpLeft", weight = 0.4f },
new BlendShapeWeight { blendShapeName = "eyeLookUpRight", weight = 0.4f }
}
}
};
}
}
void Update()
{
if (enableManualControls && headRenderer != null)
{
ApplyManualControls();
}
}
void ApplyManualControls()
{
SetBlendShapeWeight("mouthSmile", mouthSmile);
SetBlendShapeWeight("mouthOpen", mouthOpen);
SetBlendShapeWeight("eyeBlinkLeft", eyeBlinkLeft);
SetBlendShapeWeight("eyeBlinkRight", eyeBlinkRight);
SetBlendShapeWeight("browInnerUp", browInnerUp);
SetBlendShapeWeight("cheekPuff", cheekPuff);
SetBlendShapeWeight("jawOpen", jawOpen);
}
public void SetBlendShapeWeight(string blendShapeName, float weight)
{
if (headRenderer == null || blendShapeIndexMap == null) return;
if (blendShapeIndexMap.TryGetValue(blendShapeName, out int index))
{
headRenderer.SetBlendShapeWeight(index, Mathf.Clamp(weight, 0f, 1f));
}
else if (showDebugInfo)
{
Debug.LogWarning($"RPMFacialAnimator: Blend shape '{blendShapeName}' not found.");
}
}
public float GetBlendShapeWeight(string blendShapeName)
{
if (headRenderer == null || blendShapeIndexMap == null) return 0f;
if (blendShapeIndexMap.TryGetValue(blendShapeName, out int index))
{
return headRenderer.GetBlendShapeWeight(index);
}
return 0f;
}
public void PlayExpression(string expressionName)
{
FacialExpression expression = Array.Find(facialExpressions, expr => expr.name == expressionName);
if (expression != null)
{
StartCoroutine(PlayExpressionCoroutine(expression));
}
else
{
Debug.LogWarning($"RPMFacialAnimator: Expression '{expressionName}' not found.");
}
}
public void PlayExpression(int expressionIndex)
{
if (expressionIndex >= 0 && expressionIndex < facialExpressions.Length)
{
StartCoroutine(PlayExpressionCoroutine(facialExpressions[expressionIndex]));
}
else
{
Debug.LogWarning($"RPMFacialAnimator: Expression index {expressionIndex} is out of range.");
}
}
IEnumerator PlayExpressionCoroutine(FacialExpression expression)
{
isAnimating = true;
// Store starting weights
Dictionary<string, float> startWeights = new Dictionary<string, float>();
foreach (var blendWeight in expression.blendShapeWeights)
{
startWeights[blendWeight.blendShapeName] = GetBlendShapeWeight(blendWeight.blendShapeName);
}
// Animate to target weights
float elapsedTime = 0f;
while (elapsedTime < expression.duration)
{
float progress = elapsedTime / expression.duration;
float curveValue = transitionCurve.Evaluate(progress);
foreach (var blendWeight in expression.blendShapeWeights)
{
float startWeight = startWeights.ContainsKey(blendWeight.blendShapeName) ?
startWeights[blendWeight.blendShapeName] : 0f;
float targetWeight = blendWeight.weight;
float currentWeight = Mathf.Lerp(startWeight, targetWeight, curveValue);
SetBlendShapeWeight(blendWeight.blendShapeName, currentWeight);
}
elapsedTime += Time.deltaTime;
yield return null;
}
// Hold the expression
if (expression.holdTime > 0f)
{
yield return new WaitForSeconds(expression.holdTime);
}
// Return to neutral
elapsedTime = 0f;
while (elapsedTime < expression.duration)
{
float progress = elapsedTime / expression.duration;
float curveValue = transitionCurve.Evaluate(progress);
foreach (var blendWeight in expression.blendShapeWeights)
{
float currentWeight = Mathf.Lerp(blendWeight.weight, 0f, curveValue);
SetBlendShapeWeight(blendWeight.blendShapeName, currentWeight);
}
elapsedTime += Time.deltaTime;
yield return null;
}
// Ensure all weights are reset to 0
foreach (var blendWeight in expression.blendShapeWeights)
{
SetBlendShapeWeight(blendWeight.blendShapeName, 0f);
}
isAnimating = false;
}
public void StartExpressionAnimation()
{
if (expressionCoroutine != null)
{
StopCoroutine(expressionCoroutine);
}
expressionCoroutine = StartCoroutine(ExpressionAnimationLoop());
}
public void StopExpressionAnimation()
{
if (expressionCoroutine != null)
{
StopCoroutine(expressionCoroutine);
expressionCoroutine = null;
}
}
IEnumerator ExpressionAnimationLoop()
{
while (loopAnimations && !enableManualControls)
{
if (facialExpressions.Length > 0 && !isAnimating)
{
int randomIndex = UnityEngine.Random.Range(0, facialExpressions.Length);
yield return StartCoroutine(PlayExpressionCoroutine(facialExpressions[randomIndex]));
}
yield return new WaitForSeconds(timeBetweenExpressions);
}
}
public void StartAutoBlinking()
{
if (blinkCoroutine != null)
{
StopCoroutine(blinkCoroutine);
}
blinkCoroutine = StartCoroutine(AutoBlinkLoop());
}
public void StopAutoBlinking()
{
if (blinkCoroutine != null)
{
StopCoroutine(blinkCoroutine);
blinkCoroutine = null;
}
}
IEnumerator AutoBlinkLoop()
{
while (enableAutoBlink)
{
// Wait for random interval
float waitTime = blinkInterval + UnityEngine.Random.Range(-blinkRandomness, blinkRandomness);
yield return new WaitForSeconds(Mathf.Max(0.5f, waitTime));
// Blink
yield return StartCoroutine(BlinkCoroutine());
}
}
IEnumerator BlinkCoroutine()
{
// Close eyes
float elapsedTime = 0f;
float halfDuration = blinkDuration * 0.5f;
while (elapsedTime < halfDuration)
{
float progress = elapsedTime / halfDuration;
float blinkWeight = Mathf.Lerp(0f, 1f, progress);
SetBlendShapeWeight("eyeBlinkLeft", blinkWeight);
SetBlendShapeWeight("eyeBlinkRight", blinkWeight);
elapsedTime += Time.deltaTime;
yield return null;
}
// Open eyes
elapsedTime = 0f;
while (elapsedTime < halfDuration)
{
float progress = elapsedTime / halfDuration;
float blinkWeight = Mathf.Lerp(1f, 0f, progress);
SetBlendShapeWeight("eyeBlinkLeft", blinkWeight);
SetBlendShapeWeight("eyeBlinkRight", blinkWeight);
elapsedTime += Time.deltaTime;
yield return null;
}
// Ensure eyes are fully open
SetBlendShapeWeight("eyeBlinkLeft", 0f);
SetBlendShapeWeight("eyeBlinkRight", 0f);
}
public void ResetAllBlendShapes()
{
if (headRenderer == null || blendShapeIndexMap == null) return;
foreach (var kvp in blendShapeIndexMap)
{
headRenderer.SetBlendShapeWeight(kvp.Value, 0f);
}
}
// Public API Methods
public void Smile() => PlayExpression("Happy");
public void Surprise() => PlayExpression("Surprised");
public void Frown() => PlayExpression("Sad");
public void Angry() => PlayExpression("Angry");
public void Think() => PlayExpression("Thinking");
public void SetAvatarRoot(Transform newRoot)
{
avatarRoot = newRoot;
InitializeAnimator();
}
public void SetManualControlMode(bool enabled)
{
enableManualControls = enabled;
if (enabled)
{
// Stop auto-expressions
StopExpressionAnimation();
ResetAllBlendShapes();
}
else if (autoPlay)
{
// Restart auto-expressions
StartExpressionAnimation();
}
}
void OnValidate()
{
// Update manual controls in real-time during edit mode
if (Application.isPlaying && enableManualControls)
{
// Stop auto-expressions when manual controls are enabled
if (expressionCoroutine != null)
{
StopCoroutine(expressionCoroutine);
expressionCoroutine = null;
}
// Reset any ongoing animations
isAnimating = false;
ApplyManualControls();
}
else if (Application.isPlaying && autoPlay && !enableManualControls)
{
// Restart auto-expressions if manual controls are disabled
if (expressionCoroutine == null)
{
StartExpressionAnimation();
}
}
}
}