在Unity中玩转表达式树:解锁游戏逻辑的动态魔法

在Unity中玩转表达式树:解锁游戏逻辑的动态魔法

在Unity 2021 LTS版本中,结合Burst Compiler 可以将表达式树编译后的委托性能提升至接近原生C++代码水平,特别适合高频调用的游戏系统(如物理伤害计算、AI决策等)

参考:git-amend

Expression

一、为什么要学习表达式树?

传统Unity开发面临三大痛点:

  1. 逻辑固化 - 编译后无法修改行为逻辑
  2. 组件强耦合 - GameObject之间依赖关系复杂
  3. 动态性不足 - 难以实现运行时逻辑热替换

表达式树(Expression Trees) 技术通过将代码转换为可操作的数据结构,完美解决了这些问题。它允许我们:

  • 运行时动态构建逻辑运行时动态构建逻辑
  • 实现组件间的弱耦合通信实现组件间的弱耦合通信
  • 支持可视化配置游戏行为支持可视化配置游戏行为

二、核心应用场景

  1. 动态技能系统

  2. 数据驱动AI

    • 通过JSON配置行为树
    • 运行时解析并生成表达式
    • 实现无需重新编译的AI逻辑更新
  3. MOD支持系统

    • 玩家自定义逻辑脚本
    • 安全沙箱运行表达式
    • 实时加载玩家创作内容

三、实战演示

一、 属性获取器

传统模式缺陷:

复制代码
xxxxxxxxxx
复制代码
public int GetPlayerStat(Player p, string statName)
复制代码
{
复制代码
    switch(statName)
复制代码
    {
复制代码
        case "Health": return p.Health;
复制代码
        case "Mana": return p.Mana;
复制代码
        // 每新增一个属性需要修改此处
复制代码
    }
复制代码
}

表达式树:

复制代码
xxxxxxxxxx
复制代码
using System;
复制代码
using System.Linq.Expressions;
复制代码
using UnityEngine;
复制代码
复制代码
public class ExpressionTreeDemo : MonoBehaviour {
复制代码
    void Start() {
复制代码
        Player player = new () { Health = 100 };
复制代码
        Func<Player, int> healthProperty = CreatePropertyGetter<Player, int>("Health");
复制代码
        Debug.Log($"Player Health: {healthProperty(player)}");
复制代码
    }
复制代码
复制代码
    public int GetPlayerStat(Player player, string statName) {
复制代码
        Func<Player, int> propertyGetter = CreatePropertyGetter<Player, int>(statName);
复制代码
        return propertyGetter(player);
复制代码
    }
复制代码
复制代码
    public Func<T, TProperty> CreatePropertyGetter<T, TProperty>(string propertyName) {
复制代码
        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();
复制代码
    }
复制代码
}

应用场景 :获取对象属性 技术要点

  1. 属性访问表达式属性访问表达式:Expression.Property

二、条件触发系统

复制代码
xxxxxxxxxx
复制代码
public class ConditionTrigger : MonoBehaviour 
复制代码
{
复制代码
    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();
复制代码
复制代码
    void Start()
复制代码
    {
复制代码
        if (!_cache.TryGetValue(ConditionExpression, out _compiledCondition))
复制代码
        {
复制代码
            var elements = ConditionExpression.Split('.');
复制代码
            var rootObj = Expression.Parameter(typeof(GameObject), "context");
复制代码
            Expression accessChain = rootObj;
复制代码
            foreach (var element in elements.Skip(1))
复制代码
            {
复制代码
                accessChain = Expression.PropertyOrField(accessChain, element);
复制代码
            }
复制代码
复制代码
            var conditionExpr = BuildComparison(accessChain, "<", Expression.Constant(0.3f));
复制代码
            _compiledCondition = Expression.Lambda<Func<GameObject, bool>>(conditionExpr, rootObj).Compile();
复制代码
            _cache[ConditionExpression] = _compiledCondition;
复制代码
        }
复制代码
    }
复制代码
复制代码
    void Update()
复制代码
    {
复制代码
        if (_compiledCondition(ContextObject))
复制代码
        {
复制代码
            Debug.Log("触发条件!");
复制代码
        }
复制代码
    }
复制代码
复制代码
    private Expression BuildComparison(Expression left, string operatorStr, Expression right)
复制代码
    {
复制代码
        return operatorStr switch
复制代码
        {
复制代码
            "<" => Expression.LessThan(left, right),
复制代码
            ">" => Expression.GreaterThan(left, right),
复制代码
            "==" => Expression.Equal(left, right),
复制代码
            "!=" => Expression.NotEqual(left, right),
复制代码
            "<=" => Expression.LessThanOrEqual(left, right),
复制代码
            ">=" => Expression.GreaterThanOrEqual(left, right),
复制代码
            _ => throw new NotSupportedException($"不支持的运算符: {operatorStr}")
复制代码
        };
复制代码
    }
复制代码
}

