在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 表达式。
posted @ 2025-02-22 17:42  世纪末の魔术师  阅读(318)  评论(0)    收藏  举报