Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】

Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】

目录

Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】

一、简单介绍

二、单例模式 (Singleton Pattern)

1、什么时候使用单例模式

2、单例模式的好处

3、使用单例模式的注意事项

三、在 Unity 中使用 单例模式

1、普通型(new)泛型单例模式

2、继承 MonoBehaviour 的泛型单例模式

3、单例使用方式

4、实现分析

四、原型模式 (Prototype Pattern)

1、原型模式的原理

2、什么时候使用原型模式

3、使用原型模式的好处

4、使用原型模式的注意事项

五、在 Unity 中使用 原型模式

1、 定义基类 ShapePrototype

2、创建具体的形状类

3、创建 ShapeSpawner 负责克隆对象

4、设置场景中的原型对象

5、运行效果

六、建造者模式 (Builder Pattern)

1、什么时候使用建造者模式

2、使用建造者模式的好处

3、建造者模式的注意事项

七、在 Unity 中使用 建造者模式

1、定义房屋类 House

2、定义建造者接口 IHouseBuilder

3、 具体建造者类:木屋建造者 WoodenHouseBuilder 和砖屋建造者 BrickHouseBuilder

4、定义指挥者 Director

5、 在 Unity 场景中使用建造者模式

6、运行效果


一、简单介绍

设计模式 是指在软件开发中为解决常见问题而总结出的一套 可复用的解决方案。这些模式是经过长期实践证明有效的 编程经验总结,并可以在不同的项目中复用。设计模式并不是代码片段,而是对常见问题的 抽象解决方案,它提供了代码结构和模块间交互的一种设计思路,帮助开发者解决特定的设计问题。

设计模式的特点:

  1. 通用性:设计模式针对的是软件开发中常见的设计问题,适用于各种软件工程项目。
  2. 可复用性:设计模式可以在不同项目和环境下被重复使用,提高代码的可维护性和扩展性。
  3. 可扩展性:设计模式有助于让代码结构更加灵活,易于扩展和修改。
  4. 模块化:通过设计模式,可以减少代码的耦合性,增强模块间的独立性。
  5. 提高沟通效率:设计模式为开发者提供了一种通用的设计语言,使得团队成员能够快速理解并讨论设计方案。

二、单例模式 (Singleton Pattern)

单例模式 (Singleton Pattern) 是一种创建型设计模式,保证一个类只有一个实例,并提供一个全局访问点来获取该实例。它通过控制类的实例化过程,确保系统中只有一个该类的对象存在。

在单例模式中,类的构造函数通常是私有的,防止外部通过 new 来创建对象,类内部维护一个静态实例,通过公共的静态方法提供访问。

单例模式的特点

  1. 唯一实例:单例模式确保一个类只有一个实例,无论系统中的组件如何调用它。
  2. 全局访问点:单例模式为客户端提供一个全局访问点(通常是静态方法),通过这个访问点,所有客户端都能获得相同的实例。
  3. 延迟初始化:可以在首次访问时创建该实例,节省系统资源(可选)。

1、什么时候使用单例模式

  1. 全局唯一的资源管理:如果一个类需要控制对某种共享资源(如数据库连接池、文件系统、日志器、配置管理器等)的访问,并且不允许有多个实例同时存在。

  2. 配置或状态共享:当系统中某个类需要持有全局配置或共享状态时,单例模式可以使得这些状态在不同模块中保持一致。

  3. 资源开销较大的对象:如果一个类的实例化代价较高(如外部资源初始化、复杂运算等),而且只需要一个实例时,单例模式可以避免多次重复的创建操作。

  4. 需要严格控制实例数量:在某些业务逻辑中,严格要求一个类只能有一个实例,否则会引发逻辑混乱或资源竞争(如操作系统中的任务管理器)。

2、单例模式的好处

  1. 控制实例数量:确保一个类只生成一个实例,避免不必要的内存占用,尤其对于管理全局状态或资源的对象(如数据库连接、日志系统、配置管理等),控制实例数量可以提高系统的稳定性。

  2. 全局访问:单例模式提供了一个全局访问点,这使得某些系统级别的服务(如日志系统、资源管理器等)能够方便地被全局访问。

  3. 节省资源:避免重复创建相同对象,减少了资源的消耗,尤其是对于耗费较大的对象(如文件系统、数据库连接等)。

  4. 线程安全:通过实现线程安全的单例,可以确保在多线程环境下,多个线程对该对象的操作是安全的。

