#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