You need to enable JavaScript to run this app. Sprite Kit 如何实现用户手指移出按钮即停止对应按钮动作
优惠活动
大模型
产品
解决方案
定价
更多

Sprite Kit 如何实现用户手指移出按钮即停止对应按钮动作

问题核心成因

现有代码存在两个逻辑漏项:

  • 仅在触摸完全结束(手指抬起)的瞬间才检测触摸所在节点,没有监听触摸移动过程的位置变化,手指按住按钮滑出按钮区域时,不会触发任何停止逻辑
  • 没有维护按钮的按下状态,所有判断都基于事件触发瞬间的位置,无法覆盖"按住移出"这个中间状态

Sprite Kit的触摸事件机制中,只要手指未抬起,即使滑出屏幕任意区域,触摸事件仍会持续传递给场景,不会因为离开按钮区域自动触发touchesEnded,这就是移出按钮后动作一直不停止的根本原因。

具体实现方案

核心思路是维护当前按下按钮的状态标识,在三个关键触摸回调中同步校验状态:

  1. 触摸按下时记录被选中的按钮节点
  2. 触摸移动时实时判断手指是否还在按钮范围内,一旦移出立刻执行停止逻辑
  3. 触摸结束/被系统打断时,基于记录的状态做收尾清理,避免状态残留

步骤1:在SKScene类中新增状态属性

// 记录当前按下的按钮节点和对应名称
private var currentlyPressedButton: SKNode?
private var pressedButtonName: String?

步骤2:在touchesBegan中初始化按下状态

补充原有触摸按下逻辑,点中按钮时直接记录对应节点:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else { return }
    let touchPosition = touch.location(in: self)
    let touchedNode = self.atPoint(touchPosition)
    
    // 匹配左右按钮
    if let nodeName = touchedNode.name, nodeName == "leftbutton" || nodeName == "rightbutton" {
        currentlyPressedButton = touchedNode
        pressedButtonName = nodeName
        
        // 执行原有按钮按下、启动玩家移动动作的逻辑
        touchedNode.run(buttonPressedAction)
        // 你的玩家启动移动动作的原有代码
    }
}

步骤3:重写touchesMoved实现移出即停

这是解决当前问题的核心逻辑:只要手指在移动过程中离开按钮范围,立刻触发停止动作,无需等手指抬起:

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    // 没有按下按钮的状态时直接返回,减少无效计算
    guard let touch = touches.first, let pressedNode = currentlyPressedButton else { return }
    let currentTouchPosition = touch.location(in: self)
    
    // 校验当前触摸位置是否还在按钮范围内
    // 推荐用contains方法而非frame.contains:会自动适配按钮的缩放、旋转变换,判断更准确
    if !pressedNode.contains(currentTouchPosition) {
        print("\(pressedButtonName ?? "") 已停止:触摸移出按钮区域")
        pressedNode.run(buttonStoppedPressAction)
        player?.removeAllActions()
        
        // 清空状态,后续移动不再触发重复判断
        currentlyPressedButton = nil
        pressedButtonName = nil
    }
}

步骤4:优化原有touchesEnded逻辑,补充touchesCancelled处理

替换原来在touchesEnded里重新查找节点的代码,直接基于记录的状态做收尾:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    // 没有待处理的按下状态时直接返回
    guard let pressedNode = currentlyPressedButton else { return }
    print("\(pressedButtonName ?? "") 已停止:触摸抬起")
    pressedNode.run(buttonStoppedPressAction)
    player?.removeAllActions()
    
    // 清空状态
    currentlyPressedButton = nil
    pressedButtonName = nil
}

// 补充触摸被系统打断的处理(比如来电、弹窗遮挡时系统会取消触摸)
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let pressedNode = currentlyPressedButton else { return }
    pressedNode.run(buttonStoppedPressAction)
    player?.removeAllActions()
    currentlyPressedButton = nil
    pressedButtonName = nil
}
注意事项
  • 替换原有的atPoint查找逻辑后,不会再出现按钮带动画位移/缩放时,位置判断不准的问题
  • 如果按钮本身设置了较大的热区(比实际显示的节点范围大),可以在校验位置时自定义热区frame判断,不需要硬套节点原始范围
  • 有状态标识兜底就不会出现动作残留或者重复触发的问题,不需要在多个回调里重复移除玩家动作

内容的提问来源于stack exchange,提问作者Greg McKenna

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 01:54:05