├── .gitignore ├── ExtractTransformConstraint.cs ├── ExtractTransformConstraintData.cs ├── ExtractTransformConstraintJob.cs ├── ExtractTransformConstraintJobBinder.cs ├── Grounder.cs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /ExtractTransformConstraint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.Animations.Rigging; 4 | 5 | namespace Locomotion.Utils.Grounder 6 | { 7 | [DisallowMultipleComponent] 8 | [AddComponentMenu("Animation Rigging/Extract Transform Constraint")] 9 | public class ExtractTransformConstraint : RigConstraint 11 | { 12 | 13 | } 14 | } -------------------------------------------------------------------------------- /ExtractTransformConstraintData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.Animations.Rigging; 4 | 5 | namespace Locomotion.Utils.Grounder 6 | { 7 | [Serializable] 8 | public struct ExtractTransformConstraintData : IAnimationJobData 9 | { 10 | [SyncSceneToStream] public Transform bone; 11 | 12 | [HideInInspector] public Vector3 position; 13 | 14 | [HideInInspector] public Quaternion rotation; 15 | 16 | public bool IsValid() 17 | { 18 | return bone != null; 19 | } 20 | 21 | public void SetDefaultValues() 22 | { 23 | bone = null; 24 | 25 | position = Vector3.zero; 26 | 27 | rotation = Quaternion.identity; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /ExtractTransformConstraintJob.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Animations; 5 | using UnityEngine.Animations.Rigging; 6 | 7 | namespace Locomotion.Utils.Grounder 8 | { 9 | public struct ExtractTransformConstraintJob : IWeightedAnimationJob 10 | { 11 | public ReadWriteTransformHandle Bone; 12 | 13 | public FloatProperty jobWeight { get; set; } 14 | 15 | public Vector3Property Position; 16 | 17 | public Vector4Property Rotation; 18 | 19 | public void ProcessRootMotion(AnimationStream stream) 20 | { } 21 | 22 | public void ProcessAnimation(AnimationStream stream) 23 | { 24 | AnimationRuntimeUtils.PassThrough(stream, Bone); 25 | 26 | Vector3 pos = Bone.GetPosition(stream); 27 | Quaternion rot = Bone.GetRotation(stream); 28 | 29 | Position.Set(stream, pos); 30 | Rotation.Set(stream, new Vector4(rot.x, rot.y, rot.z, rot.w)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ExtractTransformConstraintJobBinder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Animations.Rigging; 5 | 6 | namespace Locomotion.Utils.Grounder 7 | { 8 | public class ExtractTransformConstraintJobBinder : AnimationJobBinder 9 | { 10 | public override ExtractTransformConstraintJob Create(Animator animator, 11 | ref ExtractTransformConstraintData data, Component component) 12 | { 13 | return new ExtractTransformConstraintJob 14 | { 15 | Bone = ReadWriteTransformHandle.Bind(animator, data.bone), 16 | 17 | Position = Vector3Property.Bind(animator, component, "m_Data." + nameof(data.position)), 18 | 19 | Rotation = Vector4Property.Bind(animator, component, "m_Data." + nameof(data.rotation)), 20 | }; 21 | } 22 | 23 | public override void Destroy(ExtractTransformConstraintJob job) 24 | { 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Grounder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using Locomotion.Controllers; 5 | using UnityEngine; 6 | using UnityEngine.Animations.Rigging; 7 | 8 | namespace Locomotion.Utils.Grounder 9 | { 10 | public class Grounder : MonoBehaviour 11 | { 12 | [SerializeField] private bool active = true; 13 | 14 | [Space] 15 | [Header("IK")] 16 | [SerializeField] private Rig ikRig; 17 | [Space] 18 | [SerializeField] private TwoBoneIKConstraint ikConstraintL; 19 | [SerializeField] private TwoBoneIKConstraint ikConstraintR; 20 | 21 | [Space] 22 | 23 | [SerializeField] private ExtractTransformConstraint extractConstraintPelvis; 24 | 25 | [Space] 26 | 27 | [SerializeField] private ExtractTransformConstraint extractConstraintL; 28 | [SerializeField] private ExtractTransformConstraint extractConstraintR; 29 | 30 | [Space] 31 | [Header("Transforms")] 32 | 33 | [Tooltip("Not Actual Pelvis Bone, Substitute in Pelvis MultiPosition Constraint (Source Object 0)")] 34 | [SerializeField] private Transform pelvis; 35 | 36 | [Space] 37 | 38 | [Tooltip("Transform Facing Forward of Left Foot")] 39 | [SerializeField] private Transform footForwardL; 40 | [Tooltip("Transform Facing Forward of Left Foot")] 41 | [SerializeField] private Transform footForwardR; 42 | 43 | [Space] 44 | 45 | [Header("Presets")] 46 | 47 | [Tooltip("Pelvis Offset Based on Specific Model, Adjust in Start Of Game")] 48 | [SerializeField] private float pelvisOffset; 49 | 50 | [Space] 51 | 52 | //limits for adjusting pelvis 53 | [SerializeField] private float maxStepHeight; 54 | [SerializeField] private float minStepHeight; 55 | 56 | [Space] 57 | 58 | //blend/lerp speed 59 | [SerializeField] private float pelvisMoveSpeed; 60 | [SerializeField] private float feetIkSpeed; 61 | 62 | //transform created as child of footForward to adjust for relative rotation 63 | private Transform _footPlacementL; 64 | private Transform _footPlacementR; 65 | 66 | //for blending/lerping since values get reset every frame in animation cycle 67 | private Vector3 _lastPelvisPosition; 68 | 69 | //just the y component 70 | private float _lastIkPositionL; 71 | private float _lastIkPositionR; 72 | 73 | private Quaternion _lastIkRotationL; 74 | private Quaternion _lastIkRotationR; 75 | 76 | private Animator _animator; 77 | 78 | private void Start() 79 | { 80 | //Create Child Transforms for relative Rotation IK and assign to initial foot rotation to get rotation offset 81 | GameObject footPlacementObjL = new GameObject("FootPlacementL"); 82 | _footPlacementL = footPlacementObjL.transform; 83 | _footPlacementL.SetParent(footForwardL); 84 | _footPlacementL.localPosition = Vector3.zero; 85 | _footPlacementL.rotation = ikConstraintL.data.tip.rotation; 86 | 87 | GameObject footPlacementObjR = new GameObject("FootPlacementR"); 88 | _footPlacementR = footPlacementObjR.transform; 89 | _footPlacementR.SetParent(footForwardR); 90 | _footPlacementR.localPosition = Vector3.zero; 91 | _footPlacementR.rotation = ikConstraintR.data.tip.rotation; 92 | 93 | _animator = GetComponent(); 94 | } 95 | 96 | private void Update() 97 | { 98 | //Get all original Bone Positions 99 | Vector3 pelvisPosition = extractConstraintPelvis.data.position; 100 | pelvisPosition.y += pelvisOffset; 101 | 102 | Vector3 bonePositionL = extractConstraintL.data.position; 103 | Vector3 bonePositionR = extractConstraintR.data.position; 104 | 105 | Quaternion boneRotationL = extractConstraintL.data.rotation; 106 | Quaternion boneRotationR = extractConstraintR.data.rotation; 107 | 108 | ikRig.weight = active ? 1f : 0f; 109 | 110 | if (!active) 111 | { 112 | _lastPelvisPosition = pelvisPosition; 113 | 114 | _lastIkPositionL = bonePositionL.y; 115 | _lastIkPositionR = bonePositionR.y; 116 | 117 | _lastIkRotationL = boneRotationL; 118 | _lastIkRotationR = boneRotationR; 119 | 120 | return; 121 | } 122 | 123 | //left Foot Raycast 124 | Vector3 originL = bonePositionL; 125 | 126 | originL.y += maxStepHeight; 127 | 128 | bool leftHit = Physics.Raycast(originL, Vector3.down, out RaycastHit hitL, maxStepHeight * 2f); 129 | 130 | //right Foot Raycast 131 | Vector3 originR = bonePositionR; 132 | 133 | originR.y += maxStepHeight; 134 | 135 | bool rightHit = Physics.Raycast(originR, Vector3.down, out RaycastHit hitR, maxStepHeight * 2f); 136 | 137 | bool hit = leftHit && rightHit; 138 | 139 | //displacement between legs 140 | float delta = hitL.point.y - hitR.point.y; 141 | 142 | //distance between legs 143 | float offset = Mathf.Abs(delta); 144 | 145 | bool adjustPelvis = offset <= maxStepHeight && offset >= minStepHeight && hit; 146 | 147 | if (adjustPelvis) 148 | { 149 | //move pelvis down (always down) 150 | pelvisPosition.y -= offset; 151 | 152 | //re-adjust right foot for pelvis movement 153 | if (delta < 0) 154 | { 155 | bonePositionR.y += offset; 156 | 157 | //rotation R 158 | boneRotationR = SolveRotation(hitR.normal, footForwardR, ref _footPlacementR); 159 | } 160 | 161 | else if (delta > 0) 162 | { 163 | bonePositionL.y += offset; 164 | 165 | //rotation L 166 | boneRotationL = SolveRotation(hitL.normal, footForwardL, ref _footPlacementL); 167 | } 168 | } 169 | 170 | //Apply lerped pelvis readjustment, foot ik position and rotation 171 | 172 | //pelvis 173 | AdjustPelvis(pelvisPosition); 174 | 175 | //IK 176 | float t = feetIkSpeed * Time.deltaTime; 177 | 178 | //ik position 179 | ApplyIkPosition(ref _lastIkPositionL, ref ikConstraintL, bonePositionL, t); 180 | ApplyIkPosition(ref _lastIkPositionR, ref ikConstraintR, bonePositionR, t); 181 | 182 | //ik rotation 183 | SetIkRotationWeight(); 184 | ApplyIkRotation(ref _lastIkRotationL, ref ikConstraintL, boneRotationL, t); 185 | ApplyIkRotation(ref _lastIkRotationR, ref ikConstraintR, boneRotationR, t); 186 | } 187 | 188 | private void AdjustPelvis(Vector3 pelvisPosition) 189 | { 190 | _lastPelvisPosition = Vector3.Lerp(_lastPelvisPosition, pelvisPosition, pelvisMoveSpeed * Time.deltaTime); 191 | 192 | pelvis.position = _lastPelvisPosition; 193 | } 194 | 195 | private void ApplyIkPosition(ref float lastIkPosition, ref TwoBoneIKConstraint ikConstraint, Vector3 bonePosition, float t) 196 | { 197 | //ik position R 198 | lastIkPosition = Mathf.Lerp(lastIkPosition, bonePosition.y, t); 199 | 200 | bonePosition.y = lastIkPosition; 201 | 202 | ikConstraint.data.target.position = bonePosition; 203 | } 204 | 205 | private void ApplyIkRotation(ref Quaternion lastIkRotation, ref TwoBoneIKConstraint ikConstraint, Quaternion boneRotation, float t) 206 | { 207 | lastIkRotation = Quaternion.Lerp(lastIkRotation, boneRotation, t); 208 | 209 | ikConstraint.data.target.rotation = lastIkRotation; 210 | } 211 | 212 | private void SetIkRotationWeight() 213 | { 214 | //Get and Set Ik rotation weight 215 | float weightL = 1f - _animator.GetFloat(NameHashGroup.LeftFootUpHash); 216 | float weightR = 1f - _animator.GetFloat(NameHashGroup.RightFootUpHash); 217 | 218 | ikConstraintL.data.targetRotationWeight = weightL; 219 | ikConstraintR.data.targetRotationWeight = weightR; 220 | } 221 | 222 | private Quaternion SolveRotation(Vector3 normal, Transform footForward, ref Transform footPlacement) 223 | { 224 | Vector3 localNormal = transform.InverseTransformDirection(normal); 225 | 226 | footForward.localRotation = Quaternion.FromToRotation(Vector3.up, localNormal); 227 | 228 | return footPlacement.rotation; 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 rob1997 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grounder 2 | Ik Grounder Based on Unity's Package Animation Rigging 3 | ## Setup 4 | 1. Import Animation Rigging Package From the Package Manager
5 | ![image](https://user-images.githubusercontent.com/36323674/185576882-38e531f1-6f07-496b-b79b-d2507ffc4ddc.png) 6 | 2. Put a Rig Builder Component on Your Bipedal GameObject
7 | ![image](https://user-images.githubusercontent.com/36323674/185576773-55a0748a-8668-4c49-95a2-41a36f8ba9f8.png) 8 | 3. Set Up Ik Rig
9 | One Ik Rig (Child of Bipedal GameObject) with Three Constraints as Children
10 | ![image](https://user-images.githubusercontent.com/36323674/185577494-3a9944a3-819f-47e9-8b30-9fb15d2bc12e.png)
11 | ![image](https://user-images.githubusercontent.com/36323674/185577419-9b9184c2-bca3-4499-8bf3-6b1376235bac.png)
12 | Reference Foot Ik Rig in Rig Builder from Step 2 13 | 4. Set Up Two Bone Constraints for Each Leg
14 | ![image](https://user-images.githubusercontent.com/36323674/185577673-d3f7f290-86a9-4915-bfb3-7db7b6b16bab.png)
15 | ![image](https://user-images.githubusercontent.com/36323674/185577708-d8faa40f-e90d-467f-990c-b827bab6f68e.png)
16 | ![image](https://user-images.githubusercontent.com/36323674/185577746-01f49ce1-45e0-4933-bdc5-cda54bd13676.png)
17 | **THE ORDER OF COMPONENTS AND GAMEOBJECTS MUST MATCH FOR THE CORRECT ORDER OF EXECUTION**
18 | Extact Transform Constraint Must Always Preceed Two Bone/Multi Position Constraint and Pelvis Constraint GameObject Must Always Preceeed Two Bone Ik Constraints Objects, This Matters because of the Burst Compiler Execution Order
19 | Set Hint Weight on Both Two Bone Ik Component to 0, to stop Leg Bending in unwanted way
20 | 5. Set Foot Forward For Feet (Create A Transform Under Each Ik Constraint Facing the Forward of Foot, Used for Relative IK Rotation)
21 | ![image](https://user-images.githubusercontent.com/36323674/185579266-171c9769-57f9-4e30-bc95-7dbc0f8051c6.png)
22 | ![image](https://user-images.githubusercontent.com/36323674/185579315-8b20f593-df76-4ec8-9990-1775187e51b8.png)
23 | 6. Set Up Grounder on Bipedal Object (where Animator Component is)
24 | ![image](https://user-images.githubusercontent.com/36323674/185579534-0a8c8e0f-5207-4a00-8167-0780f132bc0a.png)
25 | **Bonus**. You Can setup Animation Curves of "LeftFootUp" and "RighFootUp" for optimal Ik Rotation, Foot only rotates to Surface When It's on Ground!
26 | ![image](https://user-images.githubusercontent.com/36323674/185580086-1127a1a5-4c53-4e95-9d07-e3d4bc1a2cd3.png)
27 | ![image](https://user-images.githubusercontent.com/36323674/185580258-80019459-22fe-47d7-96eb-2c0d37a5c976.png)
28 | --------------------------------------------------------------------------------