3、使用单例模式的注意事项

  1. 全局状态的复杂性:虽然单例模式提供全局访问,但这也意味着它引入了全局状态,可能导致系统的状态管理变得复杂,特别是在大型系统中。

  2. 并发和线程安全:在多线程环境下,需要确保单例的实现是线程安全的,否则可能会出现多个线程同时创建实例的情况,导致数据不一致。

  3. 难以扩展:由于单例模式限制了对象的创建数量,在某些情况下,可能不利于系统的扩展或测试。比如,在单元测试中,单例模式可能难以进行模拟。

  4. 滥用风险:单例模式提供全局访问点,容易被滥用为全局变量,违背面向对象设计中高内聚、低耦合的原则。因此,使用时应考虑是否真的有必要将类设计为单例。

总之,单例模式 主要用于控制对象的实例化,确保系统中只有一个类的实例,并通过全局访问点来控制对象的使用。它适用于需要全局共享资源、统一管理的场景,如日志系统、数据库连接池等。尽管单例模式在某些场景下有助于提升系统的稳定性和效率,但也应谨慎使用,以避免全局状态管理复杂化或滥用全局访问带来的耦合问题。

三、在 Unity 中使用 单例模式

在 Unity 中,实现一个线程安全的普通类MonoBehaviour 类的泛型单例时,必须考虑以下几点:

  1. 普通类单例:不能被 new,并且在多线程环境下线程安全。
  2. MonoBehaviour 单例:由于 MonoBehaviour 的实例是通过 Unity 的 AddComponent 创建的,不能直接通过 new,也需要保证在多线程环境下的安全性。

参考类图如下:

1、普通型(new)泛型单例模式

方法一:
public abstract class Singleton<T> where T : class, new()
{
    private static T instance = null;
 
    // 多线程安全机制
    private static readonly object locker = new object();
 
    public static T Instance
    {
        get
        {
            lock (locker)
            {
                if (instance == null)
                    instance = new T();
                return instance;
            }
        }
    }
}
 
 
 
方法二:
 
using System;
using System.Reflection;
 
/// <summary>
/// 单例
/// 继承需要一个非公有的无参构造函数
/// </summary>
/// <typeparam name="T">类名的泛型</typeparam>
public abstract class Singleton<T> where T : class
{
    private static T instance = null;
 
    // 多线程安全机制
    private static readonly object locker = new object();
 
    public static T Instance
    {
        get
        {
            // 线程锁
            lock (locker)
            {
                if (null == instance)
                {
                    // 反射获取实例
                    var octors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic) ;
 
                    // 获取无参数的非公共构造函数
                    var octor = Array.Find(octors, c => c.GetParameters().Length == 0);
 
                    // 没有则提示没有私有的构造函数
                    if (null == octor)
                    {
                        throw new Exception("No NonPublic constructor without 0 parameter");
                    }
 
                    // 实例化
                    instance = octor.Invoke(null) as T;
                }
 
                return instance;
 
            }
        }
    }
 
    /// <summary>
    /// 构造函数
    /// 避免外界new
    /// </summary>
    protected Singleton() { }
}

2、继承 MonoBehaviour 的泛型单例模式

using UnityEngine;
 
public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance = null;
 
    private static readonly object locker = new object();
 
    private static bool bAppQuitting;
 
    public static T Instance
    {
        get
        {
            if (bAppQuitting)
            {
                instance = null;
                return instance;
            }
 
            lock (locker)
            {
                if (instance == null)
                {
                    // 保证场景中只有一个 单例
                    T[] managers = Object.FindObjectsOfType(typeof(T)) as T[];
                    if (managers.Length != 0)
                    {
                        if (managers.Length == 1)
                        {
                            instance = managers[0];
                            instance.gameObject.name = typeof(T).Name;
                            return instance;
                        }
                        else
                        {
                            Debug.LogError("Class " + typeof(T).Name + " exists multiple times in violation of singleton pattern. Destroying all copies");
                            foreach (T manager in managers)
                            {
                                Destroy(manager.gameObject);
                            }
                        }
                    }
 
 
                    var singleton = new GameObject();
                    instance = singleton.AddComponent<T>();
                    singleton.name = "(singleton)" + typeof(T);
                    singleton.hideFlags = HideFlags.None;
                    DontDestroyOnLoad(singleton);
 
                }
                instance.hideFlags = HideFlags.None;
                return instance;
            }
        }
    }
 
    protected virtual void Awake()
    {
        bAppQuitting = false;
    }
 
    protected virtual void OnDestroy()
    {
        bAppQuitting = true;
    }
}

