Swift 3 中的动画图集和动态物理体
Animation atlas and dynamic physicsBody in Swift 3
我使用 "Hello World" Sprite-kit 模板做了一个小测试项目,其中有一个由这些帧组成的图集动画:
-
我想展示这个骑士和它的动画。
我想设置动态物理体
所以我用了一个工具来分离单帧,我做了一个 atlasc
文件夹
所以代码应该是:
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
knight = SKSpriteNode(texture:textures.first)
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
//
self.setPhysics()
let animation = SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false)
knight.run(animation, withKey:"knight")
}
func setPhysics() {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
knight.physicsBody?.isDynamic = false
}
}
输出是:
如您所见,physicsBody
是静态的,不尊重动画:这是正常的,因为在动画期间纹理会改变尺寸/大小和我们不会更改在操作期间保持不变的 physicsBody
。
根据来源,在 SKAction.animate
期间,没有方法允许更改 physicsBody
。
尽管我们使用:
/**
Creates an compound body that is the union of the bodies used to create it.
*/
public /*not inherited*/ init(bodies: [SKPhysicsBody])
为我们的动画的每一帧创建物体,这些物体都留在场景中,造成像这张照片这样丑陋的怪异情况:
因此,正确的做法应该是在动画期间截取帧并动态更改 physicsBody
。
我们也可以使用 SKScene
中的 update()
方法,但我在考虑扩展。
我的想法是将 animation
操作与 SKAction.group
组合,制作另一个检查第一个操作执行情况的自定义操作,拦截与当前 knight.texture
匹配的帧textures
数组并更改 physicsBody
启动外部方法,在本例中为 setPhysicsBody
.
那么,我写了这篇:
extension SKAction {
class func animateWithDynamicPhysicsBody(animate:SKAction, key:String, textures:[SKTexture], duration: TimeInterval, launchMethod: @escaping ()->()) ->SKAction {
let interceptor = SKAction.customAction(withDuration: duration) { node, _ in
if node is SKSpriteNode {
let n = node as! SKSpriteNode
guard n.action(forKey: key) != nil else { return }
if textures.contains(n.texture!) {
let frameNum = textures.index(of: n.texture!)
print("frame number: \(frameNum)")
// Launch a method to change physicBody or do other things with frameNum
launchMethod()
}
}
}
return SKAction.group([animate,interceptor])
}
}
添加此扩展,我们将代码的动画部分更改为:
//
self.setPhysics()
let animation = SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false)
let interceptor = SKAction.animateWithDynamicPhysicsBody(animate: animation, key: "knight", textures: textures, duration: 60.0, launchMethod: self.setPhysics)
knight.run(interceptor,withKey:"knight")
}
func setPhysics() {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
knight.physicsBody?.isDynamic = false
}
这终于成功了,输出是:
您知道获得此结果的更好方法或更优雅的方法吗?
就像我在评论中提到的那样,因为你在做盒装物理,所以给你的骑士添加一个子 SKSpriteNode 来处理物理的接触部分,并且只根据骑士的框架进行缩放:
(注意:这仅用于演示目的,我相信您可以想出一种更优雅的方式来处理多个精灵)
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
private var child = SKSpriteNode(color:.clear,size:CGSize(width:1,height:1))
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
knight = SKSpriteNode(texture:textures.first)
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
//
self.setPhysics()
let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
knight.run(animation,withKey:"knight")
}
override func didEvaluateActions() {
child.xScale = knight.frame.size.width
child.yScale = knight.frame.size.height
}
func setPhysics() {
child.physicsBody = SKPhysicsBody.init(rectangleOf: child.size)
child.physicsBody?.isDynamic = false
knight.addChild(child)
}
}
处理基于纹理的物体。我会写一个自定义动画来处理它:
extension SKAction
{
static func animate(withPhysicsTextures textures:[(texture:SKTexture,body:SKPhysicsBody)], timePerFrame:TimeInterval ,resize:Bool, restore:Bool) ->SKAction {
var originalTexture : SKTexture!;
let duration = timePerFrame * Double(textures.count);
return SKAction.customAction(withDuration: duration)
{
node,elapsedTime in
guard let sprNode = node as? SKSpriteNode
else
{
assert(false,"animatePhysicsWithTextures only works on members of SKSpriteNode");
return;
}
let index = Int((elapsedTime / CGFloat(duration)) * CGFloat(textures.count))
//If we havent assigned this yet, lets assign it now
if originalTexture == nil
{
originalTexture = sprNode.texture;
}
if(index < textures.count)
{
sprNode.texture = textures[index].texture
sprNode.physicsBody = textures[index].body
}
else if(restore)
{
sprNode.texture = originalTexture;
}
if(resize)
{
sprNode.size = sprNode.texture!.size();
}
}
}
}
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures = [texture:SKTexture,body:SKPhysicsBody]()
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
let texture = genericAtlas.textureNamed(textureName)
let body = SKPhysicsBody(texture:texture)
body.isDynamic = false
textures.append((texture:texture,body:body))
}
if textures.count>0 {
knight = SKSpriteNode(texture:textures.first.texture)
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
//
let animation = SKAction.animate(withPhysicsTextures: textures, timePerFrame: 0.15, resize: true, restore: false)
knight.run(animation, withKey:"knight")
}
}
另一个想法可能是关于 didEvaluateActions()
搜索更通用的方法以获得 "variable" physicsBody
遵循真实当前骑士纹理的建议 或 物理设置为 矩形体 像这种情况:
更新:(感谢Knight0fDragon和0x141E的干预)
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
var lastKnightTexture : SKTexture!
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
knight = SKSpriteNode(texture:textures.first)
lastKnightTexture = knight.texture
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
knight.run(animation,withKey:"knight")
}
override func didEvaluateActions() {
if knight.action(forKey: "knight") != nil {
if knight.texture != lastKnightTexture {
setPhysics()
lastKnightTexture = knight.texture
}
}
}
func setPhysics() {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
knight.physicsBody?.isDynamic = false
}
}
isDynamic = false
的解决方案。
*下面更新到2017年
经过几天对我的答案的测试,Knight0fDragon 的答案和其他一些想法来自其他 SO 答案(Confused 和 Whirlwind 建议..)我看到 有一个新问题 : physicsBody
无法将它们的属性传播到其他物体
充分和正确地。换句话说,将所有属性从一个主体复制到另一个主体这还不够。那是因为 Apple 限制了对 physicsBody
原始 class 的某些方法和属性的访问。
当您启动 physicsBody.applyImpulse
并充分传播 velocity
时,可能会发生这种情况,重力尚未得到正确考虑。这真的很糟糕..显然这是错误的。
所以主要目标是:不要更改 physicBody
重新创建 it.In 换句话说 不要重新创建它 !
我认为,除了创建精灵 children,您还可以创建一个幽灵精灵来代替主精灵,而主精灵利用了幽灵变化,但只有主精灵有一个physicsBody。
这似乎有效!
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
private var ghostKnight:SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
var lastKnightTexture : SKTexture!
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector.zero
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
// Prepare the ghost
ghostKnight = SKSpriteNode(texture:textures.first)
addChild(ghostKnight)
ghostKnight.alpha = 0.2
ghostKnight.position = CGPoint(x:self.frame.midX,y:100)
lastKnightTexture = ghostKnight.texture
// Prepare my sprite
knight = SKSpriteNode(texture:textures.first,size:CGSize(width:1,height:1))
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
let ghostAnimation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
ghostKnight.run(ghostAnimation,withKey:"ghostAnimation")
let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: false, restore: false))
knight.run(animation,withKey:"knight")
}
override func didEvaluateActions() {
if ghostKnight.action(forKey: "ghostAnimation") != nil {
if ghostKnight.texture != lastKnightTexture {
setPhysics()
lastKnightTexture = ghostKnight.texture
}
}
}
func setPhysics() {
if let _ = knight.physicsBody{
knight.xScale = ghostKnight.frame.size.width
knight.yScale = ghostKnight.frame.size.height
} else {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.frame.size)
knight.physicsBody?.isDynamic = true
knight.physicsBody?.allowsRotation = false
knight.physicsBody?.affectedByGravity = true
}
}
}
输出:
很明显,您可以将 alpha
设置为 0.0 并将 re-positioning 鬼魂设置为隐藏。
2017 年更新:
经过几个小时的测试,我尝试改进代码,最后我设法删除了幽灵精灵,但是,要正常工作,一个条件非常重要:你不应该将 SKAction.animate
与 [= 一起使用20=] 是真的。这是因为这种方法调整了精灵的大小并且不尊重比例(我真的不明白为什么,希望未来的苹果改进......)。这是我目前获得的最好的:
- 没有孩子
- 没有其他幽灵
- 没有扩展
- 没有重新创建动画方法
代码:
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
var lastKnightSize: CGSize!
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector.zero
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
// Prepare my sprite
knight = SKSpriteNode(texture:textures.first,size:CGSize(width:1,height:1))
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
lastKnightSize = knight.texture?.size()
setPhysics()
}
let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: false, restore: false))
knight.run(animation,withKey:"knight")
}
override func didEvaluateActions() {
lastKnightSize = knight.texture?.size()
knight.xScale = lastKnightSize.width
knight.yScale = lastKnightSize.height
}
func setPhysics() {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.frame.size)
knight.physicsBody?.isDynamic = true
knight.physicsBody?.allowsRotation = false
knight.physicsBody?.affectedByGravity = true
}
}
重要细节:
关于 isDynamic = true
这是不可能的,因为在频繁更改大小的过程中,Apple 也经常重置骑士 physicsBody 但不将最新的 physicsBody
属性的继承应用到新的重置 physicsBody
,这真是太可惜了,您可以在更新打印 knight.physicsBody?.velocity
时测试它(始终为零,但应该因重力而改变...)。这可能是 Apple 建议不要在物理过程中缩放精灵的原因。在我看来是 Sprite-kit 限制。
我正准备深入研究一些纹理物理体,运行 不久前就解决了这个问题。
读完这篇文章后,我想到了这样的方法:
做动画的时候,为什么不用base node(center node),然后子节点就是动画的每一帧。然后是隐藏和取消隐藏子节点的模式,其中每个子节点(我将做的)都使用纹理的 alpha(全部加载)用于它们的 PB。但只是隐藏/取消隐藏(我的回答)。
我的意思是,相对于为动画加载和卸载图像,我们拥有 12 个节点本身(作为动画帧的基本节点)会带来多少损失。
我心目中的任何惩罚都值得能够拥有各种不同的物理体
我使用 "Hello World" Sprite-kit 模板做了一个小测试项目,其中有一个由这些帧组成的图集动画:
-
我想展示这个骑士和它的动画。
我想设置动态物理体
所以我用了一个工具来分离单帧,我做了一个 atlasc
文件夹
所以代码应该是:
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
knight = SKSpriteNode(texture:textures.first)
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
//
self.setPhysics()
let animation = SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false)
knight.run(animation, withKey:"knight")
}
func setPhysics() {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
knight.physicsBody?.isDynamic = false
}
}
输出是:
如您所见,physicsBody
是静态的,不尊重动画:这是正常的,因为在动画期间纹理会改变尺寸/大小和我们不会更改在操作期间保持不变的 physicsBody
。
根据来源,在 SKAction.animate
期间,没有方法允许更改 physicsBody
。
尽管我们使用:
/**
Creates an compound body that is the union of the bodies used to create it.
*/
public /*not inherited*/ init(bodies: [SKPhysicsBody])
为我们的动画的每一帧创建物体,这些物体都留在场景中,造成像这张照片这样丑陋的怪异情况:
因此,正确的做法应该是在动画期间截取帧并动态更改 physicsBody
。
我们也可以使用 SKScene
中的 update()
方法,但我在考虑扩展。
我的想法是将 animation
操作与 SKAction.group
组合,制作另一个检查第一个操作执行情况的自定义操作,拦截与当前 knight.texture
匹配的帧textures
数组并更改 physicsBody
启动外部方法,在本例中为 setPhysicsBody
.
那么,我写了这篇:
extension SKAction {
class func animateWithDynamicPhysicsBody(animate:SKAction, key:String, textures:[SKTexture], duration: TimeInterval, launchMethod: @escaping ()->()) ->SKAction {
let interceptor = SKAction.customAction(withDuration: duration) { node, _ in
if node is SKSpriteNode {
let n = node as! SKSpriteNode
guard n.action(forKey: key) != nil else { return }
if textures.contains(n.texture!) {
let frameNum = textures.index(of: n.texture!)
print("frame number: \(frameNum)")
// Launch a method to change physicBody or do other things with frameNum
launchMethod()
}
}
}
return SKAction.group([animate,interceptor])
}
}
添加此扩展,我们将代码的动画部分更改为:
//
self.setPhysics()
let animation = SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false)
let interceptor = SKAction.animateWithDynamicPhysicsBody(animate: animation, key: "knight", textures: textures, duration: 60.0, launchMethod: self.setPhysics)
knight.run(interceptor,withKey:"knight")
}
func setPhysics() {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
knight.physicsBody?.isDynamic = false
}
这终于成功了,输出是:
您知道获得此结果的更好方法或更优雅的方法吗?
就像我在评论中提到的那样,因为你在做盒装物理,所以给你的骑士添加一个子 SKSpriteNode 来处理物理的接触部分,并且只根据骑士的框架进行缩放:
(注意:这仅用于演示目的,我相信您可以想出一种更优雅的方式来处理多个精灵)
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
private var child = SKSpriteNode(color:.clear,size:CGSize(width:1,height:1))
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
knight = SKSpriteNode(texture:textures.first)
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
//
self.setPhysics()
let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
knight.run(animation,withKey:"knight")
}
override func didEvaluateActions() {
child.xScale = knight.frame.size.width
child.yScale = knight.frame.size.height
}
func setPhysics() {
child.physicsBody = SKPhysicsBody.init(rectangleOf: child.size)
child.physicsBody?.isDynamic = false
knight.addChild(child)
}
}
处理基于纹理的物体。我会写一个自定义动画来处理它:
extension SKAction
{
static func animate(withPhysicsTextures textures:[(texture:SKTexture,body:SKPhysicsBody)], timePerFrame:TimeInterval ,resize:Bool, restore:Bool) ->SKAction {
var originalTexture : SKTexture!;
let duration = timePerFrame * Double(textures.count);
return SKAction.customAction(withDuration: duration)
{
node,elapsedTime in
guard let sprNode = node as? SKSpriteNode
else
{
assert(false,"animatePhysicsWithTextures only works on members of SKSpriteNode");
return;
}
let index = Int((elapsedTime / CGFloat(duration)) * CGFloat(textures.count))
//If we havent assigned this yet, lets assign it now
if originalTexture == nil
{
originalTexture = sprNode.texture;
}
if(index < textures.count)
{
sprNode.texture = textures[index].texture
sprNode.physicsBody = textures[index].body
}
else if(restore)
{
sprNode.texture = originalTexture;
}
if(resize)
{
sprNode.size = sprNode.texture!.size();
}
}
}
}
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures = [texture:SKTexture,body:SKPhysicsBody]()
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
let texture = genericAtlas.textureNamed(textureName)
let body = SKPhysicsBody(texture:texture)
body.isDynamic = false
textures.append((texture:texture,body:body))
}
if textures.count>0 {
knight = SKSpriteNode(texture:textures.first.texture)
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
//
let animation = SKAction.animate(withPhysicsTextures: textures, timePerFrame: 0.15, resize: true, restore: false)
knight.run(animation, withKey:"knight")
}
}
另一个想法可能是关于 didEvaluateActions()
搜索更通用的方法以获得 "variable" physicsBody
遵循真实当前骑士纹理的建议 或 物理设置为 矩形体 像这种情况:
更新:(感谢Knight0fDragon和0x141E的干预)
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
var lastKnightTexture : SKTexture!
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
knight = SKSpriteNode(texture:textures.first)
lastKnightTexture = knight.texture
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
knight.run(animation,withKey:"knight")
}
override func didEvaluateActions() {
if knight.action(forKey: "knight") != nil {
if knight.texture != lastKnightTexture {
setPhysics()
lastKnightTexture = knight.texture
}
}
}
func setPhysics() {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
knight.physicsBody?.isDynamic = false
}
}
isDynamic = false
的解决方案。
*下面更新到2017年
经过几天对我的答案的测试,Knight0fDragon 的答案和其他一些想法来自其他 SO 答案(Confused 和 Whirlwind 建议..)我看到 有一个新问题 : physicsBody
无法将它们的属性传播到其他物体
充分和正确地。换句话说,将所有属性从一个主体复制到另一个主体这还不够。那是因为 Apple 限制了对 physicsBody
原始 class 的某些方法和属性的访问。
当您启动 physicsBody.applyImpulse
并充分传播 velocity
时,可能会发生这种情况,重力尚未得到正确考虑。这真的很糟糕..显然这是错误的。
所以主要目标是:不要更改 physicBody
重新创建 it.In 换句话说 不要重新创建它 !
我认为,除了创建精灵 children,您还可以创建一个幽灵精灵来代替主精灵,而主精灵利用了幽灵变化,但只有主精灵有一个physicsBody。
这似乎有效!
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
private var ghostKnight:SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
var lastKnightTexture : SKTexture!
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector.zero
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
// Prepare the ghost
ghostKnight = SKSpriteNode(texture:textures.first)
addChild(ghostKnight)
ghostKnight.alpha = 0.2
ghostKnight.position = CGPoint(x:self.frame.midX,y:100)
lastKnightTexture = ghostKnight.texture
// Prepare my sprite
knight = SKSpriteNode(texture:textures.first,size:CGSize(width:1,height:1))
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
let ghostAnimation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
ghostKnight.run(ghostAnimation,withKey:"ghostAnimation")
let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: false, restore: false))
knight.run(animation,withKey:"knight")
}
override func didEvaluateActions() {
if ghostKnight.action(forKey: "ghostAnimation") != nil {
if ghostKnight.texture != lastKnightTexture {
setPhysics()
lastKnightTexture = ghostKnight.texture
}
}
}
func setPhysics() {
if let _ = knight.physicsBody{
knight.xScale = ghostKnight.frame.size.width
knight.yScale = ghostKnight.frame.size.height
} else {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.frame.size)
knight.physicsBody?.isDynamic = true
knight.physicsBody?.allowsRotation = false
knight.physicsBody?.affectedByGravity = true
}
}
}
输出:
很明显,您可以将 alpha
设置为 0.0 并将 re-positioning 鬼魂设置为隐藏。
2017 年更新:
经过几个小时的测试,我尝试改进代码,最后我设法删除了幽灵精灵,但是,要正常工作,一个条件非常重要:你不应该将 SKAction.animate
与 [= 一起使用20=] 是真的。这是因为这种方法调整了精灵的大小并且不尊重比例(我真的不明白为什么,希望未来的苹果改进......)。这是我目前获得的最好的:
- 没有孩子
- 没有其他幽灵
- 没有扩展
- 没有重新创建动画方法
代码:
import SpriteKit
class GameScene: SKScene {
var knight: SKSpriteNode!
var textures : [SKTexture] = [SKTexture]()
var lastKnightSize: CGSize!
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector.zero
let plist = "knight.plist"
let genericAtlas = SKTextureAtlas(named:plist)
let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
for i in 0 ..< genericAtlas.textureNames.count
{
let textureName = (String(format:"%@%02d",filename,i))
textures.append(genericAtlas.textureNamed(textureName))
}
if textures.count>0 {
// Prepare my sprite
knight = SKSpriteNode(texture:textures.first,size:CGSize(width:1,height:1))
knight.zPosition = 2
addChild(knight)
knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
lastKnightSize = knight.texture?.size()
setPhysics()
}
let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: false, restore: false))
knight.run(animation,withKey:"knight")
}
override func didEvaluateActions() {
lastKnightSize = knight.texture?.size()
knight.xScale = lastKnightSize.width
knight.yScale = lastKnightSize.height
}
func setPhysics() {
knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.frame.size)
knight.physicsBody?.isDynamic = true
knight.physicsBody?.allowsRotation = false
knight.physicsBody?.affectedByGravity = true
}
}
重要细节:
关于 isDynamic = true
这是不可能的,因为在频繁更改大小的过程中,Apple 也经常重置骑士 physicsBody 但不将最新的 physicsBody
属性的继承应用到新的重置 physicsBody
,这真是太可惜了,您可以在更新打印 knight.physicsBody?.velocity
时测试它(始终为零,但应该因重力而改变...)。这可能是 Apple 建议不要在物理过程中缩放精灵的原因。在我看来是 Sprite-kit 限制。
我正准备深入研究一些纹理物理体,运行 不久前就解决了这个问题。
读完这篇文章后,我想到了这样的方法:
做动画的时候,为什么不用base node(center node),然后子节点就是动画的每一帧。然后是隐藏和取消隐藏子节点的模式,其中每个子节点(我将做的)都使用纹理的 alpha(全部加载)用于它们的 PB。但只是隐藏/取消隐藏(我的回答)。
我的意思是,相对于为动画加载和卸载图像,我们拥有 12 个节点本身(作为动画帧的基本节点)会带来多少损失。
我心目中的任何惩罚都值得能够拥有各种不同的物理体