using System; using System.Collections.Generic; using System.IO; using UnityEngine; using Newtonsoft.Json; public class AvatarDataReader : MonoBehaviour { [Header("Avatar Configuration")] [SerializeField] private Transform targetAvatarRoot; [SerializeField] private string fileName = "avatar_sync_data.json"; [SerializeField] private bool readEveryFrame = true; [SerializeField] private float updateRate = 60f; // Updates per second [SerializeField] private bool smoothTransitions = true; [SerializeField] private float transitionSpeed = 10f; [Header("Synchronization Options")] [SerializeField] private bool syncWorldPosition = true; // Apply avatar world position [SerializeField] private bool syncWorldRotation = true; // Apply avatar world rotation [SerializeField] private bool syncLocalScale = false; // Apply avatar local scale [Header("Debug")] [SerializeField] private bool showDebugInfo = false; [SerializeField] private bool enableDebugMode = false; [SerializeField] private string debugBoneName = "LeftArm"; // Bone to monitor private string filePath; private float lastUpdateTime; private Dictionary boneLookup; private Dictionary skinnedMeshLookup; private AvatarSyncData lastSyncData; private bool isInitialized = false; private int successfulReads = 0; private int appliedBones = 0; private Vector3 lastAppliedWorldPosition; void Start() { // Set up file path string syncFilesPath = Path.Combine(Application.dataPath, "Sync-Files"); filePath = Path.Combine(syncFilesPath, fileName); // If targetAvatarRoot is not assigned, try to find it automatically if (targetAvatarRoot == null) { targetAvatarRoot = transform; } // Cache target avatar components CacheTargetAvatarComponents(); // Initialize debug tracking if (enableDebugMode) { lastAppliedWorldPosition = targetAvatarRoot.position; } Debug.Log($"Avatar Data Reader initialized. Reading from: {filePath}"); Debug.Log($"World Position Sync: {syncWorldPosition}, World Rotation Sync: {syncWorldRotation}"); } void CacheTargetAvatarComponents() { boneLookup = new Dictionary(); skinnedMeshLookup = new Dictionary(); // Get all skinned mesh renderers SkinnedMeshRenderer[] smrs = targetAvatarRoot.GetComponentsInChildren(); // Cache bones from SkinnedMeshRenderers foreach (SkinnedMeshRenderer smr in smrs) { // Cache this skinned mesh renderer if (!skinnedMeshLookup.ContainsKey(smr.gameObject.name)) { skinnedMeshLookup.Add(smr.gameObject.name, smr); } // Cache all bones from this SkinnedMeshRenderer if (smr.bones != null) { foreach (Transform bone in smr.bones) { if (bone != null && !boneLookup.ContainsKey(bone.name)) { boneLookup.Add(bone.name, bone); } } } } isInitialized = true; if (showDebugInfo) { Debug.Log($"Cached {boneLookup.Count} bones and {skinnedMeshLookup.Count} skinned mesh renderers"); } // Debug mode: Print some bone names and check for the debug bone if (enableDebugMode) { Debug.Log("Reader - Found bones:"); int count = 0; foreach (var kvp in boneLookup) { if (count < 10) { Debug.Log($" Reader Bone {count}: {kvp.Key}"); count++; } if (kvp.Key.Contains(debugBoneName)) { Debug.Log($"Found debug bone in reader: {kvp.Key}"); } } } } void Update() { if (!isInitialized || !File.Exists(filePath)) return; if (readEveryFrame) { ReadAndApplyAvatarData(); } else { // Rate-limited updates if (Time.time - lastUpdateTime >= 1f / updateRate) { ReadAndApplyAvatarData(); lastUpdateTime = Time.time; } } } void ReadAndApplyAvatarData() { try { string json = File.ReadAllText(filePath); AvatarSyncData syncData = JsonConvert.DeserializeObject(json); if (syncData == null) { if (showDebugInfo) Debug.LogWarning("Failed to deserialize avatar sync data"); return; } successfulReads++; appliedBones = 0; // Apply root transform data ApplyRootTransformData(syncData.rootTransform); // Apply bone data ApplyBoneData(syncData.bones); // Apply blendshape data ApplyBlendShapeData(syncData.blendShapes); if (enableDebugMode && successfulReads % 60 == 0) // Log every 60 reads (about once per second) { Debug.Log($"Reader stats: {successfulReads} successful reads, {appliedBones} bones applied in last read"); } lastSyncData = syncData; } catch (Exception e) { if (showDebugInfo) Debug.LogError($"Error reading avatar data: {e.Message}"); } } void ApplyRootTransformData(RootTransformData rootData) { if (rootData == null) return; if (syncWorldPosition && rootData.worldPosition != null) { Vector3 targetWorldPosition = rootData.worldPosition.ToVector3(); // Debug: Log world position changes if (enableDebugMode && Vector3.Distance(targetWorldPosition, lastAppliedWorldPosition) > 0.01f) { Debug.Log($"Applying world position: {targetWorldPosition} (was: {lastAppliedWorldPosition})"); lastAppliedWorldPosition = targetWorldPosition; } if (smoothTransitions) { targetAvatarRoot.position = Vector3.Lerp( targetAvatarRoot.position, targetWorldPosition, transitionSpeed * Time.deltaTime ); } else { targetAvatarRoot.position = targetWorldPosition; } } if (syncWorldRotation && rootData.worldRotation != null) { Quaternion targetWorldRotation = rootData.worldRotation.ToQuaternion(); if (smoothTransitions) { targetAvatarRoot.rotation = Quaternion.Lerp( targetAvatarRoot.rotation, targetWorldRotation, transitionSpeed * Time.deltaTime ); } else { targetAvatarRoot.rotation = targetWorldRotation; } } if (syncLocalScale && rootData.localScale != null) { Vector3 targetLocalScale = rootData.localScale.ToVector3(); if (smoothTransitions) { targetAvatarRoot.localScale = Vector3.Lerp( targetAvatarRoot.localScale, targetLocalScale, transitionSpeed * Time.deltaTime ); } else { targetAvatarRoot.localScale = targetLocalScale; } } } void ApplyBoneData(List boneDataList) { if (boneDataList == null) return; foreach (BoneData boneData in boneDataList) { if (boneLookup.TryGetValue(boneData.boneName, out Transform targetBone)) { // Convert serializable types back to Unity types Vector3 targetPosition = boneData.position.ToVector3(); Quaternion targetRotation = boneData.rotation.ToQuaternion(); Vector3 targetScale = boneData.scale.ToVector3(); // Debug mode: Log changes to the monitored bone if (enableDebugMode && boneData.boneName.Contains(debugBoneName)) { Debug.Log($"Applying to bone {boneData.boneName}: Pos={targetPosition}, Rot={targetRotation}"); } if (smoothTransitions) { // Smooth interpolation targetBone.localPosition = Vector3.Lerp( targetBone.localPosition, targetPosition, transitionSpeed * Time.deltaTime ); targetBone.localRotation = Quaternion.Lerp( targetBone.localRotation, targetRotation, transitionSpeed * Time.deltaTime ); targetBone.localScale = Vector3.Lerp( targetBone.localScale, targetScale, transitionSpeed * Time.deltaTime ); } else { // Direct application targetBone.localPosition = targetPosition; targetBone.localRotation = targetRotation; targetBone.localScale = targetScale; } appliedBones++; } else if (showDebugInfo) { Debug.LogWarning($"Bone not found: {boneData.boneName}"); } } } void ApplyBlendShapeData(List blendShapeDataList) { if (blendShapeDataList == null) return; foreach (BlendShapeData blendShapeData in blendShapeDataList) { if (skinnedMeshLookup.TryGetValue(blendShapeData.meshName, out SkinnedMeshRenderer targetSmr)) { if (targetSmr.sharedMesh != null && blendShapeData.weights != null) { int maxBlendShapes = Mathf.Min(targetSmr.sharedMesh.blendShapeCount, blendShapeData.weights.Length); for (int i = 0; i < maxBlendShapes; i++) { if (smoothTransitions) { float currentWeight = targetSmr.GetBlendShapeWeight(i); float targetWeight = blendShapeData.weights[i]; float newWeight = Mathf.Lerp(currentWeight, targetWeight, transitionSpeed * Time.deltaTime); targetSmr.SetBlendShapeWeight(i, newWeight); } else { targetSmr.SetBlendShapeWeight(i, blendShapeData.weights[i]); } } } } else if (showDebugInfo) { Debug.LogWarning($"SkinnedMeshRenderer not found: {blendShapeData.meshName}"); } } } public void SetTargetAvatar(Transform newTargetRoot) { targetAvatarRoot = newTargetRoot; if (isInitialized) { CacheTargetAvatarComponents(); } } public void SetFileName(string newFileName) { fileName = newFileName; string syncFilesPath = Path.Combine(Application.dataPath, "Sync-Files"); filePath = Path.Combine(syncFilesPath, fileName); } public bool IsDataAvailable() { return File.Exists(filePath); } public float GetLastSyncTimestamp() { return lastSyncData?.timestamp ?? 0f; } }