swift – 为什么我的SCNAction序列会间歇性地停止工作?

swift – 为什么我的SCNAction序列会间歇性地停止工作?,第1张

概述我正在尝试在场景周围移动SCNNode,约束到GKGridGraph.按照PacMan的思路,但在3D中. 我有一个ControlComponent来处理我的SCNNode的移动.逻辑应该像这样…… >设置queuedDirection属性. >如果isMoving标志为false,则调用move()方法 >使用GKGridGraph评估下一步行动 3.1如果实体可以使用direction属性移动 我正在尝试在场景周围移动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动作
>创建一个调用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序列会间歇性地停止工作?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1017788.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存