You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中UniRx多点触控拖拽:手指抬起时清空Buffer的需求

解决UniRx多点触控拖拽时Buffer旧值残留的问题

我完全懂你现在卡在哪——用UniRx的Buffer(2,1)计算手指拖拽增量时,手指抬起后缓冲区里的旧位置会和下一次按下的新位置算出离谱的大距离,完全不是预期的拖拽增量。别慌,我们可以通过让拖拽流和手指的生命周期绑定,彻底解决这个问题。

核心思路

问题根源在于你原来的流是持续监听所有拖拽事件的,不管手指是否抬起,Buffer会一直攒着之前的事件。我们需要把每个手指的拖拽流改成「按下启动,抬起重置」的独立序列,这样每次手指按下都会开启全新的Buffer,抬起时序列直接终止,旧数据自然被清空。

方案一:用TakeUntil+Repeat绑定手指生命周期

修改你的CalculateDelta方法,加入对PointerUp事件的监听,让当前拖拽流在手指抬起时终止,再通过Repeat重新监听下一次拖拽:

private IObservable<TwoFingerDragEventData> CalculateDelta(
    IObservable<PointerEventData> dragEvent,
    IObservable<PointerEventData> pointerUpEvent,
    int pointerId)
{
    return dragEvent
        .Where(data => data.pointerId == pointerId)
        // 对应手指抬起时,立刻终止当前拖拽流
        .TakeUntil(pointerUpEvent.Where(upData => upData.pointerId == pointerId))
        .Select(data => data.position)
        .Buffer(2, 1)
        // 过滤掉序列首尾可能出现的不满2个元素的无效Buffer
        .Where(list => list.Count == 2)
        .Select(list => new TwoFingerDragEventData(
            Vector2.Distance(list[0], list[1]),
            list[1] - list[0]
        ))
        // 手指抬起后,自动重新监听下一次拖拽事件
        .Repeat();
}

方案二:用SelectMany结合PointerDown开启全新流

如果你希望更明确地在手指按下时才启动拖拽监听,可以用PointerDown事件作为触发信号,每次按下都创建一个完全独立的拖拽流:

private IObservable<TwoFingerDragEventData> CalculateDelta(
    IObservable<PointerEventData> dragEvent,
    IObservable<PointerEventData> pointerDownEvent,
    IObservable<PointerEventData> pointerUpEvent,
    int pointerId)
{
    return pointerDownEvent
        .Where(data => data.pointerId == pointerId)
        // 每次手指按下,就开启一个全新的拖拽监听流
        .SelectMany(_ => dragEvent
            .Where(data => data.pointerId == pointerId)
            .TakeUntil(pointerUpEvent.Where(upData => upData.pointerId == pointerId))
            .Select(data => data.position)
            .Buffer(2, 1)
            .Where(list => list.Count == 2)
            .Select(list => new TwoFingerDragEventData(
                Vector2.Distance(list[0], list[1]),
                list[1] - list[0]
            ))
        );
}

为什么这两种方案能解决问题?

  • 两种方案都确保了每个拖拽周期的事件流是独立的,Buffer只会收集当前手指按下后到抬起前的位置数据,不会跨周期留存旧值。
  • 当手指抬起时,当前的事件流会被直接终止,对应的Buffer会被销毁,从根源上避免了旧数据干扰下一次拖拽计算。
  • 加入.Where(list => list.Count == 2)是为了避免流启动初期(只有第一个位置)或结束末期(只有最后一个位置)产生无效的TwoFingerDragEventData。

内容的提问来源于stack exchange,提问作者Matthias Tylkowski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:01:35