0

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.

derHugo
  • 83,094
  • 9
  • 75
  • 115
Jyotirmoy
  • 1
  • 1
  • your player script is one way to do it, another is using the IPointer* events, but they dont seem to work with the new input system unless Im missing something obvious.. – BugFinder Jul 04 '23 at 22:14
  • oh and be warned, i ended up renaming the Outline component (if you got it from the asset store) as it clashes with the UI Outline class .. and wasnt working as a result – BugFinder Jul 04 '23 at 22:16
  • @BugFinder afaik IPointerXY interfaces work just fine with the new input system but you might need to copy over the default UI actions from the default actions asset! Otherwise none of the `UnityEngine.UI` items would be responsive using the new Input System ;) – derHugo Jul 05 '23 at 05:52
  • Yeah something was needed i didnt need it enough to work out what. :) but iight not work straight away was my point. – BugFinder Jul 05 '23 at 07:18

1 Answers1

0

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

derHugo
  • 83,094
  • 9
  • 75
  • 115