一、序列化字段
在Unity中,序列化字段是一个非常重要的概念,主要用于在Unity编辑器中显示和编辑类的成员变量,或者在运行时将对象的状态保存到文件或网络中。
1.Unity序列化字段的作用
- 在编辑器中显示和编辑字段:默认情况下,只有公共字段( public )才会在Unity编辑器的Inspector窗口中显示。如果希望私有字段( private )或受保护字段( protected )也能在编辑器中显示并被编辑,可以通过序列化字段来实现。
- 保存和加载对象状态:序列化字段还可以用于将对象的状态保存到文件或网络中,并在需要时重新加载这些状态。
2. 使用 [SerializeField] 属性
Unity提供了 [SerializeField] 属性,用于将私有或受保护的字段标记为可序列化。这样,这些字段就可以在Unity编辑器的Inspector窗口中显示和编辑。
cs
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
// 这是一个公共字段,会在Inspector中显示
public int publicField;
// 这是一个私有字段,不会在Inspector中显示
private string privateField;
// 这是一个私有字段,但使用[Serialize]标记后会在Inspector中显示
[SerializeField] private float serializedField;
void Start()
{
Debug.Log("Public Field: " + publicField);
Debug.Log("Serialized Field: " + serializedField);
}
}
//publicField和serializedField都会在Inspector中显示,而privateField不会显示。
//使用[SerializeField]属性可以让开发者更好地封装类的实现细节,同时仍然允许在编辑器中对字段进行编辑。
3. 默认序列化规则
- **公共字段:**默认情况下,所有公共字段( public )都会被序列化,无论是否使用了 [SerializeField] 属性。
- **私有字段:**默认情况下,私有字段( private )不会被序列化,除非使用了 [SerializeField] 属性。
- **静态字段:**静态字段( static )永远不会被序列化,因为它们不属于对象的实例状态。
- **只读字段:**只读字段( readonly )不会被序列化,因为它们在构造函数之后不能被修改。
- **特殊类型字段:**某些特殊类型(如 Dictionary 、 Action 、 Func 等)不会被序列化,因为它们的序列化逻辑比较复杂。
4. 使用 [SerializeField] 的注意事项
- **不要滥用:**虽然 [SerializeField] 可以让私有字段在编辑器中显示,但不要过度使用它。过多的字段暴露在Inspector中可能会导致编辑器界面变得混乱。
- **性能影响:**序列化字段可能会对性能产生一定影响,尤其是在大量对象需要序列化时。因此,在性能敏感的场景中,需要谨慎使用序列化字段。
- 与 [HideInInspector] 的区别: [SerializeField] 用于将私有字段标记为可序列化,使其在Inspector中显示;而 [HideInInspector] 用于隐藏公共字段,使其不在Inspector中显示。
5. 序列化字段的应用场景
- **组件属性编辑:**在开发自定义组件时,使用 [SerializeField] 可以让开发者更好地封装组件的实现细节,同时仍然允许用户在编辑器中配置组件的属性。
- **保存和加载游戏状态:**通过序列化字段,可以将游戏对象的状态保存到文件中,并在需要时重新加载这些状态,实现游戏的存档和读档功能。
- 网络同步:在多人游戏中,可以将需要同步的字段标记为序列化字段,然后通过网络协议将这些字段的状态发送到其他客户端。
6. 扩展:自定义序列化
如果默认的序列化机制不能满足需求,Unity还提供了自定义序列化的方法。例如,可以通过实现 ISerializationCallbackReceiver 接口,在序列化和反序列化时执行自定义逻辑。
cs
using UnityEngine;
public class CustomSerializationExample : MonoBehaviour, ISerializationCallbackReceiver
{
// 这是一个自定义字段,不会直接被序列化
private string customField;
// 用于存储自定义字段的序列化数据
[SerializeField] private string serializedData;
public void OnBeforeSerialize()
{
// 在序列化之前,将自定义字段的值转换为可序列化的格式
serializedData = customField;
}
public void OnAfterDeserialize()
{
// 在反序列化之后,将序列化的数据还原为自定义字段的值
customField = serializedData;
}
}
二、单例模式(Singleton Pattern)
在Unity中,单例模式是一种常见的创建型设计模式,用于确保某个类在程序运行期间只有一个实例存在,并提供一个全局访问点来获取这个实例。
在单例模式中,类的构造函数通常是私有的,防止外部通过 new 来创建对象,类内部维护一个静态实例,通过公共的静态方法提供访问。
单例模式的实现分为饿汉模式与懒汉模式。
1.饿汉式(Eager Initialization)单例模式:
是一种在类加载时就立即创建单例对象的实现方式。这种模式确保了单例对象在程序启动时就存在,并且可以立即使用。
适用场景:
当对象需要在程序启动时立即可用,且创建成本较低时使用,适合常用于必要的核心功能。例子:游戏设置、玩家控制、主菜单管理器。
实现方式:
1)属性实现单例模式(饿汉模式)
通过定义一个静态属性来实现单例模式,并且通过私有设置器( private set )确保实例在第一次创建时被设置,并且之后不能被更改。
cs
public class Player : MonoBehaviour
{
//单例模式,确保了Player类只有一个实例,并且提供了一个全局访问点来获取这个实例
//通过私有设置访问器,确保了Instance属性的值不会被外部代码意外修改
public static Player Instance{ get; private set; }
// ... 其他代码 ...
//Awake在游戏对象被创建时自动调用,通常用于初始化操作。
private void Awake()
{
//检查Instance是否已经为null。如果Instance不为null,说明已经有一个实例被创建了
if (Instance != null)
{
Debug.Log("已经有一个实例被创建了");
}
//将当前对象实例赋值给Instance,使其成为该类的唯一实例
Instance = this;
}
}
//这种实现方式确保了Player类在整个游戏会话中只有一个实例,并且可以通过Player.Instance访问这个实例。
注意点:
**1.立即初始化:**在类被加载时立即创建单例对象,而不是在首次使用时才创建。
**2. 简单实现:**实现简单,不需要同步控制,因为对象在第一次加载时就被创建。
**3. 资源占用:**可能会在不需要时就占用资源,因为对象在类加载时就被创建了。
2)线程安全实现单例模式(饿汉模式)
确保即使在多线程环境中,Player 类也只有一个实例,并且这个实例是在第一次被访问时创建的(懒加载)。
cs
public class Player : MonoBehaviour
{
// 私有静态字段,用于存储单例实例
private static Player instance;
// 公共静态属性,提供对单例的全局访问点
public static Player Instance
{
// 属性的get访问器,用于获取单例实例
get
{
// 如果实例为null,则进入锁定代码块
if (instance == null)
{
// 使用锁确保线程安全,防止多线程同时创建实例
lock (typeof(Player))
{
// 再次检查实例是否为null,防止多线程创建多个实例
if (instance == null)
{
// 创建Player类的新实例
instance = new Player();
}
}
}
// 返回单例实例
return instance;
}
}
// Unity生命周期方法,在对象被创建时调用
private void Awake()
{
// 如果已经存在一个实例,并且当前实例不是那个实例,则销毁当前实例
if (Instance != this)
{
Destroy(gameObject);
}
// 如果当前实例是单例,则防止它在加载新场景时被销毁
else
{
DontDestroyOnLoad(gameObject);
}
}
}
2.懒汉式(Lazy Initialization)单例模式:
是一种常见的单例实现方式,它确保单例对象在首次被访问时才创建。这种方式可以节省资源,因为它避免了在程序启动时立即创建对象,而是在真正需要时才创建。
适用场景:
当对象可能不被使用,或创建成本较高时使用,适合于可选或资源密集型功能。例子:高级图形选项、调试工具、可选模块。
实现方式:
1)属性实现单例模式(懒汉模式)
确保单例对象在首次被访问时才创建,从而节省了资源。同时,由于属性的访问器是公共的,所以可以在任何地方通过 Singleton.Instance 访问单例。
cs
using UnityEngine;
public class Player : MonoBehaviour
{
// 私有静态字段,用于存储单例实例
private static Player instance;
// 公共静态属性,提供全局访问点来获取单例实例
public static Player Instance
{
get
{
// 如果实例为null,则创建实例
if (instance == null)
{
instance = new GameObject("Player").AddComponent<Player>();
}
return instance;
}
}
// 私有构造函数,防止外部实例化
private Player() { }
// Unity生命周期方法,在游戏对象被创建时自动调用
private void Awake()
{
// 检查是否已经存在实例
if (instance != null && instance != this)
{
// 如果存在另一个实例,则销毁当前实例
Destroy(gameObject);
}
else
{
// 标记为DontDestroyOnLoad,防止在加载新场景时销毁
DontDestroyOnLoad(gameObject);
instance = this;
}
}
}
注意点:
**1. 延迟初始化:**单例对象在首次被访问时创建,而不是在类加载时立即创建。
**2. 访问延迟:**第一次访问单例属性时可能会有轻微延迟,因为需要创建对象。
**3. 资源节省:**只有在实际需要时才创建对象,节省资源。
2)线程安全实现单例模式(懒汉模式)
为了提高线程安全性,可以使用双重检查锁定(Double-Checked Locking)模式
cs
using UnityEngine;
public class Player : MonoBehaviour
{
// 私有静态字段,用于存储单例实例
private static Player instance;
// 用于同步的对象,确保线程安全
private static readonly object lockObject = new object();
// 公共静态属性,提供全局访问点来获取单例实例
public static Player Instance
{
get
{
// 如果实例为null,则进入锁定代码块
if (instance == null)
{
lock (lockObject)
{
// 再次检查实例是否为null,防止多线程创建多个实例
if (instance == null)
{
// 创建Player类的新实例
instance = new GameObject("Player").AddComponent<Player>();
}
}
}
// 返回单例实例
return instance;
}
}
// 私有构造函数,防止外部实例化
private Player() { }
// Unity生命周期方法,在游戏对象被创建时自动调用
private void Awake()
{
// 检查是否已经存在实例
if (instance != null && instance != this)
{
// 如果存在另一个实例,则销毁当前实例
Destroy(gameObject);
}
else
{
// 如果不存在实例,则调用DontDestroyOnLoad,防止在加载新场景时销毁
DontDestroyOnLoad(gameObject);
// 将当前实例赋值给单例
instance = this;
}
}
}