微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

我正在尝试在场景周围移动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 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,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的实体/组件设计.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