Unity常用设计模式
  EFTJ6596AiAP 2023年11月02日 118 0

设计模式概览

各类型对比

分类

定义

侧重点

适用案例

使用方式

优势

缺陷

单例模式

确认类只有一个对象,并提供一个全局方法来获取这个对象。

获取日志工具

能产生唯一对象的类提供全局方法;把唯一的类对象设置为静态的类。 防止类对象被任意生成而造成系统错误;获取类对象方便。

容易过度使用。

状态模式

让一个对象的行为随着内部状态的改变而变化,状态改变之后就像是改变了类一样

场景转换

定义一个各状态通用的接口;各状态继承整个接口定义内部状态。然后再用一个脚本来控制切换。

降低维护难度;状态执行环境单一;一些状态可以项目之间共享。

状态很多的时候容易类爆量。

观察者模式

对象行为型

在对象之间定义一个一对多的连接方法,当一个对象变换状态的时候,其他关联的对象也会自动收到通知。

侧重对于观察者的管理,观察者可以决定订阅或者退订。

成就系统

建立发布者和订阅者之间的关系;

发布者发布信息的时候,通知所有的订阅者

游戏事件发生的时候不必处理后续的逻辑。

容易类过多;解决方案是注册的时候使用回调函数而不是类对象,然后再用同一个类来管理这些回调函数。

命令模式

对象行为型

将请求封装成对象,将客户端不同的请求参数化,并配合队列、记录、复原等方法来执行请求的操作。

侧重对于命令的管理(对于命令的增加、删除、记录、撤销等)。

网络中封包的传递

封装请求;

操作请求。

命令被封装为对象之后,可以为命令的执行加上额外操作或者参数化。

容易类爆量。

简单工厂模式

类创建型

工厂对象决定创建哪一种产品的实例。

实现对象的创建和使用的分离

不够灵活,如果新增一个产品就需要修改工厂类,违背了开闭原则。

工厂模式

类创建型

定义一个可以产生对象的接口,让子类决定要产生哪一个类的对象。

将类“产生对象的流程”进行集合管理的模式。

先定义一个产生对象的接口,然后让子类去决定生产哪一种对象。

能对对象产生的流程制定规则

减少客户端参与对象生成的过程

当类群组过多的时候,会出现子类爆量或者switch语句过长的问题

抽象工厂模式

类创建型

提供一个能够建立整个类群组或有关联的对象,而不必指明他们的具体类。

系统中先定义一组抽象类,这些抽象类的子类,是根据不同的执行环境产生的。

扩展了具体工厂的功能,使得一个工厂可以生产多个大类。能将产生的对象整组转换到不同的类群组上。

适配器模式

将一个类的接口转换成客户端期待的类接口,让原本接口不兼容的类可以一起工作。

侧重使得不兼容的类变得兼容

俘虏野怪;转换不同版本的UI插件

关联(内部创建一个私有的被适配对象实例进行方法调用)或者继承(通过继承调用父类对象和方法)

类型转换;

隔离项目和第三方库,减少对于第三方库的依赖。

分类

对象行为型模式:观察者模式 & 命令模式

命令模式和观察者模式都是着重将“发生”和“执行”两个操作消除耦合的模式。当观察者模式中只有一个观察者的时候,很类似于命令模式。但两者的侧重点不同:

命令模式:侧重对于命令的管理(增加、删除、记录、撤销等)

观察者模式:侧重对于观察者的管理,观察者可以决定订阅或者退订

创建类模式

主要是工厂家族。

  • 简单工厂类虽然对于小型项目来说修改简单,但是因为需要修改工厂类,所以违背了开闭原则。
  • 工厂类中新增一个生产的产品需要新增一个类,在新增的类中实现生产过程。
  • 只有一个体系的抽象工厂模式就是工厂模式。新增产品体系的时候需要修改原有的工厂类,违反开闭原则。

三者的抽象程度递增。

各设计模式案例

单例模式

