unity性能优化CPU篇

CPU方面

就目前的Unity移动游戏而言,CPU方面的性能开销主要可归结为两大类:引擎模块性能开销和自身代码性能开销。其中,引擎模块中又可细致划分为渲染模块、动画模块、物理模块、UI模块、粒子系统、加载模块和GC调用等等。正因如此,我们在UWA测评报告中,就这些模块进行详细的性能分析,以方便大家快速定位项目的性能瓶颈,同时,根据我们的分析和建议对问题进行迅速排查和解决。

通过大量的性能测评数据,我们发现渲染模块、UI模块和加载模块,往往占据了游戏CPU性能开销的Top3。

一、渲染模块

渲染模块可以说是任何游戏中最为消耗CPU性能的引擎模块,因为几乎所有的游戏都离不开场景、物体和特效的渲染。对于渲染模块的优化,主要从以下两个方面入手:

(1)降低Draw Call

Draw Call是渲染模块优化方面的重中之重,一般来说,Draw
Call越高,则渲染模块的CPU开销越大。究其原因,要从底层Driver和GPU的渲染流程讲起,限于篇幅我们不在这里做过多的介绍。有兴趣的朋友可以查看[这里](http://stackoverflow.com/questions/4853856
/why-are-draw-calls-expensive),或者自行Google相关的技术文献。

降低Draw Call的方法则主要是减少所渲染物体的材质种类,并通过Draw Call Batching来减少其数量。Unity文档对于Draw Call
Batching的原理和注意事项有非常详细的讲解,感兴趣的朋友可以直接查看
Unity官方文档

但是,需要注意的是,游戏性能并非Draw Call越小越好。这是因为,决定渲染模块性能的除了Draw
Call之外,还有用于传输渲染数据的总线带宽。当我们使用Draw Call
Batching将同种材质的网格模型拼合在一起时,可能会造成同一时间需要传输的数据(Texture、VB/IB等)大大增加,以至于造成带宽“堵塞”,在资源无法及时传输过去的情况下,GPU只能等待,从而反倒降低了游戏的运行帧率。

Draw Call和总线带宽是天平的两端,我们需要做的是尽可能维持天平的平衡,任何一边过高或过低,对性能来说都是无益的。

(2)简化资源

简化资源是非常行之有效的优化手段。在大量的移动游戏中,其渲染资源其实是“过量”的,过量的网格资源、不合规的纹理资源等等。所以,我们在UWA测评报告中对资源的使用进行了详细的展示(每帧渲染的三角形面片数、网格和纹理资源的具体使用情况等),以帮助大家快速查找和完善存在问题的资源。

关于渲染模块在CPU方面的优化方法还有很多,比如LOD、Occlusion Culling和Culling
Distance等等。我们会在后续的渲染模块技术专题中进行更为详细的讲解,敬请期待。

二、UI模块

UI模块同样也是几乎所有的游戏项目中必备的模块。一个性能优异的UI模块可以将游戏的用户体验再抬高一个档次。在目前国内的大量项目中,NGUI作为UI解决方案的占比仍然非常高。所以,UWA测评报告对NGUI的性能分析进行了极大的支持,我们会根据用户所使用的UI解决方案(UGUI或NGUI)的不同提供不同的性能分析和优化建议。

在NGUI的优化方面,UIPanel.LateUpdate为性能优化的重中之重,它是NGUI中CPU开销最大的函数,没有之一。UI模块制作的难点并不在于其表现上,因为UI界面的表现力是由设计师来决定的,但两套表现完全一致的UI系统,其底层的性能开销则可能千差万别。如何让UI系统使用尽可能小的CPU开销来达到设计师所设计的表现力,则足以考验每一位UI开发人员的制作功底。

目前,我们在UWA测评报告中将统计意义上CPU开销最为耗时的几个函数进行展示,并将其详细的CPU占用和堆内存分配进行统计,从而让研发团队对UI系统的性能开销进行直接地掌握,同时结合项目截图对UI模块何时存在较大开销进行直观地定位。

对于UIPanel.LateUpdate的优化,主要着眼于UIPanel的布局,其原则如下:

尽可能将动态UI元素和静态UI元素分离到不同的UIPanel中(UI的重建以UIPanel为单位),从而尽可能将因为变动的UI元素引起的重构控制在较小的范围内;

