gpt4 book ai didi

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

转载 作者:搜寻专家 更新时间:2023-10-30 22:14:03 29 4
gpt4 key购买 nike

我正在尝试在场景中移动 SCNNode,并限制为 GKGridGraph。沿着吃 bean 人的思路思考,但在 3D 中。

我有一个 ControlComponent 来处理我的 SCNNode 的移动。逻辑应该是这样的……

  1. 设置 queuedDirection 属性。
  2. 如果 isMoving 标志为假,则调用 move() 方法
  3. 使用 GKGridGraph 评估下一步行动

    3.1 如果实体可以使用direction属性移动到GKGridGraphNode,nextNode = nodeInDirection(direction)

    3.2 如果实体可以使用 queuedDirection 属性移动到 GKGridGraphNode nextNode = nodeInDirection(queuedDirection)

    3.3 如果实体无法移动到任一方向的节点,将isMoving 标志设置为false 并返回。

  4. 创建moveTo Action
  5. 创建一个调用move()方法的runBlock
  6. moveTorunBlock 操作作为序列应用于 SCNNode

我在下面粘贴了完整的类(class)。但我会先解释我遇到的问题。上述逻辑有效,但只是间歇性的。有时动画几乎立即停止工作,有时它会运行长达一分钟。但在某些时候,出于某种原因,它只是停止工作 - 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, 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. 我的组件试图做太多事情
  2. 我没有使用 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, 0, 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!)

}

}

游戏 View Controller

这里是一切拼凑在一起的地方。类里面发生了很多事情,所以我只会发布相关的内容。

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 有一个非常方便的更新方法。

对于 SceneKit,我们必须使 GameVierwController 成为 SCNViewSCNSceneRendererDelegate。然后,使用 rendererUpdateAtTime 方法,我们可以在 Entity Manager 上调用 updateAtDeltaTime,它负责在每一帧对所有实体和组件调用相同的方法。

注意 您必须手动将 playing 属性设置为 true 才能正常工作。

现在我们转向实际的动画。 ControlComponent 正在使用 direction 属性评估 NodeComponents 下一个网格位置应该是每帧的位置(您可以忽略 queuedDirection 属性,它是一个实现细节)。

这意味着 NodeComponents.makeNextMove() 方法也在每一帧被调用。只要 newPositionoldPosition 不同,就会应用动画。每当动画结束时,节点的 gridPosition 就会更新。

现在,至于为什么我的初始 SCNNode 动画方法不起作用,我不知道。但至少它迫使我更好地理解如何使用 GameplayKit 的实体/组件设计。

关于swift - 为什么我的 SCNAction 序列间歇性地停止工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34549795/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com