Search code examples
c#unity-game-engineanimationprocedural-programmingprocedural

Procedural animation for a crab with Rigging animation & Inverse kinematic


I work on unity, I have to completely change my script which allows to do procedural animation in reverse kinematic, in fact to explain to you I have a crab with 8 legs, each with 4 pivot points, and I want to do so that when the crab moves the legs also, via procedural animation.

I have already created a script to do all this, however sometimes the point remains in place, impossible to reset it, even with checks, whether in the calculation, out of the calculation, and even by forcing with Tasks etc..

So I would like to know if some here could help me fix this bug, or completely redo everything to start from a good base.

Here is my full script.

// ReSharper disable once InvalidXmlDocComment
/**
 * Original Script by: Sanchez Maxime (@Maxime66410)
 * @version 1.0.0
 * @autor Sanchez Maxime (@Maxime66410)
 *
 * Last modification: 09/06/2023
 * File description: This script is used to procedurally animate the crab
 */

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Serialization;

// ReSharper disable once CheckNamespace
public class CrabPatteCombine : MonoBehaviour
{
      [FormerlySerializedAs("SingleLegs")] [Header("Legs GameObject"), Space(10)]
      public List<GameObject> singleLegs = new List<GameObject>(); // Paw GameObject
      [FormerlySerializedAs("SingleLegsIK")] public List<GameObject> singleLegsIK = new List<GameObject>(); // IK's GameObject

      [FormerlySerializedAs("SingleLegsTarget")] [Header("Vector3"), Space(10)]
      public List<Vector3> singleLegsTarget = new List<Vector3>(); // Leg position
      [FormerlySerializedAs("SingleLocalTarget")] public List<Vector3> singleLocalTarget = new List<Vector3>(); // Position of the IK
      
      [Header("Legs / Step"), Space(10)]
      [FormerlySerializedAs("SpeedSyncLegs")] public float speedSyncLegs = 10f; // paw speed
      [FormerlySerializedAs("DistanceToPoint")] public float distanceToPoint = 0.075f; // Distance between paw and target
      [FormerlySerializedAs("StepHeight")] public float stepHeight = 0.5f; // step height
      [FormerlySerializedAs("StepHeightCurve")] public AnimationCurve stepHeightCurve; // Step height curve
      [FormerlySerializedAs("OffsetStep")] public Vector3 offsetStep = Vector3.zero; // Step height offset
      [FormerlySerializedAs("_distanceStep")] [SerializeField] private List<float> distanceStep = new List<float>(); // Distance between paw and target
      [FormerlySerializedAs("_distanceToLegFromIK")] [SerializeField] private List<float> distanceToLegFromIK = new List<float>(); // Distance between paw and target
      [FormerlySerializedAs("DistanceToStopSyncLeg")] public float distanceToStopSyncLeg = 0.01f; // Distance between paw and target to stop synchronization
      [FormerlySerializedAs("MaxDistanceToSyncLeg")] public float maxDistanceToSyncLeg = 1f; // Maximum distance between paw and target to synchronize the paw
      [FormerlySerializedAs("MaxDistanceToSyncLeg")] public float maxDistanceDebug = 0.2f; // Maximum distance between paw and target to synchronize the paw (Debug)
      [FormerlySerializedAs("RaycastDistance")] public float raycastDistance = 0.3f; // Raycast distance
      [FormerlySerializedAs("LayerMaskGround")] public LayerMask layerMaskGround; // LayerMask
      
      public void Awake()
      {
          // Automatic initialization of the lists
          for (int i = 0; i < singleLegs.Count; i++)
          {
              distanceStep.Add(0f);
              distanceToLegFromIK.Add(0f);
              singleLegsTarget.Add(Vector3.zero);
              singleLocalTarget.Add(Vector3.zero);
          }
      }
      
      public void Start()
      {
          GetSurfaceOnStart();
#pragma warning disable CS4014
          CheckDistanceLeg();
#pragma warning restore CS4014
      }
      
      // Get crab area at start Position and rotation
      public void GetSurfaceOnStart()
      {
          // If SingleLegs is not empty
          if (singleLegs.Count > 0)
          {
              for (int i = 0; i < singleLegs.Count; i++)
              {
                  singleLocalTarget[i] = singleLegsIK[i].transform.position; // Get IK paw position

                  // Get the surface of the starting crab Position and rotation
                  RaycastHit hit;
                  if (Physics.Raycast(transform.position, Vector3.down, out hit, 0.03f, layerMaskGround))
                  {
                      singleLegs[i].transform.position = hit.point;
                      singleLegsTarget[i] = hit.point;
                      Transform transform1 = transform;
                      transform1.rotation = Quaternion.FromToRotation(transform1.up, hit.normal) * (transform).rotation;
                  }
              }
          }
      }
      
