- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我正在尝试在场景中移动 SCNNode
,并限制为 GKGridGraph
。沿着吃 bean 人的思路思考,但在 3D 中。
我有一个 ControlComponent
来处理我的 SCNNode 的移动。逻辑应该是这样的……
queuedDirection
属性。isMoving
标志为假,则调用 move() 方法使用 GKGridGraph 评估下一步行动
3.1 如果实体可以使用direction
属性移动到GKGridGraphNode,nextNode = nodeInDirection(direction)
3.2 如果实体可以使用 queuedDirection
属性移动到 GKGridGraphNode nextNode = nodeInDirection(queuedDirection)
3.3 如果实体无法移动到任一方向的节点,将isMoving
标志设置为false 并返回。
moveTo
Action move()
方法的runBlock
moveTo
和 runBlock
操作作为序列应用于 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!)
}
}
最佳答案
我必须回答我自己的问题。首先,这是一个糟糕的问题,因为我试图做错事。我犯的两个主要错误是
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!)
}
}
这里是一切拼凑在一起的地方。类里面发生了很多事情,所以我只会发布相关的内容。
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 成为 SCNView
的 SCNSceneRendererDelegate
。然后,使用 rendererUpdateAtTime
方法,我们可以在 Entity Manager 上调用 updateAtDeltaTime
,它负责在每一帧对所有实体和组件调用相同的方法。
注意 您必须手动将 playing
属性设置为 true 才能正常工作。
现在我们转向实际的动画。 ControlComponent
正在使用 direction
属性评估 NodeComponents
下一个网格位置应该是每帧的位置(您可以忽略 queuedDirection
属性,它是一个实现细节)。
这意味着 NodeComponents.makeNextMove()
方法也在每一帧被调用。只要 newPosition
和 oldPosition
不同,就会应用动画。每当动画结束时,节点的 gridPosition
就会更新。
现在,至于为什么我的初始 SCNNode 动画方法不起作用,我不知道。但至少它迫使我更好地理解如何使用 GameplayKit 的实体/组件设计。
关于swift - 为什么我的 SCNAction 序列间歇性地停止工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34549795/
我在Windows 10中使用一些简单的Powershell代码遇到了这个奇怪的问题,我认为这可能是我做错了,但我不是Powershell的天才。 我有这个: $ix = [System.Net.Dn
var urlsearch = "http://192.168.10.113:8080/collective-intellegence/StoreClicks?userid=" + userId +
我有一个非常奇怪的问题,过去两天一直让我抓狂。 我有一个我试图控制的串行设备(LS 100 光度计)。使用设置了正确参数的终端(白蚁),我可以发送命令(“MES”),然后是定界符(CR LF),然后我
我目前正试图让无需注册的 COM 使用 Excel 作为客户端,使用 .NET dll 作为服务器。目前,我只是试图让概念验证工作,但遇到了麻烦。 显然,当我使用 Excel 时,我不能简单地使用与可
我开发了简单的 REST API - https://github.com/pavelpetrcz/MandaysFigu - 我的问题是在本地主机上,WildFly 16 服务器的应用程序运行正常。
我遇到了奇怪的情况 - 从 Django shell 创建一些 Mongoengine 对象是成功的,但是从 Django View 创建相同的对象看起来成功,但 MongoDB 中没有出现任何数据。
我是 flask 的新手,只编写了一个相当简单的网络应用程序——没有数据库,只是一个航类搜索 API 的前端。一切正常,但为了提高我的技能,我正在尝试使用应用程序工厂和蓝图重构我的代码。让它与 pus
我的谷歌分析 JavaScript 事件在开发者控制台中运行得很好。 但是当从外部 js 文件包含在页面上时,它们根本不起作用。由于某种原因。 例如; 下面的内容将在包含在控制台中时运行。但当包含在单
这是一本名为“Node.js 8 the Right Way”的书中的任务。你可以在下面看到它: 这是我的解决方案: 'use strict'; const zmq = require('zeromq
我正在阅读文本行,并创建其独特单词的列表(在将它们小写之后)。我可以使它与 flatMap 一起工作,但不能使它与 map 的“子”流一起工作。 flatMap 看起来更简洁和“更好”,但为什么 di
我正在编写一些 PowerShell 脚本来进行一些构建自动化。我发现 here echo $? 根据前面的语句返回真或假。我刚刚发现 echo 是 Write-Output 的别名。 写主机 $?
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 4年前关闭。 Improve thi
我将一个工作 View Controller 类从另一个项目复制到一个新项目中。我无法在新项目中加载 View 。在旧项目中我使用了presentModalViewController。在新版本中,我
我对 javascript 很陌生,所以很难看出我哪里出错了。由于某种原因,我的功能无法正常工作。任何帮助,将不胜感激。我尝试在外部 js 文件、头部/主体中使用它们,但似乎没有任何效果。错误要么出在
我正在尝试学习Flutter中的复选框。 问题是,当我想在Scaffold(body :)中使用复选框时,它正在工作。但我想在不同的地方使用它,例如ListView中的项目。 return Cente
我们当前使用的是 sleuth 2.2.3.RELEASE,我们看不到在 http header 中传递的 userId 字段没有传播。下面是我们的代码。 BaggageField REQUEST_I
我有一个组合框,其中包含一个项目,比如“a”。我想调用该组合框的 Action 监听器,仅在手动选择项目“a”完成时才调用。我也尝试过 ItemStateChanged,但它的工作原理与 Action
你能看一下照片吗?现在,一步前我执行了 this.interrupt()。您可以看到 this.isInterrupted() 为 false。我仔细观察——“这个”没有改变。它具有相同的 ID (1
我们当前使用的是 sleuth 2.2.3.RELEASE,我们看不到在 http header 中传递的 userId 字段没有传播。下面是我们的代码。 BaggageField REQUEST_I
我正在尝试在我的网站上设置一个联系表单,当有人点击发送时,就会运行一个作业,并在该作业中向所有管理员用户发送通知。不过,我在失败的工作表中不断收到此错误: Illuminate\Database\El
我是一名优秀的程序员,十分优秀!