#if UNITY_EDITOR using System.IO; using System.Linq; using UnityEditor; using UnityEngine; /// Adds locomotion to the camp guide prefab used by the home screen. public static class BuildCampGuide { const string PrefabPath = "Assets/Bundles/Characters/Human/CampGuide/CampGuide_Rigged.prefab"; public static string Attach() { var root = PrefabUtility.LoadPrefabContents(PrefabPath); try { if (root.GetComponent() == null) root.AddComponent(); SetLayer(root.transform); PrefabUtility.SaveAsPrefabAsset(root, PrefabPath); } finally { PrefabUtility.UnloadPrefabContents(root); } return PrefabPath; } public static string Bones() { var asset = AssetDatabase.LoadAssetAtPath( "Assets/Bundles/Characters/Human/CampGuide/Rigged/CampGuide_Rigged.fbx"); var model = UnityEngine.Object.Instantiate(asset); try { var lines = new System.Text.StringBuilder(); foreach (var t in model.GetComponentsInChildren()) { if (!t.name.StartsWith("bone_")) continue; var p = t.position; lines.AppendLine($"{t.name} parent={t.parent.name} child={t.childCount} x={p.x:F3} y={p.y:F3} z={p.z:F3}"); } return lines.ToString(); } finally { UnityEngine.Object.DestroyImmediate(model); } } public static string Author() { var asset = AssetDatabase.LoadAssetAtPath( "Assets/Bundles/Characters/Human/CampGuide/Rigged/CampGuide_Rigged.fbx"); var model = Object.Instantiate(asset); try { var animator = model.GetComponent(); if (animator != null) animator.enabled = false; var bones = model.GetComponentsInChildren() .Where(t => t.name.StartsWith("bone_")).ToArray(); Transform Bone(string name) => bones.First(t => t.name == name); var hips = Bone("bone_0"); var spine = Bone("bone_1"); var chest = Bone("bone_2"); var leftArm = Bone("bone_6"); var leftElbow = Bone("bone_7"); var leftHand = Bone("bone_9"); var rightArm = Bone("bone_10"); var rightElbow = Bone("bone_11"); var rightHand = Bone("bone_13"); var leftThigh = Bone("bone_14"); var leftKnee = Bone("bone_15"); var rightThigh = Bone("bone_18"); var rightKnee = Bone("bone_19"); float ground = Lowest(model); HangArm(leftArm, leftHand, 1f); HangArm(rightArm, rightHand, -1f); leftElbow.localRotation *= Quaternion.Euler(12f, 0f, 0f); rightElbow.localRotation *= Quaternion.Euler(12f, 0f, 0f); var relaxed = Read(bones); var relaxedArmL = leftArm.rotation; var relaxedArmR = rightArm.rotation; var relaxedThighL = leftThigh.rotation; var relaxedThighR = rightThigh.rotation; var relaxedKneeL = leftKnee.localRotation; var relaxedKneeR = rightKnee.localRotation; WriteClip(model, bones, "Idle", 1.8f, 24, (t, poseBones) => { Write(poseBones, relaxed); float breath = Mathf.Sin(t * Mathf.PI * 2f); spine.localRotation *= Quaternion.Euler(1.6f * breath, 0f, 0f); chest.localRotation *= Quaternion.Euler(1.1f * breath, 0.6f * breath, 0f); hips.position += Vector3.up * (0.006f * breath); Plant(model, hips, ground); }); WriteClip(model, bones, "Walk", 1.05f, 18, (t, poseBones) => { Write(poseBones, relaxed); float swing = Mathf.Sin(t * Mathf.PI * 2f); leftThigh.rotation = Quaternion.AngleAxis(swing * 24f, Vector3.right) * relaxedThighL; rightThigh.rotation = Quaternion.AngleAxis(-swing * 24f, Vector3.right) * relaxedThighR; float leftBend = 10f + 16f * Mathf.InverseLerp(-1f, 1f, -swing); float rightBend = 10f + 16f * Mathf.InverseLerp(-1f, 1f, swing); leftKnee.localRotation = relaxedKneeL * Quaternion.Euler(leftBend, 0f, 0f); rightKnee.localRotation = relaxedKneeR * Quaternion.Euler(rightBend, 0f, 0f); leftArm.rotation = Quaternion.AngleAxis(-swing * 16f, Vector3.right) * relaxedArmL; rightArm.rotation = Quaternion.AngleAxis(swing * 16f, Vector3.right) * relaxedArmR; spine.localRotation *= Quaternion.Euler(2.5f, 0f, -swing * 3f); hips.position += Vector3.up * (Mathf.Abs(swing) * 0.012f); Plant(model, hips, ground); }); AssetDatabase.SaveAssets(); return "Authored grounded Idle and Walk on CampGuide clips"; } finally { Object.DestroyImmediate(model); } } static void HangArm(Transform upper, Transform hand, float side) { Vector3 from = hand.position - upper.position; Vector3 to = Vector3.down * 0.92f + Vector3.right * side * 0.45f + Vector3.forward * 0.12f; upper.rotation = Quaternion.FromToRotation(from.normalized, to.normalized) * upper.rotation; } static void Plant(GameObject model, Transform hips, float ground) { hips.position += Vector3.up * (ground - Lowest(model)); } static float Lowest(GameObject model) { float min = float.MaxValue; foreach (var skin in model.GetComponentsInChildren()) { var mesh = skin.sharedMesh; var matrices = skin.bones.Select((b, i) => b.localToWorldMatrix * mesh.bindposes[i]).ToArray(); var weights = mesh.boneWeights; var vertices = mesh.vertices; for (int i = 0; i < vertices.Length; i++) { var w = weights[i]; Vector3 p = matrices[w.boneIndex0].MultiplyPoint3x4(vertices[i]) * w.weight0 + matrices[w.boneIndex1].MultiplyPoint3x4(vertices[i]) * w.weight1 + matrices[w.boneIndex2].MultiplyPoint3x4(vertices[i]) * w.weight2 + matrices[w.boneIndex3].MultiplyPoint3x4(vertices[i]) * w.weight3; if (p.y < min) min = p.y; } } return min; } sealed class Pose { public Vector3[] p, s; public Quaternion[] q; } static Pose Read(Transform[] bones) => new Pose { p = bones.Select(t => t.localPosition).ToArray(), s = bones.Select(t => t.localScale).ToArray(), q = bones.Select(t => t.localRotation).ToArray() }; static void Write(Transform[] bones, Pose pose) { for (int i = 0; i < bones.Length; i++) { bones[i].localPosition = pose.p[i]; bones[i].localRotation = pose.q[i]; bones[i].localScale = pose.s[i]; } } static void WriteClip(GameObject model, Transform[] bones, string name, float seconds, int frames, System.Action pose) { var samples = new Pose[frames]; for (int f = 0; f < frames; f++) { float t = f / (float)(frames - 1); pose(t, bones); samples[f] = Read(bones); } samples[frames - 1] = samples[0]; var clip = AssetDatabase.LoadAssetAtPath( "Assets/Bundles/Characters/Human/CampGuide/Clips/" + name + ".anim"); if (clip == null) throw new FileNotFoundException(name); clip.ClearCurves(); clip.frameRate = 30f; for (int b = 0; b < bones.Length; b++) { string path = AnimationUtility.CalculateTransformPath(bones[b], model.transform); for (int c = 0; c < 10; c++) { int component = c; var keys = Enumerable.Range(0, frames).Select(f => new Keyframe( seconds * f / (frames - 1), component < 3 ? samples[f].p[b][component] : component < 7 ? samples[f].q[b][component - 3] : samples[f].s[b][component - 7])).ToArray(); string prop = component < 3 ? "m_LocalPosition." + "xyz"[component] : component < 7 ? "m_LocalRotation." + "xyzw"[component - 3] : "m_LocalScale." + "xyz"[component - 7]; AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve(path, typeof(Transform), prop), new AnimationCurve(keys)); } } var settings = AnimationUtility.GetAnimationClipSettings(clip); settings.startTime = 0f; settings.stopTime = seconds; settings.loopTime = true; settings.loopBlend = false; AnimationUtility.SetAnimationClipSettings(clip, settings); EditorUtility.SetDirty(clip); } public static string Preview() { var prefab = AssetDatabase.LoadAssetAtPath(PrefabPath); var shader = Shader.Find("AIGame/HomeExplorerCutout"); var stage = new GameObject("CampGuidePreview"); var rt = new RenderTexture(512, 512, 16, RenderTextureFormat.ARGB32); var tex = new Texture2D(512, 512, TextureFormat.RGBA32, false); try { var actor = (GameObject)PrefabUtility.InstantiatePrefab(prefab, stage.transform); actor.transform.localPosition = new Vector3(0.4f, 0f, 0f); actor.transform.localRotation = Quaternion.Euler(0f, 70f, 0f); SetLayer(actor.transform); var lightGo = new GameObject("Light"); lightGo.transform.SetParent(stage.transform, false); var light = lightGo.AddComponent(); light.type = LightType.Directional; light.intensity = 1.15f; light.cullingMask = 1 << 31; light.transform.rotation = Quaternion.Euler(38f, -28f, 0f); var camGo = new GameObject("Camera"); camGo.transform.SetParent(stage.transform, false); var camera = camGo.AddComponent(); camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = new Color(1f, 0f, 1f, 1f); camera.cullingMask = 1 << 31; camera.orthographic = true; camera.orthographicSize = 1.45f; camera.nearClipPlane = 0.05f; camera.farClipPlane = 30f; camera.transform.localPosition = new Vector3(0.15f, 1.7f, -3.6f); camera.transform.LookAt(stage.transform.TransformPoint(0.4f, 0.7f, 0f)); camera.targetTexture = rt; var anim = actor.GetComponentInChildren(); if (anim != null) { anim.Update(0f); anim.Play("Walk", 0, 0.3f); anim.Update(0f); } camera.Render(); camera.targetTexture = null; var mat = new Material(shader); mat.SetColor("_Key", new Color(1f, 0f, 1f, 1f)); var cut = RenderTexture.GetTemporary(512, 512, 0, RenderTextureFormat.ARGB32); Graphics.Blit(rt, cut, mat); RenderTexture.active = cut; tex.ReadPixels(new Rect(0, 0, 512, 512), 0, 0); tex.Apply(); RenderTexture.active = null; RenderTexture.ReleaseTemporary(cut); string path = Path.GetFullPath(Path.Combine(Application.dataPath, "../../Tools/output/campguide_review/cutout.png")); Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllBytes(path, tex.EncodeToPNG()); Object.DestroyImmediate(mat); return path; } finally { RenderTexture.active = null; Object.DestroyImmediate(tex); rt.Release(); Object.DestroyImmediate(rt); Object.DestroyImmediate(stage); } } static void SetLayer(Transform root) { root.gameObject.layer = 31; foreach (Transform child in root) SetLayer(child); } } #endif