boxmoe_header_banner_img

菜就多练喵

文章导读

[C#][Untiy][Ib_Core]Ib_Calculator——简易数值公式计算


avatar
Ib_Mccf 2026年3月23日 46

特点:使用Unity VisualScripting 中自带的NCalc库实现对string类型的复杂数值公式计算。

*可自行拓展,字符串公式中的属性([A.ATK])需要用[ ]包裹,并在Ib_Calculator拓展该属性名的解析。NCalc还支持逻辑运算,如 “Min([B.MAXHP] * 0.1,[A.ATK]*5)“,这是对目标造成其10%的最大生命值,但是最大不超过攻击者攻击力的五倍。详细使用方法可自行了解NCalc的使用。


示例:

如字符串公式 [A.ATK] * 2 + [B.MAXHP] * 0.1” , 这是造成2倍攻击力,并附加目标10%的最大生命值。
=> result = 10 * 2 + 2000 * 0.1 = 220

void Start()
{
    //攻击者属性
    Ib_Calculator.NumericBoxForCal attacker = new Ib_Calculator.NumericBoxForCal()
    {
        MaxHp = 100f,
        Hp = 100f,
        Atk = 10f,
        Def = 10f,
    };
    //受击者属性
    Ib_Calculator.NumericBoxForCal defender = new Ib_Calculator.NumericBoxForCal()
    {
        MaxHp = 2000f,
        Hp = 2000f, 
        Atk = 10f,
        Def = 10f,
    };

    var result = Ib_Calculator.GetDamageExpressionValue("[A.ATK] * 2 + [B.MAXHP] * 0.1", attacker, defender);
    Ib_Log.Info($"计算结果:{result}");
}


源码

using System;
using System.Collections.Generic;
using System.Data;
using System.Text.RegularExpressions;
using Unity.VisualScripting.Dependencies.NCalc;
using UnityEngine;

namespace Ib_Core
{
    /// <summary>
    /// 负责数值计算
    /// </summary>
    public static class Ib_Calculator
    {
        //数据源
        public interface INumericBoxForCal
        {
            public float MaxHp { get; }
            public float Hp { get; }
            public float Atk { get; }
            public float Def { get; }
        }
        public struct NumericBoxForCal : INumericBoxForCal
        {
            public float MaxHp { get; set; }
            public float Hp { get; set; }
            public float Atk { get; set; }
            public float Def { get; set; }
        }

        public static float GetDamageExpressionValue(string formula, INumericBoxForCal source, INumericBoxForCal target)
        {
            if (string.IsNullOrEmpty(formula))return 0f;
            //如果是纯数字直接运行,否则交给NCalc进行计算
            if (EfficientNCalcEvaluator.IsPureNumber(formula) && float.TryParse(formula, out var value)) return value;
            return CalculateDamage_NCalc(GetDamageExpression(formula), source, target,formula);
        }

        #region 解析变量
            
        [System.ThreadStatic] private static INumericBoxForCal _currentSource; // 当前的 a
        [System.ThreadStatic] private static INumericBoxForCal _currentTarget; // 当前的 b
        /// <summary>
        /// 每次计算公式前注入计算数据
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        private static void InsertSourceAndTarget(INumericBoxForCal? source, INumericBoxForCal? target)
        {
            _currentSource = source;
            _currentTarget = target;
        }
        private static float GetNumeric(string name)
        {
            switch (name)
            {
                case "A.MAXHP" : return _currentSource?.MaxHp ?? 0f;
                case "A.HP" : return _currentSource?.Hp ?? 0f;
                case "A.ATK" : return _currentSource?.Atk ?? 0f;
                case "A.DEF" : return _currentSource?.Def ?? 0f;

                case "B.MAXHP" : return _currentTarget?.MaxHp ?? 0f;
                case "B.HP" : return _currentTarget?.Hp ?? 0f;
                case "B.ATK" : return _currentTarget?.Atk ?? 0f;
                case "B.DEF" : return _currentTarget?.Def ?? 0f;

                default: return 0f;
            }
        }

        #endregion

        #region 处理伤害公式
        //缓存公式的Expression,相同公式复用同一个Expression
        public static Dictionary<string, Expression> DamageExpressions { get; private set; } = new(512);

        //获取缓存的Expression
        public static Expression GetDamageExpression(string formula)
        {
            if (!DamageExpressions.TryGetValue(formula, out var expression))
            {
                var newFormula = formula.ToUpper();
                // var newFormula = FormulaHelper.WrapVariablesInBrackets(formula.ToUpper());
                expression = new Expression(newFormula, EvaluateOptions.IgnoreCase);
                expression.EvaluateParameter += (flow, name, args) =>
                {
                    args.Result = GetNumeric(name);
                };
                if (DamageExpressions.TryAdd(formula, expression))
                {
                    Ib_Log.Info($"载入公式{newFormula}");
                }
            }

            return expression;
        }
        
        public static float CalculateDamage_NCalc(Expression damageExpr,INumericBoxForCal attacker, INumericBoxForCal defender,string formula = null)
        {
            try
            {
                //注入源数据和目标数据
                InsertSourceAndTarget(attacker, defender);            

                return System.Convert.ToSingle(damageExpr.Evaluate(null));
            }
            catch (Exception e)
            {
                Debug.LogError($"计算伤害公式时出错: {e.Message}.[{formula?? "Null"}].");
                return 0f;
            }
        }
        
        #endregion
    }
    
    /// <summary>
    /// 提供高效的正则表达式计算
    /// </summary>
    public static class EfficientNCalcEvaluator
    {
        // 预编译正则表达式以获得最佳性能
        private static readonly Regex _isNumberRegex = new Regex(@"^\s*[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?\s*$", RegexOptions.Compiled);

        public static bool IsPureNumber(string expression)
        {
            if (string.IsNullOrWhiteSpace(expression))
                return false;

            return _isNumberRegex.IsMatch(expression);
        }
    }
}


评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字

插入代码