3、单例使用方式

继承泛型单例模式即可

 
public class Test1 : MonoSingleton<Test1>{}
 
// 方法一
public class Test2 : Singleton<Test2>{}
// 方法二
public class Test2 : Singleton<Test2>{{private Test2(){}}

4、实现分析

  1. 普通类单例

    • 通过 lock 实现线程安全,避免了多线程环境下重复创建实例的问题。
    • 将构造函数设为 protected,确保外部不能通过 new 关键字实例化该类。
    • 在 Unity 中,适合管理游戏状态、配置数据等不需要与 Unity 引擎生命周期强绑定的对象。
  2. MonoBehaviour 类单例

    • 使用了 lock 机制确保多线程环境中的安全性。
    • 使用 FindObjectOfType<T> 检查场景中是否已经有该类型的实例,如果没有则创建新的对象并添加该组件。
    • 通过 DontDestroyOnLoad 确保实例在场景切换时不被销毁,适合音频管理器、游戏控制器等需要跨场景保持的对象。

这两种单例的模式在 Unity 中广泛适用,可以有效地管理全局对象和跨场景对象。

四、原型模式 (Prototype Pattern)

原型模式 (Prototype Pattern) 是一种创建型设计模式,它允许通过复制现有的对象来创建新对象,而不是通过直接实例化类。这意味着你可以通过克隆原型对象来生成新的实例,而不必依赖类的构造函数。该模式的核心思想是,通过创建一个对象的副本(即原型)来避免昂贵的初始化操作。

在 C# 中,通常通过实现 ICloneable 接口或者自定义的克隆方法来实现原型模式。

1、原型模式的原理

原型模式的工作原理是:

  1. 定义一个原型接口或基类,用来提供克隆的方法(如 Clone)。
  2. 具体的类实现该接口,提供自己的克隆方法,该方法返回当前对象的深拷贝或浅拷贝。
  3. 客户端可以通过克隆原型对象来创建新对象,而无需知道如何构造对象。

拷贝的类型可以分为:

  • 浅拷贝 (Shallow Copy):只复制对象的基本数据类型,对于引用类型的字段,只复制引用地址。
  • 深拷贝 (Deep Copy):不仅复制对象的所有值,还递归地复制所有引用对象,创建独立的副本。

2、什么时候使用原型模式

  1. 需要频繁创建相似对象时:如果系统需要创建大量相似或结构复杂的对象时,通过克隆原型对象来生成新对象可以提高性能。

  2. 对象的创建代价高昂时:如果对象的初始化非常复杂,包含很多属性设置或涉及昂贵的资源操作(如网络连接、文件系统操作),使用原型模式避免了重复创建过程。

  3. 避免对象过多的子类化:当不希望为了创建新对象而引入更多的子类或扩展类时,原型模式可以避免通过继承创建不同类型对象的繁琐过程。

  4. 需要保存对象的历史状态或备份时:如果需要将某个对象的状态备份或在某个时刻进行复制(如撤销操作、快照功能),可以通过原型模式来克隆对象。

3、使用原型模式的好处

  1. 提高对象创建效率:对于某些对象的创建过程非常复杂或耗时时,通过克隆现有对象(即原型)可以避免重复的复杂初始化,节省时间和资源。

  2. 避免子类膨胀:通过原型模式,程序不需要通过继承或工厂模式来生成对象,这可以减少子类的数量,避免继承层次的膨胀。

  3. 动态生成对象:原型模式允许在运行时动态生成对象,不需要事先知道具体类的名称。

  4. 减少对象依赖:克隆对象是独立于具体类的,原型模式不需要直接依赖构造函数,这降低了对象之间的耦合度。

  5. 灵活性高:通过克隆,可以对现有的对象进行修改后创建新的对象,特别适合那些需要频繁修改对象属性的场景。

4、使用原型模式的注意事项

  1. 克隆的深浅拷贝:对于引用类型字段,使用浅拷贝时需要特别小心,浅拷贝只复制引用,而不复制对象本身,可能导致两个对象共用同一份数据。若需要完全独立的对象,必须使用深拷贝。

  2. 复杂对象的克隆:当对象包含复杂的嵌套结构或其他依赖时,确保实现合适的深拷贝逻辑,否则可能会出现数据共享或数据覆盖问题。

  3. 性能考虑:尽管原型模式避免了对象的重复构造,但深拷贝可能引入较大的性能开销,特别是在对象嵌套复杂时。因此,在性能敏感的场景下要慎重考虑深拷贝的实现。