尽可能让动态UI元素按照同步性进行划分,即运动频率不同的UI元素尽可能分离放在不同的UIPanel中;

控制同一个UIPanel中动态UI元素的数量,数量越多,所创建的Mesh越大,从而使得重构的开销显著增加。比如,战斗过程中的HUD运动血条可能会出现较多,此时,建议研发团队将运动血条分离成不同的UIPanel,每组UIPanel下5~10个动态UI为宜。这种做法,其本质是从概率上尽可能降低单帧中UIPanel的重建开销。

另外,限于篇幅限制,我们在此仅介绍NGUI中重要性能问题,而对于UGUI系统以及UI系统自身的Draw
Call问题,我们将在后续的UI模块技术专题中进行详细的讲解,敬请期待。

三、加载模块

加载模块同样也是任何游戏项目中所不可缺少的组成成分。与之前两个模块不同的是,加载模块的性能开销比较集中,主要出现于场景切换处,且CPU占用峰值均较高。

这里,我们先来说说场景切换时,其性能开销的主要体现形式。对于目前的Unity版本而言,场景切换时的主要性能开销主要体现在两个方面,前一场景的场景卸载和下一场景的场景加载。下面,我们就具体来说说这两个方面的性能瓶颈:

(1)场景卸载

对于Unity引擎而言,场景卸载一般是由引擎自动完成的,即当我们调用类似Application.LoadLevel的API时,引擎即会开始对上一场景进行处理,其性能开销主要被以下几个部分占据:

Destroy

引擎在切换场景时会收集未标识成“DontDestoryOnLoad”的GameObject及其Component,然后进行Destroy。同时,代码中的OnDestory被触发执行,这里的性能开销主要取决于OnDestroy回调函数中的代码逻辑。

Resources.UnloadUnusedAssets

一般情况下,场景切换过程中,该API会被调用两次,一次为引擎在切换场景时自动调用,另一次则为用户手动调用(一般出现在场景加载后,用户调用它来确保上一场景的资源被卸载干净)。在我们测评过的大量项目中,该API的CPU开销主要集中在500ms~3000ms之间。其耗时开销主要取决于场景中Asset和Object的数量,数量越多,则耗时越慢。

** **

(2)场景加载

场景加载过程的性能开销又可细分成以下几个部分:

资源加载

资源加载几乎占据了整个加载过程的90%时间以上,其加载效率主要取决于资源的加载方式(Resource.Load或AssetBundle加载)、加载量(纹理、网格、材质等资源数据的大小)和资源格式(纹理格式、音频格式等)等等。不同的加载方式、不同的资源格式,其加载效率可谓千差万别,所以我们在UWA测评报告中,特别将每种资源的具体使用情况进行展示,以帮助用户可以立刻查找到问题资源并及时进行改正。

Instantiate实例化

在场景加载过程中,往往伴随着大量的Instantiate实例化操作,比如UI界面实例化、角色/怪物实例化、场景建筑实例化等等。在Instantiate实例化时,引擎底层会查看其相关的资源是否已经被加载,如果没有,则会先加载其相关资源,再进行实例化,这其实是大家遇到的大多数“Instantiate耗时问题”的根本原因,这也是为什么我们在之前的AssetBundle文章中所提倡的资源依赖关系打包并进行预加载,从而来缓解Instantiate实例化时的压力(关于AssetBundle资源的加载,则是另一个很大的Story了,我们会在以后的AssetBundle加载技术专题中进行详细的讲解)。

另一方面,Instantiate实例化的性能开销还体现在脚本代码的序列化上,如果脚本中需要序列化的信息很多,则Instantiate实例化时的时间亦会很长。最直接的例子就是NGUI,其代码中存在很多SerializedField标识,从而在实例化时带来了较多的代码序列化开销。因此,在大家为代码增加序列化信息时,这一点是需要大家时刻关注的。

以上是游戏项目中性能开销最大的三个模块,当然,游戏类型的不同、设计的不同,其他模块仍然会有较大的CPU占用。比如,ARPG游戏中的动画系统和物理系统,音乐休闲类游戏中的音频系统和粒子系统等。对此,我们会在后续的技术专题中进行详细的讲解,敬请期待。

四、代码效率

逻辑代码在一个较为复杂的游戏项目中往往占据较大的性能开销。这种情况在MOBA、ARPG、MMORPG等游戏类型中非常常见。

