When I move and rotate the character facing the left cube it will then rotate the character head smooth looking at the cube. but then when I’m moving and rotating the character facing the right cube he will not rotate the head looking at the cube only on the left one.
Your player (and therefore the animator) can only look at 1 target object, so you have to decide in your routine on which target he should look at. By now the player will look probably always only at the last object of your loop. You have already calculated the dot product, this you could use to decide on which object the player should look at.
// here you have to decide which target object has priority
// at the moment always the last object of the loop "wins"
animator.SetLookAtPosition(lookObj[i].position);
So basically i would make 1 loop to decide which cube becomes priority (or none of them) and after that loop you set the look at position to this cube (and rotate the player).
Something like this (you still have to adapt this to your code):
See my last addition from the above reply. Simply you find the prioritized target “primaryTarget” here (or none if the target is behind the player) in that loop. And then you use “primaryTarget” for the SetLookAtPosition function.
How you want to set the SetLookAtWeight function here iam not sure and this depends on your needs. I would simply start with the full weight here (or take closestLookWeight and use your calculation from your old code to make a smoother transition):
I removed the smooth weight transition of the animation looking of the player to the cube, but you can enable it by comment in these 3 lines (and then you need to set finalLookWeight to 0 again, because it lerps always from 0 to closestLookWeight).
Yes this is right, the player can only look (and rotate) at one cube, so this code looks at the cube that fits best to the players view line and then the player looks only at this cube (and rotates at the same time). So all depends only on the starting rotation of the player in the scene.
The question is what you are trying to achieve? At the moment the player always rotates to the cube that is “closest” to the player view line. Perhaps it would make more sense that the player only looks at the closest cube and not rotating at all. So the player can control the rotating freely and the player only looks at the cubes closest to his “view line”.
If the player rotates always automatically to the closest cube in his view line he cant never reach the other cube.
There was a litte failure in your code, here is the fixed one:
using UnityEngine;
using System;
using System.Collections;
using UnityEngine.SocialPlatforms;
using UnityEditor.Animations;
[RequireComponent(typeof(Animator))]
public class IKControl : MonoBehaviour
{
public Transform[] lookObj = null;
public float weightDamping = 1.5f;
public float playerSpeedRot;
public bool rotatePlayer = false;
protected Animator animator;
private Transform lastPrimaryTarget;
private float finalLookWeight = 0;
void Start()
{
animator = GetComponent<Animator>();
}
//a callback for calculating IK
void OnAnimatorIK()
{
if (lookObj != null)
{
Transform primaryTarget = null;
float closestLookWeight = 0;
foreach (Transform target in lookObj)
{
Vector3 flattenedLookAtVector = Vector3.ProjectOnPlane(target.position - transform.position, transform.up);
float dotProduct = Vector3.Dot(transform.forward, flattenedLookAtVector);
float lookWeight = Mathf.Clamp(dotProduct, 0f, 1f);
if (lookWeight > closestLookWeight)
{
closestLookWeight = lookWeight;
primaryTarget = target;
}
}
if (primaryTarget != null)
{
if ((lastPrimaryTarget!=null) & (lastPrimaryTarget!=primaryTarget)) finalLookWeight = 0f; // lerps starts again at 0 for a new target
lastPrimaryTarget = primaryTarget;
//finalLookWeight = Mathf.Lerp(finalLookWeight, closestLookWeight, Time.deltaTime * weightDamping);
//float bodyWeight = finalLookWeight * .5f;
//animator.SetLookAtWeight(finalLookWeight, bodyWeight);
animator.SetLookAtWeight(1f, 1f, 1f, 1f, 1f);
animator.SetLookAtPosition(primaryTarget.position);
if (rotatePlayer == true)
{
}
}
}
}
}
I improved the code by smoothing the transitions between the targets. Its now more complex and has a transition state which controls the transitions. Handled cases are:
old target → neutral look position of the player (no target)
old target → neutral look position of the player → new target
new target → neutral look position of the player (no target)
I have tested the code a little bit but could still have some issues, but works fine for me. You can control the speed of the state switch by lerpEndDistance. If set a higher value here the switch from neutral look to new target should be faster (but value of 0.1f seems fine to me)
using UnityEngine;
using System;
using System.Collections;
[RequireComponent(typeof(Animator))]
public class IKControl : MonoBehaviour
{
public Transform[] lookObj = null;
public float weightDamping = 1.5f;
private Animator animator;
private Transform lastPrimaryTarget;
private float lerpEndDistance = 0.1f;
private float finalLookWeight = 0;
private bool transitionToNextTarget = false;
void Start()
{
animator = GetComponent<Animator>();
}
// Callback for calculating IK
void OnAnimatorIK()
{
if (lookObj != null)
{
Transform primaryTarget = null;
float closestLookWeight = 0;
// Here we find the target which is closest (by angle) to the players view line
foreach (Transform target in lookObj)
{
Vector3 lookAt = target.position - transform.position;
lookAt.y = 0f;
float dotProduct = Vector3.Dot(new Vector3(transform.forward.x, 0f, transform.forward.z).normalized, lookAt.normalized);
float lookWeight = Mathf.Clamp(dotProduct, 0f, 1f);
if (lookWeight > closestLookWeight)
{
closestLookWeight = lookWeight;
primaryTarget = target;
}
}
if (primaryTarget != null)
{
if ((lastPrimaryTarget != null) && (lastPrimaryTarget != primaryTarget) && (finalLookWeight > 0f))
{
// Here we start a new transition because the player looks already to a target but
// we have found another target the player should look at
transitionToNextTarget = true;
}
}
// The player is in a neutral look position but has found a new target
if ((primaryTarget != null) && !transitionToNextTarget)
{
lastPrimaryTarget = primaryTarget;
finalLookWeight = Mathf.Lerp(finalLookWeight, closestLookWeight, Time.deltaTime * weightDamping);
float bodyWeight = finalLookWeight * .75f;
animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
animator.SetLookAtPosition(primaryTarget.position);
}
// Let the player smoothly look away from the last target to the neutral look position
if ((primaryTarget == null && lastPrimaryTarget != null) || transitionToNextTarget)
{
finalLookWeight = Mathf.Lerp(finalLookWeight, 0f, Time.deltaTime * weightDamping);
float bodyWeight = finalLookWeight * .75f;
animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
animator.SetLookAtPosition(lastPrimaryTarget.position);
if (finalLookWeight < lerpEndDistance)
{
transitionToNextTarget = false;
finalLookWeight = 0f;
lastPrimaryTarget = null;
}
}
}
}
}
You are my hero. I have just spent an entire work day attempting to achieve this in raw vector math, completely forgetting about the fact we can IK bones such as “head”.
Well, now I know not only what approach to follow, but you also gave us a solid implementation, with smooth target transition, and interpolated movement. I don’t even need to worry about implementing it now. You’re a life saver! With one simple script, dialogue in my game is much more engaging and NPCs feel much more alive.