      public void FixedUpdate()
      {
          CheckDistance();
          UpdatePosition();
      }
      
      // Check the distance between the leg and the target
      public void CheckDistance()
      {
          // If SingleLegs is not empty
          if(singleLegs.Count > 0)
          {
              // Check the distance between the leg and the target
              for (int i = 0; i < singleLegs.Count; i++)
              {
                  RaycastHit hit;
                  if (Physics.Raycast(singleLocalTarget[i], Vector3.down, out hit, raycastDistance, layerMaskGround))
                  {
                      distanceStep[i] = Vector3.Distance(singleLegs[i].transform.position, hit.point);
                      distanceToLegFromIK[i] = Vector3.Distance(singleLegs[i].transform.localPosition, singleLegsIK[i].transform.localPosition);
                
                
                      // If the remaining distance is really too much greater than the target distance, reset the leg position (Fix Bug)
                      if (distanceToLegFromIK[i] > maxDistanceToSyncLeg)
                      {
                          singleLegs[i].transform.position = singleLegsIK[i].transform.position;
                          singleLegsTarget[i] = singleLegsIK[i].transform.position;
                          singleLocalTarget[i] = singleLegsIK[i].transform.position;
                      }
                      else
                      {
                          // If the distance is greater than DistanceToPoint, update the leg position
                          if (Vector3.Distance(singleLegs[i].transform.position, hit.point) > distanceToPoint)
                          {
                              singleLegsTarget[i] = hit.point;
                          }
                      }
                  }
              }
          }
      }

      // Update leg position
      public void UpdatePosition()
      {
          if (singleLegs.Count > 0)
          {
              for (int i = 0; i < singleLegs.Count; i++)
              {
                  if (singleLegs[i].transform.position != singleLegsTarget[i])
                  {
                      // Calculate the remaining distance between the current leg position and the target
                      float remainingDistance = Vector3.Distance(singleLegs[i].transform.position, singleLegsTarget[i]);

                      // Calculate total step based on remaining distance
                      float totalStep = distanceToPoint * stepHeightCurve.length;

                      // If the remaining distance is less than the total step, adjust the height of the step
                      if (remainingDistance < totalStep)
                      {
                          // Calculate step height using StepHeightCurve
                          // ReSharper disable once LocalVariableHidesMember
                          float stepHeight = stepHeightCurve.Evaluate(1f - (remainingDistance / totalStep)) * this.stepHeight;

                          // Move paw to target with height adjustment
                          singleLegs[i].transform.position = Vector3.Lerp(singleLegs[i].transform.position, singleLegsTarget[i], Time.deltaTime * speedSyncLegs) + (offsetStep * stepHeight);

                          // Adjust paw height to simulate lifting motion
                          singleLegs[i].transform.position += Vector3.up * stepHeight;

                          // If the remaining distance is less than a small threshold value, reset the position of the paw
                          if (remainingDistance < distanceToStopSyncLeg)
                          {
                              singleLegs[i].transform.position = singleLegsTarget[i];
                          }
                            
                          // If the remaining distance is really too much greater than the distance to the target, reset the position of the paw
                          if (remainingDistance > maxDistanceToSyncLeg) 
                          { 
                              singleLegs[i].transform.position = singleLegsIK[i].transform.position;
                          }
                      }
                      else
                      {
                          // Move paw to target without height adjustment
                          singleLegs[i].transform.position = Vector3.Lerp(singleLegs[i].transform.position, singleLegsTarget[i], Time.deltaTime * speedSyncLegs);
                            
                          FixDistanceLeg();
                      } 
                  }
                  else
                  {
                      // If the remaining distance is really too much greater than the distance to the target, reset the position of the paw
                      float remainingDistance = Vector3.Distance(singleLegs[i].transform.position, singleLegsIK[i].transform.position);
                        
                      // If the remaining distance is really too much greater than the distance to the target, reset the position of the paw
                      if (remainingDistance > maxDistanceToSyncLeg)
                      {
                          singleLegs[i].transform.position = Vector3.Lerp(singleLegs[i].transform.position, singleLegsIK[i].transform.position, Time.deltaTime * speedSyncLegs);
                      }
                  }
                    
                  // Get the ground surface below the leg and adjust the local target
                  RaycastHit hit;
                  if (Physics.Raycast(singleLegsIK[i].transform.position, Vector3.down, out hit, raycastDistance, layerMaskGround))
                  { 
                      singleLocalTarget[i] = Vector3.Lerp(singleLocalTarget[i], new Vector3(singleLegsIK[i].transform.position.x, hit.point.y, singleLegsIK[i].transform.position.z), Time.deltaTime * speedSyncLegs);
                  }

                  FixDistanceLeg();
              }
          }

          // SingleLocalTarget = SingleLegsIK.transform.position; // OLD
      }
      