在项目优化过程中,我们经常会想知道,到底是哪些函数占据了大量的CPU开销。同时,绝大多数的项目中其性能开销都遵循着“二八原则”,即80%的性能开销都集中在20%的函数上。所以,我们在UWA测评报告中将项目中代码占用的CPU开销进行统计,不仅可以提供代码的总体累积CPU占用,还可以更近一步看到函数内部的性能分配,从而帮助大家更快地定位问题函数。

当然,我们还希望可以为大家提供更多的代码性能信息,比如函数任何一帧中更为详细的性能分配、更为准确的截图信息等等。这些都是我们目前正在努力研发的功能,并在后续版本中提供给大家进行使用。

Unity中的批处理优化与GPU-Instancing

Unity大中华区技术经理马瑞曾经为大家带来《Unity中的Daydream开发与实例》,本文马瑞将继续为大家分享Unity中的批处理优化与GPU
Instancing技术。

我们都希望能够在场景中投入一百万个物体,不幸的是,渲染和管理大量的游戏对象是以牺牲CPU和GPU性能为代价的,因为有太多Draw
Call的问题,最后我们必须找到其他的解决方案。

在本文中,我们将讨论两种优化技术,它们可以帮助您减少Unity游戏中的Draw Call数量以提高整体性能:批处理和GPU Instancing。

批处理

开发者在日常工作中遇到的最常见的问题之一是性能不足,这是由于CPU和GPU的运行能力不足。一些游戏可以运行在PC上,但是在移动设备上不行。游戏运行时运行是否流畅受Draw
Call数量的影响很大。有几个解决方案能帮助您解决这个问题。最常见的是批处理,包括Static Batching和Dynamic Batching。

Static Batching可以让引擎降低任何尺寸网格的Draw Call,如下图所示:

要让场景中的物体使用Static Batching,需要将其标记为Static,并在Mesh Renderer中共享相同的材质,因为Static
Batching不会在CPU上做顶点转换,所以它通常比Dynamic
Batching更有效。不过它会使用更多的内存,例如你的场景中有相同物体的多个副本,Unity会将它们组合成一个大网格并可能会增加内存使用。Unity将尽可能多的网格结合到一个静态网格中,并将其作为一个Draw
Call提交。这种方法的缺点是:标记为Static的物体在其生命周期中不能移动。

Dynamic Batching启用时,Unity将尝试自动批量移动物体到一个Draw
Call中。要使物体可以被动态批处理,它们应该共享相同的材质,但是还有一些其他限制:

顶点数量: Dynamic
Batching场景中物体的每个顶点都有一定的开销,因此批处理只适用于少于900个顶点属性的网格物体。举个例子,如果你的着色器使用顶点位置,法线和一个UV,那么你可以动态批处理多达300个顶点;而如果你的着色器使用顶点位置,法线,UV0,UV1和切线,那么只有180个顶点。值得注意的是,属性计数限制可能会在将来更改。

镜像信息: 如果物体包含的Transform具备镜像信息,例如A物体的大小是(1f, 1f, 1f),而B物体的大小则是(-1f, -1f,
-1f),则无法做批处理。

材质 :如果物体使用不同的材质实例,即使它们本质上相同,也不会被批量处理。而Shadow Caster Rendering是个例外。

渲染器:
拥有光照贴图的物体有其他渲染器参数,例如光照贴图索引或光照贴图的偏移与缩放。一般来说,动态光照贴图的游戏对象应该指向要批量处理的完全相同光照贴图的位置。

不能使用Multi-pass着色器的情况: 几乎所有的Unity着色器都支持多个灯光的正向渲染模式(Forward
Rendering),这要求额外的渲染次数,所以绘制 “额外的每像素灯”时不会被批处理;Legacy Deferred(Light Pre-
Pass)渲染路径不能被动态批处理,因为它必须绘制物体两次。

Dynamic Batching通过将所有物体的顶点转换为CPU上的世界空间来工作,所以它只能在渲染Draw
Call的工作量小于CPU顶点转换工作量的时候,才会起到提高性能的作用。当用游戏机或如Metal这样的现代API,Draw
Call的开销通常低得多,Dynamic Batching就无法提高性能了。了解到以上限制后,如果明智地使用批处理,可以显著提高您游戏的性能。

GPU Instancing

