diff --git a/CreatureControl/Editor/Scripts/Creature_Editor.cs b/CreatureControl/Editor/Scripts/Creature_Editor.cs
index e78a8df..e60705c 100644
--- a/CreatureControl/Editor/Scripts/Creature_Editor.cs
+++ b/CreatureControl/Editor/Scripts/Creature_Editor.cs
@@ -1,51 +1,54 @@
using UnityEditor;
using UnityEditor.SceneManagement;
-using UnityEngine;
-
namespace Passer.CreatureControl {
[CustomEditor(typeof(Creature), true)]
public class Creature_Editor : Editor {
+ ///
+ /// The creature managed by this editor
+ ///
protected Creature creature;
- #region Init
+ #region Start
+ ///
+ /// Enable the creature editor
+ ///
public virtual void OnEnable() {
- creature = target as Creature;
+ this.creature = target as Creature;
+ // Keep track if anything changed while enabling the creature editor
bool anythingChanged = false;
- if (!IsPrefab(creature)) {
- anythingChanged |= creature.CheckTargetRig();
- anythingChanged |= creature.CheckModel();
+ if (IsPrefab(this.creature) == false) {
+ // Only do this when it is not a prefab
+ anythingChanged |= this.creature.CheckTargetRig();
+ anythingChanged |= this.creature.CheckModel();
}
- creature.targetRig.MatchTo(creature, ref anythingChanged);
+ this.creature.targetRig.MatchTo(this.creature, ref anythingChanged);
+ // As the above functions do not use the serialized object
+ // We need to manually persist the changes.
if (anythingChanged) {
- EditorUtility.SetDirty(creature);
+ EditorUtility.SetDirty(this.creature);
AssetDatabase.SaveAssets();
}
}
+ ///
+ /// Check if the given creature is a prefab
+ ///
+ /// The creature to check
+ /// True when it is a prefab
public static bool IsPrefab(Creature creature) {
PrefabStage prefabStage = PrefabStageUtility.GetPrefabStage(creature.gameObject);
return prefabStage != null;
}
- #endregion Init
+ #endregion Start
- #region Scene
-
- public virtual void OnSceneGUI() {
- if (Application.isPlaying || creature.enabled == false)
- return;
-
- creature.UpdatePose();
- }
-
- #endregion Scene
}
}
\ No newline at end of file
diff --git a/CreatureControl/Runtime/Scripts/Creature.cs b/CreatureControl/Runtime/Scripts/Creature.cs
index fb128cd..12922f3 100644
--- a/CreatureControl/Runtime/Scripts/Creature.cs
+++ b/CreatureControl/Runtime/Scripts/Creature.cs
@@ -3,15 +3,24 @@ using UnityEngine;
namespace Passer.CreatureControl {
public class Creature : MonoBehaviour {
+ ///
+ /// The (hopefully rigged) 3D model of the creature
+ ///
+ public Transform model;
+
/// The target bones rig
/// The target bones rig contain the target pose of the creature
/// The creature movements will try to move the creature such that the target pose is reached
/// as closely as possible
- public Transform model;
-
public TargetRig targetRig;
+ ///
+ /// The positional different between the target rig and model root
+ ///
public Vector3 targetToModelTranslation;
+ ///
+ /// The rotational difference between the target rig and the model root
+ ///
public Quaternion targetToModelRotation;
#region Init
@@ -20,27 +29,32 @@ namespace Passer.CreatureControl {
/// Ensure a target rig is available
///
/// The name of the target rig resource
- /// True when the creature rig has been updated
+ /// True when the target rig has been updated
/// The parameter is used to instantiate a new target rig when none has been found.
public bool CheckTargetRig(string targetRigResourceName) {
- if (this.targetRig == null) {
- // See if there is a target rig, but we just haven't found it
- this.targetRig = this.GetComponentInChildren();
- if (this.targetRig == null) {
- GameObject targetsRigPrefab = Resources.Load(targetRigResourceName);
- GameObject targetRig = Instantiate(targetsRigPrefab);
- targetRig.name = "Target Rig";
-
- targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation);
- targetRig.transform.SetParent(this.transform);
- this.targetRig = targetRig.GetComponent();
- }
- return true;
- }
- else
+ if (this.targetRig != null)
return false;
+
+ // See if there is a target rig, but we just haven't found it
+ this.targetRig = this.GetComponentInChildren();
+ if (this.targetRig == null) {
+ // Nope, there is no target rig, so instantiate it using the given resource name
+ GameObject targetsRigPrefab = Resources.Load(targetRigResourceName);
+ GameObject targetRig = Instantiate(targetsRigPrefab);
+ targetRig.name = "Target Rig";
+
+ targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation);
+ targetRig.transform.SetParent(this.transform);
+ this.targetRig = targetRig.GetComponent();
+ }
+ return true;
}
+ ///
+ /// Ensure a target rig is available
+ ///
+ /// True when the target rig has been updated
+ /// This tries to instantiate the default target rig resource
public virtual bool CheckTargetRig() {
return CheckTargetRig("TargetRig");
}
@@ -50,25 +64,30 @@ namespace Passer.CreatureControl {
///
/// True when the creature rig has been updated
public bool CheckModel() {
- if (this.model == null) {
- SkinnedMeshRenderer[] skinnedMeshRenderers = this.GetComponentsInChildren();
- foreach (SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRenderers) {
- Transform rendererParent = skinnedMeshRenderer.transform.parent;
- if (this.model == null || this.model == rendererParent)
- this.model = rendererParent;
- else
- Debug.LogWarning("Unclear model root");
- }
- return this.model != null;
- }
- else
+ if (this.model != null)
return false;
+
+ // We determine the model root as the parent of the renderers
+ SkinnedMeshRenderer[] skinnedMeshRenderers = this.GetComponentsInChildren();
+ foreach (SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRenderers) {
+ Transform rendererParent = skinnedMeshRenderer.transform.parent;
+ if (this.model == null || this.model == rendererParent)
+ this.model = rendererParent;
+ else
+ // Oops! There are multiple renders with different parents....
+ Debug.LogWarning("Unclear model root");
+ // We still return a model root, but this may not be the correct one...
+ }
+ return this.model != null;
}
#endregion Init
#region Start
+ ///
+ /// Start the creature
+ ///
protected virtual void Start() {
this.CheckTargetRig();
this.CheckModel();
@@ -79,28 +98,38 @@ namespace Passer.CreatureControl {
#region Update
+ ///
+ /// Update the creature
+ ///
public virtual void Update() {
if (this.targetRig == null)
+ // Without a target rig, the creature cannot move
return;
UpdatePose();
- // copy animator root motion to the humanoid
+ // copy animator root motion to the creature
this.transform.SetPositionAndRotation(targetRig.transform.position, targetRig.transform.rotation);
// As target rig is probably a child of this.transform,
// We need to restore the position/rotation of the targetsRig.
- targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation);
+ targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation);
}
+ ///
+ /// Update the pose of the creature using the target rig
+ ///
public void UpdatePose() {
if (this.targetRig == null)
return;
- this.targetRig.PoseLimbs();
- UpdateBones();
+ this.targetRig.Pose();
+ UpdateModel();
}
- public virtual void UpdateBones() {
+ ///
+ /// Update the bones of the creature's rig from the target rig pose
+ ///
+ public virtual void UpdateModel() {
Vector3 newPosition = this.targetRig.transform.position + this.targetToModelTranslation;
Quaternion newOrientation = this.targetRig.transform.rotation * this.targetToModelRotation;
this.model.SetPositionAndRotation(newPosition, newOrientation);
@@ -108,12 +137,19 @@ namespace Passer.CreatureControl {
#endregion Update
+ #region Scene view
+
+ ///
+ /// Update the pose of the creature when the application is not running
+ ///
void OnDrawGizmos() {
// This ensures that the model is always following the target rig
if (Application.isPlaying == false) {
this.UpdatePose();
}
}
+
+ #endregion Scene view
}
}
\ No newline at end of file
diff --git a/CreatureControl/Runtime/Scripts/Insect/Insect.cs b/CreatureControl/Runtime/Scripts/Insect/Insect.cs
index 4d0a88a..c9e7fca 100644
--- a/CreatureControl/Runtime/Scripts/Insect/Insect.cs
+++ b/CreatureControl/Runtime/Scripts/Insect/Insect.cs
@@ -31,8 +31,8 @@ namespace Passer.CreatureControl {
#region Update
- public override void UpdateBones() {
- base.UpdateBones();
+ public override void UpdateModel() {
+ base.UpdateModel();
if (this.insectRig.leftFrontLeg != null)
this.insectRig.leftFrontLeg.UpdateBones(this.leftFrontLeg);
diff --git a/CreatureControl/Runtime/Scripts/Insect/InsectRig.cs b/CreatureControl/Runtime/Scripts/Insect/InsectRig.cs
index 68f4e57..0e51dae 100644
--- a/CreatureControl/Runtime/Scripts/Insect/InsectRig.cs
+++ b/CreatureControl/Runtime/Scripts/Insect/InsectRig.cs
@@ -12,7 +12,7 @@ namespace Passer.CreatureControl {
public TargetLeg rightMiddleLeg;
public TargetLeg rightBackLeg;
- public override void PoseLimbs() {
+ public override void Pose() {
this.leftBackLeg.PoseLimb();
this.leftMiddleLeg.PoseLimb();
this.leftFrontLeg.PoseLimb();
diff --git a/CreatureControl/Runtime/Scripts/TargetLeg.cs b/CreatureControl/Runtime/Scripts/TargetLeg.cs
index 9c8febb..5a7f9b4 100644
--- a/CreatureControl/Runtime/Scripts/TargetLeg.cs
+++ b/CreatureControl/Runtime/Scripts/TargetLeg.cs
@@ -14,6 +14,19 @@ namespace Passer.CreatureControl {
public Quaternion targetToBoneFemur;
public Quaternion targetToBoneTibia;
+ public float femurLength;
+ public float tibiaLength;
+ public float length;
+
+ ///
+ /// Update the lenghts of the leg bones
+ ///
+ private void CalculateLengths() {
+ this.femurLength = Vector3.Distance(this.femurTarget.position, this.tibiaTarget.position);
+ this.tibiaLength = Vector3.Distance(this.tibiaTarget.position, this.tarsusTarget.position);
+ this.length = femurLength + tibiaLength;
+ }
+
public void MatchTo(Leg leg) {
this.femurTarget.position = leg.femur.position;
this.tibiaTarget.position = leg.tibia.position;
@@ -21,15 +34,13 @@ namespace Passer.CreatureControl {
targetToBoneFemur = TargetRig.TargetToBoneRotation(leg.femur, leg.tibia);
targetToBoneTibia = TargetRig.TargetToBoneRotation(leg.tibia, leg.tarsus);
- float femurLength = Vector3.Distance(this.femurTarget.position, this.tibiaTarget.position);
- float tibiaLength = Vector3.Distance(this.tibiaTarget.position, this.tarsusTarget.position);
- float leglength = femurLength + tibiaLength;
+ CalculateLengths();
+ // Put the end-effector target for IK in a sensible place
Vector3 legDirection = (this.tarsusTarget.position - this.femurTarget.position).normalized;
- this.target.position = this.femurTarget.position + 0.7f * leglength * legDirection.normalized;
- this.target.rotation = Quaternion.LookRotation(legDirection);
-
- //this.target.SetPositionAndRotation(this.tarsusTarget.position, this.tarsusTarget.rotation);
+ Vector3 targetPosition = this.femurTarget.position + 0.7f * this.length * legDirection.normalized;
+ Quaternion targetRotation = Quaternion.LookRotation(legDirection);
+ this.target.SetPositionAndRotation(targetPosition, targetRotation);
this.target.localPosition = new(this.target.localPosition.x, 0, this.target.localPosition.z);
}
diff --git a/CreatureControl/Runtime/Scripts/TargetRig.cs b/CreatureControl/Runtime/Scripts/TargetRig.cs
index 9b5956d..0232505 100644
--- a/CreatureControl/Runtime/Scripts/TargetRig.cs
+++ b/CreatureControl/Runtime/Scripts/TargetRig.cs
@@ -9,14 +9,26 @@ namespace Passer.CreatureControl {
public Animator animator;
- public virtual void PoseLimbs() {
+ ///
+ /// Pose the target rig using the IK targets
+ ///
+ public virtual void Pose() {
}
-
+
+ ///
+ /// Align the target rig with a creature
+ ///
+ /// The creature to align to
public void MatchTo(Creature creature) {
bool anythingChangedDummy = false;
MatchTo(creature, ref anythingChangedDummy);
}
+ ///
+ /// Align the target rig with a creature
+ ///
+ /// The creature to align to
+ /// True when any property of the creature has changed
public virtual void MatchTo(Creature creature, ref bool anythingChanged) {
Vector3 targetToModelTranslation = creature.model.position - this.transform.position;
bool changed = targetToModelTranslation != creature.targetToModelTranslation;
@@ -33,6 +45,14 @@ namespace Passer.CreatureControl {
}
}
+ ///
+ /// Compute the rotation from the target bone to a creature's bone
+ ///
+ /// The creature's bone for this target bone
+ /// The next bone in the creature's hierarchy
+ /// The rotation from the target bone rotation to the creature bone rotation
+ /// The next bone is used to compute the direction of the bone.
+ /// The 'up'-direction of the bone is currently fixed to (world) up.
public static Quaternion TargetToBoneRotation(Transform bone, Transform nextBone) {
if (bone == null || nextBone == null)
return Quaternion.identity;
@@ -42,24 +62,6 @@ namespace Passer.CreatureControl {
Quaternion toBoneRotation = Quaternion.Inverse(targetRotation) * bone.rotation;
return toBoneRotation;
}
-
- public static Quaternion TargetToBoneRotation(Transform target, Transform nextTarget, Transform bone) {
- Vector3 direction = nextTarget.position - bone.position;
- Quaternion targetRotation = Quaternion.LookRotation(direction, Vector3.up);
- Quaternion toBoneRotation = Quaternion.Inverse(targetRotation) * bone.rotation;
- return toBoneRotation;
- }
-
- public static Quaternion TargetToBoneRotation_Debug(Transform target, Transform nextTarget, Transform bone) {
- Vector3 direction = nextTarget.position - target.position;
- Quaternion targetRotation = Quaternion.LookRotation(direction, Vector3.up);
- Debug.DrawRay(bone.position, targetRotation * Vector3.forward, Color.blue);
- Debug.DrawRay(bone.position, targetRotation * Vector3.up, Color.green);
-
- Quaternion toBoneRotation = Quaternion.Inverse(targetRotation) * bone.rotation;
- Debug.Log($"{targetRotation.eulerAngles} {bone.rotation.eulerAngles} -> {toBoneRotation.eulerAngles}");
- return toBoneRotation;
- }
}
}
\ No newline at end of file