在Unity中玩转表达式树:解锁游戏逻辑的动态魔法
在Unity 2021 LTS版本中,结合Burst Compiler可以将表达式树编译后的委托性能提升至接近原生C++代码水平,特别适合高频调用的游戏系统(如物理伤害计算、AI决策等)
参考:git-amend
Expression
一、为什么要学习表达式树?
传统Unity开发面临三大痛点:
- 逻辑固化 - 编译后无法修改行为逻辑
- 组件强耦合 - GameObject之间依赖关系复杂
- 动态性不足 - 难以实现运行时逻辑热替换
表达式树(Expression Trees)技术通过将代码转换为可操作的数据结构,完美解决了这些问题。它允许我们:
- 运行时动态构建逻辑运行时动态构建逻辑
- 实现组件间的弱耦合通信实现组件间的弱耦合通信
- 支持可视化配置游戏行为支持可视化配置游戏行为
二、核心应用场景
- 动态技能系统
- 数据驱动AI
- 通过JSON配置行为树
- 运行时解析并生成表达式
- 实现无需重新编译的AI逻辑更新
- MOD支持系统
- 玩家自定义逻辑脚本
- 安全沙箱运行表达式
- 实时加载玩家创作内容
三、实战演示
一、 属性获取器
传统模式缺陷:
- if (_compiledCondition(ContextObject))
复制代码 - public int GetPlayerStat(Player p, string statName)
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- case "Health": return p.Health;
复制代码- case "Mana": return p.Mana;
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码 表达式树:
- if (_compiledCondition(ContextObject))
复制代码 - "!=" => Expression.NotEqual(left, right),
复制代码- public class ExpressionTreeDemo : MonoBehaviour "!=" => Expression.NotEqual(left, right),
复制代码- "==" => Expression.Equal(left, right), "!=" => Expression.NotEqual(left, right),
复制代码- Player player = new () "!=" => Expression.NotEqual(left, right), Health = 100 "!=" => Expression.NotEqual(left, right),;
复制代码- Func<Player, int> healthProperty = CreatePropertyGetter<Player, int>("Health");
复制代码- Debug.Log($"Player Health: "!=" => Expression.NotEqual(left, right),healthProperty(player) "!=" => Expression.NotEqual(left, right),");
复制代码- [/code][code]public int GetPlayerStat(Player player, string statName) "!=" => Expression.NotEqual(left, right),
复制代码- Func<Player, int> propertyGetter = CreatePropertyGetter<Player, int>(statName);
复制代码- return propertyGetter(player);
复制代码- [/code][code]public Func<T, TProperty> CreatePropertyGetter<T, TProperty>(string propertyName) "!=" => Expression.NotEqual(left, right),
复制代码- ParameterExpression param = Expression.Parameter(typeof(T), "x");
复制代码- MemberExpression property = Expression.Property(param, propertyName);
复制代码- Expression<Func<T, TProperty>> lambda = Expression.Lambda<Func<T, TProperty>>(property, param);
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码 应用场景:获取对象属性 技术要点:
- 属性访问表达式属性访问表达式:Expression.Property
二、条件触发系统
- if (_compiledCondition(ContextObject))
复制代码 - public class ConditionTrigger : MonoBehaviour
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- public string ConditionExpression = "Player.Health.CurrentHP < 0.3";
复制代码- public GameObject ContextObject;
复制代码- [/code][code]private Func<GameObject, bool> _compiledCondition;
复制代码- private static Dictionary<string, Func<GameObject, bool>> _cache = new();
复制代码- [/code][code]"==" => Expression.Equal(left, right),
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- if (!_cache.TryGetValue(ConditionExpression, out _compiledCondition))
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- var elements = ConditionExpression.Split('.');
复制代码- var rootObj = Expression.Parameter(typeof(GameObject), "context");
复制代码- Expression accessChain = rootObj;
复制代码- foreach (var element in elements.Skip(1))
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- accessChain = Expression.PropertyOrField(accessChain, element);
复制代码- [/code][code]var conditionExpr = BuildComparison(accessChain, "<", Expression.Constant(0.3f));
复制代码- _compiledCondition = Expression.Lambda<Func<GameObject, bool>>(conditionExpr, rootObj).Compile();
复制代码- _cache[ConditionExpression] = _compiledCondition;
复制代码- [/code][code]void Update()
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码 应用场景:动态游戏事件触发 优势对比:
传统方式表达式树方案硬编码条件判断支持运行时修改条件逻辑需要预定义所有情况可通过配置文件动态加载三、行为链组合
- if (_compiledCondition(ContextObject))
复制代码 - "!=" => Expression.NotEqual(left, right),
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- return operatorStr switch
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- "<" => Expression.LessThan(left, right),
复制代码- [/code][code]"==" => Expression.Equal(left, right),
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- "<=" => Expression.LessThanOrEqual(left, right),
复制代码- [/code][code]_ => throw new NotSupportedException($"不支持的运算符: _comboSystem.ActionExpressions.Add(GetComboExpression(typeof(AttackAnimation), nameof(AttackAnimation.Play), Expression.Constant("SwordSwing2")));operatorStrprivate ComboSystem _comboSystem;")
复制代码- private ComboSystem _comboSystem;
复制代码- using System.Collections.Generic;
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- public class ComboExample : MonoBehaviour
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- [/code][code]void Start()
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- _comboSystem = new ComboSystem();
复制代码- _comboSystem.ActionExpressions.Add(GetComboExpression(typeof(AttackAnimation), nameof(AttackAnimation.Play), Expression.Constant("SwordSwing")));
复制代码- _comboSystem.ActionExpressions.Add(GetComboExpression(typeof(EffectsManager), nameof(EffectsManager.Spawn), Expression.Constant("SwordHit")));
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- _comboSystem.ActionExpressions.Add(GetComboExpression(typeof(DamageCalculator), nameof(DamageCalculator.Apply), Expression.Constant(new Vector3(0, 1, 0)), Expression.Constant(100f)));
复制代码- _comboSystem.ExecuteCombo();
复制代码- Expression GetComboExpression(Type type, string methodName, params Expression[] args)
复制代码- public List<Expression> ActionExpressions = new();
复制代码- return Expression.Lambda(Expression.Call(type, methodName, null, args));
复制代码- [/code][code]void Update()
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- [/code][code]public List<Expression> ActionExpressions = new();
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- [/code][code] "!=" => Expression.NotEqual(left, right),
复制代码- Debug.Log($"播放动画: "!=" => Expression.NotEqual(left, right),animationName "!=" => Expression.NotEqual(left, right),");
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- [/code][code]ActionExpressions.Select(exp => exp.Body)
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- [/code][code] "!=" => Expression.NotEqual(left, right),
复制代码- Debug.Log($"生成特效: "!=" => Expression.NotEqual(left, right),effectName "!=" => Expression.NotEqual(left, right),");
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- [/code][code] "!=" => Expression.NotEqual(left, right),
复制代码- Debug.Log($"应用伤害: "!=" => Expression.NotEqual(left, right),damage "!=" => Expression.NotEqual(left, right), 到位置: "!=" => Expression.NotEqual(left, right),position "!=" => Expression.NotEqual(left, right),");
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码- [/code][code]Debug.Log($"播放动画: {animationName var patrol = CreateActionExpression("Patrol").Compile();");
复制代码- var patrol = CreateActionExpression("Patrol").Compile();
复制代码 Expression.Block:
Expression.Block 是一个非常强大的功能,它允许我们将多个表达式组合成一个块(block),并在执行时按顺序执行这些表达式。
四、运行时状态机
- if (_compiledCondition(ContextObject))
复制代码 - "!=" => Expression.NotEqual(left, right),
复制代码- public static void Spawn(string effectName)
复制代码- [/code][code]Debug.Log($"生成特效: {effectName var patrol = CreateActionExpression("Patrol").Compile();");
复制代码- [/code][code]public class EnemyStateMachine : MonoBehaviour "!=" => Expression.NotEqual(left, right),
复制代码- [/code][code]//定义状态机委托,以Enemy和Hero为参数,返回一个以Enemy和Hero为参数的空方法
复制代码- //它会根据英雄的状态(如生命值和距离)返回一个相应的行为函数。
复制代码- [/code][code]"==" => Expression.Equal(left, right), "!=" => Expression.NotEqual(left, right),
复制代码- enemy = FindObjectOfType();
复制代码- hero = FindObjectOfType();
复制代码- stateEvaluator = CreateDynamicStateMachine();
复制代码- [/code][code]void Update() "!=" => Expression.NotEqual(left, right),
复制代码- behavior = stateEvaluator(enemy, hero);
复制代码- [/code][code]Debug.Log("Enemy Aggression Level:", enemy.AggressionLevel);
复制代码- [/code][code]public Func CreateDynamicStateMachine() "!=" => Expression.NotEqual(left, right),
复制代码- ParameterExpression enemyParam = Expression.Parameter(typeof(Enemy), "enemy");
复制代码- ParameterExpression heroParam = Expression.Parameter(typeof(Hero), "hero");
复制代码- BinaryExpression heroLowHealth = Expression.LessThan(
复制代码- Expression.Property(heroParam, "Health"),
复制代码- _comboSystem.ExecuteCombo();
复制代码- BinaryExpression heroNear = Expression.LessThan(
复制代码- Expression.Property(heroParam, "Distance"),
复制代码- _comboSystem.ExecuteCombo();
复制代码- [/code][code]Debug.Log($"HeroLowHealth"!=" => Expression.NotEqual(left, right),heroLowHealth "!=" => Expression.NotEqual(left, right),");
复制代码- Debug.Log($"HeroNear"!=" => Expression.NotEqual(left, right),heroNear "!=" => Expression.NotEqual(left, right),");
复制代码- [/code][code]var attack = CreateActionExpression("Attack").Compile();
复制代码- var taunt = CreateActionExpression("Taunt").Compile();
复制代码- var patrol = CreateActionExpression("Patrol").Compile();
复制代码- [/code][code]//条件表达式,如果heroNear为真则执行taunt,否则执行patrol
复制代码- ConditionalExpression tauntOrPatrol = Expression.Condition(heroNear, Expression.Constant(taunt), Expression.Constant(patrol));
复制代码- ConditionalExpression finalCondition = Expression.Condition(heroLowHealth, Expression.Constant(attack), tauntOrPatrol);
复制代码- var lambda = Expression.Lambda(finalCondition, enemyParam, heroParam);
复制代码- [/code][code]Expression> CreateActionExpression(string methodName) "!=" => Expression.NotEqual(left, right),
复制代码- ParameterExpression enemyParam = Expression.Parameter(typeof(Enemy), "enemy");
复制代码- ParameterExpression heroParam = Expression.Parameter(typeof(Hero), "hero");
复制代码- [/code][code]MethodInfo method = typeof(Enemy).GetMethod(methodName, new[] "!=" => Expression.NotEqual(left, right), typeof(Hero) "!=" => Expression.NotEqual(left, right),);
复制代码- [/code][code]MethodCallExpression call = Expression.Call(enemyParam, method, heroParam);
复制代码- return Expression.Lambda>(call, enemyParam, heroParam);
复制代码- "!=" => Expression.NotEqual(left, right),
复制代码 CreateDynamicStateMachine
- 参数定义:定义了两个参数 enemyParam 和 heroParam,用于表示敌人和英雄。
- 条件表达式:
- heroLowHealth: 检查英雄的生命值是否低于 30。
- heroNear: 检查英雄与敌人的距离是否小于 10。
- 行为选择:
- 使用 CreateActionExpression 方法创建 Attack、Taunt 和 Patrol 行为的表达式。
- 使用 Expression.Condition 创建条件表达式,根据条件选择行为。
- 返回 Lambda 表达式:最终返回一个 Lambda 表达式,接受 Enemy 和 Hero 作为参数,并返回相应的行为函数。
CreateActionExpression
- 参数定义:定义 enemyParam 和 heroParam。
- 获取方法信息:使用反射获取 Enemy 类中与 methodName 相对应的方法。
- 创建方法调用表达式:使用 Expression.Call 创建方法调用表达式,并返回一个 Lambda 表达式。
点赞鼓励下,(づ ̄3 ̄)づ╭❤~
作者:世纪末的魔术师
出处:https://www.cnblogs.com/Firepad-magic/
Unity最受欢迎插件推荐:点击查看
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |