在Unity中定义统一的对象搜索接口

本文介绍了在Unity中如何使用统一的接口来搜索对象,包括按名字、tag、layer搜索,以及查找特定组件、迭代查找等方法,强调了这种方法在代码管理、配置和拓展方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们经常要在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、如果还没明白有什么用,那就无视吧。总有一天你还会回来的。










<think>好的,我现在要解决用户的问题:Unity脚本中需要反序列化的对象一般定义在哪里。首先,我需要确认用户对反序列化的理解程度。可能用户在使用Unity进行数据存储或网络通信时,遇到了需要将JSON或XML数据转换为对象的情况,所以需要知道如何正确设置这些对象的结构。 接下来,我得回忆Unity中常用的反序列化方法,比如JsonUtility和Newtonsoft.Json(Json.NET)。不同的库对类的结构要求不同,比如JsonUtility可能需要使用[Serializable]属性,而Json.NET可能不需要,但需要公共字段或属性。用户可能混淆了这些差异,导致反序列化失败。 然后,我需要考虑用户可能的错误场景。比如,用户可能没有正确标记类或结构体,或者没有正确处理私有字段,导致数据无法正确反序列化。这时候需要提醒他们正确的属性和访问修饰符的使用。 另外,用户可能没有意识到Unity的序列化系统的一些限制,比如不支持字典,或者需要特定的构造函数。这些都需要在回答中提到,帮助用户避免常见陷阱。 还要考虑用户可能的进阶需求,比如处理复杂结构或继承的情况,这时候可能需要使用第三方库如Json.NET,并给出相应的示例代码。同时,提醒他们在Unity中正确安装和引用这些库,特别是之前提到的命名空间问题。 最后,总结反序列化对象定义位置,强调根据使用的工具选择合适的类结构和属性,确保用户能够根据自身情况选择最佳实践。需要保持回答结构清晰,分点说明,并附上示例代码,方便用户理解和应用。</think>在 Unity 脚本中,需要反序列化的对象通常需要满足以下结构要求: ```csharp // 基础定义规则 [System.Serializable] // 必须添加序列化标记 public class PlayerData { // 需要反序列化的字段必须为 public 或带有 [SerializeField] public string playerName; public int level; public Vector3 position; // Unity 原生类型可直接序列化 // 复杂类型需要自行实现序列化逻辑 public List<Item> inventory = new List<Item>(); } // 嵌套类型也需要序列化标记 [System.Serializable] public class Item { public string id; public int quantity; } /*━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 最佳实践指南 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━*/ 1️⃣ **定义位置建议** - 推荐在独立脚本文件中定义(如 PlayerData.cs) - 避免定义在 MonoBehaviour 派生类内部 - 优先使用 class 而非 struct(部分序列化工具对 struct 支持有限) 2️⃣ **序列化工具差异** ```csharp // ▶ Unity 内置 JsonUtility [Serializable] // 必须标记 public class SaveData { public float health; private int _secretCode; // 私有字段不会被序列化 } // ▶ Newtonsoft.Json(需通过 Package Manager 安装) public class NetworkPacket { // 无需 [Serializable] [JsonProperty("name")] // 可自定义 JSON 键名 public string PlayerName { get; set; } // 支持属性 } ``` 3️⃣ **常见陷阱规避** - 禁用构造函数初始化逻辑(反序列化时不会调用构造函数) - 避免循环引用(如 ClassA 包含 ClassB,ClassB 又包含 ClassA) - 使用 `[NonSerialized]` 属性标记不需要序列化的字段 4️⃣ **进阶用法示例** ```csharp // 多态类型处理(需配置 TypeNameHandling) [System.Serializable] public class ShapeContainer { [JsonConverter(typeof(JsonSubtypes))] // Json.NET 特性 public IShape shape; // 接口/抽象类的反序列化 } // Unity 原生 JSON 扩展方案 [Serializable] public class GameSettings { public Resolution screenResolution; [Serializable] // 嵌套自定义结构体 public struct Resolution { public int width; public int height; } } ``` **实际应用场景**: 这些序列化对象常用于: - 存档系统(PlayerPrefs 或文件存储) - 网络通信数据包解析 - ScriptableObject 数据持久化 - Addressables/AssetBundle 资源配置 建议根据具体需求选择: ✅ 简单数据 → Unity 内置 `JsonUtility` ✅ 复杂需求 → 安装 `Newtonsoft.Json`(需处理命名空间冲突问题)
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值