衍生于全局静态变量。全局静态变量有两个问题:(1)无法避免产生第二个对象;(2)容易产生全局变量重复?(没太理解)

  • 单例模式的意义:(1)同时间只存在一个对象;(2)提供了一个快速获取这个对象的方法。
  • 使用的场景:确定这个类的对象只有一个,并且不会被继承(因为他的构造函数被私有化了)。

以下代码重点演示单例的会是同一个地址,返回同一个哈希值

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class MySingleton
{
private static MySingleton instance;

public static MySingleton Instance
{
get
{
if (instance == null)
instance = new MySingleton();
return instance;
}
}

private MySingleton()
{
Debug.Log("构造函数执行");
}

public void Show()
{
Debug.Log("Show");
}

}
public class DesignPattern : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
MySingleton single1 = MySingleton.Instance;
single1.Show();
Debug.Log(single1.GetHashCode());

MySingleton single2 = MySingleton.Instance;
single2.Show();
Debug.Log(single2.GetHashCode());

MySingleton single3 = MySingleton.Instance;
single3.Show();
Debug.Log(single3.GetHashCode());
//以上这三个对象指向同一个地址
}
}

以下是一个我自己的游戏demo中使用的泛型单例脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static T instance;
public static T Instance
{
get { return instance; }
}

protected virtual void Awake()
{
if (instance != null)
Destroy(gameObject);//保证只有一个实例,如果之前有的话,先销毁
else
instance = (T)this;
}

protected virtual void OnDestroy()
{
if (instance == this)
{
instance = null;
}
}

public static bool IsInitialized
{
get { return instance != null; }
}
}


状态模式

参考文章:​​https://blog.csdn.net/m0_52361859/article/details/125588594​

概述:将复杂的逻辑判断提取到不同状态对象中,允许状态对象在其内部状态发生改变时,改变其行为。状态的改变和各个状态的行为是状态模式的核心。衍生于switch状态切换,然而switch状态切换的使用中,如果后续有新增的状态需要修改state部分;而状态模式中不需要 。

  • 优势:结构清晰;后续增加的时候成本低;易于维护。
  • 缺陷:如果状态很多,导致产生的类的数量太多。

行为树和状态机

对于状态不复杂的人物AI,可以通过状态模式来实现角色的AI行为。


有限状态机

行为树

概念

在不同阶段会呈现出不同运行状态的系统

这些状态是有限的、不重叠的

本质上是AI,涵盖了层次状态机、事件调度、事件计划、行为等

基本节点

状态

行为,是高度模块化的状态,去掉了状态中的跳转逻辑

跳转

任意跳转,实现简单

某一时刻一定处于其中的一个状态

跳转时通过父节点的类型来决定的,可以并行处理两个行为

复用性

复用性差

通过增加父节点的类型,可以复用行为


命令模式

概述:对象行为型模式,通过将请求者和执行者之间的请求抽离出来封装成一个对象,从而使发出请求的职责和执行请求的职责分离。请求者和执行者通过命令对象来进行沟通,这样使得对命令对象进行存储,管理,传递等。

可以分为两个部分:请求的封装、请求的操作。

实现的效果:解耦、可改变命令对象(对象可参数化)、对象的撤销

优劣:

  • 优势:单一职责,调用者和实现者分离
  • 劣势:会产生大量的命令类,使得系统更加的复杂

代码案例:

代码1:抽象的命令层

public abstract class Commond//这里单词拼错了还请忽略
{
public abstract void Execute(GameObject player);
//抽象方法只能存在于抽象类中
public abstract void Undo();
}

代码2:游戏主脚本,进行输入和调用

public class GameRoot : MonoBehaviour
{
public GameObject player;
//首先实例化
MoveForward forward = new MoveForward();
MoveBack back = new MoveBack();

void Update()
{
PlayerInputHandler();
}

private void PlayerInputHandler()
{
if (Input.GetKeyDown(KeyCode.W))
{
forward.Execute(player);
CommandManager.Instance.AddCommands(forward);

}
if (Input.GetKeyDown(KeyCode.S))
{
back.Execute(player);
CommandManager.Instance.AddCommands(back);
}
if (Input.GetKeyDown(KeyCode.D))
{
StartCoroutine(CommandManager.Instance.UndoStart());
}
}
}

