如何在 SKSpriteNode 中正确封装触摸事件处理并将数据传回 SKScene

Posted

技术标签:

【中文标题】如何在 SKSpriteNode 中正确封装触摸事件处理并将数据传回 SKScene【英文标题】:How to properly encapsulate touch event handling in a SKSpriteNode and pass data back to a SKScene 【发布时间】:2020-04-13 19:53:03 【问题描述】:

我正在用 SpriteKit 编写一个游戏,它有一个操纵杆来控制玩家。以前,我将大部分操纵杆逻辑保留在主 GameScene 的 touchesBegantouchesMovedtouchesEnded 方法中。

由于我有多个SKScenes,我想将此逻辑抽象为SKSpriteNode 子类JoyStick,其中包含自己的touches 事件处理程序。这似乎是一个很好的解决方案,因为它会自动处理触摸是否在操纵杆的“范围内”,并允许我从场景本身中删除逻辑。

但是,我找不到任何好的资源来概述如何在 SKScene 和已实现触摸事件处理程序的 SKSpriteNode 实例之间正确地来回传递信息。我可以将我想要修改的对象的实例(如玩家精灵)传递给操纵杆,但我很好奇是否有适当的方法来来回传递数据而不将操纵杆耦合到特定的 " 实例-修改”.

此外,在我的 SKSpriteNode 实例中实现触摸事件处理程序而不是在场景的处理程序中处理所有内容是否还有其他缺点?

【问题讨论】:

【参考方案1】:

我喜欢尽可能多地处理对象类中的代码。因此,我将处理其类文件中的任何对象触摸代码,并将触摸发送回场景以在需要时由场景单独处理,并使用委托将有关触摸的信息发送到场景。

删除此代码没有缺点,但也有几个优点。

更简洁的代码 如果场景的线条较少,xcode 中的加载时间会更快(我自己的发现) 您不必精确定位触摸所在的节点,因为它被封装在子类中

在子类(猫)中

protocol CatDelegate: class
    func catTouched(cat: Cat)


class Cat: SKSpriteNode 

    weak var delegate: CatDelegate!

    var isEnabled = true
    var sendTouchesToScene = true
    var color: SKColor = .white

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent!) 

         guard isEnabled else  return 

         //send touch to scene if you need to handle further touches by the scene
         if sendTouchesToScene 
             super.touchesBegan(touches, with event)
         

         //handle touches for cat
         delegate?.catTouched(cat: self)
    

同时在游戏场景中...

class GameScene: SKScene 

    private var cat1: Cat!
    private var cat2: Cat!

    …

    func createCats() 
   
     cat1 = Cat()
        cat1.color = .magenta
        cat1.delegate = self
        addChild(cat1)
    
        cat2 = Cat()
        cat2.color = .green
        cat2.delegate = self
        addChild(cat2)
    


extension GameScene: CatDelegate 

    func catTouched(cat: Cat) 
        print("cat of color \(cat.color)")
    

【讨论】:

感谢@RonMyschuk,我刚刚实现了这个模式并且它有效。此外,我发现我的委派类(在此示例中为Cat)需要在应用 SKConstraint 后进行计算。委托GameScene 中有一个函数didApplyConstraints,但文档建议不要直接调用它。是否有一种干净的方法可以将不可直接调用的函数集成到此模式中?更具体地说,我的委托类子类 SKSpriteNode 并在 touchesMoved 中调用委托 - 这似乎与对 didApplyConstraints 的每帧调用一次冲突。【参考方案2】:

我很抱歉,但以前的答案根本不完整。这里是相同的,包含使其工作所需的所有代码。

import SwiftUI
import SpriteKit

class GameScene: SKScene 

private var cat1: Cat!
private var cat2: Cat!

func createCats() 
    cat1 = Cat(imageNamed: "img1")
    cat1.position = CGPoint(x: 64, y: 128)
    cat1.color = .magenta
    cat1.isUserInteractionEnabled = true
    cat1.delegate = self
    addChild(cat1)
    
    cat2 = Cat(imageNamed: "img2")
    cat2.position = CGPoint(x: 128, y: 128)
    cat2.color = .green
    cat2.isUserInteractionEnabled = true
    cat2.delegate = self
    addChild(cat2)


override func didMove(to view: SKView) 
    createCats()




extension GameScene: CatDelegate 

func catTouched(cat: Cat) 
    print("cat of color \(cat.color)")




protocol CatDelegate 
  func catTouched(cat: Cat)


class Cat: SKSpriteNode 
 var delegate: CatDelegate!

 var isEnabled = true
 var sendTouchesToScene = true
 var colour: SKColor = .blue

 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent!) 
    
    guard isEnabled else  return 
    
    //send touch to scene if you need to handle further touches by the scene
    if sendTouchesToScene 
        super.touchesBegan(touches, with: event)
    
    
    //handle touches for cat
    delegate?.catTouched(cat: self)





struct ContentView: View 

var scene: SKScene 
    let scene = GameScene()
    scene.size = CGSize(width: 256, height: 256)
    scene.scaleMode = .fill
    scene.backgroundColor = .white
    return scene


var body: some View 
    SpriteView(scene: scene)
        .frame(width: 256, height: 256)
        .border(Color.red)


【讨论】:

以上是关于如何在 SKSpriteNode 中正确封装触摸事件处理并将数据传回 SKScene的主要内容,如果未能解决你的问题,请参考以下文章

如何检测是不是已触摸 SKSpriteNode

如何使用该节点的自定义类检测在单独场景中创建的 skspritenode 上的触摸

在触摸时将动画添加到 SKSpriteNode (Swift)

仅在触摸时拖动 SKSpriteNode

有没有办法在 SKSpriteNode 中分配和调用变量?

未检测到 SKSpriteNode 触摸