应用场景 :动态游戏事件触发 优势对比

传统方式 表达式树方案
硬编码条件判断 支持运行时修改条件逻辑
需要预定义所有情况 可通过配置文件动态加载

三、行为链组合

复制代码
xxxxxxxxxx
复制代码
using System;
复制代码
using System.Collections.Generic;
复制代码
using System.Linq;
复制代码
using System.Linq.Expressions;
复制代码
using UnityEngine;
复制代码
复制代码
// 使用示例
复制代码
public class ComboExample : MonoBehaviour
复制代码
{
复制代码
    private ComboSystem _comboSystem;
复制代码
复制代码
    void Start()
复制代码
    {
复制代码
        _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")));
复制代码
        _comboSystem.ActionExpressions.Add(GetComboExpression(typeof(AttackAnimation), nameof(AttackAnimation.Play), Expression.Constant("SwordSwing2")));
复制代码
        _comboSystem.ActionExpressions.Add(GetComboExpression(typeof(DamageCalculator), nameof(DamageCalculator.Apply), Expression.Constant(new Vector3(0, 1, 0)), Expression.Constant(100f)));
复制代码
复制代码
        // 执行连招
复制代码
        _comboSystem.ExecuteCombo();
复制代码
    }
复制代码
复制代码
    Expression<Action> GetComboExpression(Type type, string methodName, params Expression[] args)
复制代码
    {
复制代码
        return Expression.Lambda<Action>(Expression.Call(type, methodName, null, args));
复制代码
    }
复制代码
}
复制代码
复制代码
public class ComboSystem
复制代码
{
复制代码
    // 存储动作表达式的列表
复制代码
    public List<Expression<Action>> ActionExpressions = new();
复制代码
复制代码
    // 执行连招
复制代码
    public void ExecuteCombo()
复制代码
    {
复制代码
        // 构建复合表达式
复制代码
        var comboBlock = Expression.Block(
复制代码
            ActionExpressions.Select(exp => exp.Body)
复制代码
        );
复制代码
复制代码
        // 创建最终的表达式
复制代码
        //Compile() 方法将 Lambda 表达式编译为可执行的委托。
复制代码
        //Invoke() 方法调用该委托,从而执行块中的所有表达式。
复制代码
        var finalExpr = Expression.Lambda<Action>(comboBlock);
复制代码
        finalExpr.Compile().Invoke(); // 执行连招
复制代码
    }
复制代码
}
复制代码
复制代码
// 示例动作类
复制代码
public class AttackAnimation
复制代码
{
复制代码
    public static void Play(string animationName)
复制代码
    {
复制代码
        Debug.Log($"播放动画: {animationName}");
复制代码
    }
复制代码
}
复制代码
复制代码
public class EffectsManager
复制代码
{
复制代码
    public static void Spawn(string effectName)
复制代码
    {
复制代码
        Debug.Log($"生成特效: {effectName}");
复制代码
    }
复制代码
}
复制代码
复制代码
public class DamageCalculator
复制代码
{
复制代码
    public static void Apply(Vector3 position, float damage)
复制代码
    {
复制代码
        Debug.Log($"应用伤害: {damage} 到位置: {position}");
复制代码
    }
复制代码
}
复制代码
复制代码
//生成特效: SwordHit
复制代码
//播放动画: SwordSwing2
复制代码
//应用伤害: 100 到位置: (0.00, 1.00, 0.00)

Expression.Block:

Expression.Block 是一个非常强大的功能,它允许我们将多个表达式组合成一个块(block),并在执行时按顺序执行这些表达式。