      // If the remaining distance is really too much greater than the target distance, reset the leg position (Fix Bug)
      public void FixDistanceLeg()
      {
          for (int i = 0; i < singleLegs.Count; i++)
          {
              if (distanceToLegFromIK[i] > maxDistanceToSyncLeg)
              {
                  singleLegs[i].transform.position = singleLegsIK[i].transform.position;
                  singleLegsTarget[i] = singleLegsIK[i].transform.position;
                  singleLocalTarget[i] = singleLegsIK[i].transform.position;
              }
          }
      }
      
      // ReSharper disable once FunctionRecursiveOnAllPaths
      public async Task CheckDistanceLeg()
      {
          for (int i = 0; i < singleLegs.Count; i++)
          {
              // If the distance between the paw and the target is greater than the maximum distance, reset the position of the paw
              float distance = Vector3.Distance(singleLegs[i].transform.position, singleLegsIK[i].transform.position);
              //Debug.Log($"Distance to Leg + {singleLegsIK[i].name} = {distance}");
              if(distance > maxDistanceDebug)
              {
                  singleLegs[i].transform.position = singleLegsIK[i].transform.position;
                  singleLegsTarget[i] = singleLegsIK[i].transform.position;
                  singleLocalTarget[i] = singleLegsIK[i].transform.position;
              }
          }
          

          await Task.Delay(TimeSpan.FromSeconds(1));

          await CheckDistanceLeg();
      }

      // Draw a line between the paw and the target
      public void OnDrawGizmos()
      {
          if (singleLocalTarget.Count > 0)
          {
              for (int i = 0; i < singleLegs.Count; i++)
              {
                  if (singleLocalTarget[i] != Vector3.zero)
                  {
                      Gizmos.color = Color.magenta;
                      Gizmos.DrawLine(singleLegs[i].transform.position, singleLocalTarget[i]);
                  }
        
                  if(singleLocalTarget[i] != Vector3.zero)
                  {
                      Gizmos.color = Color.red;
                      Gizmos.DrawLine(singleLocalTarget[i], singleLocalTarget[i] + Vector3.down * raycastDistance);
                  }
              }
          }
      }
}

Example : https://media.discordapp.net/attachments/1123226386677645373/1123226391358472243/bb4ec7b4f30a5931a16048c665ab1904.mp4

https://cdn.discordapp.com/attachments/1123226386677645373/1123226391840825425/abb57db906ed7d7ef2663f3e6fc1745d.mp4

Here is my problem : As you can see, sometimes the red cube remains frozen. Since I've been stuck on a small problem on my code for several weeks, I would like to know why sometimes my "red cubes" stay stuck indefinitely in the same place despite the distance calculation between the leg (cube) and the point initial, more especially as even with checks and methods which makes it possible to force to replace the leg (cube) towards the initial position does not work all the time.

I've already tried to solve the problem with my Unity specialist programming teacher, I've tried many forums, and even chatgpt, but nothing helped and couldn't find the solution to this problem.

https://cdn.discordapp.com/attachments/1123226386677645373/1123226466650427432/0e6118b5c8351e00b49602ec1d63e237.mp4


Solution

  • Found, there is a calculation error in my code, I am calculating in world when I have to calculate locally.

    Delete the task present in the script, it will be useless and it is not done in the right way.

    public void FixDistanceLeg()
    {
         for (int i = 0; i < singleLegs.Count; i++)
         {
              var distanceIKtoLegLocal = Vector3.Distance(singleLegs[i].transform.localPosition, singleLegsIK[i].transform.localPosition);
     
              if (distanceIKtoLegLocal > distanceToFixStep)
              {
                   //singleLegs[i].transform.position = singleLegsIK[i].transform.position;
                   singleLegs[i].transform.position = Vector3.Lerp(singleLegs[i].transform.position, singleLegsIK[i].transform.position, Time.deltaTime * speedSyncLegs);
                   singleLegsTarget[i] = singleLegsIK[i].transform.position;
                   singleLocalTarget[i] = singleLegsIK[i].transform.position;
              }
         }
    }