提高图形性能的另一个好办法是使用GPU Instancing。GPU Instancing的最大优势是可以减少内存使用和CPU开销。当使用GPU
Instancing时,不需要打开批处理,GPU Instancing的目的是一个网格可以与一系列附加参数一起被推送到GPU。要利用GPU
Instancing,您必须使用相同的材质,且可以传递额外的参数到着色器,如颜色,浮点数等。

Unity从5.4版本开始支持GPU Instancing。 唯一的限制是在游戏物体上要使用相同的材质和网格。 目前支持以下平台:

Windows DX11/DX12 和 SM 4.0 或更高/OpenGL 4.1 或更高

OS X and Linux:OpenGL 4.1 and above

移动:OpenGL ES 3.0 或更高/Metal

PlayStation 4

Xbox One

如果您想要进行进一步的优化,例如减少管理场景物体的开销,您也可以使用Graphics.DrawMeshInstanced方法。
您只需要传递您的网格,材质和附加属性来绘制您的物体。现在的限制是一次最多1023个实例。在Unity
5.6中,我们添加了Graphics.DrawMeshInstancedIndirect的新方法,可以用来指定需要渲染的实例数量。

GPU Instancing案例

要创建支持GPU Instancing的基本标准表面着色器,可以在您的项目里面点击:

Create->Shader->StandardSurfaceShader(Instanced)。

然后,在材质属性中选择新创建的着色器。

虽然实例化的物体共享相同的网格和材质,但您可以使用MaterialPropertyBlock API为每一个物体设置单独的着色器属性。

如果一个游戏对象被标记为“Static”并且打开了Static Batching,那么这个游戏对象就不能进行GPU
Instancing,检视器中会出现一个警告框,提示“静态批处理”标志可以在播放器设置(Player
Settings)中取消。如果游戏对象支持Dynamic
Batching,但是它使用的某个材质可以进行实例化,那么这个游戏对象将不会被批处理,并且将被自动实例化。

当使用Forward Rendering渲染模式,受多个灯光影响的物体无法有效地实例化。只有Base
Pass可以有效地利用实例化,而不是添加的Pass。此外,使用光照贴图或受不同光或Reflection
probe影响的物体无法实例化。如下图所示,您可以在Frame Debug中发现和GPU Instancing相关的Draw Call被标记为“Draw
Mesh(Instanced)”。

GPU Instancing是一个非常强大的功能。在Unity
5.6中,您可以使用Graphics.DrawMeshInstancedIndirect绘制大量网格。在Mac
Pro中,我们能够画出约68万个具有不同颜色的移动立方体并保持稳定的60帧每秒的帧率。

下图是一个示例场景,超过6千个包子在天空中围绕一个大碗飞翔,它们都投射和接收阴影。由于使用了GPU
Instancing,几乎没有性能开销。这里的包子模型使用了StandardSurface Shader(Instanced)。

总结

在本文中,我们描述了用于优化渲染性能的两种最流行的技术:批处理和GPU
Instancing。我们向您展示了如何在实践中使用它们并讨论可能的应用。正因为有诸如批处理和GPU
Instancing等优化技术的存在,我们能够绘制大量的对象并保持稳定的性能。

通过C#使用Advanced-CSharp-Messenger

Advanced CSharp Messenger 属于C#事件的一种。
维基百科中由详细的说明<http://wiki.unity3d.com/index.php?title=Advanced_CSharp_Messenger上周的一天刚巧有朋友问到我这一块的知识,那么我研究出来将它贴在博客中,帮助了他也帮助我自己!哇咔咔。

Advanced CSharp Messenger的特点可以将游戏对象做为参数发送。到底Advanced CSharp
Messenger有什么用呢?先创建一个立方体对象,然后把Script脚本绑定在这个对象中。脚本中有一个方法叫DoSomething()。

写一段简单的代码,通常我们在调用方法的时候需要这样来写。

C#

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
private Script script;



void Awake()



{



GameObject cube = GameObject.Find("Cube");



script = cube.GetComponent<Script();



}








void Update()



{



if(Input.GetMouseButtonDown(0))



{



script.DoSomething();



}



}

代码比较简单,我就不注释了。
原理就是先获取游戏对象,接着获取脚本组件对象,最后通过脚本组件对象去调用对应脚本中的方法,这样的调用方法我们称之为直接调用。

