290 lines
12 KiB
C#
290 lines
12 KiB
C#
#if UNITY_EDITOR
|
|
using System.IO;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
/// <summary>Adds locomotion to the camp guide prefab used by the home screen.</summary>
|
|
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<HotUpdate.Gameplay.ExplorerLocomotion>() == null)
|
|
root.AddComponent<HotUpdate.Gameplay.ExplorerLocomotion>();
|
|
SetLayer(root.transform);
|
|
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath);
|
|
}
|
|
finally
|
|
{
|
|
PrefabUtility.UnloadPrefabContents(root);
|
|
}
|
|
return PrefabPath;
|
|
}
|
|
|
|
public static string Bones()
|
|
{
|
|
var asset = AssetDatabase.LoadAssetAtPath<GameObject>(
|
|
"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<Transform>())
|
|
{
|
|
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<GameObject>(
|
|
"Assets/Bundles/Characters/Human/CampGuide/Rigged/CampGuide_Rigged.fbx");
|
|
var model = Object.Instantiate(asset);
|
|
try
|
|
{
|
|
var animator = model.GetComponent<Animator>();
|
|
if (animator != null) animator.enabled = false;
|
|
var bones = model.GetComponentsInChildren<Transform>()
|
|
.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<SkinnedMeshRenderer>())
|
|
{
|
|
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<float, Transform[]> 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<AnimationClip>(
|
|
"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<GameObject>(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>();
|
|
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>();
|
|
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<Animator>();
|
|
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
|