我被要求简化这个问题,这就是我正在做的事情。
我在SpriteKit的物理关节(以及可能的身体特性)中挣扎。我尝试了每一个子类和许多配置,但接缝似乎没有用,或者我做错了什么。
我正在开发Snake游戏。用户控制蛇的头部,它应该以恒定的速度向前移动,用户可以顺时针或逆时针转动它。所有剩下的蛇的碎片都应该跟着头部 - 它们应该和前一段时间的路径完全相同。
我认为对于这个游戏,Pin关节应该是答案,哪个锚点恰好位于元素之间的中心。
不幸的是结果并不完美。结构应该是完美的圆形,但它并不是。我附加了代码,gif显示了当前的效果。是否有人经验足以给我任何建议物理身体和/或关节的哪些属性应该适用于此处以达到预期的效果?
我的代码:
class GameScene: SKScene {
private var elements = [SKNode]()
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
let dummyTurnNode = SKNode()
dummyTurnNode.position = CGPoint(x: size.width / 2 - 50, y: size.height / 2)
let dummyTurnBody = SKPhysicsBody(circleOfRadius: 1)
dummyTurnBody.isDynamic = false
dummyTurnNode.physicsBody = dummyTurnBody
addChild(dummyTurnNode)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
let label = SKLabelNode(text: "A")
label.fontSize = 10
label.fontName = "Helvetica-Bold"
element.addChild(label)
if index == 0 {
element.fillColor = UIColor.blue()
body.velocity = CGVector(dx: 0, dy: 30)
let dummyTurnJoint = SKPhysicsJointPin.joint(withBodyA: dummyTurnBody, bodyB: body, anchor: dummyTurnNode.position)
physicsWorld.add(dummyTurnJoint)
} else {
body.linearDamping = 1
element.fillColor = UIColor.red()
let previousElement = elements[index - 1]
let connectingJoint = SKPhysicsJointPin.joint(withBodyA: previousElement.physicsBody!, bodyB: body, anchor: CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index) + CGFloat(15)))
physicsWorld.add(connectingJoint)
}
}
}
override func update(_ currentTime: TimeInterval) {
let head = elements.first!.physicsBody!
var velocity = head.velocity
velocity.normalize()
velocity.multiply(30)
head.velocity = velocity
}
}
extension CGVector {
var rwLength: CGFloat {
let xSq = pow(dx, 2)
let ySq = pow(dy, 2)
return sqrt(xSq + ySq)
}
mutating func normalize() {
dx /= rwLength
dy /= rwLength
}
mutating func multiply(_ factor: CGFloat) {
dx *= factor
dy *= factor
}
}
答案 0 :(得分:2)
&#34;所有剩下的蛇的碎片应该跟着头部 - 它们应该与前一段时间的路径完全相同。&#34;
你应该注意到,对于物理关节,无论你做什么,你都可能会有变化。即使你接近完美,你也会在引擎盖下面出现舍入错误,导致路径不准确。
如果所有的尾部都相同,你也可以使用不同的方法,这是我为彗尾做的事情。基本上这个想法是你有一个尾对象数组,每帧移动最后一个尾对象总是移动到与head-object相同的位置。如果头部物体具有较高的z位置,则尾部被绘制在其下方。
如果你需要保持你的尾巴顺序,你可以通过存储一个头位置阵列(每帧路径)来改变方法,然后将尾对象沿着该路径放置在对蛇的每帧更新调用中
请参阅下面的代码,例如:
这些是头对象变量:
var tails = [SKEmitterNode]()
var tailIndex = 0
在你的head init函数中实例化尾对象:
for _ in 0...MAX_TAIL_INDEX
{
if let remnant = SKEmitterNode(fileNamed: "FireTail.sks")
{
p.tails.append(remnant)
}
}
调用以下每帧:
func drawTail()
{
if tails.count > tailIndex
{
tails[tailIndex].resetSimulation()
tails[tailIndex].particleSpeed = velocity() / 4
tails[tailIndex].emissionAngle = zRotation - CGFloat(M_PI_2) // opposite direction
tails[tailIndex].position = position
tailIndex = tailIndex < MAX_TAIL_INDEX ? tailIndex + 1 : 0
}
}
当您从场景update()函数调用时,结果效果实际上非常平滑。