这个例子中我只调用了一个对象的方法,如果说有成千上万个对象,那么这样调用是不是感觉自己的代码非常的丑?因为你需要一个一个的获取对象然后获取脚本组件然后在调用方法。。。。。
(想想都恐怖!!)

下面我们在用Advanced CSharp Messenger来实现事件的调用。按照维基百科中首先把Message.cs
和Callback.cs拷贝在你的工程中。

CallBack.cs

C#

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
public delegate void Callback();



public delegate void Callback<T(T arg1);



public delegate void Callback<T, U(T arg1, U arg2);



public delegate void Callback<T, U, V(T arg1, U arg2, V arg3);

**Message.cs**

C#

/*



* Advanced C# messenger by Ilya Suzdalnitski. V1.0



*



* Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's
"CSharpMessenger Extended".



*



* Features:



* Prevents a MissingReferenceException because of a reference to a destroyed
message handler.



* Option to log all messages



* Extensive error detection, preventing silent bugs



*



* Usage examples:



1\. Messenger.AddListener<GameObject("prop collected", PropCollected);



   Messenger.Broadcast<GameObject("prop collected", prop);



2\. Messenger.AddListener<float("speed changed", SpeedChanged);



   Messenger.Broadcast<float("speed changed", 0.5f);



*



* Messenger cleans up its evenTable automatically upon loading of a new
level.



*



* Don't forget that the messages that should survive the cleanup, should be
marked with Messenger.MarkAsPermanent(string)



*



*/








//#define LOG_ALL_MESSAGES



//#define LOG_ADD_LISTENER



//#define LOG_BROADCAST_MESSAGE



#define REQUIRE_LISTENER








using System;



using System.Collections.Generic;



using UnityEngine;








static internal class Messenger {



#region Internal variables








//Disable the unused variable warning



#pragma warning disable 0414



//Ensures that the MessengerHelper will be created automatically upon start
of the game.



static private MessengerHelper messengerHelper = ( new
GameObject("MessengerHelper") ).AddComponent< MessengerHelper ();



#pragma warning restore 0414








static public Dictionary<string, Delegate eventTable = new
Dictionary<string, Delegate();








//Message handlers that should never be removed, regardless of calling
Cleanup



static public List< string permanentMessages = new List< string ();



#endregion



#region Helper methods



//Marks a certain message as permanent.



static public void MarkAsPermanent(string eventType) {



#if LOG_ALL_MESSAGES



Debug.Log("Messenger MarkAsPermanent \t\"" + eventType + "\"");



#endif








permanentMessages.Add( eventType );



}








static public void Cleanup()



{



#if LOG_ALL_MESSAGES



Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are
removed.");



#endif








List< string messagesToRemove = new List<string();








foreach (KeyValuePair<string, Delegate pair in eventTable) {



bool wasFound = false;








foreach (string message in permanentMessages) {



if (pair.Key == message) {



wasFound = true;



break;



}



}








if (!wasFound)



messagesToRemove.Add( pair.Key );



}








foreach (string message in messagesToRemove) {



eventTable.Remove( message );



}



}








static public void PrintEventTable()



{



Debug.Log("\t\t\t=== MESSENGER PrintEventTable ===");








foreach (KeyValuePair<string, Delegate pair in eventTable) {



Debug.Log("\t\t\t" + pair.Key + "\t\t" + pair.Value);



}








Debug.Log("\n");



}



#endregion








#region Message logging and exception throwing



    static public void OnListenerAdding(string eventType, Delegate
listenerBeingAdded) {



#if LOG_ALL_MESSAGES || LOG_ADD_LISTENER



Debug.Log("MESSENGER OnListenerAdding \t\"" + eventType + "\"\t{" +
listenerBeingAdded.Target + " - " + listenerBeingAdded.Method + "}");



#endif








        if (!eventTable.ContainsKey(eventType)) {



            eventTable.Add(eventType, null );



        }








        Delegate d = eventTable[eventType];



        if (d != null && d.GetType() != listenerBeingAdded.GetType()) {



            throw new ListenerException(string.Format("Attempting to add
listener with inconsistent signature for event type {0}. Current listeners
have type {1} and listener being added has type {2}", eventType,
d.GetType().Name, listenerBeingAdded.GetType().Name));



        }



    }








