Search code examples
c#visual-studiounity-game-engineraycasting

Raycast hits 2 objects at the same time


I have recently developed in unity and I have a problem with using the raycast. I created 3 scripts:

  • Interactor: Hooked to the player, it manages all the raycast features
  • InteractionObject: Hooked to the objects that need to animate
  • InteractionRaycast: Hooked to objects that need to be destroyed

Everything works, the only problem is that when the animation of the gameobject takes place (In my case the gameobject was a pillow), the other gameobject (It is under the pillow) is destroyed at the same time as the animation begins. My goal is to first move the pillow and then click on the gameobject to be destroyed, what can I do? Thank you in advance for your help

Interactor.cs

public class Interactor : MonoBehaviour
{
    [SerializeField]
    private float _interctRange;
    private InteractionObject _interactObject;
    private InteractionRaycast _interactionRaycast;
    private Camera _camera;
    private RaycastHit _hit;
// Start is called before the first frame update
void Start()
{
    _camera = Camera.main;
}

// Update is called once per frame
void Update()
{
    if (Input.GetButton("Fire1"))
    {
        Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out _hit, _interctRange);
        if (_hit.transform)
        {
            _interactObject = _hit.transform.GetComponent<InteractionObject>();
            
        }
        if (_interactObject)
        {
            _interactObject.PerfomAction();
            
        }
   
    }
}
}

InteractionObject.cs

public class InteractionObject : MonoBehaviour
{
    [SerializeField]
    private Vector3 _openPosition, _closePosition;
    [SerializeField]
    private float _animationTime;
    private Hashtable _iTweenArgs;
    [SerializeField]
    public  bool _isOpen;

// Start is called before the first frame update
void Start()
{
    _iTweenArgs = iTween.Hash();
    _iTweenArgs.Add("position", _openPosition);
    _iTweenArgs.Add("time", _animationTime);
    _iTweenArgs.Add("islocal", true);
}

public void PerfomAction()
{
    if (Input.GetButton("Fire1"))
    {
        if (_isOpen)
        {
            _iTweenArgs["position"] = _closePosition;
        }
        else
        {
            _iTweenArgs["position"] = _openPosition;

        }

        _isOpen = !_isOpen;

        iTween.MoveTo(gameObject, _iTweenArgs);


    }
}



} 

InteractionRaycast.cs

public class InteractionRaycast : MonoBehaviour
{
    [SerializeField]
    private float _range;
    Ray _myRay;
    RaycastHit _hit;

// Update is called once per frame
void Update()
{
    if (Input.GetButton("Fire1"))
    {
        Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out _hit, _range);
        if (_hit.transform)
        {
            Destroy(gameObject);
        }


    }

}
}

Solution

  • Your InteractionRaycast will destroy this own gameObject it is attaced to completely regardless of what exactly you are hitting.

    You either want to additionally check like e.g.

    if (Input.GetButton("Fire1"))
    {
        var ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f));
        if(Physics.Raycast(ray, out var hit, _interctRange))
        {
            // Is this actually the object that was hit?
            if(hit.transform == transform)
            {
                Destroy(gameObject);
            }
        }
    }
    

    Or - and in general I would do that - instead of having such a component on each and every object you can interact with and shooting hundreds of redundant raycasts, I would rather have a component on your player object, shoot one single raycast and interact with whatever you hit.

    Both your target objects can have a common interface

    public interface IInteractionObject
    {
        void PerfomAction();
    }
    

    meaning both types need to implement a method called PerformAction without parameters.

    And rather interact directly with that in

    public class Interactor : MonoBehaviour
    {
        [SerializeField]
        private float _interctRange;
       
        private Camera _camera;
    
        // Start is called before the first frame update
        void Start()
        {
            _camera = Camera.main;
        }
    
        // Update is called once per frame
        void Update()
        {
            if (Input.GetButton("Fire1"))
            {
                var ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f));
                // was something hit at all? => Check the API and return values of methods!
                if(Physics.Raycast(ray, out var hit, _interctRange))
                {
                    // Did we hit an IInteractionObject
                    if(hit.transform.TryGetComponent<IInteractionObject>(out var interactable))
                    {
                        // This class has no idea what exactly it is interacting with and doesn't need to know
                        interactable.PerfomAction();
                    }
                }
            }
        }
    }
    

    and then you have different implementations:

    public class AnimatedInteractionObject : MonoBehaviour, IInteractionObject
    {
        [SerializeField] private Vector3 _openPosition;
        [SerializeField] private Vector3 _closePosition;
        [SerializeField] private float _animationTime;
        [SerializeField] public bool _isOpen;
    
        private Hashtable _iTweenArgs;
    
        private void Start()
        {
            _iTweenArgs = iTween.Hash();
            _iTweenArgs.Add("position", _openPosition);
            _iTweenArgs.Add("time", _animationTime);
            _iTweenArgs.Add("islocal", true);
        }
        
        public void PerfomAction()
        {
            _isOpen = !_isOpen;
    
            // use ternary makes it easier to read
            _iTweenArgs["position"] = _isOpen ? _openPosition : _closePosition;
        
            iTween.MoveTo(gameObject, _iTweenArgs);
        }
    } 
    

    and

    public class DestroyInteractionObject : MonoBehaviour, IInteractionObject
    {
        public void PerfomAction()
        {
            // This is only called by the Interactor 
            // it already does a key and raycast check so no need to do that here 
            Destroy(gameObject);
        }
    }