前言
- 实际项目开发中,需求快速迭代。音频作为独立的一个部分,降低声音设计师和游戏程序之间的耦合性,是很重要的一部分。很多时候我们完成了声音功能的开发,直接把功能接口暴露给游戏程序调用,在音频端和程序端都不灵活。我在想有没有一套比较好的代理系统,可以较快捷的进行接口绑定和管理,同时程序端也可以快速调用代理,这就是本文探讨重点。
流程描述
-
音频开发端
-
GamePlay开发端
DelegateBase
DelegateBase
类中声明了3个最常用类型的代理。AudioDelegate
绑定单个无参数函数接口。AudioMultiDelegate
绑定多个无参数函数接口。AudioBPDelegate
用于绑定蓝图实现的函数接口。
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
//代理声明
DECLARE_DELEGATE(AudioDelegate)
DECLARE_MULTICAST_DELEGATE(AudioMultiDelegate)
DECLARE_DYNAMIC_MULTICAST_DELEGATE(AudioBPDelegate)
class DelegateBase
{
public:
virtual ~DelegateBase() {}
virtual AudioDelegate &GetDelegate() const
{
return m_Delegate;
}
virtual AudioMultiDelegate &GetMultiDelegate() const
{
return m_MultiDelegate;
}
virtual AudioBPDelegate &GetBPDelegate() const
{
return m_BPDelegate;
}
//子类DelegateOnHit新添加接口
virtual AudioDelegateOneParam &GetAudioDelegateOneParam() const{}
//子类DelegateOnAttack新添加接口
virtual AudioDelegateOneParam &GetAudioMultiDelegateOneParam() const {}
protected:
AudioDelegate m_Delegate;
AudioMultiDelegate m_MultiDelegate;
UPROPERTY(BlueprintAssignable)
AudioBPDelegate m_BPDelegate;
};
- 对于不同的事件,创建不同的代理类型,可以选择是否继承
DelegateBase
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
//新代理类型声明
DECLARE_DELEGATE_OneParam(AudioDelegateOneParam, FString);
//受击时声音代理类型
class DelegateOnHit : public DelegateBase
{
public:
//override有需求的代理类型
virtual AudioDelegate &GetDelegate() const override
{
return m_Delegate;
}
virtual AudioBPDelegate &GetBPDelegate() const override
{
return m_BPDelegate;
}
//添加新的代理类型成员变量的Get函数,并且需要回DelegateBase类中添加相同签名虚函数接口
virtual AudioDelegateOneParam &GetAudioDelegateOneParam() const override
{
return m_DelegateOneParam;
}
private:
AudioDelegateOneParam m_DelegateOneParam;
};
//-----------------------------------------------------------------------------
DECLARE_MULTICAST_DELEGATE_OneParam(AudioMultiDelegateOneParam, int32)
//攻击时声音代理类型
class DelegateOnAttack : public DelegateBase
{
//override有需求的代理类型
virtual AudioDelegate &GetDelegate() const override
{
return m_Delegate;
}
//添加新的代理类型成员变量的Get函数,并且需要回DelegateBase类中添加相同签名虚函数接口
virtual AudioDelegateOneParam &GetAudioMultiDelegateOneParam() const override
{
return m_MultiDelegateOneParam;
}
private:
AudioMultiDelegateOneParam m_MultiDelegateOneParam;
};
DelegateManager(代理管理器)
DelegateManager
这个类的作用主要是,维护管理已经实例化且完成绑定的代理对象。之后可以通过名字直接查找代理类对象。
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
class DelegateManager
{
public:
static DelegateManager& Get()
{
static DelegateManager managerSingleton;
return managerSingleton;
}
//Add已经完成绑定的代理对象到管理列表
bool AddDelegate(const FString& name, DelegateManager *delegate)
{
if(name.empty())
return false;
if(m_DelegateMap.find(name) == nullptr)
{
auto pair = std::make_pair(name, TSharedPtr<DelegateBase>(delegate));
m_DelegateMap.insert(pair);
return true;
}
else
return false;
}
//Get管理列表中的代理对象
TSharedPtr<DelegateBase> GetDelegate(const FString &name)
{
if(name.empty())
return nullptr;
if(auto iter = m_DelegateMap.find(name))
{
return iter->second;
}
else
return nullptr;
}
//Remove管理列表中的代理对象
bool RemoveDelegate(const FString &name)
{
if(name.empty())
return false;
if(m_DelegateMap.find(name))
{
m_DelegateMap.erase(name);
return true;
}
else
return false;
}
//修改管理列表中的代理名称和代理对象的映射
bool ResetDelegate(const FString &name, DelegateBase *delegate);
{
if(name.empty() || delegate == nullptr)
return false;
if(auto iter = m_DelegateMap.find(name))
{
iter->second = TSharedPtr<DelegateBase>(delegate);
return true;
}
else
false;
}
protected:
//为每个代理类创建一个对象并且加入管理列表,只在第一次Get时调用
static void Initialize();
private:
//管理列表
static std::map<FString, TSharedPtr<DelegateBase>> m_DelegateMap;
//单例对象
static DelegateManager& managerSingleton;
static bool IsInitialized;
//单例
DelegateManager(){}
~DelegateManager(){}
};
//单例Get函数
DelegateManager& DelegateManager::Get()
{
static DelegateManager managerSingleton;
if(!IsInitialized)
DelegateManager::Initialize();
return managerSingleton;
}
DelegateManager& DelegateManager::managerSingleton = DelegateManager::Get();
bool DelegateManager::IsInitialized = false;
void DelegateManager::Initialize()
{
if(m_DelegateMap.find(FString("DelegateOnHit")))
return;
auto pair1 = std::make_pair(FString("DelegateOnHit"), TSharedPtr<DelegateBase>(new DelegateOnHit()));
if()
auto res1 = m_DelegateMap.insert(pair1);
if(m_DelegateMap.find(FString("DelegateOnAttack")))
return;
auto pair2 = std::make_pair(FString("DelegateOnAttack"), TSharedPtr<DelegateBase>(new DelegateOnAttack());
auto res2 = m_DelegateMap.insert(pair2);
IsInitialized = true;
}
绑定
- 在需要绑定的函数接口类中Get
DelegateManager
单例并绑定接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CharacterSound
{
public:
//游戏开始时执行的函数
virtual void BeginPlay() override
{
//在游戏开始时或者其他时刻进行声音接口绑定
DelegateManager::Get().GetDelegate(FString("DelegateOnHit"))->BindUObject(this, &CharacterSound::PlayOnHitSound);
DelegateManager::Get().GetDelegate(FString("DelegateOnAttack"))->AddUObject(this,CharacterSound::PlayOnAttackSound);
//绑定完成后添加到
}
PlayOnHitSound()
{
//播放声音代码
}
PlayOnAttackSound(int32 distance)
{
//播放声音代码
}
}
Event Pool
- 事件池中对
DelegateManager
中对象进行接口端的业务处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace EventPool;
{
void OnHit()
{
if(auto delegate = DelegateManager::Get().GetDelegate(FString("DelegateOnHit")))
delegate->GetDelegate().ExecuteIfBound();
else
return;
}
void OnAttack(int32 distance)
{
if(auto delegate = DelegateManager::Get().GetDelegate(FString("DelegateOnAttack")))
delegate->GetDelegate().Boardcast(distance);
else
return;
}
}
程序调用
1
2
3
4
5
int main()
{
EventPool::OnHit();
EventPool::OnAttack(500);
}
总结
- 整个代理管理流程还是初步设计阶段,现在对于绑定和调用都比较友好。
DelegateManager
的维护成本较高,后续继续改进。通过这套系统可以管理大量的代理对象,以及维护代理间的映射关系,以上实现部分基于Unreal。