using System; using System.Collections.Generic; using System.IO; using UnityEngine; using Newtonsoft.Json; [System.Serializable] public class SerializableVector3 { public float x, y, z; public SerializableVector3() { } public SerializableVector3(Vector3 vector) { x = vector.x; y = vector.y; z = vector.z; } public Vector3 ToVector3() { return new Vector3(x, y, z); } } [System.Serializable] public class SerializableQuaternion { public float x, y, z, w; public SerializableQuaternion() { } public SerializableQuaternion(Quaternion quaternion) { x = quaternion.x; y = quaternion.y; z = quaternion.z; w = quaternion.w; } public Quaternion ToQuaternion() { return new Quaternion(x, y, z, w); } } [System.Serializable] public class RootTransformData { public SerializableVector3 worldPosition; public SerializableQuaternion worldRotation; public SerializableVector3 localScale; } [System.Serializable] public class BoneData { public string boneName; // e.g. "LeftArm", "RightHand" public SerializableVector3 position; public SerializableQuaternion rotation; public SerializableVector3 scale; } [System.Serializable] public class BlendShapeData { public string meshName; public float[] weights; } [System.Serializable] public class AvatarSyncData { public RootTransformData rootTransform; public List bones; public List blendShapes; public float timestamp; } public class AvatarDataWriter : MonoBehaviour { [Header("Avatar Configuration")] [SerializeField] private Transform avatarRoot; [SerializeField] private string fileName = "avatar_sync_data.json"; [SerializeField] private bool writeEveryFrame = true; [SerializeField] private float updateRate = 60f; // Updates per second [Header("Synchronization Options")] [SerializeField] private bool syncWorldPosition = true; // Sync avatar world position [SerializeField] private bool syncWorldRotation = true; // Sync avatar world rotation [SerializeField] private bool syncLocalScale = false; // Sync avatar local scale [Header("Debug")] [SerializeField] private bool enableDebugMode = false; [SerializeField] private string debugBoneName = "LeftArm"; // Bone to monitor private string filePath; private float lastUpdateTime; private List allBones; private List skinnedMeshRenderers; private Vector3 lastDebugPosition; private Quaternion lastDebugRotation; private Vector3 lastRootWorldPosition; void Start() { // Create Sync-Files directory if it doesn't exist string syncFilesPath = Path.Combine(Application.dataPath, "Sync-Files"); if (!Directory.Exists(syncFilesPath)) { Directory.CreateDirectory(syncFilesPath); } filePath = Path.Combine(syncFilesPath, fileName); // If avatarRoot is not assigned, try to find it automatically if (avatarRoot == null) { avatarRoot = transform; } // Cache all bones and skinned mesh renderers CacheAvatarComponents(); // Initialize debug tracking if (enableDebugMode) { Transform debugBone = FindBoneByName(debugBoneName); if (debugBone != null) { lastDebugPosition = debugBone.localPosition; lastDebugRotation = debugBone.localRotation; Debug.Log($"Debug mode enabled. Monitoring bone: {debugBoneName}"); } else { Debug.LogWarning($"Debug bone '{debugBoneName}' not found!"); } lastRootWorldPosition = avatarRoot.position; } Debug.Log($"Avatar Data Writer initialized. Writing to: {filePath}"); Debug.Log($"World Position Sync: {syncWorldPosition}, World Rotation Sync: {syncWorldRotation}"); } void CacheAvatarComponents() { allBones = new List(); skinnedMeshRenderers = new List(); // Get all skinned mesh renderers skinnedMeshRenderers.AddRange(avatarRoot.GetComponentsInChildren()); // Get all unique bones from all SkinnedMeshRenderers HashSet uniqueBones = new HashSet(); foreach (SkinnedMeshRenderer smr in skinnedMeshRenderers) { if (smr.bones != null) { foreach (Transform bone in smr.bones) { if (bone != null) { uniqueBones.Add(bone); } } } } allBones.AddRange(uniqueBones); Debug.Log($"Cached {allBones.Count} bones and {skinnedMeshRenderers.Count} skinned mesh renderers"); // Debug: Print some bone names to help with troubleshooting if (enableDebugMode) { Debug.Log("Found bones:"); for (int i = 0; i < Mathf.Min(10, allBones.Count); i++) { Debug.Log($" Bone {i}: {allBones[i].name}"); } } } Transform FindBoneByName(string name) { foreach (Transform bone in allBones) { if (bone.name.Contains(name)) { return bone; } } return null; } void Update() { // Debug mode: Check if the monitored bone has changed if (enableDebugMode) { Transform debugBone = FindBoneByName(debugBoneName); if (debugBone != null) { if (Vector3.Distance(debugBone.localPosition, lastDebugPosition) > 0.001f || Quaternion.Angle(debugBone.localRotation, lastDebugRotation) > 0.1f) { Debug.Log($"Bone {debugBoneName} changed! Pos: {debugBone.localPosition}, Rot: {debugBone.localRotation}"); lastDebugPosition = debugBone.localPosition; lastDebugRotation = debugBone.localRotation; } } // Debug: Check if avatar root world position has changed if (Vector3.Distance(avatarRoot.position, lastRootWorldPosition) > 0.01f) { Debug.Log($"Avatar root world position changed! From: {lastRootWorldPosition} To: {avatarRoot.position}"); lastRootWorldPosition = avatarRoot.position; } } if (writeEveryFrame) { WriteAvatarData(); } else { // Rate-limited updates if (Time.time - lastUpdateTime >= 1f / updateRate) { WriteAvatarData(); lastUpdateTime = Time.time; } } } void WriteAvatarData() { try { AvatarSyncData syncData = new AvatarSyncData { rootTransform = new RootTransformData(), bones = new List(), blendShapes = new List(), timestamp = Time.time }; // Capture root transform data if (syncWorldPosition) { syncData.rootTransform.worldPosition = new SerializableVector3(avatarRoot.position); } else { syncData.rootTransform.worldPosition = new SerializableVector3(Vector3.zero); } if (syncWorldRotation) { syncData.rootTransform.worldRotation = new SerializableQuaternion(avatarRoot.rotation); } else { syncData.rootTransform.worldRotation = new SerializableQuaternion(Quaternion.identity); } if (syncLocalScale) { syncData.rootTransform.localScale = new SerializableVector3(avatarRoot.localScale); } else { syncData.rootTransform.localScale = new SerializableVector3(Vector3.one); } // Capture bone data foreach (Transform bone in allBones) { BoneData boneData = new BoneData { boneName = bone.name, position = new SerializableVector3(bone.localPosition), rotation = new SerializableQuaternion(bone.localRotation), scale = new SerializableVector3(bone.localScale) }; syncData.bones.Add(boneData); } // Capture blendshape data foreach (SkinnedMeshRenderer smr in skinnedMeshRenderers) { if (smr.sharedMesh != null && smr.sharedMesh.blendShapeCount > 0) { float[] weights = new float[smr.sharedMesh.blendShapeCount]; for (int i = 0; i < weights.Length; i++) { weights[i] = smr.GetBlendShapeWeight(i); } BlendShapeData blendShapeData = new BlendShapeData { meshName = smr.gameObject.name, // Use GameObject name instead of path weights = weights }; syncData.blendShapes.Add(blendShapeData); } } // Convert to JSON and write to file string json = JsonConvert.SerializeObject(syncData, Formatting.Indented); File.WriteAllText(filePath, json); } catch (Exception e) { Debug.LogError($"Error writing avatar data: {e.Message}"); } } void OnDisable() { // Write final data when component is disabled if (allBones != null && allBones.Count > 0) { WriteAvatarData(); } } }