在Unity中玩转表达式树:解锁游戏逻辑的动态魔法
在Unity 2021 LTS版本中,结合Burst Compiler 可以将表达式树编译后的委托性能提升至接近原生C++代码水平,特别适合高频调用的游戏系统(如物理伤害计算、AI决策等)
参考:git-amend
一、为什么要学习表达式树?
传统Unity开发面临三大痛点:
- 逻辑固化 - 编译后无法修改行为逻辑
- 组件强耦合 - GameObject之间依赖关系复杂
- 动态性不足 - 难以实现运行时逻辑热替换
表达式树(Expression Trees) 技术通过将代码转换为可操作的数据结构,完美解决了这些问题。它允许我们:
- 运行时动态构建逻辑运行时动态构建逻辑
- 实现组件间的弱耦合通信实现组件间的弱耦合通信
- 支持可视化配置游戏行为支持可视化配置游戏行为
二、核心应用场景
-
动态技能系统
-
数据驱动AI
- 通过JSON配置行为树
- 运行时解析并生成表达式
- 实现无需重新编译的AI逻辑更新
-
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();
}
}
应用场景 :获取对象属性 技术要点 :
- 属性访问表达式属性访问表达式: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
-
参数定义 :定义了两个参数
enemyParam
和heroParam
,用于表示敌人和英雄。 -
条件表达 式:
-
heroLowHealth
: 检查英雄的生命值是否低于 30。heroNear
: 检查英雄与敌人的距离是否小于 10。
-
行为选择 :
-
使用
CreateActionExpression
方法创建Attack
、Taunt
和Patrol
行为的表达式。- 使用
Expression.Condition
创建条件表达式,根据条件选择行为。
- 使用
-
返回 Lambda 表达式 :最终返回一个 Lambda 表达式,接受
Enemy
和Hero
作为参数,并返回相应的行为函数。
CreateActionExpression
- 参数定义 :定义
enemyParam
和heroParam
。 - 获取方法信息 :使用反射获取
Enemy
类中与methodName
相对应的方法。 - 创建方法调用表达式 :使用
Expression.Call
创建方法调用表达式,并返回一个 Lambda 表达式。