.sks 文件中 SKSpriteNode 的子类

Subclass of SKSpriteNode in .sks file

我正在使用 SpriteKit .sks 文件我可以将 .sks 中的精灵制作成 SKSpriteNode 的子类实例吗?

这是我子类中的初始化方法:

    init(imageNamed: String) {

    let blockTexture = SKTexture(imageNamed: imageNamed)

    super.init(texture: blockTexture, color: nil, size: blockTexture.size())
}

在 GameScene.swift 中,我可以创建这样的实例:

var myObj = Block(imageNamed: "Block")

我的问题是如何将此实例与 .sks 文件相关联?

我试过这行代码,但它不起作用。

myObj     = childNodeWithName("block1") as Block

有什么帮助吗?

谢谢。

这里有几个问题需要解决...

.sks 加载如何工作

当您加载 .sks 文件时,SpriteKit 会使用 NSKeyedUnarchiver 实例化所有内容。这意味着里面的所有节点都被加载为任何基础 classes 它们在创建 .sks 文件时由 Xcode 指定为 - SKSpriteNode 对于具有纹理艺术的精灵, SKLabelNode 用于文本,SKFieldNode 用于物理场等。并且 Xcode 目前不提供为 [=13= 中的节点设置自定义 classes 的选项] 文件。

(唯一的例外是更改场景本身的 运行 时间 class — .sks 文件中所有内容的顶级容器。那只是因为项目模板中为您提供的自定义 SKNode.unarchiveFromFile 实现。它在加载时更改 classes 的技术适用于您在存档中只有一个特定 class 实例的情况 —适合 SKScene,不适合场景中的许多节点。)

铸造的工作原理

当你写这样的东西时:

myObj = childNodeWithName("block1") as Block

你是在告诉编译器:"Hey, you know that thing you got from childNodeWithName? All you know is that it's an SKNode, but I know it's really a Block, so please let me call Block methods on it."(编译器说,"Okay, whatever.")

但是后来在运行的时候,你得到的那个东西最好真的是一个Block,或者你的应用程序会崩溃,因为你试图用 Block 之外的东西做一些 Blocky。而且,根据上面关于 .sks 加载的一点,那东西不是也不能是 Block — Xcode 不知道如何将 Block 放入一个 .sks 文件。所以你无法从中得到 Block,所以你的应用肯定会崩溃。

解决方法

那么,如果您不能将自定义 classes 放入 .sks 文件中,那么 可以 做什么?这在一定程度上取决于您要完成的目标。但是有一个技巧可能也适用于一般的 game/app 设计:使用 .sks 文件进行一般布局和配置,然后使用第二遍来引入需要自定义行为的东西。

例如,如果您在 2D 平台游戏中构建关卡,您不会真的希望 .sks 文件包含您的 Plumber class 即使你可以 - 那 class 可能有很多关于这个人有多高或多胖,他跳多高,他的胡子形状等的细节,你不想设置这些每次你创建一个新的关卡时都会重新启动,更不用说将它们再次保存在每个关卡的 .sks 文件中了。相反,在每个关卡文件中你真正需要知道的唯一一件事就是他的起始位置。因此,在 Xcode 中拖出一个 "Empty Node",并在加载时将该节点替换为 Plumber class 的实例,如下所示:

let spawnPoint = childNodeWithName("spawnPoint")
let player = Plumber()
player.position = spawnPoint.position
addChild(player)
spawnPoint.removeFromParent()

如果您想在 .sks 文件中设置更多配置详细信息,您可以考虑自动执行该过程。

  1. 制作一个执行上述节点交换技巧的方法。 (将其命名为 replaceNode(_:withNode:)。)
  2. 为您的自定义 class 创建一个初始值设定项,它接受 SKNodeSKSpriteNode,并设置它的所有继承属性(或者至少是您关心的属性,例如颜色和纹理)来自该节点。
  3. 使用enumerateChildNodesWithName:usingBlock: with a search pattern查找场景中具有某种名称的所有节点,并将它们替换为使用初始化程序创建的新节点。类似于:

    enumerateChildNodesWithName("//brick_[0-9]*") { node, stop in
        self.replaceNode(node, withNode: BrickBlock(node))
    }
    enumerateChildNodesWithName("//question_[0-9]*") { node, stop in
        self.replaceNode(node, withNode: QuestionBlock(node))
    }