- Home /
How can I rotate my player head to face a target but only when the target is in the front of the player ?
The target is a cube in this case and the cube have a script attached that make the cube rotating around the player with random height :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateAround : MonoBehaviour
{
// Add this script to rotating object
[Header("Gameobject that object/s will rotate around as center point")]
public GameObject centerObj;//to get the position in worldspace to which this gameObject will rotate around.
[Header("The axis by which it will rotate around")]
public Vector3 axis;//by which axis it will rotate. x,y or z.
[Header("Angle covered per update")]
public float angle; //or the speed of rotation.
public float upperLimit, lowerLimit, delay;// upperLimit & lowerLimit: heighest & lowest height;
private float height, prevHeight, time;//height:height it is trying to reach(randomly generated); prevHeight:stores last value of height;delay in radomness;
// Update is called once per frame
void Update()
{
//Gets the position of your 'Center' and rotates this gameObject around it by the 'axis' provided at speed 'angle' in degrees per update
transform.RotateAround(centerObj.transform.position, axis, angle);
time += Time.deltaTime;
//Sets value of 'height' randomly within 'upperLimit' & 'lowerLimit' after delay
if (time > delay)
{
prevHeight = height;
height = Random.Range(lowerLimit, upperLimit);
time = 0;
}
//Mathf.Lerp changes height from 'prevHeight' to 'height' gradually (smooth transition)
transform.position = new Vector3(transform.position.x, Mathf.Lerp(prevHeight, height, time), transform.position.z);
}
}
Now the player head rig is rotating all the time since the player root object have Animator component. So I added a new child above the head rig empty GameObject and this is the object I'm rotating to look at the cube. This script is attached to the empty GameObject above the head rig.
I also dragged the head rih object to be child of the empty GameObject :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateToTarget : MonoBehaviour
{
public Transform target;
public float speed = 1.0f;
void Update()
{
// Determine which direction to rotate towards
Vector3 targetDirection = target.position - transform.position;
// The step size is equal to speed times frame time.
float singleStep = speed * Time.deltaTime;
// Rotate the forward vector towards the target direction by one step
Vector3 newDirection = Vector3.RotateTowards(transform.forward, targetDirection, singleStep, 0.0f);
// Calculate a rotation a step closer to the target and applies rotation to this object
transform.rotation = Quaternion.LookRotation(newDirection);
}
}
The problem is when the cube is behind the player then the player head is rotating backward and I want that the head will look at the target cube smooth only when the cube enter the front view of the player. I'm trying to make the most realistic like human view when looking at something.
I tried to use Animator IK with SetWeight and SetPosition and it worked fine but I could not finding any way to change the SetWeight value from 1 to 0 slowly smooth. So I'm trying to do it on my own from scracth.
Another problem is seems to be that the player head is not rotating fast enough like the cube does move so sometimes the player head is not looking at the cube since he is rotating slower then the cube.
The main goal is to make the player head to look at the cube smooth only when the cube is in the front of the player.
Screenshot :
Here is a link for a short video clip I recorded showing the problem when the cube is behind the player and that the player head rotation speed is not the same speed as the cube so in most of the time the player is not looking at the cube but looking at it in general direction :
https://www.youtube.com/watch?v=gApXlKv7hKY&feature=youtu.be
Answer by CardboardComputers · Aug 13, 2020 at 12:44 PM
You can use Vector3.Angle
to get the angle difference between the player object's transform.forward
vector and the direction to the cube, i.e. target.position - transform.position
:
float targetAngleFromForward = Vector3.Angle(transform.forward,
target.position - transform.position);
Then, you can check this against some limiting condition, so that you only run the tracking code when it's within a certain angle of the player's forward direction.
Furthermore, if you want to use IK weights, you can lerp or smooth lerp the weight between 0 and 1. If you prefer to use vectors, you can also use their vector analogues. These methods have the added benefit of not being limited to a specific speed; instead, each will always take the same amount of time for the character to go from "not looking at the block" to "looking at the block". In other words, if the block moves very quickly, the character's head will turn faster in order to face it in the given time. If the block moves very slowly, the character's head will turn slower to accomodate.
Can you show me please how to make the IK weights lerp/smooth lerp ? I tried it a lot without a success.
I tried this script on my player root object :
using UnityEngine;
using System;
using System.Collections;
using UnityEngine.SocialPlatforms;
using UnityEditor.Animations;
[RequireComponent(typeof(Animator))]
public class IKControl : $$anonymous$$onoBehaviour
{
protected Animator animator;
public bool ikActive = false;
public Transform headObj = null;
public Transform lookObj = null;
private bool changeWeight = false;
void Start()
{
animator = GetComponent<Animator>();
}
//a callback for calculating IK
void OnAnimatorIK()
{
if (animator)
{
changeWeight = false;
//if the IK is active, set the position and rotation directly to the goal.
if (ikActive)
{
//changeWeight = false;
// Set the look target position, if one has been assigned
if (lookObj != null)
{
//animator.SetLookAtWeight(1, 1, 1, 1, 1);
animator.SetLookAtPosition(lookObj.position);
}
}
//if the IK is not active, set the position and rotation of the hand and head back to the original position
else
{
changeWeight = true;
}
}
}
private void FixedUpdate()
{
if(changeWeight == true)
{
Quaternion from = headObj.rotation;
/**/
Vector3 dir = headObj.position - lookObj.position;
/**/
float angle = $$anonymous$$athf.Atan2(dir.y, dir.x) * $$anonymous$$athf.Rad2Deg;
Quaternion to = Quaternion.AngleAxis(angle, Vector3.forward);
headObj.rotation = Quaternion.Slerp(from, to, 1f);
}
}
}
but I found that even without the else part just if I tick the flag ikActive enabled false/true it's changing the player head weight between 1 and 0.
The else part and the code in the Update has no effect/ not working at all.
Can you please show me how to do it ? I want for example to make that when the flag ikActive is change to false when the game is running to slowly rotate the head slowly smooth back to to the original rotation/position using the weight.
Ah, my bad; the third parameter in the lerp functions are actually the interpolation ratio, not the time; in other words, they specify how far into the lerp you are. Based on your code, you'll want something like:
private const float LERP_TI$$anonymous$$E = 1f;
private float currentLerpTime;
private void Update() {
if (changeWeight) {
currentLerpTime += Time.deltaTime;
float lerpProgress = currentLerpTime / LERP_TI$$anonymous$$E;
SomeKindOfLerp(startDirection, targetDirection, $$anonymous$$athf.Clamp01(lerpProgress));
}
}
where currentLerpTime
is reset to 0 each time you want to lerp somewhere. Here, startDirection
might be some stored value from the start of the lerp, or it might describe where you're facing at the current frame. These two choices will have slightly different behaviours, but I think they should both work; the former will follow the Slerp curve whereas the latter will follow some kind of an exponential curve. Notice that I put it in Update
instead of FixedUpdate
; I think that'll look prettier, since Update
calls per frame whereas FixedUpdate
calls per physics loop.
It's not very elegant and kind of messy, I know. Depending on what you want, you can also try SmoothDamp
; it follows a different curve from Slerp
and is implemented differently. Unity has a built in version for $$anonymous$$athf
and Vector3
, and someone made one for Quaternion
. Like the Slerp example above, it also requires you to keep a reference variable across different calls.
Apologies again!
I tried it like this :
using UnityEngine;
using System;
using System.Collections;
using UnityEngine.SocialPlatforms;
using UnityEditor.Animations;
[RequireComponent(typeof(Animator))]
public class IKControl : $$anonymous$$onoBehaviour
{
protected Animator animator;
public bool ikActive = false;
public Transform headObj = null;
public Transform lookObj = null;
private bool changeWeight = false;
private const float LERP_TI$$anonymous$$E = 1f;
private float currentLerpTime;
void Start()
{
animator = GetComponent<Animator>();
}
//a callback for calculating IK
void OnAnimatorIK()
{
if (animator)
{
changeWeight = false;
//if the IK is active, set the position and rotation directly to the goal.
if (ikActive)
{
//changeWeight = false;
// Set the look target position, if one has been assigned
if (lookObj != null)
{
animator.SetLookAtWeight(1, 1, 1, 1, 1);
animator.SetLookAtPosition(lookObj.position);
}
}
//if the IK is not active, set the position and rotation of the hand and head back to the original position
else
{
changeWeight = true;
}
}
}
private void Update()
{
if (changeWeight)
{
currentLerpTime += Time.deltaTime;
float lerpProgress = currentLerpTime / LERP_TI$$anonymous$$E;
Quaternion.Lerp(headObj.rotation, Quaternion.Euler(new Vector3(0,0,0)), $$anonymous$$athf.Clamp01(lerpProgress));
}
}
}
// Rotation head : X between 60 and -60
// Rotation head : Y between 100 and -100
// Rotation head : Z between 60 and -60 ( Only if needed optional )
But it's not working yet. Have you ever tried using the IK weight with lerp ? The problem is that no matter what you do when you set the flag ikActive to false it's changing the player head back to forward to the default rotation automatic and do it too fast.
The code in the Update never have a chance to execute. Once you uncheck the ikActive make it false it's changing the head rotation the weight to 0 so the code in the Update has no affect.
Even if I'm not setting the flag changeWeight to true and only set the flag ikActive to false it will change the head weight to 0.
I;m not sure if it's possible to lerp the weight or to wait before it will change it to 0 and make the code in the Update to affect
Your answer
Follow this Question
Related Questions
LookAt doesnt work with cardboard SDK or any camera movement 0 Answers
Rotating while rotating? 1 Answer
Align transform Y axis to -Physics.gravity 1 Answer
Clamping LookRotation angle 2 Answers
slow look at question 1 Answer