7/24/2014

Unity GetComponent() for Interfaces

Потратил вчера кучу времени на решение простой маленькой задачки. Так забавно, а решение оказалось совсем не очевидным для меня, не смотря на всю простоту Unity и C#. В общем решил поведать об этом здесь. Авось кому да пригодится.
Ну что же начнем!

Представим: у нас есть разные GameObject'ы на сцене. Все они наследуются от класса, предположим, ClickableObjects, который в свою очередь от MonoBehaviour и интерфейса IClickable. В интерфейсе IClickable есть следующие сигнатуры методов: void Active(), void Down(). Итак задача: получив ссылку на объект имеющий в компонентах класс унаследованный от ClickableObjects или его дочерних классов, необходимо вызвать метод Active() или Down(). Вся сложность в том, что мы не знаем какой и классов висит на данном объекте, а метод GetComponent<T>() возвращает ошибку если мы обращаемся к интерфейсу или родительскому классу.
Так, а теперь для наглядности напишу примерный код данной ситуации.
       /*
    IClickable.cs
*/
public interface IClickable
{
    void Active();
    void Down();
}
А теперь напишем наш базовый класс ClickableObjects
       
/*
    ClickableObjects.cs
*/
using UnityEngine;

public class ClickableObjects : MonoBehaviour, IClickable
{
    public virtual void Active()
    {
        Debug.Log("Active() - any actions");
    }
    
    public virtual void Down()
    {
        Debug.Log("Down() - any actions");
    }
}
Наследуем от него какой-нибудь гипотетический класс DragWindow
       
/*
    DragWindow.cs
*/
using UnityEngine;

public class DragWindow : ClickableObjects, IDragable
{
    public override void Active()
    {
        base.Active();
        Debug.Log("Active() - any more actions");
    }
    public override void Down()
    {
        base.Down();
        Debug.Log("Down() - any more actions");
        Drag();
    }
    public void Drag() { }
}
И для наглядности еще один какой-нибудь класс:
       
/*
    SimpleButton.cs
*/

using UnityEngine;

public class SimpleButton : ClickableObjects
{
    public override void Down()
    {
        Debug.Log("Down() - new any actions");
    }
}


Классы SimpleButton и DragWindow мы вешаем на наши GameObject'ы, к которым  мы и будем обращаться. Так, а теперь пишем какой-нибудь гипотетический класс из которого мы и произведем обращение.
       
/*
    GuiCamera.cs
*/
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class GuiCamera : MonoBehaviour
{
    private Ray ray;
    private RaycastHit hit;
    private IClickable obj;
    // code code code
    private void FixedUpdate()
    {
        ray = this.camera.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out hit, 20f))
     {
        /*
            Не стоит вникать в сам алгоритм данного класса, 
            поскольку он является гипотетическим.
            Под комментарием главная конструкция, 
            ради которой и затевалась эта заметка.
            Обращаем внимание: пишем -
            GetComponent(typeof(IClickable)), вместо 
            GetComponent <IClickable>() и далее кастуем объект
            к IClickable.  
        */
      obj = (IClickable)hit.transform.GetComponent(typeof(IClickable));
   if(obj != null)
    obj.Active();
        }
    }
}
    
Наверняка, сразу возникнут вопросы почему не SendMessage, или типа: почему не GetComponent <ClickableObject>() и прочие недопонимания. По поводу SendMessage скажу сразу - я считаю, что использовать отправку сообщений на мой взгляд не безопасно и не очень хорошо, к тому же работает это (очень!) медленно. А по поводу попытки вызвать метод получая компонент по имени базового класса: Unity выплюнет ошибку поскольку поиск компонента будет осуществляться по имени базового класса, которого соответственно нет на нашем объекте. Вот и всё, не думаю, что открыл для кого-то Америку, но думаю кому-нибудь да пригодится моя заметочка.

Комментариев нет:

Отправить комментарий