  4. 原型的可变性:当通过原型模式创建对象时,如果不慎修改了原型本身的状态,所有基于该原型创建的对象也可能受到影响。因此在设计时,需要确保原型对象的状态是安全的。

总之,原型模式 通过复制现有对象来生成新对象,避免了类构造的开销,特别适用于对象创建代价高昂或需要动态创建对象的场景。它提供了灵活的对象创建方式,减少了类的复杂性和耦合度。使用时需要根据具体需求选择浅拷贝或深拷贝,并确保对象的可复制性和独立性。

五、在 Unity 中使用 原型模式

在 Unity 中,原型模式可以应用于场景中需要频繁生成的对象,比如 3D 模型(如立方体、球体等)。通过原型模式,我们可以避免每次都从头实例化对象,而是通过复制现有的对象来创建新的实例。

我们将创建一个简单的原型模式应用,用于克隆 Unity 中的 3D 对象(立方体、球体等),并根据用户输入生成多个克隆对象。

步骤:

  1. 创建一个基类 ShapePrototype,它提供克隆接口。
  2. 创建不同的形状类,如立方体和球体,继承自 ShapePrototype
  3. 使用原型模式通过克隆现有对象来创建新对象,而不是直接创建新对象。

参考类图如下:

1、 定义基类 ShapePrototype

using UnityEngine;

// 定义一个抽象的原型基类
public abstract class ShapePrototype : MonoBehaviour
{
    // 定义一个抽象的克隆方法
    public abstract ShapePrototype Clone();

    // 通用的显示形状信息的方法
    public abstract void DisplayInfo();
}

2、创建具体的形状类

2.1 立方体类

using UnityEngine;

public class Cube : ShapePrototype
{
    // 重写克隆方法
    public override ShapePrototype Clone()
    {
        // 实现浅拷贝,复制当前对象的属性
        GameObject clone = Instantiate(this.gameObject);
        return clone.GetComponent<ShapePrototype>();
    }

    public override void DisplayInfo()
    {
        Debug.Log("This is a Cube.");
    }
}

2.2 球体类

using UnityEngine;

public class Sphere : ShapePrototype
{
    // 重写克隆方法
    public override ShapePrototype Clone()
    {
        // 实现浅拷贝,复制当前对象的属性
        GameObject clone = Instantiate(this.gameObject);
        return clone.GetComponent<ShapePrototype>();
    }

    public override void DisplayInfo()
    {
        Debug.Log("This is a Sphere.");
    }
}

3、创建 ShapeSpawner 负责克隆对象

using UnityEngine;

public class ShapeSpawner : MonoBehaviour
{
    public ShapePrototype cubePrototype;   // 立方体原型
    public ShapePrototype spherePrototype; // 球体原型

    private void Update()
    {
        // 按下 C 键克隆立方体
        if (Input.GetKeyDown(KeyCode.C))
        {
            ShapePrototype cubeClone = cubePrototype.Clone();
            cubeClone.transform.position = new Vector3(Random.Range(-5, 5), 1, Random.Range(-5, 5));
            cubeClone.DisplayInfo();
        }

        // 按下 S 键克隆球体
        if (Input.GetKeyDown(KeyCode.S))
        {
            ShapePrototype sphereClone = spherePrototype.Clone();
            sphereClone.transform.position = new Vector3(Random.Range(-5, 5), 1, Random.Range(-5, 5));
            sphereClone.DisplayInfo();
        }
    }
}

4、设置场景中的原型对象

  1. 创建一个 Unity 场景,并添加一个空物体 ShapeSpawner,将上面的 ShapeSpawner 脚本挂载到该物体上。
  2. 在场景中创建一个立方体和一个球体,并将它们的 CubeSphere 脚本分别挂载到立方体和球体上。
  3. ShapeSpawner 脚本的 cubePrototypespherePrototype 变量中,分别拖拽场景中的立方体和球体作为原型对象。

5、运行效果

