【Unity】【CSharp】【设计模式】Unity设计模式

单例模式

懒汉模式:

指在第一次访问单例对象时才创建实例

特点是在多线程环境下可能会存在线程安全问题,因为多个线程可能在同一时间检查到实例不存在,从而导致多个实例被创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UnitySingleton<T> : MonoBehaviour
where T : Component
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType(typeof(T)) as T;
if (_instance == null)
{
GameObject obj = new GameObject();
_instance = obj.AddComponent<T>();
}
}
return _instance;
}
}
}

饿汉模式:

指在类加载时就创建实例,无论是否需要

这样就可以避免多线程环境下的线程安全问题,但可能会增加启动时间和内存消耗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour, new()
{
public static T Instance = null;

public virtual void Awake()
{
if (Instance == null)
{
Instance = this as T;
}
else
{
Destroy(this.gameObject);
Debug.LogError("单例只允许存在一个,架构存在错误,本物体" + this.name + " 已删除");
}
}
}

拓展:

懒汉模式下,结束运行时可能会报错Some objects were not cleaned up when closing

这是因为我们在OnDestroy里访问了这个单例,结束运行时这个单例已经变成null

访问这个单例的时候又产生了一个新的实例才会报这个错误

可以将上述懒汉单例改成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class UnitySingleton<T> : MonoBehaviour
where T : Component
{
private static bool applicationIsQuitting = false;
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
if (applicationIsQuitting)
return _instance;
_instance = FindObjectOfType(typeof(T)) as T;
if (_instance == null)
{
GameObject obj = new GameObject();
_instance = obj.AddComponent<T>();
}
}
return _instance;
}
}

protected virtual void OnDestory()
{
applicationIsQuitting = true;
}
}

观察者模式

使用委托和事件实现

下面的方法使用的是委托和列表的方式,原理是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EventManager : Singleton<EventManager>
{
public delegate void EventCallBack(object param);// 事件回调函数
// 事件字典,键可以用枚举来实现
Dictionary<int, List<EventCallBack>> mDictEvent = new Dictionary<int, List<EventCallBack>>();

/// <summary>
/// 添加事件监听
/// </summary>
public void AddEvent(int eventId, EventCallBack callBack)
{
if(!mDictEvent.ContainsKey(eventId))
{
mDictEvent.Add(eventId,new List<EventCallBack>());
}
if(!mDictEvent[eventId].Contains(callBack))
{
mDictEvent[eventId].Add(callBack);
}
else
{
Debug.LogWarning("Repeat Add Event CallBack,EventId = " + eventId + ",CallBack = " + callBack.ToString());
}
}

/// <summary>
/// 删除事件监听
/// </summary>
public void DelEvent(int eventId, EventCallBack callBack)
{
if(!mDictEvent.ContainsKey(eventId))
{
return;
}
if(!mDictEvent[eventId].Contains(callBack))
{
return;
}
mDictEvent[eventId].Remove(callBack);

// 如果回调都被移除了 那么key也从字典移除
if (mDictEvent[eventId].Count < 1)
{
mDictEvent.Remove(eventId);
}

}

/// <summary>
/// 通知事件
/// </summary>
public void NotifyEvent(int eventId,object param)
{
if(mDictEvent.ContainsKey(eventId))
{
foreach(var callback in mDictEvent[eventId])
{
callback(param);
}
}
}
}

组合模式

把公共方法抽象成组件,需要用到这个方法的对象可以将对应的组件添加到该对象上

比较简单,就不举例了


命令模式

适用场景:实现撤销功能

大致由三部分组成:命令类、命令管理器类、输入类

命令类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using UnityEngine;

/// <summary>
/// 命令基类
/// </summary>
public abstract class CommandBase
{
/// <summary>
/// 执行
/// </summary>
public abstract void Execute();

/// <summary>
/// 撤销
/// </summary>
public abstract void Undo();
}

public class MoveForWard : CommandBase
{
private GameObject _player;
public MoveForWard(GameObject player)
{
_player = player;
}

public override void Execute()
{
_player.transform.Translate(Vector3.forward);
}

public override void Undo()
{
_player.transform.Translate(Vector3.back);
}
}

public class MoveLeft : CommandBase
{
private GameObject _player;
public MoveLeft(GameObject player)
{
_player = player;
}
public override void Execute()
{
_player.transform.Translate(Vector3.left);
}

public override void Undo()
{
_player.transform.Translate(Vector3.right);
}
}

命令管理器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CommandManager : MonoBehaviour
{
public static CommandManager Instance;
private readonly List<CommandBase> _commandList = new List<CommandBase>();

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

public void AddCommands(CommandBase command)
{
_commandList.Add(command);
}

public IEnumerator UndoStart()
{
_commandList.Reverse();
foreach (CommandBase command in _commandList)
{
yield return new WaitForSeconds(.2f);
command.Undo();
}

_commandList.Clear();
}
}

输入类:

监听按键点击,只负责接受输入,具体的操作放在命令的Execute方法中,这样实现解耦
点击W或A键时执行命令,并把命令添加的管理器的列表中,点击B键撤销之前所有的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using UnityEngine;

public class InputHandler : MonoBehaviour
{
private MoveForWard _moveForward;
private MoveLeft _moveLeft;
private GameObject _playerCube;

private void Start()
{
_playerCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
}

private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
_moveForward = new MoveForWard(_playerCube);
_moveForward.Execute();
CommandManager.Instance.AddCommands(_moveForward);//顺序不能弄混,因为要等赋值完后再加入
}

if (Input.GetKeyDown(KeyCode.A))
{
_moveLeft = new MoveLeft(_playerCube);
_moveLeft.Execute();
CommandManager.Instance.AddCommands(_moveLeft);
}

if (Input.GetKeyDown(KeyCode.B))
{
StartCoroutine(CommandManager.Instance.UndoStart());
}
}
}

状态模式

一般使用有限状态机实现

  • 将对象的行为抽象成几个独立的状态
  • 某一时刻只能处于其中一种状态
  • 通过管理控制状态的互相切换

有两张使用场景:场景切换、怪物AI

核心代码(状态机):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System.Collections.Generic;
using UnityEngine;
using System;

namespace MY_FSM
{
public enum StateType
{
Idle,
MOVE,
Find_Enemy,
Attack,
Die,
Success,
}

/// 接口,AI的状态需要接收这个接口
public interface IState
{
void OnEnter();
void OnExit();
void OnUpdate();
// void OnCheck();
// void OnFixUpdate();
}

[Serializable]
public class Blackboard
{
// 此处存储共享数据,或者向外展示的数据,可配置的数据
}

public class FSM
{
public IState curState;
public Dictionary<StateType, IState> states;
public Blackboard blackboard;

public FSM(Blackboard blackboard)
{
this.states = new Dictionary<StateType, IState>();
this.blackboard = blackboard;
}

public void AddState(StateType stateType, IState state)
{
if (states.ContainsKey(stateType))
{
Debug.Log("[AddState] >>>>>>>>>>>>> map has contain key: " + stateType);
return;
}
states.Add(stateType, state);
}

public void SwitchState(StateType stateType)
{
if (!states.ContainsKey(stateType))
{
Debug.Log("[SwitchState] >>>>>>>>>>>>>>>>> not contain key: " + stateType);
return;
}
if (curState != null)
{
curState.OnExit();
}
curState = states[stateType];
curState.OnEnter();
}

public void OnUpdate()
{
curState.OnUpdate();
}

public void OnFixUpdate()
{
// curState.OnFixUpdate();
}

public void OnCheck()
{
// curState.OnCheck();
}
}
}