在Unity中玩转表达式树:解锁游戏逻辑的动态魔法
在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),switch(statName) "!=" => Expression.NotEqual(left, right),case "Health": return p.Health;case "Mana": return p.Mana;// 每新增一个属性需要修改此处void Update() "!=" => 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),");void Update()public int GetPlayerStat(Player player, string statName) "!=" => Expression.NotEqual(left, right),Func<Player, int> propertyGetter = CreatePropertyGetter<Player, int>(statName);return propertyGetter(player);void Update()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);return lambda.Compile();void Update() "!=" => 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;private Func<GameObject, bool> _compiledCondition;private static Dictionary<string, Func<GameObject, bool>> _cache = new();"==" => 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);void Update()var conditionExpr = BuildComparison(accessChain, "<", Expression.Constant(0.3f));_compiledCondition = Expression.Lambda<Func<GameObject, bool>>(conditionExpr, rootObj).Compile();_cache = _compiledCondition;void Update()void Update()void Update() "!=" => Expression.NotEqual(left, right), 应用场景:动态游戏事件触发 优势对比:
传统方式表达式树方案硬编码条件判断支持运行时修改条件逻辑需要预定义所有情况可通过配置文件动态加载三、行为链组合
if (_compiledCondition(ContextObject)) "!=" => Expression.NotEqual(left, right),Debug.Log("触发条件!");{{ "!=" => Expression.NotEqual(left, right),return operatorStr switch"!=" => Expression.NotEqual(left, right),"<" => Expression.LessThan(left, right),"==" => Expression.Equal(left, right), "!=" => Expression.NotEqual(left, right),"<=" => Expression.LessThanOrEqual(left, right),_ => 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;using System.Linq;void Update() "!=" => Expression.NotEqual(left, right),public class ComboExample : MonoBehaviourvoid Update() "!=" => Expression.NotEqual(left, right),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));void Update() "!=" => Expression.NotEqual(left, right),public List<Expression> ActionExpressions = new();// 存储动作表达式的列表"!=" => Expression.NotEqual(left, right), "!=" => Expression.NotEqual(left, right),Debug.Log($"播放动画: "!=" => Expression.NotEqual(left, right),animationName "!=" => Expression.NotEqual(left, right),");void Update() "!=" => Expression.NotEqual(left, right),ActionExpressions.Select(exp => exp.Body)"!=" => Expression.NotEqual(left, right), "!=" => Expression.NotEqual(left, right),Debug.Log($"生成特效: "!=" => Expression.NotEqual(left, right),effectName "!=" => Expression.NotEqual(left, right),");void Update() "!=" => Expression.NotEqual(left, right),{"!=" => Expression.NotEqual(left, right), "!=" => Expression.NotEqual(left, right),Debug.Log($"应用伤害: "!=" => Expression.NotEqual(left, right),damage "!=" => Expression.NotEqual(left, right), 到位置: "!=" => Expression.NotEqual(left, right),position "!=" => Expression.NotEqual(left, right),");void Update() "!=" => Expression.NotEqual(left, right),Debug.Log($"播放动画: {animationName var patrol = CreateActionExpression("Patrol").Compile();");using System; var patrol = CreateActionExpression("Patrol").Compile(); Expression.Block:
Expression.Block 是一个非常强大的功能,它允许我们将多个表达式组合成一个块(block),并在执行时按顺序执行这些表达式。
四、运行时状态机
if (_compiledCondition(ContextObject)) "!=" => Expression.NotEqual(left, right),{public static void Spawn(string effectName)Debug.Log($"生成特效: {effectName var patrol = CreateActionExpression("Patrol").Compile();");public class EnemyStateMachine : MonoBehaviour "!=" => Expression.NotEqual(left, right),//定义状态机委托,以Enemy和Hero为参数,返回一个以Enemy和Hero为参数的空方法//它会根据英雄的状态(如生命值和距离)返回一个相应的行为函数。Func stateEvaluator;//存储当前行为函数Action behavior;Enemy enemy;Hero hero;"==" => Expression.Equal(left, right), "!=" => Expression.NotEqual(left, right),enemy = FindObjectOfType();hero = FindObjectOfType();stateEvaluator = CreateDynamicStateMachine();void Update()void Update() "!=" => Expression.NotEqual(left, right),//获取当前行为behavior = stateEvaluator(enemy, hero);//执行当前行为behavior(enemy, hero);Debug.Log("Enemy Aggression Level:", enemy.AggressionLevel);void Update()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"),Expression.Constant(30)_comboSystem.ExecuteCombo();BinaryExpression heroNear = Expression.LessThan(Expression.Property(heroParam, "Distance"),Expression.Constant(10f)_comboSystem.ExecuteCombo();Debug.Log($"HeroLowHealth"!=" => Expression.NotEqual(left, right),heroLowHealth "!=" => Expression.NotEqual(left, right),");Debug.Log($"HeroNear"!=" => Expression.NotEqual(left, right),heroNear "!=" => Expression.NotEqual(left, right),");var attack = CreateActionExpression("Attack").Compile();var taunt = CreateActionExpression("Taunt").Compile();var patrol = CreateActionExpression("Patrol").Compile();//条件表达式,如果heroNear为真则执行taunt,否则执行patrolConditionalExpression 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);return lambda.Compile();void Update()Expression> CreateActionExpression(string methodName) "!=" => Expression.NotEqual(left, right),ParameterExpression enemyParam = Expression.Parameter(typeof(Enemy), "enemy");ParameterExpression heroParam = Expression.Parameter(typeof(Hero), "hero");MethodInfo method = typeof(Enemy).GetMethod(methodName, new[] "!=" => Expression.NotEqual(left, right), typeof(Hero) "!=" => Expression.NotEqual(left, right),);MethodCallExpression call = Expression.Call(enemyParam, method, heroParam);return Expression.Lambda>(call, enemyParam, heroParam);void Update() "!=" => 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最受欢迎插件推荐:点击查看
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]