    static public void OnListenerRemoving(string eventType, Delegate
listenerBeingRemoved) {



#if LOG_ALL_MESSAGES



Debug.Log("MESSENGER OnListenerRemoving \t\"" + eventType + "\"\t{" +
listenerBeingRemoved.Target + " - " + listenerBeingRemoved.Method + "}");



#endif








        if (eventTable.ContainsKey(eventType)) {



            Delegate d = eventTable[eventType];








            if (d == null) {



                throw new ListenerException(string.Format("Attempting to
remove listener with for event type \"{0}\" but current listener is null.",
eventType));



            } else if (d.GetType() != listenerBeingRemoved.GetType()) {



                throw new ListenerException(string.Format("Attempting to
remove listener with inconsistent signature for event type {0}. Current
listeners have type {1} and listener being removed has type {2}", eventType,
d.GetType().Name, listenerBeingRemoved.GetType().Name));



            }



        } else {



            throw new ListenerException(string.Format("Attempting to remove
listener for type \"{0}\" but Messenger doesn't know about this event type.",
eventType));



        }



    }








    static public void OnListenerRemoved(string eventType) {



        if (eventTable[eventType] == null) {



            eventTable.Remove(eventType);



        }



    }








    static public void OnBroadcasting(string eventType) {



#if REQUIRE_LISTENER



        if (!eventTable.ContainsKey(eventType)) {



            throw new BroadcastException(string.Format("Broadcasting message
\"{0}\" but no listener found. Try marking the message with
Messenger.MarkAsPermanent.", eventType));



        }



#endif



    }








    static public BroadcastException
CreateBroadcastSignatureException(string eventType) {



        return new BroadcastException(string.Format("Broadcasting message
\"{0}\" but listeners have a different signature than the broadcaster.",
eventType));



    }








    public class BroadcastException : Exception {



        public BroadcastException(string msg)



            : base(msg) {



        }



    }








    public class ListenerException : Exception {



        public ListenerException(string msg)



            : base(msg) {



        }



    }



#endregion








#region AddListener



//No parameters



    static public void AddListener(string eventType, Callback handler) {



        OnListenerAdding(eventType, handler);



        eventTable[eventType] = (Callback)eventTable[eventType] + handler;



    }








//Single parameter



static public void AddListener<T(string eventType, Callback<T handler) {



        OnListenerAdding(eventType, handler);



        eventTable[eventType] = (Callback<T)eventTable[eventType] +
handler;



    }








//Two parameters



static public void AddListener<T, U(string eventType, Callback<T, U
handler) {



        OnListenerAdding(eventType, handler);



        eventTable[eventType] = (Callback<T, U)eventTable[eventType] +
handler;



    }








//Three parameters



static public void AddListener<T, U, V(string eventType, Callback<T, U, V
handler) {



        OnListenerAdding(eventType, handler);



        eventTable[eventType] = (Callback<T, U, V)eventTable[eventType] +
handler;



    }



#endregion








#region RemoveListener



//No parameters



    static public void RemoveListener(string eventType, Callback handler) {



        OnListenerRemoving(eventType, handler);



        eventTable[eventType] = (Callback)eventTable[eventType] - handler;



        OnListenerRemoved(eventType);



    }








//Single parameter



static public void RemoveListener<T(string eventType, Callback<T handler)
{



        OnListenerRemoving(eventType, handler);



        eventTable[eventType] = (Callback<T)eventTable[eventType] -
handler;



        OnListenerRemoved(eventType);



    }








//Two parameters



static public void RemoveListener<T, U(string eventType, Callback<T, U
handler) {



        OnListenerRemoving(eventType, handler);



        eventTable[eventType] = (Callback<T, U)eventTable[eventType] -
handler;



        OnListenerRemoved(eventType);



    }








//Three parameters



static public void RemoveListener<T, U, V(string eventType, Callback<T, U,
V handler) {



        OnListenerRemoving(eventType, handler);



        eventTable[eventType] = (Callback<T, U, V)eventTable[eventType] -
handler;



        OnListenerRemoved(eventType);



    }



#endregion








#region Broadcast



//No parameters



    static public void Broadcast(string eventType) {



#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE



Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") +
"\t\t\tInvoking \t\"" + eventType + "\"");



#endif



        OnBroadcasting(eventType);








        Delegate d;



        if (eventTable.TryGetValue(eventType, out d)) {



            Callback callback = d as Callback;








            if (callback != null) {



                callback();



            } else {



                throw CreateBroadcastSignatureException(eventType);



            }



        }



    }








//Single parameter



    static public void Broadcast<T(string eventType, T arg1) {



#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE



Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") +
"\t\t\tInvoking \t\"" + eventType + "\"");



#endif



        OnBroadcasting(eventType);








        Delegate d;



        if (eventTable.TryGetValue(eventType, out d)) {



            Callback<T callback = d as Callback<T;








            if (callback != null) {



                callback(arg1);



            } else {



                throw CreateBroadcastSignatureException(eventType);



            }



        }



}








//Two parameters