代码3:命令的管理脚本,主要用来执行撤销动作

public class CommandManager : MonoBehaviour
{
public static CommandManager Instance;

private readonly List<Commond> commandList = new List<Commond>();

private void Awake()
{
if (Instance) Destroy(Instance);
else Instance = this;
}

public void AddCommands(Commond commond)
{
commandList.Add(commond);
}

public IEnumerator UndoStart()
{
commandList.Reverse();//逆转
foreach(Commond commond in commandList)
{
yield return new WaitForSeconds(.2f);
commond.Undo();
}
commandList.Clear();
}
}

输出效果:

Unity常用设计模式_设计模式

观察者模式

概述:简单来说就是在事件的发布者中定义一个委托,在事件响应者中向这个委托中添加响应方法。然后在适当的时候触发这个委托方法,响应者注册的方法就也会执行了。

代码示例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

//这里介绍观察者模式
//例子是:猫来了,所有的老鼠开始逃跑

public class Animal//这是一个父类
{
protected string strName;

public Animal(string name)//构造函数
{
this.strName = name;
}

public virtual void Run()//父类中的虚方法
{

}
}

public class Cat: Animal
{
public Action action;//发布者
public Cat(string name):base(name)//构造函数
{
}

public void Coming(/*Animal mouseA, Animal mouseB, Animal mouseC*/)
{
Debug.Log(strName + "来了");
//mouseA.Run();
//mouseB.Run();
//mouseC.Run();
//在没有观察者模式的时候需要挨个调用

if (action != null)
action();//触发事件;通过多播委托完成一对多的关系

this.Run();
}

public override void Run()//函数的重载
{
Debug.Log(strName + "开始追");
}
}

public class Mouse: Animal
{
public Mouse(string name, Cat cat):base(name)
{
cat.action += this.Run;//订阅者
}

public override void Run()//事件处理器
{
Debug.Log( strName + "开始逃跑");
}
}

public class ObserverPattern : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Cat cat = new Cat("小野猫");

Animal mouseA = new Mouse("mouseA", cat);
Animal mouseB = new Mouse("mouseB", cat);
Animal mouseC = new Mouse("mouseC", cat);
Animal mouseD = new Mouse("mouseD", cat);

//cat.Coming(mouseA, mouseB, mouseC);
cat.Coming();
}
}

运行结果:

Unity常用设计模式_设计模式_02

以下代码展示观察者模式和非观察者模式的写法差异

//普通写法
//缺点在于如果后续需要新增相应的对象,那么每次都需要在cat的函数中手动新增响应对象
public class Cat: Animal
{
public Cat(string name):base(name)//构造函数
{
}

public void Coming(Animal mouseA, Animal mouseB, Animal mouseC)
{
Debug.Log(strName + "来了");
mouseA.Run();
mouseB.Run();
mouseC.Run();
this.Run();
}

public override void Run()//函数的重载
{
Debug.Log(strName + "开始追");
}
}

//观察者模式的写法
public class Cat: Animal
{
public Action action;
public Cat(string name):base(name)//构造函数
{
}

public void Coming(Animal mouseA, Animal mouseB, Animal mouseC)
{
Debug.Log(strName + "来了");
if(action!= null)
action();//在Mouse的构造函数中将其Run方法注册给这个cat的
this.Run();
}

public override void Run()//函数的重载
{
Debug.Log(strName + "开始追");
}
}

public class Mouse: Animal
{
public Mouse(string name, Cat cat):base(name)
{
cat.action += this.Run;//订阅者
}

public override void Run()//事件处理器
{
Debug.Log( strName + "开始逃跑");
}
}


工厂家族

简单工厂模式

