Search Unity

Discussion How to Add Foot Alternating Procedural Animation

Discussion in 'Animation' started by procheckmate, Feb 7, 2024.

  1. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    Hello, I'm new to Unity and coding, and I'm working on a procedural foot placement animation using inverse kinematics. I'm stuck on making the foot alternate after each step. Can you help me with this, please

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class footIK : MonoBehaviour
    4. {
    5.     // Debug flags
    6.     [SerializeField] private bool groundDebug;
    7.     [SerializeField] private bool IKDebug;
    8.  
    9.     // References
    10.     [Header("References")]
    11.     [SerializeField] private GameObject Steppartical;
    12.     [SerializeField] private Transform StepparticalParent;
    13.  
    14.     // Ground check parameters
    15.     [Header("GroundCheck")]
    16.     [SerializeField] private LayerMask groundLayer;
    17.     [SerializeField] private Transform groundRaycastOrigin;
    18.     [SerializeField] private float groundRaycastDistance = 0.1f, groundRaycastRadius = 0.1f;
    19.  
    20.     // Body movement parameters
    21.     [Header("Body")]
    22.     [SerializeField] private Transform Body;
    23.     [SerializeField] private float BodyHight;
    24.     [SerializeField] private float BodyMoveSpeed;
    25.  
    26.     // Foot IK Visuals parameters
    27.     [Header("Foot IK Visuals")]
    28.     [SerializeField] private float FootSpeed;
    29.     [SerializeField] private float StepHight;
    30.     [SerializeField] private float timeBetweenSteps;
    31.  
    32.     // Foot IK parameters
    33.     [Header("Foot IK")]
    34.     [SerializeField] private Transform LeftFootTarget;
    35.     [SerializeField] private Transform RightFootTarget;
    36.     [SerializeField] private Transform IKRaycastOrigin;
    37.     [SerializeField] private Transform LeftFootRaycastOrigin;
    38.     [SerializeField] private Transform RightFootRaycastOrigin;
    39.     [SerializeField] private float StepDistance;
    40.     [SerializeField] private float StepLength;
    41.     [SerializeField] private float MaxFootReach;
    42.     [SerializeField] private float FootYOffset;
    43.     [SerializeField] private LayerMask IKLayer;
    44.  
    45.     bool isGrounded;
    46.     RaycastHit Groundhit;
    47.  
    48.     Vector3 oldPosR, oldPosL;
    49.     Vector3 NewPosR, NewPosL;
    50.     Vector3 FTTargetPosR, FTTargetPosL;
    51.     float targetStepLength;
    52.  
    53.     // Update is called once per frame
    54.     void Update()
    55.     {
    56.         FootIK(LeftFootRaycastOrigin, LeftFootTarget, ref NewPosR, ref oldPosR, ref FTTargetPosR);
    57.         FootIK(RightFootRaycastOrigin, RightFootTarget, ref NewPosL, ref oldPosL, ref FTTargetPosL);
    58.  
    59.         // Perform spherecast downwards to check for ground contact
    60.         isGrounded = Physics.SphereCast(groundRaycastOrigin.position, groundRaycastRadius, Vector3.down, out Groundhit, groundRaycastDistance, groundLayer);
    61.  
    62.         // Calculate and update the position of the Body based on foot positions
    63.         Vector3 bodyTarget = ((FTTargetPosR + FTTargetPosL) / 2) + (Vector3.up * BodyHight) - (Body.forward * StepLength);
    64.  
    65.         if (isGrounded)
    66.             Body.position = Vector3.Lerp(Body.position, bodyTarget, BodyMoveSpeed * Time.deltaTime);
    67.     }
    68.  
    69.     // Initialize variables
    70.     void Start()
    71.     {
    72.         NewPosL = FTTargetPosL;
    73.         NewPosR = FTTargetPosR;
    74.         oldPosL = FTTargetPosL;
    75.         oldPosR = FTTargetPosR;
    76.  
    77.         targetStepLength = StepLength;
    78.     }
    79.  
    80.     // Function to perform Foot IK
    81.     void FootIK(Transform RayOrigin, Transform foot, ref Vector3 NewPos, ref Vector3 oldPos, ref Vector3 targetPos)
    82.     {
    83.         // Raycast forward to find the adjusted position for the foot
    84.         RaycastHit FTHitForward;
    85.         bool FTRayForward = Physics.Raycast(RayOrigin.position, RayOrigin.forward, out FTHitForward, targetStepLength, IKLayer);
    86.  
    87.         Vector3 FTHipPos;
    88.  
    89.         if (FTRayForward)
    90.         {
    91.             FTHipPos = FTHitForward.point; // Set position to the forward hit position
    92.         }
    93.         else
    94.         {
    95.             FTHipPos = RayOrigin.position + targetStepLength * IKRaycastOrigin.forward; // Set position to default forward position
    96.         }
    97.  
    98.         // Raycast downward to find the adjusted position for the foot on the ground
    99.         RaycastHit FTHitDown;
    100.         RaycastHit FTIDLE;
    101.         bool FTRayDown = Physics.Raycast(FTHipPos, Vector3.down, out FTHitDown, MaxFootReach, IKLayer);
    102.         bool FTRayIDL = Physics.Raycast(RayOrigin.position, Vector3.down, out FTIDLE, MaxFootReach, IKLayer);
    103.  
    104.         // If foot isn't on the ground, go to a default state
    105.         if (FTRayDown)
    106.         {
    107.             targetPos = Vector3.up * FootYOffset + FTHitDown.point;
    108.             targetStepLength = StepLength;
    109.         }
    110.         else
    111.         {
    112.             targetPos = Vector3.up * (FootYOffset - MaxFootReach) + FTHipPos;
    113.             targetStepLength = targetStepLength - 0.01f; // Decrease step length
    114.  
    115.             Debug.Log(targetStepLength);
    116.         }
    117.  
    118.         float ftDist = Vector3.Distance(oldPos, targetPos);
    119.  
    120.         // Check if the character is moving
    121.         if (InputManager.Manager.GetMovementInput().magnitude > 0)
    122.         {
    123.             if (ftDist > StepDistance)
    124.             {
    125.                 oldPos = NewPos;
    126.                 NewPos = targetPos;
    127.  
    128.                 // Spawn particle underneath feet
    129.                 if (FTRayDown)
    130.                 {
    131.                     GameObject instant = Instantiate(Steppartical, targetPos, Quaternion.Euler(Steppartical.transform.rotation.eulerAngles), StepparticalParent);
    132.                     ParticleSystem PS = instant.GetComponent<ParticleSystem>();
    133.                     PS.Play();
    134.                     float PSduration = PS.duration + PS.startLifetime;                
    135.                     Destroy(instant, PSduration);
    136.                 }
    137.                 else
    138.                 {
    139.                     NewPos = RayOrigin.position - (MaxFootReach * Vector3.up) + (FootYOffset * Vector3.up);
    140.                 }
    141.             }
    142.         }
    143.         else
    144.         {
    145.             // If not moving, set to idle position
    146.             if (FTRayIDL)
    147.             {
    148.                 NewPos = Vector3.up * FootYOffset + FTIDLE.point;
    149.                 oldPos = NewPos;
    150.             }
    151.             else
    152.             {
    153.                 NewPos = RayOrigin.position - (MaxFootReach * Vector3.up) + (FootYOffset * Vector3.up);
    154.                 oldPos = NewPos;
    155.             }
    156.         }
    157.  
    158.         // Move foot to the calculated position
    159.         foot.position = Vector3.Lerp(oldPos, NewPos, FootSpeed);
    160.  
    161.         // Debug visualization
    162.         if (IKDebug)
    163.         {
    164.             Debug.DrawLine(IKRaycastOrigin.position, FTHipPos, Color.red); // Draw line for the adjusted position
    165.             Debug.DrawLine(FTHipPos , FTHitDown.point, Color.red); // Draw line for the adjusted downward position
    166.         }
    167.     }
    168. }
    169.  
     
  2. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    heres a video of my code so far in play
     
  3. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,433
    You're getting close.

    You need to have a flag for each leg that is being moved, and a calculation of "how far behind the intended direction" each foot is at any given time. If the foot is behind the body, then it's more likely to need to take a step. While a leg is in motion, it needs set the flag while it completes its own stride, hopefully ending up being ahead of the body in the intended direction. When a leg is planted on the ground, the flag clears to say it's supporting the body. However, before a leg can start a stride from the "behind" position to the "ahead" position, the whole body has to decide if it has enough other legs already ahead of the body.

    For a biped, you should always have a foot on the ground unless you're running, and when you're running, you can't start a leg striding forward until the other leg is almost done with its stride. For a spider, you can decide that the spider needs five legs firmly touching surfaces before it's allowed to move a leg forward. When the spider realizes it's free to move a leg, it should choose the one that is most "behind" the direction of travel from its neutral pose.
     
    Hideous likes this.
  4. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    updated the code and removed some unnecessary bits

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class footIK : MonoBehaviour
    4. {
    5.     // Debug flags
    6.     [SerializeField] private bool groundDebug;
    7.     [SerializeField] private bool IKDebug;
    8.  
    9.     // References
    10.     [Header("References")]
    11.     [SerializeField] private GameObject Steppartical;
    12.     [SerializeField] private Transform StepparticalParent;
    13.  
    14.     // Ground check parameters
    15.     [Header("GroundCheck")]
    16.     [SerializeField] private LayerMask groundLayer;
    17.     [SerializeField] private Transform groundRaycastOrigin;
    18.     [SerializeField] private float groundRaycastDistance = 0.1f, groundRaycastRadius = 0.1f;
    19.  
    20.     // Body movement parameters
    21.     [Header("Body")]
    22.     [SerializeField] private Transform Body;
    23.     [SerializeField] private float BodyHight;
    24.     [SerializeField] private float BodyMoveSpeed;
    25.  
    26.     // Foot IK Visuals parameters
    27.     [Header("Foot IK Visuals")]
    28.     [SerializeField] private float FootSpeed;
    29.  
    30.     // Foot IK parameters
    31.     [Header("Foot IK")]
    32.     [SerializeField] private Transform LeftFootTarget;
    33.     [SerializeField] private Transform RightFootTarget;
    34.     [SerializeField] private Transform IKRaycastOrigin;
    35.     [SerializeField] private Transform LeftFootRaycastOrigin;
    36.     [SerializeField] private Transform RightFootRaycastOrigin;
    37.     [SerializeField] private float StepDistance;
    38.     [SerializeField] private float StepLength;
    39.     [SerializeField] private float MaxFootReach;
    40.     [SerializeField] private float FootYOffset;
    41.     [SerializeField] private LayerMask IKLayer;
    42.  
    43.     Vector3 NewPosR, NewPosL;
    44.     Vector3 FTTargetPosR, FTTargetPosL;
    45.  
    46.     // Update is called once per frame
    47.     void Update()
    48.     {
    49.         FootIK(LeftFootRaycastOrigin, LeftFootTarget, ref NewPosR, ref FTTargetPosR);
    50.         FootIK(RightFootRaycastOrigin, RightFootTarget, ref NewPosL, ref FTTargetPosL);
    51.  
    52.         // Calculate and update the position of the Body based on foot positions
    53.         Vector3 bodyTarget = ((FTTargetPosR + FTTargetPosL) / 2) + (Vector3.up * BodyHight) - (Body.forward * StepLength);
    54.  
    55.         Body.position = Vector3.Lerp(Body.position, bodyTarget, BodyMoveSpeed * Time.deltaTime);
    56.     }
    57.  
    58.     // Initialize variables
    59.     void Start()
    60.     {
    61.         NewPosL = FTTargetPosL;
    62.         NewPosR = FTTargetPosR;
    63.     }
    64.  
    65.     // Function to perform Foot IK
    66.     void FootIK(Transform RayOrigin, Transform foot, ref Vector3 NewPos, ref Vector3 targetPos)
    67.     {  
    68.         // Raycast forward to find the adjusted position for the foot
    69.         RaycastHit FTHitForward;
    70.         bool FTRayForward = Physics.Raycast(RayOrigin.position, RayOrigin.forward, out FTHitForward, StepLength, IKLayer);
    71.  
    72.         Vector3 FTHipPos;
    73.  
    74.         if (FTRayForward)
    75.         {
    76.             FTHipPos = FTHitForward.point; // Set position to the forward hit position
    77.         }
    78.         else
    79.         {
    80.             FTHipPos = RayOrigin.position + StepLength * IKRaycastOrigin.forward; // Set position to default forward position
    81.         }
    82.  
    83.         // Raycast downward to find the adjusted position for the foot on the ground
    84.         RaycastHit FTHitDown;
    85.         RaycastHit FTIDLE;
    86.         bool FTRayDown = Physics.Raycast(FTHipPos, Vector3.down, out FTHitDown, MaxFootReach, IKLayer);
    87.         bool FTRayIDL = Physics.Raycast(RayOrigin.position, Vector3.down, out FTIDLE, MaxFootReach, IKLayer);
    88.  
    89.         // If foot isn't on the ground, go to a default state
    90.         if (FTRayDown)
    91.         {
    92.             targetPos = Vector3.up * FootYOffset + FTHitDown.point;
    93.         }
    94.         else
    95.         {
    96.             targetPos = Vector3.up * (FootYOffset - MaxFootReach) + FTHipPos;
    97.         }
    98.  
    99.         float ftDist = Vector3.Distance(NewPos, targetPos);
    100.  
    101.         // Check if the character is moving
    102.         if (InputManager.Manager.GetMovementInput().magnitude > 0)
    103.         {
    104.             if (ftDist > StepDistance)
    105.             {
    106.                 NewPos = targetPos;
    107.  
    108.                 // Spawn particle underneath feet
    109.                 if (FTRayDown)
    110.                 {
    111.                     GameObject instant = Instantiate(Steppartical, targetPos, Quaternion.Euler(Steppartical.transform.rotation.eulerAngles), StepparticalParent);
    112.                     ParticleSystem PS = instant.GetComponent<ParticleSystem>();
    113.                     PS.Play();
    114.                     float PSduration = PS.duration + PS.startLifetime;                  
    115.                     Destroy(instant, PSduration);
    116.                 }
    117.                 else
    118.                 {
    119.                     NewPos = RayOrigin.position - (MaxFootReach * Vector3.up) + (FootYOffset * Vector3.up);
    120.                 }
    121.             }
    122.         }
    123.         else
    124.         {
    125.             // If not moving, set to idle position
    126.             if (FTRayIDL)
    127.             {
    128.                 NewPos = Vector3.up * FootYOffset + FTIDLE.point;
    129.             }
    130.             else
    131.             {
    132.                 NewPos = RayOrigin.position - (MaxFootReach * Vector3.up) + (FootYOffset * Vector3.up);
    133.             }
    134.         }
    135.  
    136.         // Move foot to the calculated position
    137.         foot.position = NewPos;
    138.  
    139.         // Debug visualization
    140.         if (IKDebug)
    141.         {
    142.             Debug.DrawLine(IKRaycastOrigin.position, FTHipPos, Color.red); // Draw line for the adjusted position
    143.             Debug.DrawLine(FTHipPos , FTHitDown.point, Color.red); // Draw line for the adjusted downward position
    144.         }
    145.     }
    146. }
    147.  
     
    Last edited: Feb 7, 2024
  5. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    alright i know its been a bit, I've been busy with school and extra curricular activity but I think I've figured it out
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class footIK : MonoBehaviour
    4. {
    5.     // Debug flags
    6.     [SerializeField] private bool groundDebug;
    7.     [SerializeField] private bool IKDebug;
    8.  
    9.     // References
    10.     [Header("References")]
    11.     [SerializeField] private GameObject Steppartical;
    12.     [SerializeField] private Transform StepparticalParent;
    13.  
    14.     // Ground check parameters
    15.     [Header("GroundCheck")]
    16.     [SerializeField] private LayerMask groundLayer;
    17.     [SerializeField] private Transform groundRaycastOrigin;
    18.     [SerializeField] private float groundRaycastDistance = 0.1f, groundRaycastRadius = 0.1f;
    19.  
    20.     // Body movement parameters
    21.     [Header("Body")]
    22.     [SerializeField] private Transform Body;
    23.     [SerializeField] private float BodyHight;
    24.     [SerializeField] private float BodyMoveSpeed;
    25.  
    26.     // Foot IK Visuals parameters
    27.     [Header("Foot IK Visuals")]
    28.     [SerializeField] private float FootSpeed;
    29.     [SerializeField] private float StepHight;
    30.  
    31.     // Foot IK parameters
    32.     [Header("Foot IK")]
    33.     [SerializeField] private Transform LeftFootTarget;
    34.     [SerializeField] private Transform RightFootTarget;
    35.     [SerializeField] private Transform IKRaycastOrigin;
    36.     [SerializeField] private Transform LeftFootRaycastOrigin;
    37.     [SerializeField] private Transform RightFootRaycastOrigin;
    38.     [SerializeField] private float StepDistance;
    39.     [SerializeField] private float StepLength;
    40.     [SerializeField] private float MaxFootReach;
    41.     [SerializeField] private float FootYOffset;
    42.     [SerializeField] private LayerMask IKLayer;
    43.  
    44.     bool isGrounded;
    45.     RaycastHit Groundhit;
    46.  
    47.     Vector3 oldPosR, oldPosL;
    48.     Vector3 NewPosR, NewPosL;
    49.     Vector3 CurrentPosL, CurrentPosR;
    50.     Vector3 FTTargetPosR, FTTargetPosL;
    51.     float LLerp = 0f, RLerp = .5f;
    52.     bool RightFootStep = true, LeftFootStep = false;
    53.  
    54.     // Update is called once per frame
    55.     void Update()
    56.     {
    57.         FootIK(LeftFootRaycastOrigin, LeftFootTarget, ref NewPosR, ref oldPosR, ref FTTargetPosR, LeftFootStep, ref RightFootStep, ref LLerp, ref CurrentPosR);
    58.         FootIK(RightFootRaycastOrigin, RightFootTarget, ref NewPosL, ref oldPosL, ref FTTargetPosL, RightFootStep, ref LeftFootStep, ref RLerp,ref CurrentPosL);
    59.  
    60.         // Perform spherecast downwards to check for ground contact
    61.         isGrounded = Physics.SphereCast(groundRaycastOrigin.position, groundRaycastRadius, Vector3.down, out Groundhit, groundRaycastDistance, groundLayer);
    62.  
    63.         // Calculate and update the position of the Body based on foot positions
    64.         Vector3 bodyTarget = ((FTTargetPosR + FTTargetPosL) / 2) + (Vector3.up * BodyHight) - (Body.forward * StepLength);
    65.  
    66.         if (isGrounded)
    67.             Body.position = Vector3.Lerp(Body.position, bodyTarget, BodyMoveSpeed * Time.deltaTime);
    68.     }
    69.  
    70.     // Initialize variables
    71.     void Start()
    72.     {
    73.         FootIK(LeftFootRaycastOrigin, LeftFootTarget, ref NewPosR, ref oldPosR, ref FTTargetPosR, LeftFootStep, ref RightFootStep, ref LLerp, ref CurrentPosR);
    74.         FootIK(RightFootRaycastOrigin, RightFootTarget, ref NewPosL, ref oldPosL, ref FTTargetPosL, RightFootStep, ref LeftFootStep, ref RLerp, ref CurrentPosL);
    75.  
    76.         NewPosL = NewPosR = oldPosL = oldPosR = FTTargetPosL;
    77.     }
    78.  
    79.     // Function to perform Foot IK
    80.     void FootIK(Transform RayOrigin, Transform foot, ref Vector3 NewPos, ref Vector3 oldPos, ref Vector3 targetPos,bool AltFTStep, ref bool FTStep,ref float lerp, ref Vector3 currentPos)
    81.     {
    82.         // Raycast forward to find the adjusted position for the foot
    83.         RaycastHit FTHitForward;
    84.         bool FTRayForward = Physics.Raycast(RayOrigin.position, RayOrigin.forward, out FTHitForward, StepLength, IKLayer);
    85.  
    86.         Vector3 FTHipPos;
    87.  
    88.         if (FTRayForward)
    89.         {
    90.             FTHipPos = FTHitForward.point; // Set position to the forward hit position
    91.         }
    92.         else
    93.         {
    94.             FTHipPos = RayOrigin.position + StepLength * IKRaycastOrigin.forward; // Set position to default forward position
    95.         }
    96.  
    97.         // Raycast downward to find the adjusted position for the foot on the ground
    98.         RaycastHit FTHitDown;
    99.         RaycastHit FTIDLE;
    100.         bool FTRayDown = Physics.Raycast(FTHipPos, Vector3.down, out FTHitDown, MaxFootReach, IKLayer);
    101.         bool FTRayIDL = Physics.Raycast(RayOrigin.position, Vector3.down, out FTIDLE, MaxFootReach, IKLayer);
    102.  
    103.         // If foot isn't on the ground, go to a default state
    104.         if (FTRayDown)
    105.         {
    106.             targetPos = Vector3.up * FootYOffset + FTHitDown.point;
    107.         }
    108.         else
    109.         {
    110.             targetPos = Vector3.up * (FootYOffset - MaxFootReach) + FTHipPos;
    111.         }
    112.  
    113.         float ftDist = Vector3.Distance(oldPos, targetPos);
    114.  
    115.         // Check if the character is moving
    116.         if (FTRayDown && !AltFTStep)
    117.         {
    118.             if (ftDist > StepDistance && lerp > 1)
    119.             {
    120.                 FTStep = true;
    121.  
    122.                 NewPos = targetPos;
    123.  
    124.                 // Spawn particle underneath feet
    125.                 GameObject instant = Instantiate(Steppartical, targetPos, Quaternion.Euler(Steppartical.transform.rotation.eulerAngles), StepparticalParent);
    126.                 ParticleSystem PS = instant.GetComponent<ParticleSystem>();
    127.                 PS.Play();
    128.                 float PSduration = PS.duration + PS.startLifetime;
    129.                 Destroy(instant, PSduration);
    130.             }
    131.             else
    132.             {
    133.                 FTStep = false;
    134.             }
    135.  
    136.             if (lerp < 1)
    137.             {
    138.                 currentPos = Vector3.Lerp(foot.position, NewPos, lerp);
    139.  
    140.                 lerp += Time.deltaTime * FootSpeed;
    141.             }
    142.             else
    143.             {
    144.                 oldPos = NewPos;
    145.  
    146.                 lerp = 0;
    147.             }
    148.  
    149.             foot.position = currentPos;
    150.         }
    151.  
    152.         // Debug visualization
    153.         if (IKDebug)
    154.         {
    155.             Debug.DrawLine(IKRaycastOrigin.position, FTHipPos, Color.red); // Draw line for the adjusted position
    156.             Debug.DrawLine(FTHipPos, FTHitDown.point, Color.red); // Draw line for the adjusted downward position
    157.         }
    158.     }
    159. }
    160.  
     
  6. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    theres a few bugs like going up the stairs looks weird and going down slopes looks off plus jumping but I think that can be sorted out easily
     
  7. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    nvm it doesnt work but im on to something
     
  8. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class footIK : MonoBehaviour
    4. {
    5.     // Debug flags
    6.     [Header("Debug")]
    7.     [SerializeField] private bool groundDebug; // Enable ground debug visualization
    8.     [SerializeField] private bool ikDebug; // Enable IK debug visualization
    9.  
    10.     // References
    11.     [Header("References")]
    12.     [SerializeField] private GameObject stepParticle; // Particle effect for footstep
    13.     [SerializeField] private Transform stepParticleParent; // Parent object for step particles
    14.     [SerializeField] private PlayerMovement movement; // Reference to the player movement script
    15.  
    16.     // Ground check parameters
    17.     [Header("Ground Check")]
    18.     [SerializeField] private LayerMask groundLayer; // Layer mask for ground objects
    19.     [SerializeField] private Transform groundRaycastOrigin; // Origin of ground raycasts
    20.     [SerializeField] private float groundRaycastDistance = 0.1f; // Distance of ground raycasts
    21.     [SerializeField] private float groundRaycastRadius = 0.1f; // Radius of ground raycasts
    22.  
    23.     // Body movement parameters
    24.     [Header("Body")]
    25.     [SerializeField] private Transform body; // Reference to the body transform
    26.     [SerializeField] private float bodyHeight; // Height of the body above the ground
    27.     [SerializeField] private float bodyMoveSpeed; // Speed of body movement
    28.  
    29.     // Foot IK Visuals parameters
    30.     [Header("Foot IK Visuals")]
    31.     [SerializeField] private float footSpeed; // Speed of foot movement
    32.     [SerializeField] private float stepHeight; // Height of footstep
    33.  
    34.     // Foot IK parameters
    35.     [Header("Foot IK")]
    36.     [SerializeField] private Transform leftFootTarget; // Target transform for left foot
    37.     [SerializeField] private Transform rightFootTarget; // Target transform for right foot
    38.     [SerializeField] private Transform ikRaycastOrigin; // Origin of IK raycasts
    39.     [SerializeField] private Transform leftFootRaycastOrigin; // Origin of raycast for left foot
    40.     [SerializeField] private Transform rightFootRaycastOrigin; // Origin of raycast for right foot
    41.     [SerializeField] private float stepDistance; // Distance threshold for footstep
    42.     [SerializeField] private float stepLength; // Length of each step
    43.     [SerializeField] private float maxFootReach; // Maximum reach of the foot
    44.     [SerializeField] private float footYOffset; // Vertical offset of the foot
    45.     [SerializeField] private LayerMask ikLayer; // Layer mask for IK objects
    46.  
    47.     // Private variables
    48.     private bool isGrounded; // Whether the player is grounded
    49.     private RaycastHit groundHit; // Information about the ground hit
    50.  
    51.     private Vector3 oldPosRight, oldPosLeft; // Previous positions of the feet
    52.     private Vector3 newPosRight, newPosLeft; // New positions of the feet
    53.     private Vector3 currentPosLeft, currentPosRight; // Current positions of the feet
    54.     private Vector3 footTargetPosRight, footTargetPosLeft; // Target positions for the feet
    55.     private float lerpLeft = 0f, lerpRight = .5f; // Lerp values for smooth foot movement
    56.     private bool rightFootStep = true, leftFootStep = false; // Flags for alternating footstep
    57.  
    58.     // Update is called once per frame
    59.     void Update()
    60.     {
    61.         // Perform foot IK for left and right feet
    62.         FootIK(leftFootRaycastOrigin, leftFootTarget, ref newPosRight, ref oldPosRight, ref footTargetPosRight, leftFootStep, ref rightFootStep, ref lerpLeft, ref currentPosRight);
    63.         FootIK(rightFootRaycastOrigin, rightFootTarget, ref newPosLeft, ref oldPosLeft, ref footTargetPosLeft, rightFootStep, ref leftFootStep, ref lerpRight, ref currentPosLeft);
    64.  
    65.         // Perform ground check
    66.         isGrounded = Physics.SphereCast(groundRaycastOrigin.position, groundRaycastRadius, Vector3.down, out groundHit, groundRaycastDistance, groundLayer);
    67.  
    68.         // Calculate and update the position of the body based on foot positions
    69.         Vector3 bodyTarget = ((footTargetPosRight + footTargetPosLeft) / 2) + (Vector3.up * bodyHeight) - (body.forward * stepLength);
    70.  
    71.         // Move the body towards the calculated target position
    72.         if (isGrounded)
    73.             body.position = Vector3.Lerp(body.position, bodyTarget, bodyMoveSpeed * Time.deltaTime);
    74.     }
    75.  
    76.     // Start is called before the first frame update
    77.     void Start()
    78.     {
    79.         // Initialize foot positions
    80.         FootIK(leftFootRaycastOrigin, leftFootTarget, ref newPosRight, ref oldPosRight, ref footTargetPosRight, leftFootStep, ref rightFootStep, ref lerpLeft, ref currentPosRight);
    81.         FootIK(rightFootRaycastOrigin, rightFootTarget, ref newPosLeft, ref oldPosLeft, ref footTargetPosLeft, rightFootStep, ref leftFootStep, ref lerpRight, ref currentPosLeft);
    82.  
    83.         // Set initial foot positions
    84.         newPosLeft = newPosRight = oldPosLeft = oldPosRight = footTargetPosLeft;
    85.     }
    86.  
    87.     // Function to perform Foot IK
    88.     void FootIK(Transform rayOrigin, Transform foot, ref Vector3 newPos, ref Vector3 oldPos, ref Vector3 targetPos, bool altFootStep, ref bool footStep, ref float lerp, ref Vector3 currentPos)
    89.     {
    90.         // Raycast forward to find the adjusted position for the foot
    91.         RaycastHit hitForward;
    92.         bool rayForward = Physics.Raycast(rayOrigin.position, rayOrigin.forward, out hitForward, stepLength, ikLayer);
    93.         Vector3 hipPos = rayForward ? hitForward.point : rayOrigin.position + stepLength * ikRaycastOrigin.forward;
    94.  
    95.         // Raycast downward to find the adjusted position for the foot on the ground
    96.         RaycastHit hitDown;
    97.         bool rayDown = Physics.Raycast(hipPos, Vector3.down, out hitDown, maxFootReach, ikLayer);
    98.  
    99.         // Calculate target position for the foot
    100.         targetPos = rayDown ? Vector3.up * footYOffset + hitDown.point : Vector3.up * (footYOffset - maxFootReach) + hipPos;
    101.  
    102.         // Check if foot has moved beyond step distance threshold
    103.         float footDistance = Vector3.Distance(oldPos, targetPos);
    104.         if (footDistance > stepDistance)
    105.             newPos = targetPos;
    106.  
    107.         // Smoothly move the foot towards the target position
    108.         if (lerp < 1 && !altFootStep)
    109.         {
    110.             footStep = true;
    111.             currentPos = Vector3.Lerp(foot.position, newPos, lerp);
    112.             lerp += Time.deltaTime * footSpeed;
    113.         }
    114.         else
    115.         {
    116.             footStep = false;
    117.             oldPos = newPos;
    118.             lerp = 0;
    119.         }
    120.  
    121.         // Calculate vertical offset for footstep
    122.         float yStep = movement.movement.magnitude >= 1 ? Mathf.Sin(lerp) * stepHeight : 0;
    123.         foot.position = currentPos + Vector3.up * yStep;
    124.  
    125.         // Debug visualization
    126.         if (ikDebug)
    127.         {
    128.             Debug.DrawLine(ikRaycastOrigin.position, hipPos, Color.red); // Draw line for the adjusted position
    129.             Debug.DrawLine(hipPos, hitDown.point, Color.red);
    130.         }
    131.     }
    132. }
    133.  
    I figured it out :)
     
  9. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    the feet just jitter a bit when its moving up and down
     
  10. procheckmate

    procheckmate

    Joined:
    Nov 7, 2020
    Posts:
    15
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class footIK : MonoBehaviour
    4. {
    5.     // Debug flags
    6.     [Header("Debug")]
    7.     [SerializeField] private bool groundDebug; // Enable ground debug visualization
    8.     [SerializeField] private bool ikDebug; // Enable IK debug visualization
    9.  
    10.     // References
    11.     [Header("References")]
    12.     [SerializeField] private PlayerMovement movement; // Reference to the player movement script
    13.  
    14.     // Ground check parameters
    15.     [Header("Ground Check")]
    16.     [SerializeField] private LayerMask groundLayer; // Layer mask for ground objects
    17.     [SerializeField] private Transform groundRaycastOrigin; // Origin of ground raycasts
    18.     [SerializeField] private float groundRaycastDistance = 0.1f; // Distance of ground raycasts
    19.     [SerializeField] private float groundRaycastRadius = 0.1f; // Radius of ground raycasts
    20.  
    21.     // Body movement parameters
    22.     [Header("Body")]
    23.     [SerializeField] private Transform body; // Reference to the body transform
    24.     [SerializeField] private float bodyHeight; // Height of the body above the ground
    25.     [SerializeField] private float bodyMoveSpeed; // Speed of body movement
    26.  
    27.     // Foot IK Visuals parameters
    28.     [Header("Foot IK Visuals")]
    29.     [SerializeField] private float footSpeed; // Speed of foot movement
    30.     [SerializeField] private float stepHeight; // Height of footstep
    31.  
    32.     // Foot IK parameters
    33.     [Header("Foot IK")]
    34.     [SerializeField] private Transform leftFootTarget; // Target transform for left foot
    35.     [SerializeField] private Transform rightFootTarget; // Target transform for right foot
    36.     [SerializeField] private Transform ikRaycastOrigin; // Origin of IK raycasts
    37.     [SerializeField] private Transform leftFootRaycastOrigin; // Origin of raycast for left foot
    38.     [SerializeField] private Transform rightFootRaycastOrigin; // Origin of raycast for right foot
    39.     [SerializeField] private float stepDistance; // Distance threshold for footstep
    40.     [SerializeField] private float stepLength; // Length of each step
    41.     [SerializeField] private float maxFootReach; // Maximum reach of the foot
    42.     [SerializeField] private float footYOffset; // Vertical offset of the foot
    43.     [SerializeField] private LayerMask ikLayer; // Layer mask for IK objects
    44.  
    45.     // Private variables
    46.     private bool isGrounded; // Whether the player is grounded
    47.     private RaycastHit groundHit; // Information about the ground hit
    48.  
    49.     private Vector3 oldPosRight, oldPosLeft; // Previous positions of the feet
    50.     private Vector3 newPosRight, newPosLeft; // New positions of the feet
    51.     private Vector3 currentPosLeft, currentPosRight; // Current positions of the feet
    52.     private Vector3 footTargetPosRight, footTargetPosLeft; // Target positions for the feet
    53.     private float lerpLeft = 0f, lerpRight = .5f; // Lerp values for smooth foot movement
    54.     private bool rightFootStep = true, leftFootStep = false; // Flags for alternating footstep
    55.  
    56.     // Update is called once per frame
    57.     void Update()
    58.     {
    59.         // Perform foot IK for left and right feet
    60.         FootIK(leftFootRaycastOrigin, leftFootTarget, ref newPosRight, ref oldPosRight, ref footTargetPosRight, leftFootStep, ref rightFootStep, ref lerpLeft, ref currentPosRight);
    61.         FootIK(rightFootRaycastOrigin, rightFootTarget, ref newPosLeft, ref oldPosLeft, ref footTargetPosLeft, rightFootStep, ref leftFootStep, ref lerpRight, ref currentPosLeft);
    62.  
    63.         // Perform ground check
    64.         isGrounded = Physics.SphereCast(groundRaycastOrigin.position, groundRaycastRadius, Vector3.down, out groundHit, groundRaycastDistance, groundLayer);
    65.  
    66.         // Calculate and update the position of the body based on foot positions
    67.         Vector3 bodyTarget = ((footTargetPosRight + footTargetPosLeft) / 2) + (Vector3.up * bodyHeight) - (body.forward * stepLength);
    68.  
    69.         // Move the body towards the calculated target position
    70.         if (isGrounded)
    71.             body.position = Vector3.Lerp(body.position, bodyTarget, bodyMoveSpeed * Time.deltaTime);
    72.     }
    73.  
    74.     // Start is called before the first frame update
    75.     void Start()
    76.     {
    77.         // Initialize foot positions
    78.         FootIK(leftFootRaycastOrigin, leftFootTarget, ref newPosRight, ref oldPosRight, ref footTargetPosRight, leftFootStep, ref rightFootStep, ref lerpLeft, ref currentPosRight);
    79.         FootIK(rightFootRaycastOrigin, rightFootTarget, ref newPosLeft, ref oldPosLeft, ref footTargetPosLeft, rightFootStep, ref leftFootStep, ref lerpRight, ref currentPosLeft);
    80.  
    81.         // Set initial foot positions
    82.         newPosLeft = newPosRight = oldPosLeft = oldPosRight = footTargetPosLeft;
    83.     }
    84.  
    85.     // Function to perform Foot IK
    86.     void FootIK(Transform rayOrigin, Transform foot, ref Vector3 newPos, ref Vector3 oldPos, ref Vector3 targetPos, bool altFootStep, ref bool footStep, ref float lerp, ref Vector3 currentPos)
    87.     {
    88.         // Raycast forward to find the adjusted position for the foot
    89.         RaycastHit hitForward;
    90.         bool rayForward = Physics.Raycast(rayOrigin.position, rayOrigin.forward, out hitForward, stepLength, ikLayer);
    91.         Vector3 hipPos = rayForward ? hitForward.point : rayOrigin.position + stepLength * ikRaycastOrigin.forward;
    92.  
    93.         // Raycast downward to find the adjusted position for the foot on the ground
    94.         RaycastHit hitDown;
    95.         bool rayDown = Physics.Raycast(hipPos, Vector3.down, out hitDown, maxFootReach, ikLayer);
    96.  
    97.         // Calculate target position for the foot
    98.         targetPos = rayDown ? Vector3.up * footYOffset + hitDown.point : Vector3.up * (footYOffset - maxFootReach) + hipPos;
    99.  
    100.         // Check if foot has moved beyond step distance threshold
    101.         float footDistance = Vector3.Distance(oldPos, targetPos);
    102.         if (footDistance > stepDistance)
    103.             newPos = targetPos;
    104.  
    105.         // Smoothly move the foot towards the target position
    106.         if (lerp < 1 && !altFootStep)
    107.         {
    108.             footStep = true;
    109.             currentPos = Vector3.Lerp(foot.position, newPos, lerp);
    110.             lerp += Time.deltaTime * footSpeed;
    111.         }
    112.         else
    113.         {
    114.             footStep = false;
    115.             oldPos = newPos;
    116.             lerp = 0;
    117.         }
    118.  
    119.         // Calculate vertical offset for footstep
    120.         float yStep = movement.movement.magnitude >= 1 ? (0.5f*(1 + Mathf.Sin(2 * Mathf.PI * lerp))) * stepHeight : 0;
    121.         foot.position = currentPos + Vector3.up * yStep;
    122.  
    123.         // Debug visualization
    124.         if (ikDebug)
    125.         {
    126.             Debug.DrawLine(ikRaycastOrigin.position, hipPos, Color.red); // Draw line for the adjusted position
    127.             Debug.DrawLine(rayOrigin.position, hitDown.point, Color.red);
    128.         }
    129.     }
    130. }
    131.  
    all done
     
  11. EthanBarron

    EthanBarron

    Joined:
    Dec 22, 2020
    Posts:
    7
    First time really responding on a unity forum post so sorry if this necroposts or something
    I've found a really good alternative to be (at least for 2 legs) calculating the average position between the left and right foot. If the average is too far away from the body, take a step with the leg that's furthest away from the body
    This consistently provides 2 alternating feet because when the furthest leg moves forwards the average also moves forwards. Stepping is 100% consistent. I've found offsetting the ray direction by the velocity of the rigidbody to make up for speed is good and makes it look like it's running

    Tip: I would only compare the X and Z. I've found the Y has way too much influence
     
  12. nodayshalleraseyou

    nodayshalleraseyou

    Joined:
    Dec 5, 2017
    Posts:
    3
    Thanks for the tip! This is yielding much better results than my existing solution. For anyone confused by the meaning of "average position" like I was, I believe this is what they're talking about.

    Code (csharp):
    1.  
    2.     Vector3 rightDifference = (rightFoot.transform.position - bodyTransform.position);
    3.     rightDifference.y = 0;
    4.  
    5.     Vector3 leftDifference = (leftFoot.transform.position - bodyTransform.position);
    6.     leftDifference.y = 0;
    7.  
    8.     float avgDistance = (rightDifference.magnitude + leftDifference.magnitude)/2;
    9.  
    10.     //if avgDistance is greater than threshold, move furthest foot from body
    11.