[UniRx] 操作符 - 转换可观察对象(Transforming Observables) 第二篇
- #UniRx
- #教程
- Yaoming
在UniRx中,Transforming Observables,是指对Observable数据流进行转换的操作。通过这些操作符,可以将一个Observable的数据流转换为另一种形式,或者对数据进行处理、筛选、组合等操作。这些操作符是响应式编程的核心,允许你以声明式的方式处理数据流,而无需手动管理复杂的状态或回调逻辑。
1. Buffer
Buffer操作符会定期收集Observable中的值,并将它们作为一个集合发射,而不是逐个发射。
使用场景
- 批量处理数据,例如每 3 次用户点击后统一处理一次。
示例:
每 3 次鼠标点击作为一组发射
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0)) // 检测鼠标点击
.Buffer(3) // 每 3 次点击作为一组
.Subscribe(clicks =>
{
Debug.Log($"Mouse clicked {clicks.Count} times: {string.Join(", ", clicks)}");
});
输出:
每 3 次点击后打印:
Mouse clicked 3 times: 0, 0, 0
Mouse clicked 3 times: 0, 0, 0
示例:玩家连击检测
在游戏中,玩家快速点击攻击按钮可以触发连击技能。
Observable.EveryUpdate()
.Where(_ => Input.GetKeyDown(KeyCode.Space)) // 检测玩家按下空格键
.Buffer(System.TimeSpan.FromSeconds(1)) // 收集 1 秒内的所有按键
.Subscribe(clicks =>
{
if (clicks.Count >= 3) // 如果 1 秒内按下 3 次或更多
{
Debug.Log("连击被触发!");
}
else
{
Debug.Log($"普通攻击 ({clicks.Count} clicks)");
}
});
输出:
如果玩家在 1 秒内按下空格键 3 次或更多:
连击被触发
如果按键次数不足 3 次:
普通攻击 (2 clicks)
2. FlatMap
FlatMap, (在 UniRx 中对应 , code, SelectMany, )会将Observable, 中的每个值映射为一个新的Observable, 的值合并为一个流。
使用场景
- 处理嵌套的异步操作,例如从多个数据源加载内容
示例:生成敌人波次,在游戏中,每次生成一波敌人,每波敌人包含多个单位
Observable.Range(1, 3) // 模拟 3 波敌人
.SelectMany(wave =>
{
Debug.Log($"第 {wave} 波敌人开始!");
return Observable.Range(1, 5) // 每波生成 5 个敌人
.Delay(System.TimeSpan.FromSeconds(0.5f)); // 每个敌人间隔 0.5 秒生成
})
.Subscribe(enemy =>
{
Debug.Log($"敌人 {enemy} 生成!");
});
输出:
第1波敌人开始!
敌人 1 生成!
敌人 2 生成!
...
第2波敌人开始!
敌人 1 生成!
...
3. GroupBy
GroupBy会将Observable, 中的值按键分组,并为每个组创建一个新的Observable。
使用场景:
- 对数据进行分组处理,例如按奇偶分组处理数字流。
示例:按敌人类型分组处理,假设游戏中有不同类型的敌人(比如近战和远程),我们可以按类型分组处理。
var enemies = new[] { "近战", "远程", "近战", "远程", "近战" };
Observable.FromArray(enemies) // 模拟敌人生成事件
.GroupBy(type => type) // 按敌人类型分组
.Subscribe(group =>
{
group.Subscribe(enemy =>
{
Debug.Log($"敌人类型: {group.Key}, 敌人: {enemy}");
});
});
输出:
敌人类型: 近战, 敌人: 近战
敌人类型: 近战, 敌人: 近战
敌人类型: 近战, 敌人: 近战
敌人类型: 远程, 敌人: 远程
敌人类型: 远程, 敌人: 远程
4. Map (Select)
Map, (在 UniRx 中对应Select)会将Observable, 中的每个值通过函数映射为另一个值。
使用场景:
- 数据流的转换,例如将数字转换为字符串。
示例:玩家输入映射为动作,将玩家按键映射为具体的游戏动作。
Observable.EveryUpdate()
.Where(_ => Input.anyKeyDown) // 检测玩家按键
.Select(_ =>
{
if (Input.GetKeyDown(KeyCode.W)) return "Move Up";
if (Input.GetKeyDown(KeyCode.S)) return "Move Down";
if (Input.GetKeyDown(KeyCode.A)) return "Move Left";
if (Input.GetKeyDown(KeyCode.D)) return "Move Right";
return "Unknown Action";
})
.Subscribe(action => Debug.Log($"Player Action: {action}"));
输出:
//玩家按下W键:
Player Action: Move Up
//玩家按下D键:
Player Action: Move Right
5. Scan
Scan会对数据流中的值进行累积计算,并发射每次计算的结果。
使用场景:
- 实现累加器、状态管理等
示例:玩家分数累加
玩家每次击杀敌人都会增加分数。
var enemyKills = Observable.Range(1, 5); // 模拟玩家击杀 5 个敌人
enemyKills
.Scan((totalScore, kill) => totalScore + 10) // 每次击杀增加 10 分
.Subscribe(score => Debug.Log($"玩家分数: {score}"));
输出:
玩家分数: 10
玩家分数: 20
玩家分数: 30
玩家分数: 40
玩家分数: 50
总结
通过这些操作符,你可以轻松实现复杂的数据流处理逻辑,同时保持代码的简洁和可读性。
这里是RuntimeCube.com,下一篇,我们来了解一下Filtering Observables(过滤可观察对象)