Unity Outline object Only when Player is Looking

151 Views Asked by At

I am Working on a 3d Game In Unity. So the problem I am facing is with Outlining the object only when player look towards that object, I have a C# Interface "IInteractable", whenever player looks at an object which is IInteractable It Calls function "Looking()" and when player Press "Q" It Calls "Trigger()"

public interface IInteractable 
{

    void trigger();
    void Looking();
    void NotLooking();
    

}

for outlining currently I am doing this "Looking()" calls in each Update function when player look at the object and "trigger()" only calls when player player press "Q" & "NotLooking()" currently have no use

public class exampleObject:MonoBehaviour,IInteractable
{
    private Outline outline;
    private float timmer=0;

    void Start()
    {
        outline=GetComponent<Outline>();
        outline.enabled=false;
    }
    void Update()
    {
        if(timmer>5)
        {
            outline.enabled=true;
            timmer-=4;
        }else{
            outline.enabled=false;
        }
    }

    //Interface functions
    public void Looking()
    {
        timmer+=5;
    }
    public void NotLooking()
    {
        outline.enabled=false;
    }
    public void trigger()
    {
        // code to interact with the Object
    }

}

I Know this is not a good approach and also if I look too long at an Object then after leaving it it still having outline, as we can clearly see the problem in the script;

If Needed Player Script

    private Ray ray;
    private RaycastHit hit;
    
    [SerializeField] bool alreadyInHand=false;
    [SerializeField] IGrabable data;
    [SerializeField] float distance=5.0f;
    private bool showingText=false;
    
    

    void Start()
    {
        
    }

    void Update()
    {
        ray = Camera.main.ScreenPointToRay(Input.mousePosition);
       #region IIntractable
       if (Physics.Raycast(ray, out hit, distance))
        {
           
            IInteractable interactable=hit.collider.gameObject.GetComponent<IInteractable>();
            IGrabable grabObje=hit.collider.gameObject.GetComponent<IGrabable>();

            if(interactable!=null)
            {
                interactable.Looking();
                
            }
        
            if(Input.GetKeyDown(KeyCode.Q) && interactable!=null)
            {
                interactable.trigger();
            }

}

can any one please help me with this, I am new to this field,

I Have tried to make a to check if (peek()==current_Interacatable) then return; else call NotLooking() in that object and remove it from the Queue, But still its not working.

1

There are 1 best solutions below

1
On

Not sure if your player script is missing parts (there is an unmatched opening #region) but in general this is how I would make the raycasting work:

// store currently focused instance!
private IInteractable currentInteractable;

private void SetInteractable(IInteractable interactable)
{
    // if is same instance (or both null) -> ignore
    if(currentInteractable == interactable) return;

    // otherwise if current focused exists -> reset
    if(currentInteractable != null) currentInteractable.IsLooking = false;

    // store new focused
    currentInteractable = interactable;

    // if not null -> set looking
    if(currentInteractable != null) currentInteractable.IsLooking = true;
}

void Update()
{
    // in general I'd use vars .. no need to have class fields for those
    var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
   
    if (Physics.Raycast(ray, out var hit, distance))
    {
        if(hit.collider.TryGetComponent<IInteractable>(out var interactable)
        {
            // hitting an IInteractable -> store
            SetInteractable(interactable);
        }
        else
        {
            // hitting something that is not IInteractable -> reset
            SetInteractable(null);
        }
    }
    else
    {
        // hitting nothing at all -> reset
        SetInteractable(null);
    }

    // if currently focusing an IInteractable and click -> interact
    if(currentInteractable != null && Input.GetKeyDown(KeyCode.Q))
    {
        currentInteractable.Interact();
    }
}

and then according adjustments

public interface IInteractable 
{
    // I would use a property for things that are basically working like a switch
    bool IsLooking { get; set; }

    // I would say this is a better name than trigger - otherwise why is this interface not called ITriggerable ;)
    void Interact();
}

and

public class exampleObject:MonoBehaviour,IInteractable
{
    [SerializeField] private Outline outline;
    // adjust delays in seconds
    [SerializeField] private float outlineEnableDelay = 1f;
    [SerializeField] private float outlineDisableDelay = 1f;

    // stores currently running routine (see below)
    private Coroutine lookingRoutine;
    // backing field for the IsLooking property
    private bool isLooking;

    private void Awake()
    {
        if(!outline) outline = GetComponent<Outline>();
        outline.enabled = false;
    }

    public bool IsLooking
    {
        // when accessing the property simply return the value
        get => isLooking;

        // when assigning the property apply visuals
        set
        {
            // same value ignore to save some work
            if(isLooking == value) return;

            // store the new value in the backing field
            isLooking = value;

            // if one was running cancel the current routine
            if(lookingRoutine != null) StopCoroutine(lookingRoutine);

            // start a new routine to apply the outline delayed
            lookingRoutine = StartCoroutine(EnabledOutlineDelayed(value));
        }
    }

    public void Interact()
    {
        Debug.Log($"Interacted with {name}", this);
    }

    // This routine simply has an initial delay and then
    // applies the target state to the outline
    private IEnumerator EnabledOutlineDelayed(bool enable)
    {
        // wait for the according delay - you can of course adjust this according to your needs
        yield return new WaitForSeconds(enable ? outlineEnableDelay : outlineDisableDelay);     

        // apply state
        outline.enabled = enable;

        // reset the routine field just to be sure
        lookingRoutine = null;
    }
}

Of course you might have to adapt this according to your exact needs but I hope this gives you a solid start point