GKAgent 不会更新在 ARKit/SCNScene/GamplayKit 游戏中注册为实体的 SCNNode 的位置
GKAgent won't update position of SCNNodes registered as entities on teams in an ARKit/SCNScene/GamplayKit game
我正在使用 GKAgent 移动注册为实体的 SCNNode,分配了一个团队组件和一个移动组件。正在触发 agentWillUpdate 和 agentDidUpdate 方法,但添加到场景的实体的位置拒绝更新它们的位置。有没有办法让 GKAgent 根据以下移动行为更新位置?
亲切的问候,
蒙特勒
MoveComponent.swift
import SceneKit
import GameplayKit
extension float3 {
var length: Float {
return sqrt(x*x + y*y + z*z)
}
}
class MoveComponent: GKAgent3D, GKAgentDelegate {
let entityManager: EntityManager!
init(maxSpeed: Float, maxAcceleration: Float, radius: Float, entityManager: EntityManager) {
self.entityManager = entityManager
super.init()
self.delegate = self
self.maxSpeed = maxSpeed
self.maxAcceleration = maxAcceleration
self.radius = radius
print(self.mass)
self.mass = 0.01
}
func agentWillUpdate(_ agent: GKAgent) {
guard let spriteComponent = entity?.component(ofType: SpriteComponent.self) else
{
return
}
let pos = spriteComponent.node.presentation.position
print("Agent Will Update \(spriteComponent.node.name) from \(pos) to \(self.position)")
self.position = float3(pos)
}
func agentDidUpdate(_ agent: GKAgent) {
guard let spriteComponent = entity?.component(ofType: SpriteComponent.self) else {
return
}
spriteComponent.node.position = vec3(self.position)
print("Agent DID Update to: \(spriteComponent.node.name) to \(position)")
let xVelocity = self.velocity.x
let zVelocity = self.velocity.z
let angle = -Float(atan2(zVelocity, xVelocity)) + Float.pi/2
spriteComponent.node.rotation = SCNVector4(0,1,0, angle)
}
func closestMoveComponent(_ team: Team) -> GKAgent3D? {
let moveComponents = entityManager.moveComponentsForTeam(team)
var closestMoveComponent: GKAgent3D? = nil
var closestDistance: Float = MAXFLOAT
for component in moveComponents {
let distance = (self.position - component.position).length
if distance < closestDistance {
closestDistance = distance
closestMoveComponent = component
}
}
return closestMoveComponent
}
override func update(deltaTime seconds: TimeInterval) {
// Determine team
guard let entity = entity,
let teamComponent = entity.component(ofType: TeamComponent.self) else {
return
}
// Find team castle
guard let team = entityManager.castleForTeam(teamComponent.team),
let teamCastleComponent = team.component(ofType: HeroComponent.self),
let teamMoveComponent = team.component(ofType: MoveComponent.self) else {
return
}
var targetMoveComponent: GKAgent3D
if teamCastleComponent.attacking {
// Find closest enemy
guard let enemyMoveComponent = closestMoveComponent( teamComponent.team.oppositeTeam()) else {
print("No Opposite enemy components for Agent!!!")
return
}
targetMoveComponent = enemyMoveComponent
// Override target for ranged attackers
if let fireComponent = entity.component(ofType: ParticleComponent.self) {
let newTarget = GKAgent3D()
let node1Pos = SCNVector3ToGLKVector3(SCNVector3(targetMoveComponent.position))
let node2Pos = SCNVector3ToGLKVector3(SCNVector3(position))
let distance = GLKVector3Distance(node1Pos, node2Pos)
newTarget.position = float3(x: targetMoveComponent.position.x, y: targetMoveComponent.position.y, z: targetMoveComponent.position.z * Float(fireComponent.range))
// newTarget.position = float3(targetMoveComponent.position + direction * fireComponent.range)
targetMoveComponent = newTarget
}
} else {
targetMoveComponent = teamMoveComponent
}
// Find allies
let alliedMoveComponents = entityManager.moveComponentsForTeam(teamComponent.team)
// Reset behavior
// print("Reset move behavior")
self.behavior = MoveBehavior(targetSpeed: maxSpeed, seek: targetMoveComponent, avoid: alliedMoveComponents)
print("Agent moving to \(targetMoveComponent), avoiding \(alliedMoveComponents)")
super.update(deltaTime: seconds)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
MoveBehavior.swift
import GameplayKit
import SceneKit
class MoveBehavior: GKBehavior {
init(targetSpeed: Float, seek: GKAgent3D, avoid: [GKAgent3D]) {
super.init()
if targetSpeed > 0 {
print("Running Move Behavior")
setWeight(0.1, for: GKGoal(toReachTargetSpeed: targetSpeed))
setWeight(0.5, for: GKGoal(toSeekAgent: seek))
setWeight(1.0, for: GKGoal(toAvoid: avoid, maxPredictionTime: 1.0))
}
}
}
EntityManager.swift
import Foundation
import Foundation
import ARKit
import SceneKit
import GameplayKit
class EntityManager {
var toRemove = Set<GKEntity>()
let costQuirk = 20
lazy var componentSystems: [GKComponentSystem] = {
let moveSystem = GKComponentSystem(componentClass: MoveComponent.self)
let meleeSystem = GKComponentSystem(componentClass: MeleeComponent.self)
let firingSystem = GKComponentSystem(componentClass: ParticleComponent.self)
let castleSystem = GKComponentSystem(componentClass: HeroComponent.self)
let heroSystem = GKComponentSystem(componentClass: HeroComponent.self)
let aiSystem = GKComponentSystem(componentClass: AiComponent.self)
let nodeComponent = GKComponentSystem(componentClass: NodeComponent.self)
return [moveSystem, meleeSystem, firingSystem, castleSystem, aiSystem,heroSystem,nodeComponent]
}()
// 1
var entities: Set<GKEntity>
let scene: SCNScene
// 2
init(scene: SCNScene) {
self.scene = scene
self.entities = Set<GKEntity>()
}
// 3
func add(_ entity: GKEntity) {
if let spriteNode = entity.component(ofType: SpriteComponent.self)?.node {
scene.rootNode.addChildNode(spriteNode)
let speed = Int.random(in: 7 ... 10)
let smoke = SCNParticleSystem(named: "art.scnassets/Models/spawnSmoke.scnp", inDirectory: nil)
let smokeNode = SCNNode()
spriteNode.addChildNode(smokeNode)
print("Monster Added")
}
for componentSystem in componentSystems {
componentSystem.addComponent(foundIn: entity)
}
entities.insert(entity)
}
// 4
func remove(_ entity: GKEntity) {
if let spriteNode = entity.component(ofType: SpriteComponent.self)?.node {
let confetti = SCNParticleSystem(named: "Media.scnassets/Fire.scnp", inDirectory: nil)
confetti?.loops = false
confetti?.particleLifeSpan = 0.05
confetti?.particleSize -= 1
confetti?.particleIntensity -= 0.5
print("Making Explosion")
if let geometry = spriteNode.geometry {
confetti?.emitterShape = geometry
}
spriteNode.addParticleSystem(confetti!)
print("Exploding Node!")
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
spriteNode.removeFromParentNode()
spriteNode.removeAllActions()
self.entities.remove(entity)
self.toRemove.insert(entity)
}
}
entities.remove(entity)
}
func update(_ deltaTime: CFTimeInterval) {
// 1
for system in componentSystems {
system.update(deltaTime: deltaTime)
}
// 2
for currentRemove in toRemove {
for componentSystem in componentSystems {
componentSystem.removeComponent(foundIn: currentRemove)
}
}
toRemove.removeAll()
}
func castle(for team: Team) -> GKEntity? {
for entity in self.entities {
if let teamComponent = entity.component(ofType: TeamComponent.self),
let _ = entity.component(ofType: HeroComponent.self) {
if teamComponent.team == team {
return entity
}
}
}
return nil
}
func spawnQuirk(team: Team , positon: SCNVector3) {
// 1
guard let teamEntity = castle(for: team) else {return}
// 2
if let teamCastleComponent = teamEntity.component(ofType: HeroComponent.self) {
if teamCastleComponent.coins < costQuirk , team == .team1{
return
}
teamCastleComponent.coins -= costQuirk
}
// 3
guard let floor = GAMEFLOOR as? SCNNode else {return}
let monster = Quirk(team: team, entityManager: self, floor:floor)
if let spriteComponent = monster.component(ofType: SpriteComponent.self) {
let randomX = Float.random(in: -10.0 ... -0.0)
let randomZ = Float.random(in: -10.0 ... -1.0)
spriteComponent.node.position = positon
let teamRing = SCNParticleSystem(named: "Media.scnassets/teamRing.scnp", inDirectory: nil)
switch team {
case .team1 :
teamRing?.particleColor = .blue
case .team2:
teamRing?.particleColor = .red
}
spriteComponent.node.addParticleSystem(teamRing!)
}
add(monster)
}
func spawnZap(_ team: Team, positon: SCNVector3) {
guard let teamEntity = castle(for: team) else {return}
if let teamCastleComponent = teamEntity.component(ofType: HeroComponent.self) {
if teamCastleComponent.coins < costZap , team == .team1{
return
}
teamCastleComponent.coins -= costZap
let monster = Zap(team: team, entityManager: self)
if let spriteComponent = monster.component(ofType: SpriteComponent.self) {
let randomX = Float.random(in: -10.0 ... -0.0)
let randomZ = Float.random(in: -10.0 ... -1.0)
spriteComponent.node.position = positon
}
add(monster)
}
}
func spawnMunch(_ team: Team, positon: SCNVector3) {
guard let teamEntity = castleForTeam(team),
let teamCastleComponent = teamEntity.component(ofType: HeroComponent.self),
let teamSpriteComponent = teamEntity.component(ofType: SpriteComponent.self) else {
return
}
if teamCastleComponent.coins < costMunch {
return
}
teamCastleComponent.coins -= costMunch
guard let floor = GAMEFLOOR as? SCNNode else {return}
let monster = Munch(team: team, entityManager: self, floor:floor)
if let spriteComponent = monster.component(ofType: SpriteComponent.self) {
let randomX = Float.random(in: -10.0 ... -0.0)
let randomZ = Float.random(in: -10.0 ... -1.0)
spriteComponent.node.position = positon
}
add(monster)
}
func entitiesForTeam(_ team: Team) -> [GKEntity] {
return entities.compactMap{ entity in
if let teamComponent = entity.component(ofType: TeamComponent.self) {
if teamComponent.team == team {
return entity
}
}
return nil
}
}
func moveComponentsForTeam(_ team: Team) -> [MoveComponent] {
let entities = entitiesForTeam(team)
var moveComponents = [MoveComponent]()
for entity in entities {
if let moveComponent = entity.component(ofType: MoveComponent.self) {
moveComponents.append(moveComponent)
}
}
return moveComponents
}
func castleForTeam(_ team: Team) -> GKEntity? {
for entity in entities {
if let teamComponent = entity.component(ofType: TeamComponent.self),
let _ = entity.component(ofType: HeroComponent.self) {
if teamComponent.team == team {
return entity
}
}
}
return nil
}
func entities(for team: Team) -> [GKEntity] {
return entities.compactMap{ entity in
if let teamComponent = entity.component(ofType: TeamComponent.self) {
if teamComponent.team == team {
return entity
}
}
return nil
}
}
func moveComponents(for team: Team) -> [MoveComponent] {
let entitiesToMove = entities(for: team)
var moveComponents = [MoveComponent]()
for entity in entitiesToMove {
if let moveComponent = entity.component(ofType: MoveComponent.self) {
moveComponents.append(moveComponent)
}
}
return moveComponents
}
}
TL;DR
我宁愿使用 GKSCNNodeComponent
作为代理的委托,它会自动处理相互位置更新。
如果一个组件被添加到组件系统,更新实体将不会影响它。
也许更新组件系统的顺序很重要。
我正在使用 GKAgent 移动注册为实体的 SCNNode,分配了一个团队组件和一个移动组件。正在触发 agentWillUpdate 和 agentDidUpdate 方法,但添加到场景的实体的位置拒绝更新它们的位置。有没有办法让 GKAgent 根据以下移动行为更新位置?
亲切的问候,
蒙特勒
MoveComponent.swift
import SceneKit
import GameplayKit
extension float3 {
var length: Float {
return sqrt(x*x + y*y + z*z)
}
}
class MoveComponent: GKAgent3D, GKAgentDelegate {
let entityManager: EntityManager!
init(maxSpeed: Float, maxAcceleration: Float, radius: Float, entityManager: EntityManager) {
self.entityManager = entityManager
super.init()
self.delegate = self
self.maxSpeed = maxSpeed
self.maxAcceleration = maxAcceleration
self.radius = radius
print(self.mass)
self.mass = 0.01
}
func agentWillUpdate(_ agent: GKAgent) {
guard let spriteComponent = entity?.component(ofType: SpriteComponent.self) else
{
return
}
let pos = spriteComponent.node.presentation.position
print("Agent Will Update \(spriteComponent.node.name) from \(pos) to \(self.position)")
self.position = float3(pos)
}
func agentDidUpdate(_ agent: GKAgent) {
guard let spriteComponent = entity?.component(ofType: SpriteComponent.self) else {
return
}
spriteComponent.node.position = vec3(self.position)
print("Agent DID Update to: \(spriteComponent.node.name) to \(position)")
let xVelocity = self.velocity.x
let zVelocity = self.velocity.z
let angle = -Float(atan2(zVelocity, xVelocity)) + Float.pi/2
spriteComponent.node.rotation = SCNVector4(0,1,0, angle)
}
func closestMoveComponent(_ team: Team) -> GKAgent3D? {
let moveComponents = entityManager.moveComponentsForTeam(team)
var closestMoveComponent: GKAgent3D? = nil
var closestDistance: Float = MAXFLOAT
for component in moveComponents {
let distance = (self.position - component.position).length
if distance < closestDistance {
closestDistance = distance
closestMoveComponent = component
}
}
return closestMoveComponent
}
override func update(deltaTime seconds: TimeInterval) {
// Determine team
guard let entity = entity,
let teamComponent = entity.component(ofType: TeamComponent.self) else {
return
}
// Find team castle
guard let team = entityManager.castleForTeam(teamComponent.team),
let teamCastleComponent = team.component(ofType: HeroComponent.self),
let teamMoveComponent = team.component(ofType: MoveComponent.self) else {
return
}
var targetMoveComponent: GKAgent3D
if teamCastleComponent.attacking {
// Find closest enemy
guard let enemyMoveComponent = closestMoveComponent( teamComponent.team.oppositeTeam()) else {
print("No Opposite enemy components for Agent!!!")
return
}
targetMoveComponent = enemyMoveComponent
// Override target for ranged attackers
if let fireComponent = entity.component(ofType: ParticleComponent.self) {
let newTarget = GKAgent3D()
let node1Pos = SCNVector3ToGLKVector3(SCNVector3(targetMoveComponent.position))
let node2Pos = SCNVector3ToGLKVector3(SCNVector3(position))
let distance = GLKVector3Distance(node1Pos, node2Pos)
newTarget.position = float3(x: targetMoveComponent.position.x, y: targetMoveComponent.position.y, z: targetMoveComponent.position.z * Float(fireComponent.range))
// newTarget.position = float3(targetMoveComponent.position + direction * fireComponent.range)
targetMoveComponent = newTarget
}
} else {
targetMoveComponent = teamMoveComponent
}
// Find allies
let alliedMoveComponents = entityManager.moveComponentsForTeam(teamComponent.team)
// Reset behavior
// print("Reset move behavior")
self.behavior = MoveBehavior(targetSpeed: maxSpeed, seek: targetMoveComponent, avoid: alliedMoveComponents)
print("Agent moving to \(targetMoveComponent), avoiding \(alliedMoveComponents)")
super.update(deltaTime: seconds)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
MoveBehavior.swift
import GameplayKit
import SceneKit
class MoveBehavior: GKBehavior {
init(targetSpeed: Float, seek: GKAgent3D, avoid: [GKAgent3D]) {
super.init()
if targetSpeed > 0 {
print("Running Move Behavior")
setWeight(0.1, for: GKGoal(toReachTargetSpeed: targetSpeed))
setWeight(0.5, for: GKGoal(toSeekAgent: seek))
setWeight(1.0, for: GKGoal(toAvoid: avoid, maxPredictionTime: 1.0))
}
}
}
EntityManager.swift
import Foundation
import Foundation
import ARKit
import SceneKit
import GameplayKit
class EntityManager {
var toRemove = Set<GKEntity>()
let costQuirk = 20
lazy var componentSystems: [GKComponentSystem] = {
let moveSystem = GKComponentSystem(componentClass: MoveComponent.self)
let meleeSystem = GKComponentSystem(componentClass: MeleeComponent.self)
let firingSystem = GKComponentSystem(componentClass: ParticleComponent.self)
let castleSystem = GKComponentSystem(componentClass: HeroComponent.self)
let heroSystem = GKComponentSystem(componentClass: HeroComponent.self)
let aiSystem = GKComponentSystem(componentClass: AiComponent.self)
let nodeComponent = GKComponentSystem(componentClass: NodeComponent.self)
return [moveSystem, meleeSystem, firingSystem, castleSystem, aiSystem,heroSystem,nodeComponent]
}()
// 1
var entities: Set<GKEntity>
let scene: SCNScene
// 2
init(scene: SCNScene) {
self.scene = scene
self.entities = Set<GKEntity>()
}
// 3
func add(_ entity: GKEntity) {
if let spriteNode = entity.component(ofType: SpriteComponent.self)?.node {
scene.rootNode.addChildNode(spriteNode)
let speed = Int.random(in: 7 ... 10)
let smoke = SCNParticleSystem(named: "art.scnassets/Models/spawnSmoke.scnp", inDirectory: nil)
let smokeNode = SCNNode()
spriteNode.addChildNode(smokeNode)
print("Monster Added")
}
for componentSystem in componentSystems {
componentSystem.addComponent(foundIn: entity)
}
entities.insert(entity)
}
// 4
func remove(_ entity: GKEntity) {
if let spriteNode = entity.component(ofType: SpriteComponent.self)?.node {
let confetti = SCNParticleSystem(named: "Media.scnassets/Fire.scnp", inDirectory: nil)
confetti?.loops = false
confetti?.particleLifeSpan = 0.05
confetti?.particleSize -= 1
confetti?.particleIntensity -= 0.5
print("Making Explosion")
if let geometry = spriteNode.geometry {
confetti?.emitterShape = geometry
}
spriteNode.addParticleSystem(confetti!)
print("Exploding Node!")
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
spriteNode.removeFromParentNode()
spriteNode.removeAllActions()
self.entities.remove(entity)
self.toRemove.insert(entity)
}
}
entities.remove(entity)
}
func update(_ deltaTime: CFTimeInterval) {
// 1
for system in componentSystems {
system.update(deltaTime: deltaTime)
}
// 2
for currentRemove in toRemove {
for componentSystem in componentSystems {
componentSystem.removeComponent(foundIn: currentRemove)
}
}
toRemove.removeAll()
}
func castle(for team: Team) -> GKEntity? {
for entity in self.entities {
if let teamComponent = entity.component(ofType: TeamComponent.self),
let _ = entity.component(ofType: HeroComponent.self) {
if teamComponent.team == team {
return entity
}
}
}
return nil
}
func spawnQuirk(team: Team , positon: SCNVector3) {
// 1
guard let teamEntity = castle(for: team) else {return}
// 2
if let teamCastleComponent = teamEntity.component(ofType: HeroComponent.self) {
if teamCastleComponent.coins < costQuirk , team == .team1{
return
}
teamCastleComponent.coins -= costQuirk
}
// 3
guard let floor = GAMEFLOOR as? SCNNode else {return}
let monster = Quirk(team: team, entityManager: self, floor:floor)
if let spriteComponent = monster.component(ofType: SpriteComponent.self) {
let randomX = Float.random(in: -10.0 ... -0.0)
let randomZ = Float.random(in: -10.0 ... -1.0)
spriteComponent.node.position = positon
let teamRing = SCNParticleSystem(named: "Media.scnassets/teamRing.scnp", inDirectory: nil)
switch team {
case .team1 :
teamRing?.particleColor = .blue
case .team2:
teamRing?.particleColor = .red
}
spriteComponent.node.addParticleSystem(teamRing!)
}
add(monster)
}
func spawnZap(_ team: Team, positon: SCNVector3) {
guard let teamEntity = castle(for: team) else {return}
if let teamCastleComponent = teamEntity.component(ofType: HeroComponent.self) {
if teamCastleComponent.coins < costZap , team == .team1{
return
}
teamCastleComponent.coins -= costZap
let monster = Zap(team: team, entityManager: self)
if let spriteComponent = monster.component(ofType: SpriteComponent.self) {
let randomX = Float.random(in: -10.0 ... -0.0)
let randomZ = Float.random(in: -10.0 ... -1.0)
spriteComponent.node.position = positon
}
add(monster)
}
}
func spawnMunch(_ team: Team, positon: SCNVector3) {
guard let teamEntity = castleForTeam(team),
let teamCastleComponent = teamEntity.component(ofType: HeroComponent.self),
let teamSpriteComponent = teamEntity.component(ofType: SpriteComponent.self) else {
return
}
if teamCastleComponent.coins < costMunch {
return
}
teamCastleComponent.coins -= costMunch
guard let floor = GAMEFLOOR as? SCNNode else {return}
let monster = Munch(team: team, entityManager: self, floor:floor)
if let spriteComponent = monster.component(ofType: SpriteComponent.self) {
let randomX = Float.random(in: -10.0 ... -0.0)
let randomZ = Float.random(in: -10.0 ... -1.0)
spriteComponent.node.position = positon
}
add(monster)
}
func entitiesForTeam(_ team: Team) -> [GKEntity] {
return entities.compactMap{ entity in
if let teamComponent = entity.component(ofType: TeamComponent.self) {
if teamComponent.team == team {
return entity
}
}
return nil
}
}
func moveComponentsForTeam(_ team: Team) -> [MoveComponent] {
let entities = entitiesForTeam(team)
var moveComponents = [MoveComponent]()
for entity in entities {
if let moveComponent = entity.component(ofType: MoveComponent.self) {
moveComponents.append(moveComponent)
}
}
return moveComponents
}
func castleForTeam(_ team: Team) -> GKEntity? {
for entity in entities {
if let teamComponent = entity.component(ofType: TeamComponent.self),
let _ = entity.component(ofType: HeroComponent.self) {
if teamComponent.team == team {
return entity
}
}
}
return nil
}
func entities(for team: Team) -> [GKEntity] {
return entities.compactMap{ entity in
if let teamComponent = entity.component(ofType: TeamComponent.self) {
if teamComponent.team == team {
return entity
}
}
return nil
}
}
func moveComponents(for team: Team) -> [MoveComponent] {
let entitiesToMove = entities(for: team)
var moveComponents = [MoveComponent]()
for entity in entitiesToMove {
if let moveComponent = entity.component(ofType: MoveComponent.self) {
moveComponents.append(moveComponent)
}
}
return moveComponents
}
}
TL;DR
我宁愿使用 GKSCNNodeComponent
作为代理的委托,它会自动处理相互位置更新。
如果一个组件被添加到组件系统,更新实体将不会影响它。
也许更新组件系统的顺序很重要。