四、运行时状态机

复制代码
xxxxxxxxxx
复制代码
using System;
复制代码
using System.Linq.Expressions;
复制代码
using System.Reflection;
复制代码
using UnityEngine;
复制代码
using Object = UnityEngine.Object;
复制代码
复制代码
public class EnemyStateMachine : MonoBehaviour {
复制代码
    
复制代码
    //定义状态机委托,以Enemy和Hero为参数,返回一个以Enemy和Hero为参数的空方法
复制代码
    //它会根据英雄的状态(如生命值和距离)返回一个相应的行为函数。
复制代码
    Func<Enemy, Hero, Action<Enemy, Hero>> stateEvaluator;
复制代码
    //存储当前行为函数
复制代码
    Action<Enemy, Hero> behavior;
复制代码
    Enemy enemy;
复制代码
    Hero hero;
复制代码
复制代码
    void Start() {
复制代码
        enemy = FindObjectOfType<Enemy>();
复制代码
        hero = FindObjectOfType<Hero>();
复制代码
        stateEvaluator = CreateDynamicStateMachine();
复制代码
    }
复制代码
复制代码
    void Update() {
复制代码
        //获取当前行为
复制代码
        behavior = stateEvaluator(enemy, hero);
复制代码
        //执行当前行为
复制代码
        behavior(enemy, hero);
复制代码
        
复制代码
        Debug.Log("Enemy Aggression Level:", enemy.AggressionLevel);
复制代码
    }
复制代码
复制代码
    public Func<Enemy, Hero, Action<Enemy, Hero>> CreateDynamicStateMachine() {
复制代码
        //定义参数表达式
复制代码
        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)
复制代码
        );
复制代码
        BinaryExpression heroNear = Expression.LessThan(
复制代码
            Expression.Property(heroParam, "Distance"),
复制代码
            Expression.Constant(10f)
复制代码
        );
复制代码
        
复制代码
        Debug.Log($"HeroLowHealth{heroLowHealth}");
复制代码
        Debug.Log($"HeroNear{heroNear}");
复制代码
        
复制代码
        var attack = CreateActionExpression("Attack").Compile();
复制代码
        var taunt = CreateActionExpression("Taunt").Compile();
复制代码
        var patrol = CreateActionExpression("Patrol").Compile();
复制代码
        
复制代码
        //条件表达式,如果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<Func<Enemy, Hero, Action<Enemy, Hero>>>(finalCondition, enemyParam, heroParam);
复制代码
        return lambda.Compile();
复制代码
    }
复制代码
    
复制代码
    Expression<Action<Enemy, Hero>> CreateActionExpression(string methodName) {
复制代码
        ParameterExpression enemyParam = Expression.Parameter(typeof(Enemy), "enemy");
复制代码
        ParameterExpression heroParam = Expression.Parameter(typeof(Hero), "hero");
复制代码
        
复制代码
        MethodInfo method = typeof(Enemy).GetMethod(methodName, new[] { typeof(Hero) });
复制代码
        
复制代码
        MethodCallExpression call = Expression.Call(enemyParam, method, heroParam);
复制代码
        return Expression.Lambda<Action<Enemy, Hero>>(call, enemyParam, heroParam);
复制代码
    }
复制代码
}

CreateDynamicStateMachine

  • 参数定义 :定义了两个参数 enemyParamheroParam,用于表示敌人和英雄。

  • 条件表达 式:

  • heroLowHealth: 检查英雄的生命值是否低于 30。

    • heroNear: 检查英雄与敌人的距离是否小于 10。
  • 行为选择

  • 使用 CreateActionExpression 方法创建 AttackTauntPatrol 行为的表达式。

    • 使用 Expression.Condition 创建条件表达式,根据条件选择行为。
  • 返回 Lambda 表达式 :最终返回一个 Lambda 表达式,接受 EnemyHero 作为参数,并返回相应的行为函数。

CreateActionExpression

  • 参数定义 :定义 enemyParamheroParam
  • 获取方法信息 :使用反射获取 Enemy 类中与 methodName 相对应的方法。
  • 创建方法调用表达式 :使用 Expression.Call 创建方法调用表达式,并返回一个 Lambda 表达式。