489 lines
17 KiB
C#

using Rokoko;
using Rokoko.Core;
using Rokoko.CommandAPI;
using Rokoko.Helper;
using Rokoko.Inputs;
using Rokoko.UI;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Rokoko
{
public class StudioManager : MonoBehaviour
{
private const string ACTOR_DEMO_IDLE_NAME = "ActorIdle";
private static StudioManager instance;
[Header("Network")]
[Tooltip("ReceivePort must match Studio Live Stream port settings")]
public int receivePort = 14043;
[Tooltip("Use LZ4 compression stream")]
public bool useLZ4Compression = true;
[Tooltip("Log the stream frame information")]
public bool receiverVerbose = false;
[Header("Default Inputs - Used when no overrides found (Optional)")]
[Tooltip("Actor Prefab to create actors when no overrides found")]
public Actor actorPrefab;
[Tooltip("Character Prefab to create characters when no overrides found")]
public Character characterPrefab;
[Tooltip("Prop Prefab to create props when no overrides found")]
public Prop propPrefab;
[Header("UI (Optional)")]
public UIHierarchyManager uiManager;
[Header("Command API (Optional)")]
public StudioCommandAPI CommandAPI;
public bool AutoSendTrackerCommands;
[Header("Input Overrides - Automatically updated")]
public List<Actor> actorOverrides = new List<Actor>();
public List<Character> characterOverrides = new List<Character>();
public List<Prop> propOverrides = new List<Prop>();
[Header("Extra Behiavours")]
public bool autoGenerateInputsWhenNoOverridesFound = false;
public bool showDefaultActorWhenNoData = false;
private StudioReceiver studioReceiver;
private PrefabInstancer<string, Actor> actors;
private PrefabInstancer<string, Character> characters;
private PrefabInstancer<string, Prop> props;
private object actionsOnMainThread = new object();
private List<LiveFrame_v4> packetsToProcess = new List<LiveFrame_v4>();
#region MonoBehaviour
private void Awake()
{
if (instance == null)
instance = this;
}
// Start is called before the first frame update
private IEnumerator Start()
{
studioReceiver = new StudioReceiver();
studioReceiver.receivePortNumber = receivePort;
studioReceiver.useLZ4Compression = useLZ4Compression;
studioReceiver.verbose = receiverVerbose;
studioReceiver.Initialize();
studioReceiver.StartListening();
studioReceiver.onStudioDataReceived += StudioReceiver_onStudioDataReceived;
if (actorPrefab != null)
actors = new PrefabInstancer<string, Actor>(actorPrefab, this.transform);
if (characterPrefab != null)
characters = new PrefabInstancer<string, Character>(characterPrefab, this.transform);
if (propPrefab != null)
props = new PrefabInstancer<string, Prop>(propPrefab, this.transform);
yield return null;
if (actorOverrides.Count == 0 && characterOverrides.Count == 0)
{
Debug.Log("No custom characters found. Will generate scene from default ones");
}
}
private void Update()
{
// Run all actions inside Unity's main thread
lock (actionsOnMainThread)
{
if (packetsToProcess.Count > 0)
{
ProcessLiveFrame(packetsToProcess[packetsToProcess.Count-1]);
packetsToProcess.Clear();
}
}
}
private void FixedUpdate()
{
if (AutoSendTrackerCommands && CommandAPI != null)
{
if (!CommandAPI.IsTrackerRequestInProgress)
{
CommandAPI.Tracker();
}
}
}
private void OnDestroy()
{
studioReceiver.Dispose();
}
#endregion
private void StudioReceiver_onStudioDataReceived(object sender, LiveFrame_v4 e)
{
lock (actionsOnMainThread)
packetsToProcess.Add(e);
}
/// <summary>
/// Main process logic of live data
/// </summary>
private void ProcessLiveFrame(LiveFrame_v4 frame)
{
int numberOfActors = frame?.scene.actors?.Length ?? 0;
int numberOfCharacters = frame?.scene.characters?.Length ?? 0;
if (numberOfActors == 0 && numberOfCharacters == 0)
return;
// Update each actor from live data
for (int i = 0; i < numberOfActors; i++)
{
ActorFrame actorFrame = frame.scene.actors[i];
List<Actor> actorOverrides = GetActorOverride(actorFrame.name);
// Update custom actors if any
if (actorOverrides.Count > 0)
{
for (int a = 0; a < actorOverrides.Count; a++)
{
actorOverrides[a].UpdateActor(actorFrame);
}
}
// Update default actor
else if (autoGenerateInputsWhenNoOverridesFound && actors != null)
{
actors[actorFrame.name].UpdateActor(actorFrame);
}
}
// Update each character from live data
for (int i = 0; i < numberOfCharacters; i++)
{
CharacterFrame charFrame = frame.scene.characters[i];
List<Character> characterOverrides = GetCharacterOverride(charFrame.name);
// Update custom characters if any
if (characterOverrides.Count > 0)
{
for (int a = 0; a < characterOverrides.Count; a++)
{
characterOverrides[a].UpdateCharacter(charFrame);
}
}
// Update default character
else if (autoGenerateInputsWhenNoOverridesFound && characters != null)
{
characters[charFrame.name].UpdateCharacter(charFrame);
}
}
// Update each prop from live data
if (frame.scene.props != null)
{
for (int i = 0; i < frame.scene.props.Length; i++)
{
PropFrame propFrame = frame.scene.props[i];
List<Prop> propOverrides = GetPropOverride(propFrame.name);
// Update custom props if any
if (propOverrides.Count > 0)
{
for (int a = 0; a < propOverrides.Count; a++)
{
propOverrides[a].UpdateProp(propFrame);
}
}
// Update default prop
else if (autoGenerateInputsWhenNoOverridesFound && props != null)
{
props[propFrame.name].UpdateProp(propFrame);
}
}
}
// Remove all default Actors that doesn't exist in data
ClearUnusedDefaultInputs(frame);
// Show default character
UpdateDefaultActorWhenIdle();
// Update Hierarchy UI
uiManager?.UpdateHierarchy(frame);
}
/// <summary>
/// Show default T pose character when not playback data
/// </summary>
private void UpdateDefaultActorWhenIdle()
{
if (!showDefaultActorWhenNoData) return;
if (actors == null || props == null) // || characters == null)
return;
// Create default actor
if (actors.Count == 0 && props.Count == 0)
{
actors[ACTOR_DEMO_IDLE_NAME].CreateIdle(ACTOR_DEMO_IDLE_NAME);
}
// No need to update
else if (actors.Count == 1 && actors.ContainsKey(ACTOR_DEMO_IDLE_NAME))
{
}
// Remove default actor when playback data available
else
{
actors.Remove(ACTOR_DEMO_IDLE_NAME);
}
}
/// <summary>
/// Remove all default Actors that doesn't exist in data
/// </summary>
private void ClearUnusedDefaultInputs(LiveFrame_v4 frame)
{
if (actors != null)
{
foreach (Actor actor in new List<Actor>((IEnumerable<Actor>)actors.Values))
{
// Don't remove idle demo
if (actor.profileName == ACTOR_DEMO_IDLE_NAME) continue;
if (!frame.HasProfile(actor.profileName))
actors.Remove(actor.profileName);
}
}
if (characters != null)
{
foreach (Character character in new List<Character>((IEnumerable<Character>)characters.Values))
{
// Don't remove idle demo
if (character.profileName == ACTOR_DEMO_IDLE_NAME) continue;
if (!frame.HasCharacter(character.profileName))
characters.Remove(character.profileName);
}
}
if (props != null)
{
foreach (Prop prop in new List<Prop>((IEnumerable<Prop>)props.Values))
{
if (!frame.HasProp(prop.propName))
props.Remove(prop.propName);
}
}
}
public List<Actor> GetActorOverride(string profileName)
{
List<Actor> overrides = new List<Actor>();
for (int i = 0; i < actorOverrides.Count; i++)
{
if (profileName.ToLower() == actorOverrides[i].profileName.ToLower())
overrides.Add(actorOverrides[i]);
}
return overrides;
}
public List<Character> GetCharacterOverride(string profileName)
{
List<Character> overrides = new List<Character>();
for (int i = 0; i < characterOverrides.Count; i++)
{
if (characterOverrides[i] == null)
continue;
if (profileName.ToLower() == characterOverrides[i].profileName.ToLower())
overrides.Add(characterOverrides[i]);
}
return overrides;
}
public List<Prop> GetPropOverride(string profileName)
{
List<Prop> overrides = new List<Prop>();
for (int i = 0; i < propOverrides.Count; i++)
{
if (profileName.ToLower() == propOverrides[i].propName.ToLower())
overrides.Add(propOverrides[i]);
}
return overrides;
}
public static void AddActorOverride(Actor actor)
{
if (instance == null) return;
if (instance.actorOverrides.Contains(actor)) return;
instance.actorOverrides.Add(actor);
}
public static void AddCharacterOverride(Character character)
{
if (instance == null) return;
if (instance.characterOverrides.Contains(character)) return;
instance.characterOverrides.Add(character);
}
public static void AddPropOverride(Prop prop)
{
if (instance == null) return;
if (instance.propOverrides.Contains(prop)) return;
instance.propOverrides.Add(prop);
}
#region Recording Control Functions
/// <summary>
/// CommandAPI를 자동으로 찾아서 설정합니다.
/// </summary>
private void EnsureCommandAPI()
{
if (CommandAPI == null)
{
// 같은 GameObject에서 찾기
CommandAPI = GetComponent<StudioCommandAPI>();
// 씬 전체에서 찾기
if (CommandAPI == null)
{
CommandAPI = FindObjectOfType<StudioCommandAPI>();
}
if (CommandAPI != null)
{
Debug.Log("Rokoko: CommandAPI를 자동으로 찾아서 설정했습니다.");
}
}
}
/// <summary>
/// Rokoko 녹화를 시작합니다.
/// </summary>
public void StartRokokoRecording()
{
EnsureCommandAPI();
if (CommandAPI != null)
{
CommandAPI.StartRecording();
Debug.Log("Rokoko: 녹화를 시작했습니다.");
}
else
{
Debug.LogWarning("Rokoko: CommandAPI를 찾을 수 없습니다. StudioCommandAPI 컴포넌트가 씬에 있는지 확인해주세요.");
}
}
/// <summary>
/// Rokoko 녹화를 중지합니다.
/// </summary>
public void StopRokokoRecording()
{
EnsureCommandAPI();
if (CommandAPI != null)
{
CommandAPI.StopRecording();
Debug.Log("Rokoko: 녹화를 중지했습니다.");
}
else
{
Debug.LogWarning("Rokoko: CommandAPI를 찾을 수 없습니다. StudioCommandAPI 컴포넌트가 씬에 있는지 확인해주세요.");
}
}
/// <summary>
/// Rokoko 녹화 상태를 토글합니다.
/// Note: Rokoko API에는 현재 녹화 상태를 확인하는 직접적인 방법이 없으므로,
/// 시작 명령을 먼저 시도하고 실패하면 중지를 시도합니다.
/// </summary>
public void ToggleRokokoRecording()
{
EnsureCommandAPI();
if (CommandAPI != null)
{
// 실제로는 사용자가 수동으로 시작/중지를 선택하는 것이 좋습니다.
// 여기서는 예시로 시작 명령만 실행합니다.
CommandAPI.StartRecording();
Debug.Log("Rokoko: 녹화 시작을 시도했습니다.");
}
else
{
Debug.LogWarning("Rokoko: CommandAPI를 찾을 수 없습니다. StudioCommandAPI 컴포넌트가 씬에 있는지 확인해주세요.");
}
}
/// <summary>
/// 현재 CommandAPI 상태를 확인합니다.
/// </summary>
public bool IsCommandAPIAvailable()
{
EnsureCommandAPI();
return CommandAPI != null;
}
/// <summary>
/// CommandAPI 상태와 설정 정보를 로그로 출력합니다.
/// </summary>
[ContextMenu("Check Rokoko CommandAPI Status")]
public void CheckCommandAPIStatus()
{
EnsureCommandAPI();
if (CommandAPI != null)
{
Debug.Log($"Rokoko CommandAPI 상태: 사용 가능\n" +
$"API 키: {(string.IsNullOrEmpty(CommandAPI.apiKey) ? " " : "")}\n" +
$"포트: {CommandAPI.port}\n" +
$"IP 주소: {CommandAPI.ipAddress}");
}
else
{
Debug.LogWarning("Rokoko CommandAPI를 찾을 수 없습니다. 다음을 확인해주세요:\n" +
"1. StudioCommandAPI 컴포넌트가 씬에 있는지 확인\n" +
"2. StudioManager의 CommandAPI 필드에 수동으로 할당\n" +
"3. Rokoko Studio가 실행 중이고 Command API가 활성화되어 있는지 확인");
}
}
/// <summary>
/// 정적 메서드로 Rokoko 녹화 시작
/// </summary>
public static void StartGlobalRokokoRecording()
{
if (instance != null)
{
instance.StartRokokoRecording();
}
else
{
Debug.LogError("Rokoko: StudioManager 인스턴스를 찾을 수 없습니다.");
}
}
/// <summary>
/// 정적 메서드로 Rokoko 녹화 중지
/// </summary>
public static void StopGlobalRokokoRecording()
{
if (instance != null)
{
instance.StopRokokoRecording();
}
else
{
Debug.LogError("Rokoko: StudioManager 인스턴스를 찾을 수 없습니다.");
}
}
#endregion
}
}