  • 按下 C 键:克隆一个新的立方体,随机放置在场景中,并在控制台输出 "This is a Cube."
  • 按下 S 键:克隆一个新的球体,随机放置在场景中,并在控制台输出 "This is a Sphere."

每次克隆都是基于场景中的原型对象,这样可以避免重新生成对象的开销,并且保证克隆对象与原型对象的所有属性相同。

节省资源,通过克隆现有对象,而不是每次都从头实例化对象,可以提高性能,尤其是在场景中需要大量生成对象时;灵活性,可以动态调整原型对象的属性,并通过克隆生成新的实例,而不需要修改对象的创建逻辑;方便扩展,如果需要新的形状,可以轻松扩展原型类,无需修改现有代码。

这种模式在 Unity 的游戏开发中非常适合用于生成大量相似对象(如敌人、物品、特效等)。

六、建造者模式 (Builder Pattern)

建造者模式 (Builder Pattern) 是一种创建型设计模式,它将复杂对象的构建过程与对象的表示分离,使得同样的构建过程可以创建不同的对象。建造者模式特别适用于那些构建步骤复杂且具有多种配置的对象。

在建造者模式中,通常有以下几个关键部分:

  1. Builder (建造者):定义了创建产品对象的步骤,通常是一个抽象类或接口。
  2. Concrete Builder (具体建造者):实现了 Builder 接口的类,负责具体的构建细节,创建具体的产品对象。
  3. Director (指挥者):负责控制构建过程,按照一定的顺序调用 Builder 的方法来构建对象。
  4. Product (产品对象):最终构建的复杂对象。

1、什么时候使用建造者模式

  1. 构建复杂对象时:如果一个对象的创建步骤非常复杂,需要按照一定的顺序构建多个部件,建造者模式可以帮助你将这些步骤组织清晰,避免直接调用复杂的构造函数。

  2. 需要生成多种类型对象时:当需要通过相同的构建过程生成不同类型或配置的对象时,建造者模式可以帮助你在同一个框架下灵活构建不同的产品。

  3. 构造函数参数过多时:如果一个类的构造函数有很多可选参数或者参数组合非常复杂,使用建造者模式可以避免构造函数的复杂性,通过链式调用来简化参数设置。

  4. 对象的创建流程固定但表示方式多样时:在一些场景下,产品的创建过程是相同的,但是生成的产品可能有不同的表现方式或配置。此时,建造者模式可以将构建流程抽象化,从而生成不同的产品。

2、使用建造者模式的好处

  1. 简化对象构建过程:建造者模式将复杂对象的构建步骤集中在一个地方,减少了创建对象的复杂度,尤其是当对象拥有多个可选参数或需要按特定步骤构建时。

  2. 灵活构建复杂对象:通过建造者模式,可以将对象的构建过程拆分成多个步骤,允许构建者在不同的构建过程中创建不同类型的对象或者配置不同的参数。

  3. 代码清晰、易于维护:建造者模式使代码更清晰,因为对象的构建逻辑被独立封装在 Builder 中,而不是混杂在产品对象内部。这使得代码易于维护和扩展。

  4. 支持不同的产品变化:同样的构建过程可以生成不同的产品,通过使用不同的建造者,能够灵活应对产品的变化需求。

  5. 简化对象的可变参数管理:如果对象有很多可选参数,使用建造者模式可以避免复杂的构造函数,提供链式调用来设置参数,使得代码更加简洁。

3、建造者模式的注意事项

  1. 多个建造者的协作:如果你需要构建多个复杂对象,可以为每种对象提供不同的具体建造者,并通过同一个指挥者来协调构建过程。

  2. 对象的不可变性:建造者模式可以配合不可变对象使用,确保对象在构建完成后无法被修改。

  3. 建造顺序的灵活性:确保建造者的接口设计支持灵活的建造顺序,允许客户端根据需要选择不同的建造步骤。

总之,建造者模式通过分离对象的构建过程和表示方式,使得构建复杂对象的流程更加清晰和灵活。它非常适合用于那些具有多个可选参数、构建过程复杂或者有不同表示方式的对象。建造者模式的使用可以提高代码的可读性和可维护性,同时减少对象构建中的复杂性。

七、在 Unity 中使用 建造者模式

在 Unity 中,建造者模式可以应用于构建复杂的 3D 场景或对象。假设我们要在场景中建造一个由多个部分组成的复杂建筑(如房子),包括地基、墙壁、屋顶等,这正是使用建造者模式的典型场景。

我们将设计一个简单的场景,展示如何通过建造者模式生成不同配置的房屋。房屋由三部分组成:地基、墙壁和屋顶。通过建造者模式,我们可以灵活地创建不同样式的房屋。

步骤:

