游戏中用到的各种公式一般写死在程序里,虽然简单快捷,但会有些问题
不具备通用性,公式越多越复杂,向其他项目转移就越容易出问题。
不利于工种协作,策划要反复调整公式看数值平衡性,就需要依赖程序,增加沟通成本。
不利于调试,只能在游戏运行时看到结果,内部细节被忽略。
批量测试验证数值不方便。
运算操作可视化编辑。
通过输入设定的值,可以自动计算得到结果。
可以查看每一步操作的结果和运算细节。
可以反复操作验证数值。
将公式运算拆解为一步多值的简单运算,最后依次计算单步运算,得到每一步的值并展示。
学习shder的编写思路,将if语句通过step从逻辑判断降解为纯运算,以实现对简单逻辑的支持。

设定公式输入,程序需输入这些变量值

每一步运算配置,这是一个考虑暴击率的伤害计算公式,每一步的运算结果按name存储在cache里

测试界面,通过在这里输入变量值,计算后查看每一步的计算结果和计算细节,可反复运算
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace QSGame.Base
{
[Serializable]
public enum QS_FormulaNumType
{
Variable,
Constant,
}
[Serializable]
public enum QS_FormulaOperateType
{
Add,
Sub,
Mul,
Div,
Step,
Random,
}
[Serializable]
public class QS_FormulaNum
{
public QS_FormulaNumType _NumType = QS_FormulaNumType.Constant;
public string _NumStrValue;
public float _NumFloatValue;
}
[Serializable]
public class QS_FormulaOperate
{
public string _Name;
public QS_FormulaOperateType _Type = QS_FormulaOperateType.Add;
public List<QS_FormulaNum> _Nums = new List<QS_FormulaNum>();
}
[CreateAssetMenu(menuName = "QSGame/Base/Config/FormulaConfig", fileName = "New FormulaConfig", order = 0)]
public class QS_FormulaConfig : QS_Config
{
public List<string> _InputVarName = new List<string>();
public List<QS_FormulaOperate> _Operatea = new List<QS_FormulaOperate>();
public QS_Dictionary<string, float> _Cache = new QS_Dictionary<string, float>();
public void ClearCache()
{
_Cache.Clear();
}
public void SetInputVar(string varName,float varNum)
{
if (!_InputVarName.Contains(varName))
{
Debug.LogError("QS_FormulaConfig SetInputVar err,varName not exsit:" + varName);
return;
}
_Cache[varName] = varNum;
}
public float GetResult(string varName)
{
if (!_Cache.ContainsKey(varName))
{
Debug.LogError("QS_FormulaConfig GetResult err,varName not exsit:" + varName);
return 0;
}
return _Cache[varName];
}
private float getOneNum(QS_FormulaNum formulaNum)
{
float num = 0;
switch (formulaNum._NumType)
{
case QS_FormulaNumType.Variable:
if (!_Cache.ContainsKey(formulaNum._NumStrValue))
{
Debug.LogError("QS_FormulaConfig getOneNum err,numStrValue not exsit in cache:" + formulaNum._NumStrValue);
return num;
}
num = _Cache[formulaNum._NumStrValue];
break;
case QS_FormulaNumType.Constant:
num = formulaNum._NumFloatValue;
break;
default:
Debug.LogError("QS_FormulaConfig getOneNum err,numType not handled:" + formulaNum._NumType.ToString());
break;
}
return num;
}
public void Calculate()
{
float result = 0;
foreach (QS_FormulaOperate formulaOperate in _Operatea)
{
switch (formulaOperate._Type)
{
case QS_FormulaOperateType.Add:
if (formulaOperate._Nums.Count < 2)
{
Debug.LogError("QS_FormulaConfig Calculate err,num count err, [Add]");
return;
}
result = getOneNum(formulaOperate._Nums[0]) + getOneNum(formulaOperate._Nums[1]);
break;
case QS_FormulaOperateType.Sub:
if (formulaOperate._Nums.Count < 2)
{
Debug.LogError("QS_FormulaConfig Calculate err,num count err, [Sub]");
return;
}
result = getOneNum(formulaOperate._Nums[0]) - getOneNum(formulaOperate._Nums[1]);
break;
case QS_FormulaOperateType.Mul:
if (formulaOperate._Nums.Count < 2)
{
Debug.LogError("QS_FormulaConfig Calculate err,num count err, [Mul]");
return;
}
result = getOneNum(formulaOperate._Nums[0]) * getOneNum(formulaOperate._Nums[1]);
break;
case QS_FormulaOperateType.Div:
if (formulaOperate._Nums.Count < 2)
{
Debug.LogError("QS_FormulaConfig Calculate err,num count err, [Div]");
return;
}
result = getOneNum(formulaOperate._Nums[0]) / getOneNum(formulaOperate._Nums[1]);
break;
case QS_FormulaOperateType.Step:
if (formulaOperate._Nums.Count < 2)
{
Debug.LogError("QS_FormulaConfig Calculate err,num count err, [Step]");
return;
}
result = getOneNum(formulaOperate._Nums[0]) <= getOneNum(formulaOperate._Nums[1]) ? 1f : 0f;
break;
case QS_FormulaOperateType.Random:
result = UnityEngine.Random.Range(1,101);
break;
}
_Cache[formulaOperate._Name] = result;
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(QS_FormulaConfig), true)]
public class QS_FormulaConfigEditor : Editor
{
private QS_FormulaConfig _target = null;
void OnEnable()
{
_target = target as QS_FormulaConfig;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Clear Cache"))
{
_target._Cache.Clear();
foreach (string needVarName in _target._InputVarName)
{
_target._Cache[needVarName] = 0;
}
}
if (GUILayout.Button("Calculate"))
{
_target.Calculate();
}
if (GUI.changed)
EditorUtility.SetDirty(_target);
}
}
[CustomPropertyDrawer(typeof(QS_FormulaNum), true)]
public class QS_FormulaNumDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
SerializedProperty numTypeProperty = property.FindPropertyRelative("_NumType");
SerializedProperty numStrValueProperty = property.FindPropertyRelative("_NumStrValue");
SerializedProperty numFloatValueProperty = property.FindPropertyRelative("_NumFloatValue");
Rect numTypePropertyRect = new Rect(position.x, position.y, position.width * 0.5f
, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(numTypePropertyRect, numTypeProperty, new GUIContent(""));
Rect numValuePropertyRect = new Rect(numTypePropertyRect.x + numTypePropertyRect.width, position.y, position.width * 0.5f
, EditorGUIUtility.singleLineHeight);
switch (numTypeProperty.enumValueIndex)
{
case (int)QS_FormulaNumType.Variable:
EditorGUI.PropertyField(numValuePropertyRect, numStrValueProperty, new GUIContent(""));
break;
case (int)QS_FormulaNumType.Constant:
EditorGUI.PropertyField(numValuePropertyRect, numFloatValueProperty, new GUIContent(""));
break;
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight;
}
}
[CustomPropertyDrawer(typeof(QS_FormulaOperate), true)]
public class QS_FormulaOperateDrawer : PropertyDrawer
{
private const int TOP_SPACE = 4;
private const int BOTTOM_SPACE = 4;
private const int BETWEEN_SPACE = 3;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
SerializedProperty nameProperty = property.FindPropertyRelative("_Name");
SerializedProperty typeProperty = property.FindPropertyRelative("_Type");
SerializedProperty numsProperty = property.FindPropertyRelative("_Nums");
GUI.Box(position, GUIContent.none, EditorStyles.helpBox);
float posY = position.y;
posY += TOP_SPACE;
Rect namePropertyRect = new Rect(position.x + 0.01f * position.width / 2f, posY, position.width * 0.99f
, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(namePropertyRect, nameProperty,new GUIContent("Name"));
posY += namePropertyRect.height + BETWEEN_SPACE;
Rect typePropertyRect = new Rect(position.x + 0.01f * position.width / 2f, posY, position.width * 0.99f
, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(typePropertyRect, typeProperty, new GUIContent("Type"));
posY += typePropertyRect.height + BETWEEN_SPACE;
Rect tipRect = new Rect(position.x + 0.01f * position.width / 2f, posY, position.width * 0.99f
, EditorGUIUtility.singleLineHeight);
string tip = "";
switch (typeProperty.enumValueIndex)
{
case (int)QS_FormulaOperateType.Add:
tip = "Num1 + Num2";
break;
case (int)QS_FormulaOperateType.Sub:
tip = "Num1 - Num2";
break;
case (int)QS_FormulaOperateType.Mul:
tip = "Num1 * Num2";
break;
case (int)QS_FormulaOperateType.Div:
tip = "Num1 / Num2";
break;
case (int)QS_FormulaOperateType.Step:
tip = "if Num1 <= Num2 get 1 ,else get 0";
break;
case (int)QS_FormulaOperateType.Random:
tip = "get random num in [1,100]";
break;
}
EditorGUI.LabelField(tipRect, new GUIContent(tip));
posY += tipRect.height + BETWEEN_SPACE;
float NumsHeight = EditorGUI.GetPropertyHeight(numsProperty, numsProperty.isExpanded);
Rect numsRect = new Rect(position.x + 0.01f * position.width / 2f, posY, position.width * 0.99f
, NumsHeight);
EditorGUI.PropertyField(numsRect, numsProperty, new GUIContent("Nums"));
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
SerializedProperty numsProperty = property.FindPropertyRelative("_Nums");
float NumsHeight = EditorGUI.GetPropertyHeight(numsProperty, numsProperty.isExpanded);
float height = 0;
height += EditorGUIUtility.singleLineHeight * 3 + NumsHeight + BETWEEN_SPACE * 3;
height += TOP_SPACE + BOTTOM_SPACE;
return height;
}
}
#endif
}
这里只支持了加减乘除,随机,step运算,如果需要其他运算可自行扩充。
QS_FormulaConfig formula;
//清空cache
formula.ClearCache();
//设置需输入的变量
formula.SetInputVar("攻击者的攻击",10);
//计算
formula.Calculate();
//得到计算结果
formula.GetResult("最终伤害");
formula.GetResult("是否暴击");