创建型模式,本质上是一种静态工厂方法模式(Static Factory Method)。需要先定义一个可以产生对象的接口,但是让子类去决定要产生哪一个类的对象。工厂模式使得实例化程序延迟到子类中实现。

主要思想:

Unity常用设计模式_游戏开发_03

我们创建了两个脚本,包含四个类:

  • MouseProduct:父类,还有两个品牌的子类Dell和HP
  • MainFactory:一个MainFactory类+一个SimpleFactory类,SimpleFactory类在MainFactory中进行调用;其中SimpleFactory类中还用到了一个枚举

MouseProduct脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MouseManufactoring
{
public abstract class MouseProduct
{
public abstract void Print();
}

public class DellMouse : MouseProduct
{
public override void Print()
{
Debug.Log("生产了一个戴尔鼠标");
}
}

public class HpMouse : MouseProduct
{
public override void Print()
{
Debug.Log("生产了一个HP鼠标");
}
}
}

MainFactory脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MouseManufactoring;

public enum MouseType { DellMouse, HpMouse,};

public class MainFactory : MonoBehaviour
{
void Start()
{
RunFactoryMode();
}

public void RunNormal()
{
DellMouse dellMouse = new DellMouse();
dellMouse.Print();

HpMouse hpMouse = new HpMouse();
hpMouse.Print();
}

public void RunFactoryMode()
{
SimpleFactory simpleFactory = new SimpleFactory();//将简单工厂实例化

simpleFactory.CreateMouse(MouseType.DellMouse).Print();
simpleFactory.CreateMouse(MouseType.HpMouse).Print();
}

}


public class SimpleFactory
{
public MouseProduct CreateMouse(MouseType type)
{
MouseProduct mouse = null;
switch (type)
{
case MouseType.DellMouse:
mouse = new DellMouse();
break;
case MouseType.HpMouse:
mouse = new HpMouse();
break;
}

return mouse;
}
}

也可以做如下改造使得可以控制生产的品牌:

public enum MouseType { DellMouse, HpMouse,};

public class MainFactory : MonoBehaviour
{
public MouseType brand;
void Start()
{
RunFactoryMode(brand);
}

public void RunNormal()
{
DellMouse dellMouse = new DellMouse();
dellMouse.Print();

HpMouse hpMouse = new HpMouse();
hpMouse.Print();
}

public void RunFactoryMode(MouseType type)
{
SimpleFactory simpleFactory = new SimpleFactory();//将简单工厂实例化

simpleFactory.CreateMouse(type).Print();
}
}

缺陷:简单工厂模式如果需要再增加一个产品,需要新建一个类、枚举,并且需要修改CreateMouse的方法,违背了开闭原则。


工厂模式

关键词:解决简单工厂模式中,新增产品品牌的时候,需要直接修改工厂类的问题。

解决了简单工厂模式中出现的开闭原则问题,一个中间件只能生产一种产品。以下展示了简单工厂和工厂模式的区别。

Unity常用设计模式_中间件_04

本次我们创建了3个脚本:

  • 产品脚本:由一个抽象类和其子类组成,子类分别代表不同的品牌,包含各自的Print函数
  • 中间件脚本:由一个抽象类和其子类组成,子类分别代表不同品牌的工厂,包含各自的生产函数
  • 主要工厂脚本:挂载在游戏物体上,在Start中调用一个运行方法

产品脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace FactoryPattern
{
public abstract class PorductFactoryPattern
{
public abstract void Print();
}

public class DellMouse: PorductFactoryPattern
{
public override void Print()
{
Debug.Log("生产了一个Dell鼠标");
}
}

public class HPMouse : PorductFactoryPattern
{
public override void Print()
{
Debug.Log("生产了一个HP鼠标");
}
}
}

中间件脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using FactoryPattern;
public abstract class MidBase
{
public abstract PorductFactoryPattern CreateMouse();
}

public class DellFactoryMid: MidBase//Dell中间件
{
public override PorductFactoryPattern CreateMouse()
{
return new DellMouse();
}
}