    static public void Broadcast<T, U(string eventType, T arg1, U arg2) {



#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE



Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") +
"\t\t\tInvoking \t\"" + eventType + "\"");



#endif



        OnBroadcasting(eventType);








        Delegate d;



        if (eventTable.TryGetValue(eventType, out d)) {



            Callback<T, U callback = d as Callback<T, U;








            if (callback != null) {



                callback(arg1, arg2);



            } else {



                throw CreateBroadcastSignatureException(eventType);



            }



        }



    }








//Three parameters



    static public void Broadcast<T, U, V(string eventType, T arg1, U arg2,
V arg3) {



#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE



Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") +
"\t\t\tInvoking \t\"" + eventType + "\"");



#endif



        OnBroadcasting(eventType);








        Delegate d;



        if (eventTable.TryGetValue(eventType, out d)) {



            Callback<T, U, V callback = d as Callback<T, U, V;








            if (callback != null) {



                callback(arg1, arg2, arg3);



            } else {



                throw CreateBroadcastSignatureException(eventType);



            }



        }



    }



#endregion



}








//This manager will ensure that the messenger's eventTable will be cleaned
up upon loading of a new level.



public sealed class MessengerHelper : MonoBehaviour {



void Awake ()



{



DontDestroyOnLoad(gameObject);



}








//Clean up eventTable every time a new level loads.



public void OnDisable() {



Messenger.Cleanup();



}



}

然后就可以开始使用了,Messager.Broadcast()这样就好比我们发送了一条广播。

C#

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
void Update()



{



if(Input.GetMouseButtonDown(0))



{



Messenger.Broadcast("Send");



}



}

在需要这条广播的类中来接受它,同样是刚刚说的Script类。接受广播的标志是
Messager.AddListener()参数1表示广播的名称,参数2表示广播所调用的方法。

C#

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
using UnityEngine;



using System.Collections;








public class Script : MonoBehaviour {








void Awake()



{



Messenger.AddListener( "Send", DoSomething );



}



public void DoSomething()



{



Debug.Log("DoSomething");



}



}

这样一来,只要发送名称为”Send”的方法,就可以在别的类中接收它了。

我们在说说如何通过广播来传递参数,这也是那天那个哥们主要问我的问题。(其实是维基百科上写的不是特别特别的清楚,那哥们误解了)在Callback中可以看出参数最多可以是三个,参数的类型是任意类型,也就是说我们不仅能传递
int float bool 还能传递gameObject类型。

如下所示,发送广播的时候传递了两个参数,参数1是一个游戏对象,参数2是一个int数值。

C#

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
void Update()



{



if(Input.GetMouseButtonDown(0))



{



GameObject cube = GameObject.Find("Cube");



Messenger.Broadcast<GameObject,int("Send",cube,1980);



}



}

然后是接受的地方 参数用 <存在一起。游戏对象也可以完美的传递。

C#

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
using UnityEngine;



using System.Collections;








public class Script : MonoBehaviour {








void Awake()



{



Messenger.AddListener<GameObject,int( "Send", DoSomething );



}



public void DoSomething(GameObject obj,int i)



{



Debug.Log("name " + obj.name + " id =" + i);



}



}

如果传递一个参数

两个参数 <T,T>

三个参数 <T,T,T>

怎么样使用起来还是挺简单的吧?

我觉得项目中最好不要大量的使用代理事件这类的方法(根据需求而定),虽然可以让你的代码非常的简洁,但是它的效率不高大概比直接调用慢5-倍左右吧,就好比美好的东西一定都有瑕疵一样。
还记得Unity自身也提供了一种发送消息的方法吗?,用过的都知道效率也非常低下,虽然我们看不到它具体实现的源码是如何实现的,但是我觉得原理可能也是这样的。
欢迎和大家一起讨论与学习。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×