1. 程式人生 > >在Unity中定義統一的物件搜尋介面

在Unity中定義統一的物件搜尋介面

我們經常要在Unity中以各種方式搜尋物件。比如按名字搜尋、按tag、layer或者是查詢名字為xxx開頭的物件。

本文是介紹以一種統一的介面來搜尋物件。

1、定義統一的搜尋介面

    /// <summary>
    /// 遊戲物件搜尋介面
    /// </summary>
    public interface IGameObjectFinder
    {
        /// <summary>
        /// 搜尋
        /// </summary>
        /// <param name="root">搜尋的開始位置/根節點</param>
        /// <param name="findResult">搜尋存放的結果</param>
        void Find(Transform root, List<Transform> findResult);
    }

2、定義一個使用上面搜尋介面的方法

public class Finder{

    /// <summary>
    /// 查詢指定根節點下符合指定的查詢器的Transform並保持到findResult中
    /// </summary>
    /// <param name="root"></param>
    /// <param name="findResult"></param>
    /// <param name="finder"></param>
    public static void Find(Transform root, List<Transform> findResult, IGameObjectFinder finder)
    {
        if (root == null)
        {
            throw new Exception("root can not be null, it defines the starting point of the find path");
        }

        if (findResult == null)
        {
            throw new Exception("findResult can not be null, it used to collect the find result");
        }

        if (finder == null)
        {
            throw new Exception("finder can not be null, it defines how to find transform");
        }
        finder.Find(root, findResult);
    }
}

可以看到,2步驟只是簡單呼叫1的介面進行搜尋。但是1只是介面,有啥用?接著看。

3、實現一個查詢某個元件的介面

    /// <summary>
    /// 根據元件搜尋
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GameObjectFinderByComponent<T> : IGameObjectFinder where T : Component
    {
        public void Find(Transform root, List<Transform> findResult)
        {
            foreach (var componentsInChild in root.GetComponentsInChildren<T>())
            {
                findResult.Add(componentsInChild.transform);
            }
        }
    }

可以看到只要實現IGameObjectFinder就可以了。那麼如果這時候想呼叫,應該怎麼呼叫呢?比如想查詢transform下面的剛體元件,然後儲存到result裡面。只要:Finder.Find(transform, result, new GameObjectFinderByComponent<Rigidbody>());什麼?看起來好像有點小題大作,直接用GetComponentsInChildren不就好嗎?先不急。我們繼續看看其它查詢需求。比如我想查詢名字為xxx的物件。

4、實現一個迭代查詢

首先,要查詢指定節點下的某個名字的所有節點(不管深度多少),這個需要遍歷。那麼,我們可以先抽象出一個遍歷的介面。為了保證搜尋的統一,那麼我們還是從IGameObjectFinder中繼承。
    /// <summary>
    /// 迭代遍歷搜尋
    /// </summary>
    public class GameObjectFinderByIteration : IGameObjectFinder
    {
        private IGameObjectFinderForIteration finderForIteration;
        public GameObjectFinderByIteration(IGameObjectFinderForIteration finderForIteration)
        {
            this.finderForIteration = finderForIteration;
        }

        public void Find(Transform root, List<Transform> findResult)
        {
            for (int i = 0, childCount = root.childCount; i < childCount; i++)
            {
                Transform t = root.GetChild(i);
                if (finderForIteration.isVaild(t))
                {
                    findResult.Add(t);
                }
                Find(t, findResult);
            }
        }
    }
這個程式碼的意思就是:我先把開始節點下面的每個子節點通過另一個介面IGameObjectFinderForIteration來判斷是否符合要求,是的話則加入結果列表。然後繼續查詢這個子結點下的其他子節點。(該搜尋是不包括第一個開始節點的)
IGameObjectFinderForIteration的介面也很簡單:
    /// <summary>
    /// 迭代搜尋判斷
    /// </summary>
    public interface IGameObjectFinderForIteration
    {
        /// <summary>
        /// 指定節點是否合法
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        bool isVaild(Transform node);
    }
好的,這樣就意味著我們要想查詢某個根節點下的所有子節點(包括直接和間接的),只要實現一個IGameObjectFinderForIteration。那麼OK。看看怎麼按名字搜尋。
    /// <summary>
    /// 迭代遍歷按名字搜尋
    /// </summary>
    public class FinderForIterationByName : IGameObjectFinderForIteration
    {
        protected readonly string NAME;
        public FinderForIterationByName(string name)
        {
            NAME = name;
        }

        public bool isVaild(Transform getChild)
        {
            return getChild.gameObject.name.Equals(NAME);
        }
    }

使用也很簡單,加入想找transform節點下,名字為“abc”的物件,並儲存在result列表裡面。只要這樣:Finder.Find(transform, result, new GameObjectFinderByIteration(new FinderForIterationByName("abc")));

5、有什麼用?

很多人可能還不明白有什麼用,通過上面的抽象。你可以發現我們把搜尋全部統一為:Finder.Find(transform, result, objectFinderImpl);1、程式碼是統一的,不管你的搜尋條件有多複雜。便於程式碼管理、維護、拓展。2、可配置,你可以簡單通過數字關聯不同的搜尋器的實現,然後通過附帶的引數傳遞給具體的搜尋器,來實現通過配置文字採用不同的搜尋器進行搜尋。這個在你做新手引導的時候很好用,因為,策劃可能有時候要把一個xxx手指放到什麼圖示、什麼按鈕那裡。那你就可以把這些查詢交給策劃來配置了~3、如果還沒明白有什麼用,那就無視吧。總有一天你還會回來的。