public class HPFactoryMid: MidBase//HP中间件
{
public override PorductFactoryPattern CreateMouse()
{
return new HPMouse();
}
}

主要工厂脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using FactoryPattern;

public class MainFactoryPattern : MonoBehaviour
{
void Start()
{
RunFactoryMode();
}

public void RunNormal()//普通模式的运行方法
{
DellMouse dellMouse = new DellMouse();
dellMouse.Print();

HPMouse hpMouse = new HPMouse();
hpMouse.Print();
}

public void RunFactoryMode(MouseType type)//简单工厂模式的运行方法
{
SimpleFactory simpleFactory = new SimpleFactory();//将简单工厂实例化
simpleFactory.CreateMouse(type).Print();
}

public void RunFactoryMode()
{
DellFactoryMid dellFactoryMid = new DellFactoryMid();
dellFactoryMid.CreateMouse().Print();

HPFactoryMid hpFactoryMid = new HPFactoryMid();
hpFactoryMid.CreateMouse().Print();
}
}

抽象工厂模式

关键词:解决生产系列产品的问题,比如我们不只是生产鼠标,我们还生产键盘

具体的脚本构成:

鼠标脚本:鼠标抽象父类+鼠标子类

键盘脚本:键盘抽象父类+键盘子类

中间件脚本:中间件抽象父类+中间件子类(生成鼠标和键盘)

主要工厂脚本:Start中调用运行方法


适配器模式(Adapter Pattern)

Unity常用设计模式_设计模式_05

脚本构成:

具体数据线脚本:可以用抽象父类,也可以不用

适配器脚本:根据枚举判断不同的类型调用各自的方法

主要调用脚本:在Start中进行调用


具体数据线脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace AdapterPattern
{
public abstract class Lines
{
public abstract void Charge();
}

public class AndroidLine: Lines
{
public override void Charge()
{
Debug.Log("这是安卓充电线在充电中……");
}
}

public class IOSLine: Lines
{
public override void Charge()
{
Debug.Log("这是苹果充电线在充电中……");
}
}
}

适配器脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using AdapterPattern;

public interface IAdapter
{
void Charge(LineType type);
}

public enum LineType { None, ios, android, };

public class Adapter: IAdapter
{
AndroidLine androidLine = new AndroidLine();
IOSLine iOSLine = new IOSLine();
public void Charge(LineType type)
{
if (type == LineType.ios)
{
iOSLine.Charge();
}
else if (type == LineType.android)
{
androidLine.Charge();
}
}
}

主要调用脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using AdapterPattern;

public class MainAdapter : MonoBehaviour
{
public LineType type;
// Start is called before the first frame update
void Start()
{
Adapter adapter = new Adapter();
//IAdapter adapter = new Adapter();
//这里这种写法也是可以的,还没有弄懂为什么,记个Todo
adapter.Charge(type);
}
}


事件系统(待补充)


MVC

MVC概览

参考文档:

​https://blog.csdn.net/qq_39574690/article/details/80757261?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166920790116782414943044%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166920790116782414943044&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-3-80757261-null-null.142^v66^control,201^v3^add_ask,213^v2^t3_control1&utm_term=mvc%20unity&spm=1018.2226.3001.4187​

MVC && MVVM


MVC

MVP

MVVM

处理业务逻辑

Controller

Presenter

ViewModel

效果


View是依赖Model的,会有一些逻辑在View中实现;并且单独修改View是比较困难的

所有的交互都发生在presenter内部

将View和Model的通信封装在VM中,进行双向通信

通信

单向


双向

缺陷

View对model的访问效率低

View层也会处理业务逻辑

代码清晰但是会增加很多类;

代码量大

优势

耦合性低,部署快

高效的使用model;

使得View和Model之间不存在耦合;

也使得View层不用接触业务逻辑

充分解耦,模块清晰

适合场景

小型项目

中型项目



参考文献

《设计模式和游戏完美开发》蔡升达

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

EFTJ6596AiAP