以编程方式创建 SKScene 子类,没有大小信息?
create SKScene subclasses programmatically, without size info?
我正在尝试学习如何制作 GameManager 类型 class,并为我的每个 GameScenes 制作单独的 classes...可能是错误的做法,但对于为了这个问题,请接受这个作为做事的方式。
我的 GameManager 看起来像这样,引用了每个场景,这是静态的:
import SpriteKit
class GM {
static let scene2 = SecondScene()
static let scene3 = ThirdScene()
static let home = SKScene(fileNamed: "GameScene")
}
我如何以编程方式创建没有大小信息的 SKScene,因为它们位于 SKScene 的子class 中并且不知道视图大小是多少,而且我不想要他们需要为此担心:
我正在这样做,但是 EXC_BAD_Access 在 convenience override init()
class SecondScene: SKScene {
override init(size: CGSize){
super.init(size: size)
}
convenience override init(){
self.init()
self.backgroundColor = SKColor.red
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
}
正如我提到的,您的问题有点含糊,但让我们举一些例子来说明 GameManager class 可以是什么。
在我开始之前,让我们区分调用这个
let scene = StartScene(size: ...)
还有这个
let scene = SKScene(fileNamed: "StartScene")
第一种方法,使用大小,是当你在代码中创建你的场景并且你没有使用 xCode 视觉关卡编辑器时。
第二种方法是当你使用Xcode关卡编辑器时,所以你需要创建一个StartScene.sks文件。它是它在 fileNamed.
中查找的那个 .sks 文件
现在以游戏管理器为例,首先假设我们有 3 个 SKScenes。
class StartScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameOverScene: SKScene {
override func didMove(to view: SKView) { ... }
}
假设您想从 StartScene 过渡到 GameScene,您可以在 StartScene 中的正确位置添加此代码,例如当按下播放按钮时。这是从一个 SKScene 移动到下一个 SKScene 的最简单方法,直接从 SKScene 本身。
// Code only, no xCode level editor
let gameScene = GameScene(size: CGSize(...))
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
// With xCode level editor (returns an optional so needs if let
// This will need the GameScene.sks file with the correct custom class set up in the inspector
// Returns optional
if let gameScene = SKScene(fileNamed: "GameScene") {
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
}
现在来看一些 GameManager 的实际示例,我相信您已经了解其中的一些。
示例 1
假设我们想要一个场景加载管理器。你使用静态方法的方法将不起作用,因为当你转换到一个 SKScene 时需要创建一个新的实例,否则敌人等东西将不会重置。您使用静态方法的方法意味着您每次都会使用相同的实例,这并不好。
我个人为此使用协议扩展。
创建一个新的 .swift 文件并将其命名为 SceneLoaderManager 或其他名称并添加此代码
enum SceneIdentifier: String {
case start = "StartScene"
case game = "GameScene"
case gameOver = "GameOverScene"
}
private let sceneSize = CGSize(width: ..., height: ...)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
// No xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
let scene: SKScene
switch identifier {
case .start:
scene = StartScene(size: sceneSize)
case .game:
scene = GameScene(size: sceneSize)
case .gameOver:
scene = GameOverScene(size: sceneSize)
}
let transition = SKTransition...\
scene.scaleMode = .aspectFill
view?.presentScene(scene, transition: transition)
}
// With xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
guard let scene = SKScene(fileNamed: identifier.rawValue) else { return }
scene.scaleMode = .aspectFill
let transition = SKTransition...
view?.presentScene(scene, transition: transition)
}
}
现在3个场景符合协议
class StartScene: SKScene, SceneManager { ... }
并像这样调用加载方法,使用 3 个枚举案例中的 1 个作为场景标识符。
loadScene(withIdentifier: .game)
示例 2
让我们使用 Singleton 方法为游戏数据创建一个游戏管理器 class。
class GameData {
static let shared = GameData()
private init() { } // Private singleton init
var highscore = 0
func updateHighscore(forScore score: Int) {
guard score > highscore else { return }
highscore = score
save()
}
func save() {
// Some code to save the highscore property e.g UserDefaults or by archiving the whole GameData class
}
}
现在您可以在项目的任何地方说
GameData.shared.updateHighscore(forScore: SOMESCORE)
对于只需要 class 的 1 个实例的事情,您倾向于使用 Singleton。 Singleton classes 的一个很好的用法示例是 Game Center、InAppPurchases、GameData 等的助手 classes
示例 3
用于存储所有场景中可能需要的一些值的通用助手。这使用类似于您尝试执行的静态方法方法。我喜欢将它用于诸如游戏设置之类的事情,以便将它们放在一个很好的集中位置。
class GameHelper {
static let enemySpawnTime: TimeInterval = 5
static let enemyBossHealth = 5
static let playerSpeed = ...
}
在你的场景中像这样使用它们
... = GameHelper.playerSpeed
示例 4
A class 管理 SKSpriteNodes 例如敌人
class Enemy: SKSpriteNode {
var health = 5
init(imageNamed: String) {
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: SKColor.clear, size: texture.size())
}
func reduceHealth(by amount: Int) {
health -= amount
}
}
在您的场景中,您可以使用这个助手 class 创建敌人并调用它的方法和属性。这样你就可以轻松添加 10 个敌人并单独管理他们的健康等。例如
let enemy1 = Enemy(imageNamed: "Enemy1")
let enemy2 = Enemy(imageNamed: "Enemy2")
enemy1.reduceHealth(by: 3)
enemy2.reduceHealth(by: 1)
这是一个庞大的答案,但我希望这对您有所帮助。
我正在尝试学习如何制作 GameManager 类型 class,并为我的每个 GameScenes 制作单独的 classes...可能是错误的做法,但对于为了这个问题,请接受这个作为做事的方式。
我的 GameManager 看起来像这样,引用了每个场景,这是静态的:
import SpriteKit
class GM {
static let scene2 = SecondScene()
static let scene3 = ThirdScene()
static let home = SKScene(fileNamed: "GameScene")
}
我如何以编程方式创建没有大小信息的 SKScene,因为它们位于 SKScene 的子class 中并且不知道视图大小是多少,而且我不想要他们需要为此担心:
我正在这样做,但是 EXC_BAD_Access 在 convenience override init()
class SecondScene: SKScene {
override init(size: CGSize){
super.init(size: size)
}
convenience override init(){
self.init()
self.backgroundColor = SKColor.red
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
}
正如我提到的,您的问题有点含糊,但让我们举一些例子来说明 GameManager class 可以是什么。
在我开始之前,让我们区分调用这个
let scene = StartScene(size: ...)
还有这个
let scene = SKScene(fileNamed: "StartScene")
第一种方法,使用大小,是当你在代码中创建你的场景并且你没有使用 xCode 视觉关卡编辑器时。
第二种方法是当你使用Xcode关卡编辑器时,所以你需要创建一个StartScene.sks文件。它是它在 fileNamed.
中查找的那个 .sks 文件现在以游戏管理器为例,首先假设我们有 3 个 SKScenes。
class StartScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameOverScene: SKScene {
override func didMove(to view: SKView) { ... }
}
假设您想从 StartScene 过渡到 GameScene,您可以在 StartScene 中的正确位置添加此代码,例如当按下播放按钮时。这是从一个 SKScene 移动到下一个 SKScene 的最简单方法,直接从 SKScene 本身。
// Code only, no xCode level editor
let gameScene = GameScene(size: CGSize(...))
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
// With xCode level editor (returns an optional so needs if let
// This will need the GameScene.sks file with the correct custom class set up in the inspector
// Returns optional
if let gameScene = SKScene(fileNamed: "GameScene") {
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
}
现在来看一些 GameManager 的实际示例,我相信您已经了解其中的一些。
示例 1
假设我们想要一个场景加载管理器。你使用静态方法的方法将不起作用,因为当你转换到一个 SKScene 时需要创建一个新的实例,否则敌人等东西将不会重置。您使用静态方法的方法意味着您每次都会使用相同的实例,这并不好。
我个人为此使用协议扩展。 创建一个新的 .swift 文件并将其命名为 SceneLoaderManager 或其他名称并添加此代码
enum SceneIdentifier: String {
case start = "StartScene"
case game = "GameScene"
case gameOver = "GameOverScene"
}
private let sceneSize = CGSize(width: ..., height: ...)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
// No xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
let scene: SKScene
switch identifier {
case .start:
scene = StartScene(size: sceneSize)
case .game:
scene = GameScene(size: sceneSize)
case .gameOver:
scene = GameOverScene(size: sceneSize)
}
let transition = SKTransition...\
scene.scaleMode = .aspectFill
view?.presentScene(scene, transition: transition)
}
// With xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
guard let scene = SKScene(fileNamed: identifier.rawValue) else { return }
scene.scaleMode = .aspectFill
let transition = SKTransition...
view?.presentScene(scene, transition: transition)
}
}
现在3个场景符合协议
class StartScene: SKScene, SceneManager { ... }
并像这样调用加载方法,使用 3 个枚举案例中的 1 个作为场景标识符。
loadScene(withIdentifier: .game)
示例 2
让我们使用 Singleton 方法为游戏数据创建一个游戏管理器 class。
class GameData {
static let shared = GameData()
private init() { } // Private singleton init
var highscore = 0
func updateHighscore(forScore score: Int) {
guard score > highscore else { return }
highscore = score
save()
}
func save() {
// Some code to save the highscore property e.g UserDefaults or by archiving the whole GameData class
}
}
现在您可以在项目的任何地方说
GameData.shared.updateHighscore(forScore: SOMESCORE)
对于只需要 class 的 1 个实例的事情,您倾向于使用 Singleton。 Singleton classes 的一个很好的用法示例是 Game Center、InAppPurchases、GameData 等的助手 classes
示例 3
用于存储所有场景中可能需要的一些值的通用助手。这使用类似于您尝试执行的静态方法方法。我喜欢将它用于诸如游戏设置之类的事情,以便将它们放在一个很好的集中位置。
class GameHelper {
static let enemySpawnTime: TimeInterval = 5
static let enemyBossHealth = 5
static let playerSpeed = ...
}
在你的场景中像这样使用它们
... = GameHelper.playerSpeed
示例 4
A class 管理 SKSpriteNodes 例如敌人
class Enemy: SKSpriteNode {
var health = 5
init(imageNamed: String) {
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: SKColor.clear, size: texture.size())
}
func reduceHealth(by amount: Int) {
health -= amount
}
}
在您的场景中,您可以使用这个助手 class 创建敌人并调用它的方法和属性。这样你就可以轻松添加 10 个敌人并单独管理他们的健康等。例如
let enemy1 = Enemy(imageNamed: "Enemy1")
let enemy2 = Enemy(imageNamed: "Enemy2")
enemy1.reduceHealth(by: 3)
enemy2.reduceHealth(by: 1)
这是一个庞大的答案,但我希望这对您有所帮助。