どうも毎日2万人弱がプレイする
英語学習ソーシャルゲーム、英語物語の開発者、ゴン氏です。
英語物語では火属性・水属性・風属性、火水属性、水風属性、風火属性、火水風属性
の7つの属性があるのですが、
そのダメージ計算や、属性の演算(火水パネルに反応するのは、火属性と水属性)を定義するのに、
もともと下記のような汚いコードを書いてたんですが、
一念発起してきれいなコードに書き直したのでその備忘録です。
もし、もっとCoolな方法あったら、ぜひ教えてください😊
追記:コメントでビット演算を使ったスーパーCoolな方法を教えてもらってます。
ぜひコメントまでお読み頂けると嬉しいです。
#属性を表すenum
OneEventEnumAttribute.cs
public enum OneEventEnumAttribute
{
Dami = 0,
Fire = 1,
Water = 2,
Wind = 3,
FireWater = 4,
WaterWind = 5,
WindFire = 6,
FireWaterWind = 7,
}
#以前の汚いコード
OneEventAttributeDamageLogic.cs
private float OneEventAttributeDamageLogic(OneEventEnumAttribute from, OneEventEnumAttribute to)
{
if (from == to)
{
return 1;
}
switch (from)
{
case OneEventEnumAttribute.Fire:
switch (to)
{
case OneEventEnumAttribute.Fire:
return 1f;
case OneEventEnumAttribute.Water:
return 0.5f;
case OneEventEnumAttribute.Wind:
return 1.5f;
case OneEventEnumAttribute.FireWater:
return 0.75f;
case OneEventEnumAttribute.WaterWind:
return 1f;
case OneEventEnumAttribute.WindFire:
return 1.25f;
case OneEventEnumAttribute.FireWaterWind:
return 1f;
default:
break;
}
break;
case OneEventEnumAttribute.Water:
switch (to)
{
case OneEventEnumAttribute.Fire:
return 1.5f;
case OneEventEnumAttribute.Water:
return 1f;
case OneEventEnumAttribute.Wind:
return 0.5f;
case OneEventEnumAttribute.FireWater:
return 1.25f;
case OneEventEnumAttribute.WaterWind:
return 0.75f;
case OneEventEnumAttribute.WindFire:
return 1f;
case OneEventEnumAttribute.FireWaterWind:
return 1f;
default:
break;
}
break;
case OneEventEnumAttribute.Wind:
switch (to)
{
case OneEventEnumAttribute.Fire:
return 0.5f;
case OneEventEnumAttribute.Water:
return 1.5f;
case OneEventEnumAttribute.Wind:
return 1f;
case OneEventEnumAttribute.FireWater:
return 1f;
case OneEventEnumAttribute.WaterWind:
return 1.25f;
case OneEventEnumAttribute.WindFire:
return 0.75f;
case OneEventEnumAttribute.FireWaterWind:
return 1f;
default:
break;
}
break;
case OneEventEnumAttribute.FireWater:
switch (to)
{
case OneEventEnumAttribute.Fire:
return 1.25f;
case OneEventEnumAttribute.Water:
return 0.75f;
case OneEventEnumAttribute.Wind:
return 1f;
case OneEventEnumAttribute.FireWater:
return 1f;
case OneEventEnumAttribute.WaterWind:
return 0.875f;
case OneEventEnumAttribute.WindFire:
return 1.125f;
case OneEventEnumAttribute.FireWaterWind:
return 1f;
default:
break;
}
break;
case OneEventEnumAttribute.WaterWind:
switch (to)
{
case OneEventEnumAttribute.Fire:
return 1f;
case OneEventEnumAttribute.Water:
return 1.25f;
case OneEventEnumAttribute.Wind:
return 0.75f;
case OneEventEnumAttribute.FireWater:
return 1.125f;
case OneEventEnumAttribute.WaterWind:
return 1f;
case OneEventEnumAttribute.WindFire:
return 0.875f;
case OneEventEnumAttribute.FireWaterWind:
return 1f;
default:
break;
}
break;
case OneEventEnumAttribute.WindFire:
switch (to)
{
case OneEventEnumAttribute.Fire:
return 0.75f;
case OneEventEnumAttribute.Water:
return 1f;
case OneEventEnumAttribute.Wind:
return 1.25f;
case OneEventEnumAttribute.FireWater:
return 0.875f;
case OneEventEnumAttribute.WaterWind:
return 1.125f;
case OneEventEnumAttribute.WindFire:
return 1f;
case OneEventEnumAttribute.FireWaterWind:
return 1f;
default:
break;
}
break;
case OneEventEnumAttribute.FireWaterWind:
return 1f;
default:
return 1f;
}
return 1f;
}
OneEventAttributeLogic.cs
public bool CanAttack(OneEventEnumAttribute attacker, OneEventEnumAttribute panelAttributle)
{
switch (panelAttributle)
{
case OneEventEnumAttribute.Fire:
switch (attacker)
{
case OneEventEnumAttribute.Fire:
case OneEventEnumAttribute.FireWater:
case OneEventEnumAttribute.WindFire:
case OneEventEnumAttribute.FireWaterWind:
return true;
}
break;
case OneEventEnumAttribute.Water:
switch (attacker)
{
case OneEventEnumAttribute.Water:
case OneEventEnumAttribute.FireWater:
case OneEventEnumAttribute.WaterWind:
case OneEventEnumAttribute.FireWaterWind:
return true;
}
break;
case OneEventEnumAttribute.Wind:
switch (attacker)
{
case OneEventEnumAttribute.Wind:
case OneEventEnumAttribute.WaterWind:
case OneEventEnumAttribute.WindFire:
case OneEventEnumAttribute.FireWaterWind:
return true;
}
break;
case OneEventEnumAttribute.FireWater:
switch (attacker)
{
case OneEventEnumAttribute.Fire:
case OneEventEnumAttribute.Water:
case OneEventEnumAttribute.FireWater:
case OneEventEnumAttribute.WaterWind:
case OneEventEnumAttribute.WindFire:
case OneEventEnumAttribute.FireWaterWind:
return true;
}
break;
case OneEventEnumAttribute.WaterWind:
switch (attacker)
{
case OneEventEnumAttribute.Water:
case OneEventEnumAttribute.Wind:
case OneEventEnumAttribute.FireWater:
case OneEventEnumAttribute.WaterWind:
case OneEventEnumAttribute.WindFire:
case OneEventEnumAttribute.FireWaterWind:
return true;
}
break;
case OneEventEnumAttribute.WindFire:
switch (attacker)
{
case OneEventEnumAttribute.Fire:
case OneEventEnumAttribute.Wind:
case OneEventEnumAttribute.FireWater:
case OneEventEnumAttribute.WaterWind:
case OneEventEnumAttribute.WindFire:
case OneEventEnumAttribute.FireWaterWind:
return true;
}
break;
case OneEventEnumAttribute.FireWaterWind:
return true;
default:
Debug.LogError("no attribute like " + attacker.ToString());
break;
}
return false;
}
}
#今回きれいにしたポイント
- enumの拡張メソッドとして定義する。
- 複合属性は、単属性に分けてから計算する。
コードの行数的にも半分以下になったし、なにより、読みやすくなりました。
きもちいぃ!
#今回きれいにしたコード
OneEventEnumAttribute.cs
public enum OneEventEnumAttribute
{
Dami = 0,
Fire = 1,
Water = 2,
Wind = 3,
FireWater = 4,
WaterWind = 5,
WindFire = 6,
FireWaterWind = 7,
}
public static partial class OneEventEnumAttributeExtend
{
public static bool Contains(this OneEventEnumAttribute param, OneEventEnumAttribute target)
{
var thisAttributes = param.ConvertSingleAttributeArray();
var targetAttributes = target.ConvertSingleAttributeArray();
foreach (var targetAttribute in targetAttributes)
{
foreach (var thisAttribute in thisAttributes)
{
if (targetAttribute == thisAttribute)
{
return true;
}
}
}
return false;
}
public static float GetDamageRate(this OneEventEnumAttribute param, OneEventEnumAttribute target)
{
var thisAttributes = param.ConvertSingleAttributeArray();
var targetAttributes = target.ConvertSingleAttributeArray();
int counta = 0;
float sumRate = 0;
foreach (var targetAttribute in targetAttributes)
{
foreach (var thisAttribute in thisAttributes)
{
sumRate += GetSingleDamageRate(thisAttribute, targetAttribute);
counta++;
}
}
return sumRate / counta;
}
private static float GetSingleDamageRate(this OneEventEnumAttribute param, OneEventEnumAttribute target)
{
if (param == target)
{
return 1f;
}
if (param.IsStrongFor(target))
{
return 1.5f;
}
return 0.5f;
}
private static bool IsStrongFor(this OneEventEnumAttribute param, OneEventEnumAttribute target)
{
if (param == target)
{
return false;
}
switch (param)
{
case OneEventEnumAttribute.Fire:
return target == OneEventEnumAttribute.Wind;
case OneEventEnumAttribute.Water:
return target == OneEventEnumAttribute.Fire;
case OneEventEnumAttribute.Wind:
return target == OneEventEnumAttribute.Water;
default:
throw new ArgumentOutOfRangeException(nameof(param), param, null);
}
}
private static OneEventEnumAttribute[] ConvertSingleAttributeArray(this OneEventEnumAttribute param)
{
switch (param)
{
case OneEventEnumAttribute.Dami:
return new OneEventEnumAttribute[0];
case OneEventEnumAttribute.Fire:
return new OneEventEnumAttribute[1] {OneEventEnumAttribute.Fire};
case OneEventEnumAttribute.Water:
return new OneEventEnumAttribute[1] {OneEventEnumAttribute.Water};
case OneEventEnumAttribute.Wind:
return new OneEventEnumAttribute[1] {OneEventEnumAttribute.Wind};
case OneEventEnumAttribute.FireWater:
return new OneEventEnumAttribute[2] {OneEventEnumAttribute.Fire, OneEventEnumAttribute.Water};
case OneEventEnumAttribute.WaterWind:
return new OneEventEnumAttribute[2] {OneEventEnumAttribute.Water, OneEventEnumAttribute.Wind};
case OneEventEnumAttribute.WindFire:
return new OneEventEnumAttribute[2] {OneEventEnumAttribute.Wind, OneEventEnumAttribute.Fire};
case OneEventEnumAttribute.FireWaterWind:
return new OneEventEnumAttribute[3]
{OneEventEnumAttribute.Fire, OneEventEnumAttribute.Water, OneEventEnumAttribute.Wind};
default:
throw new ArgumentOutOfRangeException(nameof(param), param, null);
}
}
}
#参考:修正したコードを検証するテストコード(NUnit)
OneEventEnumAttributeTest.cs
using NUnit.Framework;
public class OneEventEnumAttributeTest
{
[Test]
public void TestGetDamageRate()
{
OneEventEnumAttribute[] attackerAttributes = new OneEventEnumAttribute[7]
{
OneEventEnumAttribute.Fire, OneEventEnumAttribute.Water, OneEventEnumAttribute.Wind,
OneEventEnumAttribute.FireWater, OneEventEnumAttribute.WaterWind, OneEventEnumAttribute.WindFire,
OneEventEnumAttribute.FireWaterWind
};
OneEventEnumAttribute[] targetAttributes = new OneEventEnumAttribute[7]
{
OneEventEnumAttribute.Fire, OneEventEnumAttribute.Water, OneEventEnumAttribute.Wind,
OneEventEnumAttribute.FireWater, OneEventEnumAttribute.WaterWind, OneEventEnumAttribute.WindFire,
OneEventEnumAttribute.FireWaterWind
};
foreach (var attackerAttribute in attackerAttributes)
{
foreach (var targetAttribute in targetAttributes)
{
var expectedResult = new OneEventAttributeDamageLogic().Caluculate(attackerAttribute, targetAttribute);
var damageRate = attackerAttribute.GetDamageRate(targetAttribute);
Assert.AreEqual(expectedResult, damageRate, "from:" + attackerAttribute + " to:" + targetAttribute);
}
}
}
[Test]
public void TestContains()
{
OneEventEnumAttribute[] attackerAttributes = new OneEventEnumAttribute[7]
{
OneEventEnumAttribute.Fire, OneEventEnumAttribute.Water, OneEventEnumAttribute.Wind,
OneEventEnumAttribute.FireWater, OneEventEnumAttribute.WaterWind, OneEventEnumAttribute.WindFire,
OneEventEnumAttribute.FireWaterWind
};
OneEventEnumAttribute[] targetAttributes = new OneEventEnumAttribute[7]
{
OneEventEnumAttribute.Fire, OneEventEnumAttribute.Water, OneEventEnumAttribute.Wind,
OneEventEnumAttribute.FireWater, OneEventEnumAttribute.WaterWind, OneEventEnumAttribute.WindFire,
OneEventEnumAttribute.FireWaterWind
};
foreach (var attackerAttribute in attackerAttributes)
{
foreach (var targetAttribute in targetAttributes)
{
var expectedResult = new OneEventAttributeLogic().CanAttack(attackerAttribute, targetAttribute);
var nowResult = attackerAttribute.Contains(targetAttribute);
Assert.AreEqual(expectedResult, nowResult, "from:" + attackerAttribute + " to:" + targetAttribute);
}
}
}
}
#おわりに
開発者のツイッターはこちら
プログラマさん。ゲーム開発者さん。仲良くしたいです。
英語学習ゲーム 英語物語で遊んでみてね♪