  1. 定义一个 House 类,包含地基、墙壁和屋顶。
  2. 定义一个 IHouseBuilder 接口,声明创建房屋各部分的方法。
  3. 创建具体的建造者类,如 WoodenHouseBuilderBrickHouseBuilder,实现不同材料的房屋建造。
  4. 创建一个 Director 类,用于控制房屋的建造流程。
  5. 在 Unity 场景中通过建造者模式生成不同风格的房屋。

参考类图如下:

1、定义房屋类 House

using UnityEngine;

// 房屋类,包含房屋的各个部分
public class House
{
    public GameObject Foundation { get; set; }
    public GameObject Walls { get; set; }
    public GameObject Roof { get; set; }

    public void ShowHouseInfo()
    {
        Debug.Log("House has been built with Foundation, Walls, and Roof.");
    }
}

2、定义建造者接口 IHouseBuilder

public interface IHouseBuilder
{
    void BuildFoundation();
    void BuildWalls();
    void BuildRoof();
    House GetResult();
}

3、 具体建造者类:木屋建造者 WoodenHouseBuilder 和砖屋建造者 BrickHouseBuilder

3.1 木屋建造者

using UnityEngine;

public class WoodenHouseBuilder : IHouseBuilder
{
    private House house = new House();

    public void BuildFoundation()
    {
        house.Foundation = GameObject.CreatePrimitive(PrimitiveType.Cube);
        house.Foundation.transform.localScale = new Vector3(5, 0.5f, 5);
        house.Foundation.GetComponent<Renderer>().material.color = Color.gray;
    }

    public void BuildWalls()
    {
        house.Walls = GameObject.CreatePrimitive(PrimitiveType.Cube);
        house.Walls.transform.localScale = new Vector3(5, 2.5f, 5);
        house.Walls.transform.position = new Vector3(0, 1.5f, 0);
        house.Walls.GetComponent<Renderer>().material.color = Color.yellow; // 木墙
    }

    public void BuildRoof()
    {
        house.Roof = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
        house.Roof.transform.localScale = new Vector3(5, 1, 5);
        house.Roof.transform.position = new Vector3(0, 3f, 0);
        house.Roof.GetComponent<Renderer>().material.color = Color.red;
    }

    public House GetResult()
    {
        return house;
    }
}

3.2 砖屋建造者

using UnityEngine;

public class BrickHouseBuilder : IHouseBuilder
{
    private House house = new House();

    public void BuildFoundation()
    {
        house.Foundation = GameObject.CreatePrimitive(PrimitiveType.Cube);
        house.Foundation.transform.localScale = new Vector3(5, 0.5f, 5);
        house.Foundation.GetComponent<Renderer>().material.color = Color.gray;
    }

    public void BuildWalls()
    {
        house.Walls = GameObject.CreatePrimitive(PrimitiveType.Cube);
        house.Walls.transform.localScale = new Vector3(5, 2.5f, 5);
        house.Walls.transform.position = new Vector3(0, 1.5f, 0);
        house.Walls.GetComponent<Renderer>().material.color = Color.red; // 砖墙
    }

    public void BuildRoof()
    {
        house.Roof = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
        house.Roof.transform.localScale = new Vector3(5, 1, 5);
        house.Roof.transform.position = new Vector3(0, 3f, 0);
        house.Roof.GetComponent<Renderer>().material.color = Color.green;
    }

    public House GetResult()
    {
        return house;
    }
}

4、定义指挥者 Director

public class Director
{
    private IHouseBuilder houseBuilder;

    public Director(IHouseBuilder builder)
    {
        houseBuilder = builder;
    }

    // 控制房屋的建造流程
    public void ConstructHouse()
    {
        houseBuilder.BuildFoundation();
        houseBuilder.BuildWalls();
        houseBuilder.BuildRoof();
    }

    // 获取构建好的房屋
    public House GetHouse()
    {
        return houseBuilder.GetResult();
    }
}

5、 在 Unity 场景中使用建造者模式

using UnityEngine;

public class HouseBuilderExample : MonoBehaviour
{
    void Start()
    {
        // 创建一个木屋建造者
        IHouseBuilder woodenHouseBuilder = new WoodenHouseBuilder();
        Director director = new Director(woodenHouseBuilder);
        director.ConstructHouse();
        House woodenHouse = director.GetHouse();
        woodenHouse.ShowHouseInfo();

        // 创建一个砖屋建造者
        IHouseBuilder brickHouseBuilder = new BrickHouseBuilder();
        Director brickDirector = new Director(brickHouseBuilder);
        brickDirector.ConstructHouse();
        House brickHouse = brickDirector.GetHouse();
        brickHouse.ShowHouseInfo();
    }
}

6、运行效果

