是否有可能简化检查组合的if语句?

我目前正在为游戏添加声音效果,虽然我目前的代码工作正常,但我正在寻找一种方法来简化它。 基本上,游戏中的每个对象都有一个string值,表示它的材质(即“木头”,“金属”等),当两个对象发生碰撞时,基于这个组合播放音效。 代码基本如下所示:

if( (matA == "metal" && matB == "wood") || (matA == "wood" && matB == "metal") ) { //play sound for metal-wood collision } 

但我想知道是否有一种方法来简化if语句,如下所示:

 if( one of the materials is wood && one of the materials is metal ) { //play sound for metal-wood collision } 

你应该使用一个enum的材料,而不是string,你可以使用一个Dictionary来保存相应的声音组合。 您可以跳过多个if语句,并使用Dictionary自动为每个材料select相应的对象。 例如:

 [Flags] enum Material { Wood=1, Iron=2, Glass=4 //... } Dictionary<Material,SoundObject> sounds = new Dictionary<Material,SoundObject>(); sounds.add(Material.Wood,woodSound); sounds.add(Material.Iron,ironSound); sounds.add(Material.Wood | Material.Iron,woodAndIronSound); // And play corresponding sound directly without any if statement. sounds[object.Material].Play(); sounds[matA | matB].Play(); 

性能优势:

你也将通过使用这种方法来提高性能。 因为枚举值或散列码的绝对整数比较比string比较更容易和更快。 关于字典VS多个if-else语句, if/else if语句系列线性执行; 所以它的性能很大程度上取决于if语句的数目和对象的相等比较器; 而Dictionary则是基于一个Hashtable。 它使用索引优化的集合来存储值,有效地保持访问时间。 这意味着无论字典中有多less个键,通常都会在一个常量时间内访问值,而且在大多数情况下,它会比多个if语句快得多。

性能比较:

在这个例子中我们将比较两种方法的性能:

 //If you want to try, just copy the code and see the result. static Dictionary<char, short> myHashTable = Enumerable.Range((short)'A', (short)'z').ToDictionary((ch) => (char)ch, (sh) => (short)sh); static void Main(string[] args) { System.Diagnostics.Stopwatch SW = new System.Diagnostics.Stopwatch(); short temp = 0; SW.Start(); for(int i=0;i<10000000;i++) temp = getValue('z'); SW.Stop(); Console.WriteLine(SW.ElapsedMilliseconds ); SW.Reset(); SW.Start(); for(int i =0;i<10000000;i++) temp = myHashTable['a']; SW.Stop(); Console.WriteLine(SW.ElapsedMilliseconds); } static short getValue(char input) { if (input == 'a') return (short)'a'; else if (input == 'b') return (short)'b'; else if (input == 'c') return (short)'c'; else if (input == 'd') return (short)'d'; else if (input == 'e') return (short)'e'; else if (input == 'f') return (short)'f'; else if (input == 'g') return (short)'g'; else if (input == 'h') return (short)'h'; else if (input == 'i') return (short)'i'; else if (input == 'j') return (short)'j'; else if (input == 'k') return (short)'k'; else if (input == 'l') return (short)'l'; else if (input == 'm') return (short)'m'; else if (input == 'n') return (short)'n'; else if (input == 'o') return (short)'o'; else if (input == 'p') return (short)'p'; else if (input == 'q') return (short)'q'; else if (input == 'r') return (short)'r'; else if (input == 's') return (short)'s'; else if (input == 't') return (short)'t'; else if (input == 'u') return (short)'u'; else if (input == 'v') return (short)'v'; else if (input == 'w') return (short)'w'; else if (input == 'x') return (short)'x'; else if (input == 'y') return (short)'y'; else if (input == 'z') return (short)'z'; return 0; } 

结果:

if语句有26个项| | 有122个项目的字典。
593 254
579 256
572 252
570 246
587 248
574 291
576 246
685 265
599 282
723 338

这表明字典比if/else if语句快2倍以上。

当你发现自己重复代码时,正常的方法是提取一个方法:

 if (IsWoodAndMetal(matA, matB) || IsWoodAndMetal(matB, matA)) { // play sound for metal-wood collision } 

IsWoodAndMetal被定义为:

 public static bool IsWoodAndMetal(string matA, string matB) { return matA == "wood" && matB == "metal"; } 

这与原始代码一样快,不像所有分配内存的linq / list和string连接解决方​​案,这对频繁的游戏循环来说是个坏消息 ,因为它会导致更频繁的和/或更长的垃圾回收。

||我们可以走得更远 仍然困扰着你,提取:

 public static bool EitherParameterOrder<T>(Func<T, T, bool> func, T a, T b) { return func(a, b) || func(b, a); } 

它现在写道:

 if (EitherParameterOrder(IsWoodAndMetal, matA, matB)) { // play sound for metal-wood collision } 

而且我还是会看到其他解决scheme的性能(除了字典解决scheme,当你有几个条目)。

这可能不是最现代化的解决scheme,但使用素数作为材料的参考可能会提高您的performance。 我知道并且明白,“在必要之前进行优化”是很多程序员不推荐的,然而,在这种情况下,我认为它不会增加代码的复杂性,而是增加了这个(相当平凡的)任务的性能。

 public static class Materials { public static uint Wood = 2, public static uint Metal = 3, public static uint Dirt = 5, // etc... } if(matA*matB == Materials.Wood*Materials.Metal) { //play sound for metal-wood collision } //or with enums but annoying casts are necessary... enum Materials:uint { Wood = 2, Metal = 3, Dirt = 5, // etc... } if((uint)matA*(uint)matB == (uint)Materials.Wood*(uint)Materials.Metal) { //play sound for metal-wood collision } 

这种方法是独立于材料的顺序(交换乘法),并不需要比string或任何更复杂的结构比整数长的比较。

假设你想保留所有的参考数字4字节的整数,最大的4字节整数的平方根在65535左右,那么65535以下的可能的素数就可以得到6550左右的数字,所以没有产品会产生一个整数溢出。 对于任何普通游戏来说,这应该足够了。

我感到不得不张贴我认为最明显的解决scheme,而这个解决scheme似乎还没有人发布。 如果接受的答案适合你,那么去那个。 我只是补充完整性。

首先,定义一个静态辅助方法,它可以同时进行比较:

 public static bool MaterialsMatch(string candidate1, string candidate2, string expected1, string expected2) { return (candidate1 == expected1 && candidate2 == expected2) || (candidate1 == expected2 && candidate2 == expected1); } 

然后在你的if语句中使用它:

 if (MaterialsMatch(matA, matB, "wood", "metal")) { // play sound for wood-metal collision } else if (MaterialsMatch(matA, matB, "wood", "rubber")) { // play sound for wood-rubber collision } else if (MaterialsMatch(matA, matB, "metal", "rubber")) { // play sound for metal-rubber collision } // etc. 

您应该更改垫子{A,B}types为枚举。 这将被定义如下:

 [Flags] enum Materials { Wood = 1, Metal = 2, Concrete = 4, // etc ... } 

那么代码将如下所示:

 Meterials matA = Materials.Wood; Meterials matB = Materials.Metal; if ((matA | matB) == (Materials.Metal | Materials.Wood)) { // Do something } 

这里唯一的问题是,matA现在可以同时是木头和金属types,但是这个问题也出现在string解决scheme中。

—编辑—

也可以创build木材和金属的枚举别名

 [Flags] enum Materials { Wood = 1, Metal = 2, WoodMetal = Wood | Metal, Concrete = 4, // etc } 

那么代码将如下所示:

 Materials matA = Materials.Wood; Materials matB = Materials.Metal; if ((matA | matB) == Materials.WoodMetal) { // Do something }