I have a problem with my humanoid character's Foot IK.
I'm building an Isometric Top-Down game with NavMesh to move the Player on click, and I wanted to add some life to the characters so I decided to add IK Foot Placement by following this tutorial.
It worked well, but I wanted to go further by making it more sensitive to colliders so the player's foot gets placed on the ground's surface perfectly.
This script below works well when the ground is rotated.
I tried to fix it a lot but it still doesn't work.
[ Preview Images ]
Here's my script:
using UnityEngine;
using UnityEngine.AI;
public class IKFootPlacement : MonoBehaviour
{
public bool ikActive = false;
public Animator anim;
public NavMeshSurface navmesh;
public LayerMask layerMask;
public Transform LeftFoot_Transform;
public Transform RightFoot_Transform;
public float LeftFoot_DistanceToGround;
public float RightFoot_DistanceToGround;
public float footGap = 0.0f;
private Vector3 L_TargetPosition;
private Vector3 R_TargetPosition;
private Ray L_Ray;
private Ray R_Ray;
void Start()
{
anim = GetComponent<Animator>();
}
void OnAnimatorIK(int layerIndex)
{
if (anim)
{
// Vector3 LeftFoot_position = anim.GetIKPosition(AvatarIKGoal.LeftFoot);
// Vector3 RightFoot_Position = anim.GetIKPosition(AvatarIKGoal.RightFoot);
float IKLeftWeight = anim.GetFloat("IKLeftFootWeight");
anim.SetIKPositionWeight(AvatarIKGoal.LeftFoot, IKLeftWeight);
anim.SetIKRotationWeight(AvatarIKGoal.LeftFoot, IKLeftWeight);
float IKRightWeight = anim.GetFloat("IKRightFootWeight");
anim.SetIKPositionWeight(AvatarIKGoal.RightFoot, IKRightWeight);
anim.SetIKRotationWeight(AvatarIKGoal.RightFoot, IKRightWeight);
//~ Left Foot
if (LeftFoot_Transform)
{
RaycastHit L_Hit;
L_Ray = new Ray(LeftFoot_Transform.position + Vector3.up, Vector3.down);
if (Physics.Raycast(L_Ray, out L_Hit, 2f, layerMask))
{
L_TargetPosition = L_Hit.point;
LeftFoot_DistanceToGround = Vector3.Distance(LeftFoot_Transform.position, L_TargetPosition);
if (LeftFoot_DistanceToGround > 0.15)
L_TargetPosition.y -= LeftFoot_DistanceToGround;
else
L_TargetPosition.y += LeftFoot_DistanceToGround;
L_TargetPosition.y += footGap;
anim.SetIKPosition(AvatarIKGoal.LeftFoot, L_TargetPosition);
anim.SetIKRotation(AvatarIKGoal.LeftFoot, Quaternion.LookRotation(transform.forward, L_Hit.normal));
}
}
//~ Right Foot
if (RightFoot_Transform)
{
RaycastHit R_Hit;
R_Ray = new Ray(RightFoot_Transform.position + Vector3.up, Vector3.down);
if (Physics.Raycast(R_Ray, out R_Hit, 2f, layerMask))
{
R_TargetPosition = R_Hit.point;
RightFoot_DistanceToGround = Vector3.Distance(RightFoot_Transform.position, R_TargetPosition);
if (RightFoot_DistanceToGround > 0.15)
R_TargetPosition.y -= RightFoot_DistanceToGround;
else
R_TargetPosition.y += RightFoot_DistanceToGround;
R_TargetPosition.y += footGap;
anim.SetIKPosition(AvatarIKGoal.RightFoot, R_TargetPosition);
anim.SetIKRotation(AvatarIKGoal.RightFoot, Quaternion.LookRotation(transform.forward, R_Hit.normal));
}
}
}
}
void OnDrawGizmos()
{
Gizmos.color = Color.magenta;
Gizmos.DrawWireSphere(L_TargetPosition, 0.05f);
Gizmos.DrawWireSphere(R_TargetPosition, 0.05f);
Gizmos.color = Color.red;
Gizmos.DrawRay(L_Ray);
Gizmos.DrawRay(R_Ray);
}
}
Related
Create a new project, add a sphere, a rigidbody and add this small script. Then add 2 points in the points list. This script does nothing but moving the object each points of the list.
using System;
using UnityEngine;
public class MoveAlongPath : MonoBehaviour
{
public Vector3[] points;
public float speed = 1.0f;
public float minDistance = Vector3.kEpsilon;
[SerializeField] private Vector3 _posCurrent;
[SerializeField] private Vector3 _posNext;
[SerializeField] private Vector3 _newPos;
[SerializeField] private int _currentIndex;
[SerializeField] private Vector3 _rotate;
[SerializeField] private Vector3 _oldPosition;
[SerializeField] private Rigidbody _rigidbody;
private void NextPoint()
{
_posCurrent = points[_currentIndex];
transform.position = _posCurrent;
_currentIndex++;
if (_currentIndex == points.Length) {
_currentIndex = 0;
}
_posNext = points[_currentIndex];
}
private void Start()
{
_rigidbody = GetComponent<Rigidbody>();
NextPoint();
}
private void FixedUpdate()
{
Transform t = transform;
_oldPosition = t.position;
float singleStep = speed * Time.fixedDeltaTime;
_newPos = new Vector3(
_oldPosition.x + singleStep,
_oldPosition.y + singleStep,
_oldPosition.z + singleStep
);
_rigidbody.MovePosition(_newPos);
if (Vector3.Distance(_newPos, _posNext) <= minDistance) {
/* close enough -> next point: */
NextPoint();
}
}
}
Well... it doesn't work. I've tried many ways:
using Update() instead of FixedUpdate() - but AFAIK Update() should only handle inputs and FixedUpdate() should make actual 3D physics computation
using transform.position = _newPos; doesn't work, and MovePosition() doesn't work too...
I'm stuck!
You're not moving in a meaningful direction, you're just moving +1 unit per second on each axis.
Try
var direction = _posNext - _posCurrent;
This is the direction, but if you moved that vector you'd be there in one tick. What you want is the normalized unit vector, which has a magnitude of 1, then you can multiply your speed by that.
I'm writing this on a cell phone so I may not have the methods exact:
var singleStep = speed * Time.fixedDeltaTime * direction.normalized;
And now you want to take the SMALLER of your direction or singleStep. It's extremely likely that your singleStep would overshoot the target, and you'll know you're going to overshoot because the singleStep is a bigger step than just going to the target.
if(singleStep.magnitude > direction.magnitude)
{
singleStep = direction; // don't overshoot
}
Then you can move and that should get it :)
_newPos = _oldPosition + singleStep;
_rigidbody.MovePosition(_newPos);
Edited by OP: here's the full code of the working solution, thanks again!
using UnityEngine;
public class MoveAlongPath : MonoBehaviour
{
public Vector3[] points;
public float speed = 1.0f;
public float minDistance = Vector3.kEpsilon;
private SphereCollider _c;
[SerializeField] private Vector3 _posStart;
[SerializeField] private Vector3 _posEnd;
[SerializeField] private int _currentIndex;
[SerializeField] private Rigidbody _rigidbody;
[SerializeField] private Vector3 _direction;
private void NextPoint()
{
_posStart = points[_currentIndex];
transform.position = _posStart;
_currentIndex = ++_currentIndex % points.Length;
_posEnd = points[_currentIndex];
_direction = _posEnd - _posStart;
_direction = _direction.normalized;
Debug.Log("Going from "+_posStart+" -> to -> "+_posEnd);
}
private void Start()
{
_rigidbody = GetComponent<Rigidbody>();
NextPoint();
}
private void FixedUpdate()
{
Vector3 step = speed * Time.fixedDeltaTime * _direction.normalized;
if(step.magnitude > _direction.magnitude) {
step = _direction; // don't overshoot
}
Vector3 newPos = transform.position + step;
_rigidbody.MovePosition(newPos);
if (Vector3.Distance(_posEnd, transform.position) < minDistance) {
/* close enough -> next point: */
NextPoint();
}
}
}
I'm creating a pool game. And I want to shoot Raycast on the reflect direction so I can draw LineRenderer on it.
Main Cue Ball Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawCueBallTrajectory : MonoBehaviour
{
private RaycastHit hit;
private LineRenderer lineRender;
private float cueBallRadius;
private void Awake()
{
lineRender = GetComponent<LineRenderer>();
cueBallRadius = GetComponent<SphereCollider>().radius;
}
void Update()
{
Vector3 reduceTransPos = new Vector3(transform.position.x, transform.position.y - 2f, transform.position.z);
Ray ray = new Ray(transform.position, -transform.forward);
if (Physics.SphereCast(ray, cueBallRadius, out hit))
{
if (hit.collider != null)
{
lineRender.enabled = true;
lineRender.SetPosition(0, transform.position);
lineRender.SetPosition(1, -transform.forward + new Vector3(hit.point.x, hit.point.y + 1f, hit.point.z));
if (hit.collider.gameObject.CompareTag("OtherCueBall"))
{
Vector3 newDirection = Vector3.Reflect(hit.transform.position, hit.normal);
hit.collider.gameObject.GetComponent<OtherCueBallTrajectory>().DrawPredictionLine(newDirection);
}
}
}
}
}
Other Ball Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OtherCueBallTrajectory : MonoBehaviour
{
private RaycastHit hit;
private LineRenderer lineRender;
private float cueBallRadius;
private void Awake()
{
lineRender = GetComponent<LineRenderer>();
cueBallRadius = GetComponent<SphereCollider>().radius;
}
public void DrawPredictionLine(Vector3 targetDestination)
{
Ray ray = new Ray(transform.position); // what to put here
}
}
You must emit another sphere cast along the point of impact. Also adjust the lineRender points count according to the number of fractures. I made these changes to the script main cue ball and do not see the need to refer the second sphere cast to the other ball.
if (Physics.SphereCast(ray, cueBallRadius, out hit))
{
if (hit.collider != null)
{
lineRender.enabled = true;
lineRender.positionCount = 2; // here I set lineRender position count
lineRender.SetPosition(0, transform.position);
lineRender.SetPosition(1, -transform.forward + new Vector3(hit.point.x, hit.point.y + 1f, hit.point.z));
if (hit.collider.gameObject.CompareTag("OtherCueBall"))
{
Vector3 newDirection = Vector3.Reflect(hit.transform.position, hit.normal);
lineRender.positionCount = 3; // we need 1 more break point
// second sphere cast begin from hit.point along reflectDirection
if (Physics.SphereCast(hit.point, cueBallRadius, newDirection, out var secondHit))
{
lineRender.SetPosition(2, secondHit.point); // stop when hit wall or anything
}
else
{
lineRender.SetPosition(2, hit.point + newDirection * 10f); // point along direction for maximum of 10f length for e.g..
}
}
}
}
I wanted to make an endless runner game where the player can jump and he moves forward automatically, so, for the forward movement i wanted to use the rigidbody velocity function in order to make the movement smoother and for the jump i made a custom function. My problem is, when i use in my update method the velocity function alone, it works, when i use it with my jump function it stops working. What can i do?. I tried changing the material of the floor and of the player's box collider to a really slippery one but nothing
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovePrima : MonoBehaviour
{
[SerializeField]
private float _moveSpeed = 0f;
[SerializeField]
private float _gravity = 9.81f;
[SerializeField]
private float _jumpspeed = 3.5f;
public float speedAccel = 0.01f;
private CharacterController _controller;
private float _directionY;
public float startSpeed;
public float Yspeed = 10f;
private int i = 0;
public float touchSensibility = 50f;
public int accelTime = 600;
[SerializeField]
Rigidbody rb;
// Start is called before the first frame update
void Start()
{
startSpeed = _moveSpeed;
_controller = GetComponent<CharacterController>();
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
rb.velocity = new Vector3(0f, 0f, 1f) * _moveSpeed;
touchControls();
}
public void touchControls()
{
if (SwipeManager.swipeUp && _controller.isGrounded)
{
_directionY = _jumpspeed;
print("Entrato");
}
_directionY -= _gravity * Time.fixedDeltaTime;
_controller.Move((new Vector3(0, _directionY, 0)) * Time.fixedDeltaTime * Yspeed);
i += 1;
if (i % accelTime == 0)
{
_moveSpeed += startSpeed * speedAccel;
i = 0;
}
}
private void OnControllerColliderHit(ControllerColliderHit hit) //Checks if it collided with obstacles
{
if(hit.transform.tag == "Obstacle")
{
GameOverManager.gameOver = true;
}
}
}
You should either use the CharacterController or the Rigidbody. With the CharacterController you tell Unity where to place the character and you have to check yourself if it is a valid position. With the Rigidbody you push it in the directions you want, but the physics around the character is also interfering.
You can use Rigidbody.AddForce to y when jumping instead of _controller.Move.
I was trying to achieve this kind of player ball movement:
Catch Up (Ketchapp)
From my side I have tried and record a video of my current implementation:
CatchUpBallMovementDemo
Two kinds of problem, I was facing:
ball making so much jerk while moving on the plain track that I hope you have clearly noticed in my recorded video
when ball reach left or right edge and you try to swipe its making jerk again rather than remain restricted because clamping related code already added
I have just created a demo project so here I am providing the link for it so personally you can check and provide me a suggestion for making ball movement perfect.
Demo Project Source Link: CatchUpBallDemo
Demo Project SIZE 20MB
What at present making jerk in ball movement that I can't able to decide, whether its following camera jerk, whether ball not moving properly though I have created a plain track for checking purpose.
Ball Inspector Detail:
Complete code added within the working demo project. Share your suggestions with me to solve this.
Code Scripts:
BallController
[RequireComponent (typeof(Rigidbody))]
public class BallController : MonoBehaviour
{
//
private Rigidbody myRigidBody;
private bool isJumper;
private bool allowSpeedIncrease;
private BallInputHandler ballInputHandler;
private float speed;
private float speedMilestone;
private float jumpCounter;
private float scoreElapsedTime;
[SerializeField]
private bool isGrounded;
//
public float ballHorzRange;
public float ballStartSpeed;
public float ballTopSpeed;
public float smoothnessValue;
public float smoothnessX;
private void Awake ()
{
DoOnAwake ();
}
private void DoOnAwake ()
{
ballInputHandler = GetComponent<BallInputHandler> ();
myRigidBody = GetComponent<Rigidbody> ();
speed = ballStartSpeed;
speedMilestone = ballStartSpeed;
}
public void Start ()
{
DoOnStart ();
}
private void DoOnStart ()
{
// assinging player transform to camera to follow
Camera.main.GetComponent<CameraFollow> ().FollowPlayer (transform);
}
void Update ()
{
// slowly increase ball moving speed
if (allowSpeedIncrease) {
speed += Time.deltaTime;
if (speed >= speedMilestone) {
allowSpeedIncrease = false;
speed = speedMilestone;
}
}
}
void FixedUpdate ()
{
// do jumping
if (isJumper) {
jumpCounter++;
if (jumpCounter >= 3) {
isJumper = false;
jumpCounter = 0;
}
myRigidBody.AddForce (Vector3.up * 700f);
}
// applying continuous forward velocity
Vector3 nextVelocity = myRigidBody.velocity;
nextVelocity.x = ballInputHandler.horizontalInput * smoothnessX;
nextVelocity.z = speed;
if (isGrounded) {
nextVelocity.y = 0;
} else if (!isJumper) {
nextVelocity.y -= speed * 0.1f;
}
myRigidBody.velocity = nextVelocity.normalized * speed;
ClampingBallMovement ();
}
// ball horizontal movement limitation
private void ClampingBallMovement ()
{
Vector3 currRigidbodyPos = myRigidBody.position;
if (currRigidbodyPos.x <= -ballHorzRange || currRigidbodyPos.x >= ballHorzRange) {
currRigidbodyPos.x = Mathf.Clamp (currRigidbodyPos.x, -ballHorzRange, ballHorzRange);
myRigidBody.position = currRigidbodyPos;
}
}
void OnTriggerEnter (Collider other)
{
if (other.CompareTag (GameConstants.TAG_TRACK_SPAWNER)) {
GameController.Instance.SpawnPlateform ();
} else if (other.CompareTag (GameConstants.TAG_TRACK_DESTROYER)) {
Destroy (other.transform.parent.gameObject);
}
}
}
BallMeshRolling
public class BallMeshRolling : MonoBehaviour
{
private Vector3 ballLastPosition;
void Start ()
{
ballLastPosition = transform.parent.position;
}
void Update ()
{
// implementation-1
float speed = Vector3.Distance (transform.parent.position, ballLastPosition) * 30f;
transform.RotateAround (transform.position, Vector3.right, speed);
// float dragDifference = (transform.position.x - ballLastPosition.x) * 30f;
// transform.RotateAround (transform.position, Vector3.forward, dragDifference);
ballLastPosition = transform.parent.position;
}
}
CameraFollow
public class CameraFollow : MonoBehaviour
{
//
private Vector3 newPos;
private Vector3 initialPosition;
//
public Transform player;
public Vector3 offSet;
void Awake ()
{
initialPosition = transform.position;
}
void LateUpdate ()
{
if (!player)
return;
newPos = player.position + offSet;
newPos.x = ReMap (newPos.x);
newPos.y = Mathf.Clamp (newPos.y, initialPosition.y, initialPosition.y + 1f);
// transform.position = newPos;
transform.position = Vector3.Lerp (transform.position, newPos, 10f * Time.deltaTime);
}
public void FollowPlayer (Transform target)
{
player = target;
ResetCamera ();
}
public float ReMap (float value, float from1 = -4f, float to1 = 4f, float from2 = -2.5f, float to2 = 2.5f)
{
return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
}
public void ResetCamera ()
{
transform.position = initialPosition;
}
}
I could solve the second problem by simply adding this to BallController.ClampingBallMovement():
private void ClampingBallMovement ()
{
Vector3 currRigidbodyPos = myRigidBody.position;
if (currRigidbodyPos.x <= -ballHorzRange || currRigidbodyPos.x >= ballHorzRange) {
currRigidbodyPos.x = Mathf.Clamp (currRigidbodyPos.x, -ballHorzRange, ballHorzRange);
myRigidBody.position = currRigidbodyPos;
}
// I ADDED THIS
// Clamp the velocity as well
if (currRigidbodyPos.x <= -ballHorzRange && myRigidBody.velocity.x < 0 || currRigidbodyPos.x >= ballHorzRange && myRigidBody.velocity.x > 0)
{
myRigidBody.velocity = new Vector3(0, myRigidBody.velocity.y, myRigidBody.velocity.z);
}
}
you clamped the position but did not clamp the velocity as well.
I could not reproduce the first jerking arround on my PC.
I am new to unity and don't know a lot of stuff. I've been watching tutorials and I saw one in which the guy created a replica of famous 'Chilly Snow'. The game is complete but the movement of ball isn't like the one in chilly snow. The ball starts orbiting continuously when I press mouse button. I wanted to know how to create that kind of movement, so that the ball turns left and right in a curve but doesn't go in to an orbit. I googled a lot but wasn't able to find my required result. I would really appreciate if anyone could point me in the right direction. Images are attached.Chilly Snow | Movement of my ball
public class movement : MonoBehaviour {
private float points;
public float playerSpeed;
private float rotationSpeed;
public Text score;
private bool isMovingLeft;
public GameObject player;
public bool isDead;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
transform.Translate (Vector3.down * playerSpeed * Time.deltaTime);
if (Input.GetMouseButtonDown (0))
{
Time.timeScale = 1;
isMovingLeft = !isMovingLeft;
rotationSpeed += 0.5f * Time.deltaTime;
}
if (Input.GetMouseButton (0))
{
rotationSpeed = 1f;
}
if (isMovingLeft) {
rotationSpeed += 1.5f * Time.deltaTime;
transform.Rotate(0,0,rotationSpeed);
} else
transform.Rotate(0,0, - rotationSpeed);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
Here is how I would approach it without doing a rotation... using your code.
public class movement : MonoBehaviour {
private float points;
public Text score;
public GameObject player;
public bool isDead;
private float currentXSpeed;
private float targetSpeed;
public float maxXSpeed;
public float speedChange;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
targetSpeed = maxXSpeed;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
if(Input.GetMouseButtonDown(0))
{
Time.timeScale = 1;
targetSpeed = -targetSpeed;
}
currentSpeed = mathf.MoveTowards(currentSpeed, targetSpeed, speedChange * Time.deltaTime);
Vector3 movementDirection = new Vector3(currentSpeed, Vector3.down.y * playerSpeed, 0.0f);
transform.Translate (movementDirection * Time.deltaTime);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
You need something like sinusoidal movement or any other graph you fancy.
An example would be for this is like;
gameObject.transform.Translate(Vector3.right * Time.deltaTime*cubeSpeed);
gameObject.transform.position += transform.up * Mathf.Sin (Time.fixedTime * 3.0f ) * 0.1f;
Above pseudo is for 2D graph simulation, can be adapted to your situation.
The object is always moving to right and going up and down while making a sinusoidal movement. Because the up and down speed is not fixed hence you get the sinusoidal or like sinusoidal movement.
In your case, while the object is always going down it will make the sinusoidal movement to left and right.
Your movement is based on the rotation so, if you give this sinusoidal speed as your rotation speed, you can achieve this.
Another aproach can be lerp or slerp
Lerp allows you to make kinda smooth transactions between 2 vectors.
Like moving from pointA to pointB in X seconds.
For rotation you will need Quaternion.Lerp There is a great answer on Unity Answers you can check that if you haven't before.
Hope this helps! Cheers!