我有一个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动作
>创建一个调用move()方法的runBlock
>将moveto和runBlock *** 作作为序列应用于SCNNode
我已经粘贴在下面的全班中.但我会解释我先遇到的问题.上述逻辑有效,但只是间歇性的.有时动画停止工作几乎immediatley,有时它运行长达一分钟.但在某些时候,由于某种原因,它只是停止工作 – setDirection()将触发,move()将触发,SCNNode将在指定方向上移动一次空间,然后move()方法停止被调用.
我并不是100%确信我当前的方法是正确的,所以我很高兴听到是否有更惯用的SceneKit / GameplayKit方法来做到这一点.
这是完整的类,但我认为重要的是setDirection()和move()方法.
import GameplayKitimport SceneKitenum 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,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!) }}解决方法 我必须回答我自己的问题.首先,这是一个糟糕的问题,因为我试图做错事.我犯的两个主要错误是
>我的组合试图做太多
>我没有使用updateWithDeltaTime方法.
这就是使用GameplayKit的实体组件结构来构建代码和行为的方式.我会试着告诉所有价格最后是如何组合在一起的.
结点组件
该组件负责管理代表我的游戏角色的实际SCNNode.我已经移动了代码,用于将角色设置为ControlComponent并进入此组件.
class NodeComponent: GKComponent { let node: SCNNode let animationSpeed:NSTimeInterval = 0.25 var nextGrIDposition: vector_int2 { dIDSet { makeNextMove(nextGrIDposition,oldposition: oldValue) } } init(node:SCNNode,startposition: vector_int2){ self.node = node self.nextGrIDposition = startposition } func makeNextMove(newposition: vector_int2,oldposition: vector_int2) { if ( newposition.x != oldposition.x || newposition.y != oldposition.y ){ let xPos: float = float(newposition.x) let zPos: float = float(newposition.y) let nextposition: SCNVector3 = SCNVector3Make(xPos,zPos) let moveto = SCNAction.moveto(nextposition,duration: self.animationSpeed) let updateEntity = SCNAction.runBlock( { _ in (self.entity as! PlayerEntity).grIDposition = newposition }) self.node.runAction(SCNAction.sequence([moveto,updateEntity]),forKey: "move") } }}
请注意,每次设置组件grIDposition属性时,都会调用makeNextMove方法.
控制组件
我最初的例子是试图做太多.现在,这个组件的唯一责任是为它的实体的NodeComponent评估下一个grIDposition.请注意,由于updateWithDeltaTime,它将像调用该方法一样经常评估下一个移动.
class ControlComponent: GKComponent { var level: BRLevel! var direction: BRDirection = .None var queuedDirection: BRDirection? init(level: BRLevel) { self.level = level super.init() } overrIDe func updateWithDeltaTime(seconds: NSTimeInterval) { self.evaluateNextposition() } func setDirection( nextDirection: BRDirection) { self.queuedDirection = nextDirection } func evaluateNextposition() { var nextNode = self.nodeInDirection(self.direction) if let _ = self.queuedDirection { let nextposition = self.entity?.componentForClass(NodeComponent.self)?.nextGrIDposition let targetposition = (self.entity! as! PlayerEntity).grIDposition let attemptednextNode = self.nodeInDirection(self.queuedDirection) if (nextposition!.x == targetposition.x && nextposition!.y == targetposition.y){ 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 return } self.entity!.componentForClass(NodeComponent.self)?.nextGrIDposition = nextNode!.grIDposition } func getCurrentGrIDGraphNode() -> GKGrIDGraphNode { // Access grID position let currentGrIDposition = (self.entity as! PlayerEntity).grIDposition // 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 } guard let _ = node 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!) }}
GameVIEwController
这里是一切都在一起的地方.课堂上有很多内容,所以我只发布相关内容.
class GameVIEwController: UIVIEwController,SCNSceneRendererDelegate { var entityManager: BREntityManager? var prevIoUsUpdateTime: NSTimeInterval?; var playerEntity: GKEntity? overrIDe func vIEwDIDLoad() { super.vIEwDIDLoad() // create a new scene let scene = SCNScene(named: "art.scnassets/game.scn")! // retrIEve the SCNVIEw let scnVIEw = self.vIEw as! SCNVIEw // set the scene to the vIEw scnVIEw.scene = scene // show statistics such as fps and timing information scnVIEw.showsstatistics = true scnVIEw.delegate = self entityManager = BREntityManager(level: level!) createPlayer() configureSwipeGestures() scnVIEw.playing = true } func createPlayer() { guard let playerNode = level!.scene.rootNode.childNodeWithname("player",recursively: true) else { fatalError("No player node in scene") } // convert scene coords to grID coords let scenePos = playerNode.position; let startingGrIDposition = vector_int2( Int32( floor(scenePos.x) ),Int32( floor(scenePos.z) ) ) self.playerEntity = PlayerEntity(grIDPos: startingGrIDposition) let nodeComp = NodeComponent(node: playerNode,startposition: startingGrIDposition) let controlComp = ControlComponent(level: level!) playerEntity!.addComponent(nodeComp) playerEntity!.addComponent(controlComp) entityManager!.add(playerEntity!) } func configureSwipeGestures() { let directions: [UISwipeGestureRecognizerDirection] = [.Right,.left,.Up,.Down] for direction in directions { let gesture = UISwipeGestureRecognizer(target: self,action: Selector("handleSwipe:")) gesture.direction = direction self.vIEw.addGestureRecognizer(gesture) } } func handleSwipe( gesture: UISwipeGestureRecognizer ) { let controlComp = playerEntity!.componentForClass(ControlComponent.self)! switch gesture.direction { case UISwipeGestureRecognizerDirection.Up: controlComp.setDirection(BRDirection.Up) break case UISwipeGestureRecognizerDirection.Down: controlComp.setDirection(BRDirection.Down) break case UISwipeGestureRecognizerDirection.left: controlComp.setDirection(BRDirection.left) break case UISwipeGestureRecognizerDirection.Right: controlComp.setDirection(BRDirection.Right) break default: break } } // MARK: SCNSceneRendererDelegate func renderer(renderer: SCNSceneRenderer,updateAtTime time: NSTimeInterval) { let delta: NSTimeInterval if let _ = self.prevIoUsUpdateTime { delta = time - self.prevIoUsUpdateTime! }else{ delta = 0.0 } self.prevIoUsUpdateTime = time self.entityManager!.update(withDelaTime: delta) }}
实体经理
我在Ray Wenderlich tutorial之后提到了这个提示.从本质上讲,它是一个保存所有实体和组件的桶,以简化管理和更新它们的工作.我非常推荐给该教程一个更好的理解.
class BREntityManager { var entitIEs = Set<GKEntity>() var toRemove = Set<GKEntity>() let level: BRLevel //---------------------------------------------------------------------------------- lazy var componentSystems: [GKComponentSystem] = { return [ GKComponentSystem(componentClass: ControlComponent.self),GKComponentSystem(componentClass: NodeComponent.self) ] }() //---------------------------------------------------------------------------------- init(level:BRLevel) { self.level = level } //---------------------------------------------------------------------------------- func add(entity: GKEntity){ if let node:SCNNode = entity.componentForClass(NodeComponent.self)!.node { if !level.scene.rootNode.childNodes.contains(node){ level.scene.rootNode.addChildNode(node) } } entitIEs.insert(entity) for componentSystem in componentSystems { componentSystem.addComponentWithEntity(entity) } } //---------------------------------------------------------------------------------- func remove(entity: GKEntity) { if let _node = entity.componentForClass(NodeComponent.self)?.node { _node.removeFromparentNode() } entitIEs.remove(entity) toRemove.insert(entity) } //---------------------------------------------------------------------------------- func update(withDelaTime deltaTime: NSTimeInterval) { // update components for componentSystem in componentSystems { componentSystem.updateWithDeltaTime(deltaTime) } // remove components for curRemove in toRemove { for componentSystem in componentSystems { componentSystem.removeComponentWithEntity(curRemove) } } toRemove.removeAll() }}
那么这一切如何融合在一起呢?
可以随时调用ControlComponent.setDirection()方法.
如果实体或组件实现updateWithDeltaTime方法,则应该每帧调用它.我花了一些时间来弄清楚如何用SceneKit做这个,因为大多数GameplayKit示例是为SpriteKit设置的,而SKScene有一个非常方便的updatet方法.
对于SceneKit,我们必须使GameVIErwController成为SCNVIEw的SCNSceneRendererDelegate.然后,使用rendererUpdateAtTime方法,我们可以在实体管理器上调用updateAtDeltaTime,该实体管理器处理每帧对所有实体和组件调用相同的方法.
注意您必须手动将播放属性设置为true才能使其生效.
现在我们转到实际的动画. ControlComponent使用direction属性评估NodeComponents下一个网格位置应该是每帧的(您可以忽略queuedDirection属性,它是一个实现细节).
这意味着每帧都会调用NodeComponents.makeNextMove()方法.每当newposition和oldposition不相同时,都会应用动画.每当动画结束时,节点的grIDposition都会更新.
现在,为什么我的动画我的SCNNode的初始方法不起作用,我不知道.但至少它迫使我更好地理解如何使用GameplayKit的实体/组件设计.
总结以上是内存溢出为你收集整理的swift – 为什么我的SCNAction序列会间歇性地停止工作?全部内容,希望文章能够帮你解决swift – 为什么我的SCNAction序列会间歇性地停止工作?所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)