Я сделал небольшой тестовый проект, используя шаблон Sprite-kit "Hello World", где есть атласная анимация, составленная из этих кадров:
-
Я хочу показать этого рыцаря и его анимацию.
Я хочу установить ДИНАМИЧЕСКОЕ физическое тело.
Итак, я использовал инструмент для разделения отдельных кадров и создал папку 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
на лету. Мы также можем использовать метод update()
из SKScene
, но я думал о расширении.
Моя идея состоит в том, чтобы объединить действие 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
}
Это, наконец, работает, вывод:
Знаете ли вы лучший способ или более элегантный метод для получения этого результата?