【发布时间】:2020-04-16 02:03:56
【问题描述】:
我创建了一个名为 JoyStick 的 SKSpriteNode 的子类,它遵循委托模式,其中它的委托是 GameScene。为了进一步清理GameScene,JoyStick 类还实现了自己的touchesBegan、touchesMoved 和touchesEnded 方法。
问题是我在JoyStick的touchesMoved方法中有一些位置计算需要在将某些SKConstraints应用于操纵杆后进行(例如将操纵杆手柄绑定到底座) .我知道SKScene 有一个didApplyConstraints 实例方法,并且文档表明它应该被覆盖而不是调用,因为它已经每帧调用一次。
但是,我不知道如何集成 didApplyConstraints 到我的委派操纵杆类的touchesMoved 方法中,而无需将逻辑移回我的GameScene 的didApplyConstraints。
这是我目前使用委托模式实现的简化示例。请注意,以下代码允许变量 joyVector 采用可接受范围之外的值,因为尚未应用约束。
GameScene.swift
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var shipNode: SKSpriteNode?
var joyStick: JoyStick?
override func didMove(to view: SKView) {
self.view?.isMultipleTouchEnabled = true
// Init the ship
guard let shipNode = self.childNode(withName: "ShipNode") as? SKSpriteNode else {
fatalError("Player node not loaded!")
}
self.shipNode = shipNode
// Init the joystick
joyStick = JoyStick()
joyStick?.isUserInteractionEnabled = true
joyStick?.position = CGPoint(x: -500, y: -200)
joyStick?.delegate = self
self.addChild(joyStick!)
}
}
extension GameScene: JoyStickDelegate{
var objPhysicsBody: SKPhysicsBody? {
return self.shipNode?.physicsBody
}
}
JoyStick.swift
import Foundation
import SpriteKit
protocol JoyStickDelegate: class {
var objPhysicsBody: SKPhysicsBody? {get}
}
class JoyStick: SKSpriteNode {
weak var delegate: JoyStickDelegate!
var joyVector: CGVector = CGVector()
var handle: SKSpriteNode?
init () {
let baseTexture = SKTexture(imageNamed: "joyStickBase")
super.init(texture: baseTexture, color: UIColor.clear, size: baseTexture.size())
// Add the joystick handle onto the base
handle = SKSpriteNode(imageNamed: "joyStickHandle")
handle?.position = CGPoint(x: 0 , y: 0)
// Add constraints
let range = CGFloat(self.size.width/2 - (handle?.size.width)! / 2)
let moveRange = SKRange(lowerLimit: 0, upperLimit: range)
let rangeConstraint = SKConstraint.distance(moveRange, to: CGPoint(x: 0,y: 0))
handle?.constraints = [rangeConstraint]
self.addChild(handle!)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveJoyStick(_ touches: Set<UITouch>) {
guard let fingerPosition = touches.first?.location(in: self) else { return }
self.handle!.position = fingerPosition
}
func getJoyVector(handlePosition: CGPoint) -> CGVector {
return CGVector(dx: handlePosition.x, dy: handlePosition.y)
}
func resetJoyStick() {
delegate?.objPhysicsBody?.velocity = CGVector(dx: 0, dy: 0)
//animation to return the joystick to the center...
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.moveJoyStick(touches)
joyVector = getJoyVector(handlePosition: self.handle!.position)
delegate?.objPhysicsBody?.velocity = joyVector
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
self.moveJoyStick(touches)
joyVector = getJoyVector(handlePosition: self.handle!.position)
delegate?.objPhysicsBody?.velocity = joyVector
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
resetJoyStick()
}
override func touchesEstimatedPropertiesUpdated(_ touches: Set<UITouch>) {}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {}
}
另一个正常工作的替代实现是将触摸事件方法保留在JoyStick 类中,但同时也放弃了委托模式。相反,我通过getter 方法访问GameScene 内的JoyStick handlePosition(作为向量),并将其作为GameScene 的didApplyConstraints 方法内的速度应用。以下是添加代码的两个 sn-ps 以使其工作:
添加到 JoyStick.swift:
func getPositionVelocity() -> CGVector {
guard let handlePosition = self.handle?.position else { return CGVector() }
return CGVector(dx: handlePosition.x, dy: handlePosition.y)
}
添加到 GameScene.swift:
...
//joyStick?.delegate = self
...
override func didApplyConstraints() {
self.shipNode?.physicsBody?.velocity = self.joyStick?.getPositionVelocity() ?? CGVector()
}
虽然这实现了仅应用受操纵杆半径限制的速度的预期行为,但它似乎远没有那么优雅,并且大大减少了我对委托模式的封装/通用性 - 随着游戏的发展,这种情况变得很明显.
最终,是否有可能避免将后约束逻辑堆积到 GameScene 中的单个 didApplyConstraints 定义中以实现此功能?为什么UIResponder 触摸事件似乎总是在:didApplyConstraints 之前处理——即使它们似乎在SKScene Frame-Cycle 之外?
【问题讨论】:
-
如果您查看 SpriteKit 生命周期,并且如果我理解您的问题,也许您可以在 :update 上进行“距离计算”?那是在 didApplyConstraints 之后,当然是在下一次渲染时。我没有看到那里的问题。
-
@Maetschl 我想我会遇到同样的问题 - 我在操纵杆的 touchesMoved 方法(GameScene 之外)中有计算,所以我不确定如何确保它被调用更新?我想我对 touchesMoved 究竟在哪里适合更广泛的 :update 或 :didApplyConstraints 等帧周期事件感到困惑?
-
如果您使用 GameplayKit,您可以直接调用场景的覆盖更新或 GKComponent 的更新时间间隔。
-
@Maetschl 你能详细说明一下吗?我可以调用 SKScene 代表的覆盖
:update吗?我认为不应直接调用此方法,因为它已经每帧调用一次?另外,从委派班级的touchesMoved调用它会做什么?这会确保在执行touchesMoved代码时调用didApplyConstraints吗? -
也许如果您编辑您的问题并添加一些代码,我可以为您提供更多帮助(请不要粘贴所有代码,为此目的做一个简单的示例)
标签: swift sprite-kit event-handling skspritenode skscene