Home > Net >  How to make the player to look at other objects only if there a clear path view?
How to make the player to look at other objects only if there a clear path view?

Time:07-11

Now the player can look at other objects even if there are objects in the way and blocking the view.

This screenshot is a fine situation the player is looking at the small up there helmet and there is a clear view path so it's find :

Clear view

This screenshot is when the player is behind the big space ship there is no free path clear view to the helmet but he can still looking at the helmet :

No clear view path but the player is still looking at the object

This script is attached to the player and that allow the player to look at other objects :

using UnityEngine;
using System;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Linq;
using TMPro;

[RequireComponent(typeof(Animator))]
public class IKControl : MonoBehaviour
{
    public List<InteractableItem> lookObjs = new List<InteractableItem>();
    public TextMeshProUGUI text;
    public float weightDamping = 1.5f;
    public bool RightHandToTarget = false;
    public GameObject descriptionTextImage;
    public float duration;

    private List<InteractableItem> allDetectedItems;
    private Animator animator;
    private InteractableItem lastPrimaryTarget;
    private float lerpEndDistance = 0.1f;
    private float finalLookWeight = 0;
    private bool transitionToNextTarget = false;
    private float t;
    private bool showText = true;

    void Start()
    {
        animator = GetComponent<Animator>();
        allDetectedItems = new List<InteractableItem>();
        t = 0;
    }

    // Callback for calculating IK
    void OnAnimatorIK()
    {
        if (lookObjs != null)
        {
            lookObjs.RemoveAll(x => x == null);

            InteractableItem primaryTarget = null;

            float closestLookWeight = 0;

            // Here we find the target which is closest (by angle) to the players view line
            allDetectedItems.Clear();
            foreach (InteractableItem target in lookObjs)
            {
                if (target.enabledInteraction == false)
                {
                    continue;
                }

                Vector3 lookAt = target.transform.position - transform.position;
                lookAt.y = 0f;

                // Filter out all objects that are too far away
                if (lookAt.magnitude > target.distance) continue;

                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 > 0.1f && lookWeight > closestLookWeight)
                {
                    closestLookWeight = lookWeight;
                    primaryTarget = target;

                    if (showText)
                    {
                        StartCoroutine(WaitBeforeShowingText(primaryTarget));
                        showText = false;
                    }
                }
                else
                {
                    showText = true;
                    text.text = "";
                    descriptionTextImage.SetActive(false);
                }

                allDetectedItems.Add(target);
            }

            InteractWithTarget(primaryTarget, closestLookWeight);
        }
    }

    private void InteractWithTarget(InteractableItem primaryTarget, float closestLookWeight)
    {
        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)
        {
            if (primaryTarget.IsAnyAction())//.interactableMode == InteractableItem.InteractableMode.ActionWithoutThrow)
            {
                RightHandToTarget = true;
            }

            lastPrimaryTarget = primaryTarget;
            finalLookWeight = Mathf.Lerp(finalLookWeight, 1f, Time.deltaTime * weightDamping);
            float bodyWeight = finalLookWeight * .1f;
            animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
            animator.SetLookAtPosition(primaryTarget.transform.position);

            if (RightHandToTarget && primaryTarget.IsAnyAction())
            {
                Vector3 relativePos = primaryTarget.transform.position - transform.position;
                Quaternion rotationtoTarget = Quaternion.LookRotation(relativePos, Vector3.up);

                if (primaryTarget.interactableMode == InteractableItem.InteractableMode.ActionWithoutThrow)
                {
                    animator.SetIKRotationWeight(AvatarIKGoal.RightHand, finalLookWeight);
                    animator.SetIKRotation(AvatarIKGoal.RightHand, rotationtoTarget);
                    animator.SetIKPositionWeight(AvatarIKGoal.RightHand, finalLookWeight * 1f * closestLookWeight);
                    animator.SetIKPosition(AvatarIKGoal.RightHand, primaryTarget.transform.position);
                }

                if (primaryTarget.interactableMode == InteractableItem.InteractableMode.Action)
                {
                    animator.SetIKRotationWeight(AvatarIKGoal.RightHand, finalLookWeight);
                    animator.SetIKRotation(AvatarIKGoal.RightHand, rotationtoTarget);
                    animator.SetIKPositionWeight(AvatarIKGoal.RightHand, finalLookWeight * 0.1f * closestLookWeight);
                    animator.SetIKPosition(AvatarIKGoal.RightHand, primaryTarget.transform.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, t / duration);//Time.deltaTime * weightDamping);
            t  = Time.deltaTime;

            float bodyWeight = finalLookWeight * .1f;

            animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
            animator.SetLookAtPosition(lastPrimaryTarget.transform.position);

            if (RightHandToTarget)
            {
                Vector3 relativePos = lastPrimaryTarget.transform.position - transform.position;
                Quaternion rotationtoTarget = Quaternion.LookRotation(relativePos, Vector3.up);
                animator.SetIKRotationWeight(AvatarIKGoal.RightHand, finalLookWeight);
                animator.SetIKRotation(AvatarIKGoal.RightHand, rotationtoTarget);
                animator.SetIKPositionWeight(AvatarIKGoal.RightHand, finalLookWeight * 0.5f * closestLookWeight);
                animator.SetIKPosition(AvatarIKGoal.RightHand, lastPrimaryTarget.transform.position);
            }

            if (finalLookWeight < lerpEndDistance)
            {
                transitionToNextTarget = false;
                finalLookWeight = 0f;
                lastPrimaryTarget = null;
                transform.rotation = Quaternion.Euler(0, transform.eulerAngles.y, 0);
            }
        }
    }

    IEnumerator WaitBeforeShowingText(InteractableItem primaryTarget)
    {
        yield return new WaitForSeconds(1f);

        descriptionTextImage.SetActive(true);
        text.text = primaryTarget.description;
    }
}