  1. 当运行场景时,系统会根据不同的建造者生成不同的房屋类型(木屋和砖屋),并将房屋的各个部分放置在场景中。
  2. WoodenHouseBuilder 会生成带有灰色地基、黄色墙壁和红色屋顶的木屋。
  3. BrickHouseBuilder 会生成带有灰色地基、红色砖墙和绿色屋顶的砖屋。

通过这两种建造者类的不同实现,我们展示了如何通过建造者模式灵活创建不同类型的复杂对象。

建造者模式允许分步骤创建对象,并且能够复用构建过程生成不同类型的产品。它也提高了代码的可读性和维护性,特别是在对象构建逻辑复杂时。

当对象的构建步骤固定,但最终产品可能有不同的表现方式时,建造者模式尤其适用。在 Unity 中,这种模式可以用于场景中复杂对象(如建筑物、场景)的灵活构建。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/883431.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

sheng的学习笔记-logback

基础知识 什么是logback Logback是一个用于Java应用程序的日志框架&#xff0c;提供了更好的性能、可扩展性和灵活性。 与Log4j相比&#xff0c;Logback提供了更快的速度和更低的内存占用&#xff0c;这使得它成为大型企业级应用程序的理想选择。 ‌logback和slf4j的关系是…

Hadoop安装与配置

一、Hadoop安装与配置 1、解压Hadoop安装包 找到hadoop-2.6.0.tar.gz,将其复到master0节点的”/home/csu”目录内&#xff0c;解压hadoop [csumaster0 ~]$ tar -zxvf ~/hadoop-2.6.0.tar.gz 解压成成功后自动在csu目录下创建hadoop-2.6.0子目录&#xff0c;可以用cd hadoo…

WGS1984快速度确定平面坐标系UTM分带(快速套表、公式计算、软件范围判定)

之前我们介绍了坐标系3带6带快速确定带号及中央经线&#xff08;快速套表、公式计算、软件范围判定&#xff09;就&#xff0c;讲的是CGCS2000 高斯克吕格的投影坐标系。 那还有我们经常用的WGS1984的平面坐标系一般用什么投影呢? 对于全球全国的比如在线地图使用&#xff1a…

9.sklearn-K-means算法

文章目录 环境配置&#xff08;必看&#xff09;头文件引用K-means算法1.简介2.API3.代码工程4.运行结果5.模型评估6.小结优缺点 环境配置&#xff08;必看&#xff09; Anaconda-创建虚拟环境的手把手教程相关环境配置看此篇文章&#xff0c;本专栏深度学习相关的版本和配置&…

前端sm2国密加密时注意

如下方法&#xff1a; export function encrypt(str) {const sm2 require("sm-crypto").sm2;const cipherMode 1; // 1 - C1C3C2&#xff0c;0 - C1C2C3&#xff0c;默认为1//自定义密钥let publicKey "xxxxxxxx";//此处加密let a sm2.doEncrypt(str,…

django项目添加测试数据的三种方式

文章目录 自定义终端命令Faker添加模拟数据基于终端脚本来完成数据的添加编写python脚本编写shell脚本执行脚本需要权限使用shell命令来完成测试数据的添加 添加测试数据在工作中一共有三种方式&#xff1a; 可以根据django的manage.py指令进行[自定义终端命令]可以采用第三方…

数据集-目标检测系列-兔子检测数据集 rabbit >> DataBall

数据集-目标检测系列-兔子检测数据集 rabbit >> DataBall 数据集-目标检测系列-兔子检测数据集 rabbit 数据量&#xff1a;8k 想要进一步了解&#xff0c;请联系。 DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加…

如何在Excel中快速找出前 N 名,后 N 名

有如下销售额统计表&#xff1a; 找出销售额排前 10 名的产品及其销售额&#xff0c;和销售额排倒数 10 名以内的产品及其销售额&#xff0c;结果如下所示&#xff1a; 前 10 名&#xff1a; spl("E(?1).sort(ProductSales:-1).to(10)",A1:C78)后 10 名&#xff1…

当大语言模型应用到教育领域时会有什么火花出现?

当大语言模型应用到教育领域时会有什么火花出现&#xff1f; LLM Education会出现哪些机遇与挑战? 今天笔者分享一篇来自New York University大学的研究论文&#xff0c;另外一篇则是来自Michigan State University与浙江师范大学的研究论文&#xff0c;希望对这个话题感兴趣…

顶点缓存对象(VBO)与顶点数组对象(VAO)

我们的顶点数组在CPU端的内存里是以数组的形式存在,想要GPU去绘制三角形,那么需要将这些数据传输给GPU。那这些数据在显存端是怎么存储的呢?VBO上场了,它代表GPU上的一段存储空间对象,表现为一个unsigned int类型的变量,GPU端内存对象的一个ID编号、地址、大小。一个VBO对…

Spring:项目中的统一异常处理和自定义异常

介绍异常的处理方式。在项目中&#xff0c;都会进行自定义异常&#xff0c;并且都是需要配合统一结果返回进行使用。 1.背景引入 &#xff08;1&#xff09;背景介绍 为什么要处理异常&#xff1f;如果不处理项目中的异常信息&#xff0c;前端访问我们后端就是显示访问失败的…

c# 子类继承父类接口问题

在C#中&#xff0c;子类并不直接“继承”父类继承的接口&#xff0c;但子类的确会继承父类对接口的实现&#xff08;如果父类实现了该接口&#xff09;。这里有一些关键的概念需要澄清&#xff1a; 接口继承&#xff1a;当一个类实现了某个接口时&#xff0c;它必须实现接口中…

新峰商城之订单(一):确认页面开发

新峰商城订单从生成到处理结束&#xff0c;主要以下几个流程&#xff1a; &#xff08;1&#xff09;提交订单&#xff08;商城用户发起&#xff09; &#xff08;2&#xff09;订单入库&#xff08;后台逻辑&#xff09; &#xff08;3&#xff09;支付订单&#xff08;商城…

化繁为简:中介者模式如何管理复杂对象交互

化繁为简&#xff1a;中介者模式如何管理复杂对象交互 中介者模式 是一种行为型设计模式&#xff0c;定义了一个中介者对象&#xff0c;来封装一组对象之间的交互。中介者模式通过将对象之间的交互行为从多个对象中抽离出来&#xff0c;集中封装在一个中介者对象中&#xff0c;…

【开源】 mRemoteNG 一键搞定!推荐一款强大的.NET多协议远程连接管理器

今天给大家推荐一款.NET开发的多协议、选项卡式远程连接管理器mRemoteNG。 mRemoteNG 是 mRemote 的一个分支&#xff1a;一个开源的、标签式的、多协议的、用于 Windows 的远程连接管理器。 mRemoteNG是一个开源的Windows远程连接管理器&#xff0c;它支持多种协议&#xff0c…

基于小安派AiPi-Eyes-Rx的N合1触摸屏游戏

基于小安派AiPi-Eyes-Rx的N合1触摸屏游戏 目前存在的游戏&#xff1a; 植物大战僵尸&#xff1a;demos/pvz羊了个羊&#xff1a;demos/yang消消乐&#xff1a;demos/xiaoxiaole华容道&#xff1a;demos/huarongdao PVZ功能展示可见&#xff1a; 羊了个羊&#xff1a; 消消…

开闭原则(OCP)

开闭原则&#xff08;OCP&#xff09;&#xff1a;Open Closed Princide&#xff1a;对扩展开放&#xff0c;对修改关闭。在程序需要进行拓展的时候&#xff0c;不能去修改原有代码&#xff0c;实现一个热插拔的效果。 简言之&#xff0c;是为了使程序的扩展性更好&#xff0c;…

【STM32】 TCP/IP通信协议(1)

一、前言 TCP/IP是干啥的&#xff1f;它跟SPI、IIC、CAN有什么区别&#xff1f;它如何实现stm32的通讯&#xff1f;如何去配置&#xff1f;为了搞懂这些问题&#xff0c;查询资料可解决如下疑问&#xff1a; 1.为什么要用以太网通信? 以太网(Ethernet) 是指遵守 IEEE 802.3 …

【React】组件基础使用

1. react组件 在react中&#xff0c;组件就是首字母大写的函数&#xff0c;内部存放了组件的逻辑、UI&#xff0c;渲染组件只需要把组件当成标签书写。 使用组件有两种方式&#xff1a;自闭和 、成对标签 function App() {// 定义组件function Component() {return <div&…

快手一面:给定一棵二叉树,要求将其转换为其镜像?

目录标题 题解&#xff1a;二叉树的镜像&#xff08;Invert Binary Tree&#xff09;问题描述示例解题思路代码实现详细分析复杂度分析优点注意事项&#x1f495; 题解&#xff1a;二叉树的镜像&#xff08;Invert Binary Tree&#xff09; 问题描述 给定一棵二叉树&#xff…