我试图围绕一个场景移动 SCNNode
,约束为 GKGridGraph
. 按照PacMan的思路,但在3D中 .
我有一个 ControlComponent
来处理我的SCNNode的移动 . 逻辑应该像这样......
-
设置
queuedDirection
属性 . -
如果
isMoving
标志为false,则调用move()方法 -
使用GKGridGraph评估下一步行动
3.1如果实体可以使用 direction
属性移动到GKGridGraphNode, nextNode = nodeInDirection(direction)
3.2如果实体可以使用 queuedDirection
属性 nextNode = nodeInDirection(queuedDirection)
移动到GKGridGraphNode
3.3如果实体无法移动到具有任一方向的节点,请将 isMoving
标志设置为false并返回 .
-
创建
moveTo
动作 -
创建
runBlock
,调用move()
方法 -
将
moveTo
和runBlock
操作作为序列应用于SCNNode
我先解释一下我遇到的问题 . 上述逻辑有效,但只是间歇性的 . 有时动画停止工作几乎immediatley,有时它运行长达一分钟 . 但在某些时候,由于某种原因,它只是停止工作 - setDirection()
将触发, move()
将触发,SCNNode将在指定方向上移动一次空间,然后 move()
方法停止被调用 .
我并不是100%确信我当前的方法是正确的,所以我很高兴听到是否有更惯用的SceneKit / GameplayKit方法来做到这一点 .
这里's the full class, but I think the important bit'是 setDirection()
和 move()
方法 .
import GameplayKit
import SceneKit
enum BRDirection {
case Up, Down, Left, Right, None
}
class ControlComponent: GKComponent {
var level: BRLevel!
var direction: BRDirection = .None
var queuedDirection: BRDirection?
var isMoving: Bool = false
var speed: NSTimeInterval = 0.5
//----------------------------------------------------------------------------------------
init(level: BRLevel) {
self.level = level
super.init()
}
//----------------------------------------------------------------------------------------
func setDirection( nextDirection: BRDirection) {
self.queuedDirection = nextDirection;
if !self.isMoving {
self.move()
}
}
//----------------------------------------------------------------------------------------
func move() {
let spriteNode: SCNNode = (self.entity?.componentForClass(NodeComponent.self)!.node)!
var nextNode = nodeInDirection( direction )
if let _ = self.queuedDirection {
let attemptedNextNode = nodeInDirection(self.queuedDirection! )
if let _ = attemptedNextNode {
nextNode = attemptedNextNode
self.direction = self.queuedDirection!
self.queuedDirection = nil
}
}
// Bail if we don't have a valid next node
guard let _ = nextNode else {
self.direction = .None
self.queuedDirection = nil
self.isMoving = false
return
}
// Set flag
self.isMoving = true;
// convert graphNode coordinates to Scene coordinates
let xPos: Float = Float(nextNode!.gridPosition.x) + 0.5
let zPos: Float = Float(nextNode!.gridPosition.y) + 0.5
let nextPosition: SCNVector3 = SCNVector3Make(xPos, 0, zPos)
// Configure actions
let moveTo = SCNAction.moveTo(nextPosition, duration: speed)
let repeatAction = SCNAction.runBlock( { _ in self.move() } )
let sequence = SCNAction.sequence([ moveTo, repeatAction ])
spriteNode.runAction( sequence )
}
//----------------------------------------------------------------------------------------
func getCurrentGridGraphNode() -> GKGridGraphNode {
// Acces the node in the scene and gett he grid positions
let spriteNode: SCNNode = (self.entity?.componentForClass(NodeComponent.self)!.node)!
// Account for visual offset
let currentGridPosition: vector_int2 = vector_int2(
Int32( floor(spriteNode.position.x) ),
Int32( floor(spriteNode.position.z) )
)
// return unwrapped node
return level.gridGraph.nodeAtGridPosition(currentGridPosition)!
}
//----------------------------------------------------------------------------------------
func nodeInDirection( nextDirection:BRDirection? ) -> GKGridGraphNode? {
guard let _ = nextDirection else { return nil }
let currentGridGraphNode = self.getCurrentGridGraphNode()
return self.nodeInDirection(nextDirection!, fromNode: currentGridGraphNode)
}
//----------------------------------------------------------------------------------------
func nodeInDirection( nextDirection:BRDirection?, fromNode node:GKGridGraphNode ) -> GKGridGraphNode? {
guard let _ = nextDirection else { return nil }
var nextPosition: vector_int2?
switch (nextDirection!) {
case .Left:
nextPosition = vector_int2(node.gridPosition.x + 1, node.gridPosition.y)
break
case .Right:
nextPosition = vector_int2(node.gridPosition.x - 1, node.gridPosition.y)
break
case .Down:
nextPosition = vector_int2(node.gridPosition.x, node.gridPosition.y - 1)
break
case .Up:
nextPosition = vector_int2(node.gridPosition.x, node.gridPosition.y + 1)
break;
case .None:
return nil
}
return level.gridGraph.nodeAtGridPosition(nextPosition!)
}
}
1 回答
我必须回答我自己的问题 . 首先,这是一个糟糕的问题,因为我试图做错事 . 我犯的两个主要错误是
我的组合试图做太多
我没有使用
updateWithDeltaTime
方法 .这就是使用GameplayKit的实体组件结构来构建代码和行为的方式 . 我会试着告诉所有价格最后是如何组合在一起的 .
NodeComponent
该组件负责管理代表我的游戏角色的实际SCNNode . 我已经移动了代码,用于将字符设置为
ControlComponent
并进入此组件 .请注意,每次设置组件
gridPosition
属性时,都会调用makeNextMove
方法 .控制组件
我最初的例子是试图做太多 . 这个组件现在唯一的责任就是为它评估下一个's entity'
NodeComponent
. 请注意,由于updateWithDeltaTime
,它将像调用该方法一样经常评估下一个移动 .GameViewController
这里是一切都在一起的地方 . 课堂上有很多内容,所以我只发布相关内容 .
实体经理
我在这个Ray Wenderlich tutorial之后拿到了这个小费 . 从本质上讲,它是一个保持所有实体和组件的桶,以简化管理和更新它们的工作 . 我非常推荐给该教程一个更好的理解 .
那么这一切如何融合在一起呢?
可以随时调用
ControlComponent.setDirection()
方法 .如果实体或组件实现了
updateWithDeltaTime
方法,则应该每帧调用它 . 我花了一些时间来弄清楚如何用SceneKit做这个,因为大多数GameplayKit示例是为SpriteKit设置的,而SKScene有一个非常方便的updatet方法 .对于SceneKit,我们必须为
SCNView
设置GameVierwControllerSCNSceneRendererDelegate
. 然后,使用rendererUpdateAtTime
方法,我们可以在实体管理器上调用updateAtDeltaTime
,该处理器每帧处理对所有实体和组件调用相同的方法 .Note 您必须手动将
playing
属性设置为true才能使其生效 .现在我们转到实际的动画 .
ControlComponent
正在使用direction
属性评估每帧的NodeComponents
下一个网格位置(您可以忽略queuedDirection
属性,它是一个实现细节) .这意味着每帧也会调用
NodeComponents.makeNextMove()
方法 . 只要newPosition
和oldPosition
不相同,就会应用动画 . 每当动画结束时,节点的gridPosition
都会更新 .现在,为什么我的动画我的SCNNode的初始方法不起作用,我不知道 . 但至少它迫使我更好地理解如何使用GameplayKit的实体/组件设计 .