using UnityEngine; namespace BrewMonster { /// /// Shared static helpers used by ElsePlayerPortraitCapture and HostPlayerPortraitCapture. /// public static class PortraitCaptureUtils { private static readonly string[] HeadBoneNames = { "Bip01 Head", "Bip001 Head", "head", "Head", "Bone_Head", "HEAD" }; // ────────────────────────────────────────────────────────────────────────────── // Model / bone search // ────────────────────────────────────────────────────────────────────────────── /// /// Returns the root transform of the visual model inside . /// Tries to find the first child that owns a SkinnedMeshRenderer; falls back to the /// root itself if a SkinnedMeshRenderer is found anywhere in the hierarchy. /// public static Transform FindVisualModelRoot(Transform playerRoot) { if (playerRoot == null) return null; // Walk immediate children first — model root is usually a direct child for (int i = 0; i < playerRoot.childCount; i++) { var child = playerRoot.GetChild(i); if (child.GetComponentInChildren() != null) return child; } // Fallback: if the root itself contains a SMR, use it if (playerRoot.GetComponentInChildren() != null) return playerRoot; return null; } /// /// Searches 's hierarchy for a head bone by common name patterns. /// public static Transform FindHeadBone(Transform modelRoot) { if (modelRoot == null) return null; foreach (var boneName in HeadBoneNames) { var bone = FindChildByName(modelRoot, boneName); if (bone != null) return bone; } return null; } /// Recursive depth-first search for a child transform by exact name. public static Transform FindChildByName(Transform root, string name) { if (root.name == name) return root; for (int i = 0; i < root.childCount; i++) { var result = FindChildByName(root.GetChild(i), name); if (result != null) return result; } return null; } // ────────────────────────────────────────────────────────────────────────────── // Clone setup // ────────────────────────────────────────────────────────────────────────────── /// /// Strips non-visual components from a cloned GameObject so it becomes a lightweight /// render-only puppet. Keeps Animator so idle animations continue to play. /// public static void CleanupCloneComponents(GameObject go) { foreach (var col in go.GetComponentsInChildren(true)) Object.Destroy(col); foreach (var rb in go.GetComponentsInChildren(true)) Object.Destroy(rb); foreach (var mb in go.GetComponentsInChildren(true)) { if (mb is Animator) continue; Object.Destroy(mb); } } /// Recursively sets the layer of and all its children. public static void SetLayerRecursive(GameObject go, int layer) { go.layer = layer; foreach (Transform child in go.transform) SetLayerRecursive(child.gameObject, layer); } // ────────────────────────────────────────────────────────────────────────────── // Camera / RenderTexture factories // ────────────────────────────────────────────────────────────────────────────── /// /// Creates a Camera child GameObject under whose culling mask /// covers only . Camera starts disabled. /// public static Camera CreatePortraitCamera(Transform parent, string goName, int portraitLayer, float fov) { var camGO = new GameObject(goName); camGO.transform.SetParent(parent, false); var cam = camGO.AddComponent(); cam.cullingMask = 1 << portraitLayer; cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = Color.clear; cam.fieldOfView = fov; cam.nearClipPlane = 0.1f; cam.farClipPlane = 200f; cam.enabled = false; return cam; } /// /// Creates a 256×256 ARGB32 RenderTexture and wires it to if provided. /// public static RenderTexture CreatePortraitRT(string rtName, Camera cam) { var rt = new RenderTexture(256, 256, 16, RenderTextureFormat.ARGB32) { name = rtName, antiAliasing = 1 }; rt.Create(); if (cam != null) cam.targetTexture = rt; return rt; } } }