This script is attached to each object i want the player to be able to look at :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InteractableItem : MonoBehaviour
{
    public string currentMode;
    public bool enabledInteraction = true;

    private InteractableMode currentInteractableMode;
    private string currentDesription;

    private void Awake()
    {

    }

    public enum InteractableMode
    {
        Description,
        Action,
        ActionWithoutThrow
    };

    public InteractableMode interactableMode = InteractableMode.Description;//public InteractableLookAtDirection interactableLookAtDirection = InteractableLookAtDirection.Forward;
    public float distance;

    [TextArea(1, 10)]
    public string description = "";

    public bool IsAnyAction()
    {
        return interactableMode == InteractableMode.ActionWithoutThrow || interactableMode == InteractableMode.Action;
    }

    public bool IsActionWithoutThrow()
    {
        return interactableMode == InteractableMode.ActionWithoutThrow;
    }

    private void Start()
    {
        currentMode = GetComponent<InteractableItem>().interactableMode.ToString();
        currentInteractableMode = GetComponent<InteractableItem>().interactableMode;
        currentDesription = GetComponent<InteractableItem>().description;

        
    }

    private void Update()
    {
        
    }
}

When i set the distance for example on the InteractableItem script to 100 then in the IKControl script the player will look at objects from very long distance even if there are blocking objects in the middle of the looking view.

How yo make the player not looking at object if other objects are blocking the looking view?

This is what i tried using raycast :

void OnAnimatorIK()
    {
        if (lookObjs != null)
        {
            lookObjs.RemoveAll(x => x == null);

            InteractableItem primaryTarget = null;

            float closestLookWeight = 0;

            // Here we find the target which is closest (by angle) to the players view line
            allDetectedItems.Clear();
            foreach (InteractableItem target in lookObjs)
            {
                if (target.enabledInteraction == false)
                {
                    continue;
                }

                Vector3 lookAt = target.transform.position - transform.position;
                lookAt.y = 0f;

                // Filter out all objects that are too far away
                if (lookAt.magnitude > target.distance) continue;

                RaycastHit hit;
                if (Physics.Raycast(playerEyes.transform.position, target.transform.position - playerEyes.transform.position, out hit, target.distance, ~LayerMask.GetMask("kid_from_space")))
                {
                    if (hit.collider.gameObject == target.gameObject)
                    {
                        // First object hit was the target so there is a clear line of sight

                        Debug.DrawRay(playerEyes.transform.position, Vector3.forward, Color.red);

                        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 > 0.1f && lookWeight > closestLookWeight)
                        {
                            closestLookWeight = lookWeight;
                            primaryTarget = target;

                            if (showText)
                            {
                                StartCoroutine(WaitBeforeShowingText(primaryTarget));
                                showText = false;
                            }
                        }
                        else
                        {
                            showText = true;
                            text.text = "";
                            descriptionTextImage.SetActive(false);
                        }

                        allDetectedItems.Add(target);
                    }
                }
            }

            InteractWithTarget(primaryTarget, closestLookWeight);
        }
    }

It seems to be working for the first time but then not working on the next times.

and should i add a layer name of the player to the Layer list in the editor and then to assign the layer name to the player ?

CodePudding user response:

You can use Physics.Raycast to check if a line of sight is blocked. https://docs.unity3d.com/ScriptReference/Physics.Raycast.html

To check if there is something between the player and the object you're looking for you can use this in the player code.

RaycastHit hit;
if (Physics.Raycast(transform.position, target.transform.position - transform.position, out hit, maxRaycastDistance))
{
    if (hit.collider.gameObject == target.gameObject)
    {
        // First object hit was the target so there is a clear line of sight
    }
}

You may need to use a layer mask to prevent the raycast from colliding with the player which you can do easily with the LayerMask.GetMask function.

RaycastHit hit;
if (Physics.Raycast(transform.position, target.transform.position - transform.position, out hit, maxRaycastDistance, ~LayerMask.GetMask("nameOfPlayerLayer")))
{
    if (hit.collider.gameObject == target.gameObject)
    {
        // First object hit was the target so there is a clear line of sight
    }
}

The ~ before the GetMask function is necessary to invert the layer mask so that the raycast will collide with everything except what is on the player layer.

CodePudding user response:

You could shoot a raycast at the target and if it collides to something other than the target dont look at the target.

  • Related