通过转换保存 GameScene 的状态
Save state of GameScene through transitions
我想为我的 SpriteKit 游戏建立一个清单。为此,当我按下暂停按钮时,我想转到另一个代表我的库存的 SKScene 文件。我的问题是,当我从我的 InventoryScene 转换回我的 GameScene 时,GameScene 会加载全新的。这是我的 GameScene class:
的转换代码
func loadInventory(){
if !transitionInProgress{
transitionInProgress = true
if let scene = InventoryScene(fileNamed: "Inventory"){
Globals.InventoryGlobals.levelBeforeSwitch = currentLevel
scene.scaleMode = .aspectFill
let transition = SKTransition.push(with: .down, duration: 0.5)
self.view?.presentScene(scene, transition: transition)
}
}
}
使用此代码,我将转到我的 InventoryScene。
现在在我的 InventoryScene 中,我想返回到我的 GameScene 中:
func loadLevel(level: String){
if !transitionInProgress{
transitionInProgress = true
if let scene = GameScene(fileNamed: level){
scene.currentLevel = level
scene.scaleMode = .aspectFill
let transition = SKTransition.doorsOpenHorizontal(withDuration: 1)
self.view?.presentScene(scene, transition: transition)
}
}
}
过渡正常,但我的问题是 GameScene 加载全新,这显然是因为我实例化了一个新的 GameScene。所以当玩家在关卡中间然后转到 Inventory 并返回 GameScene 时,玩家回到了关卡的开头。如果我从 Inventory 回到场景,我想像以前一样成为 GameScene(玩家位置、敌人生命值等)
有人知道我该怎么做吗?
留住你的场景很简单,你只需要留住强引用的场景就可以了
在你的ViewController
中,就像存储一个变量一样简单
class ViewController : UIViewController
{
var gameScene = GameScene(fileNamed:"GameScene")
}
现在,只要您的视图控制器处于活动状态,您的场景就会处于活动状态。
要访问它,您只需要找到一种方法告诉 MenuScene 您的视图控制器在哪里,然后呈现场景。
class ViewController : UIViewController
{
var gameScene = GameScene(fileNamed:"GameScene")
lazy var skView : SKView = self.view as! SKView
func gotoMenu()
{
let menu = MenuScene(fileNamed"MenuScene")
menu.viewController = self
skView.presentScene(menu)
}
}
class MenuScene : SKScene
{
var viewController : ViewController!
func returnToGame()
{
view.presentScene(viewcontroller.gameScene)
}
}
但是,如果您不想一直使用自定义 SKScene classes,使用视图控制器,或者宁愿依赖组件,为什么没有方便的方法呢?回到场景
好吧,我的朋友,这就是 userData
发挥作用的地方
class GameScene : SKScene
{
func gotoMenu()
{
let menu = MenuScene(fileNamed:"MenuScene")
menu.userData = menu.userData ?? ["":Any]()
menu.userData["backToScene"] = self
view.presentScene(menu)
}
}
class MenuScene : SKScene
{
func returnToGame()
{
guard let userData = userData, let scene = userData["backToScene"] as? SKScene
view.presentScene(scene)
}
}
由于我们将其保留在用户数据中,因此我们现在可以在我们可以访问菜单场景的任何地方呈现旧场景。
userData
也很擅长转移库存,当然我会创建一个 class 来管理库存,并通过 userData
传递参考
现在,要创建一个覆盖当前场景的菜单,就像在场景中应用一个新节点一样简单。
您甚至可以使用单独的 SKS 文件来布置您的菜单,并叠加它:
class GameScene : SKScene
{
let menu = MenuScene(fileNamed:"MenuScene")
func overlayMenu()
{
scene.addChild(menu) //You probably want to add an SKCameraNode, and add it to the camera instead
}
override func update(currentTime: CFTimeInterval)
{
if menu.parent != nil
{
menu.update(currentTime:currentTime) //do this only when you need to have a constant update call, be sure to include additional functionality like `didFinishUpdate` in the approprate functions when needed
}
}
}
当然现在是开发什么叫worldNode
的好时机,也可以称为gameNode
从本质上讲,这个节点是包含所有游戏元素的节点。
这允许您添加可以暂停游戏的覆盖节点。
您的场景层次结构如下所示:
SKScene
--worldNode
----属于游戏的所有节点
--menuNode
----属于菜单的所有节点
现在任何时候,menu 都可以将 worldNode 的 isPaused 状态设置为 true,允许游戏暂停并仍然让您能够与 menuNode 进行交互
这正是您想要的。
class GameScene: SKScene {
class InventoryManager: UIView {
override init(frame: CGRect) {
super.init(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
//here is where you set up anything you want to display inside the view
self.backgroundColor = UIColor.green
let Button = UIButton()
Button.frame = CGRect(x: 0, y: 0, width: 40, height: 20)
Button.backgroundColor = UIColor.red
let TouchView = UIView()
TouchView.frame = self.frame
Button.center.x = TouchView.center.x
Button.center.y = TouchView.center.y
TouchView.backgroundColor = UIColor.brown
self.addSubview(TouchView)
//make sure you add any objects that you want to the TouchView and not to the self
TouchView.addSubview(Button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var InventoryView = InventoryManager()
var Node = SKSpriteNode()
override func didMove(to view: SKView) {
//this keeps the view centered in the screen
InventoryView.center.x = (self.view?.center.x)!
InventoryView.center.y = (self.view?.center.y)!
Node = SKSpriteNode(color: UIColor.blue, size: CGSize(width: 40, height: 40))
Node.position = CGPoint(x: self.frame.size.width / 3, y: self.frame.size.height / 3)
self.addChild(Node)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if !(self.view?.frame.contains(location))! {
InventoryView.removeFromSuperview()
}
if Node.contains(location) && InventoryView.isDescendant(of: self.view!) {
self.view?.addSubview(InventoryView)
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
单击蓝色按钮再次将其添加到场景中,单击其他任何地方将其从场景中删除
我在我的 Spritekit 游戏中一直进行中途停留 windows,它不一定像您想的那么复杂。以下是如何在不离开场景的情况下在 Spritekit 中完成所有操作。
为您的 InventoryDialog 创建一个新的 class,它是 SKSpriteNode 的子class。
import SpriteKit
protocol InventoryDialogDelegate: class {
func close()
}
class InventoryDialog: SKSpriteNode {
private var closeButton: SKSpriteNode!
weak var delegate: InventoryDialogDelegate?
init(size: CGSize) {
super.init(texture: nil, color: .clear, size: size)
name = "inventoryDialog"
//do some background design work here
let background = SKSpriteNode(color: .white, size: self.size)
background.zPosition = 1
addChild(background)
closeButton = SKSpriteNode(texture: SKTexture(imageNamed: "closeButton"))
closeButton.position = CGPoint(x: self.size.width / 2 - closeButton.size.width / 2, y: self.size.height / 2 - closeButton.size.height / 2)
closeButton.zPosition = 2
addChild(closeButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if closeButton.contains(touchLocation) {
close()
}
}
func close() {
self.delegate?.close()
}
}
在您的 GameScene 文件中
class GameScene: SKScene {
var inventoryDialog: InventoryDialog!
var openButton: SKSpriteNode!
override func didMove(to view: SKView) {
openButton = SKSpriteNode(texture: SKTexture(imageNamed: "openButton"))
openButton.position = CGPoint(x: self.size.width / 2 - closeButton.size.width / 2, y: self.size.height / 2 - closeButton.size.height / 2)
openButton.zPosition = 2
addChild(openButton)
}
func displayInventoryDialog() {
backgroundBlocker = SKSpriteNode(imageNamed: "background3")
backgroundBlocker.size = self.size
backgroundBlocker.zPosition = 4999
addChild(backgroundBlocker)
inventoryDialog = InventoryDialog(size: CGSize(width: 500, height: 800))
inventoryDialog.delegate = self
inventoryDialog.zPosition = 5000
addChild(inventoryDialog)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//pause any action that you don't want running while the dialog is open
gameLayer.isPaused = true
let touch = touches.first
let touchLocation = touch!.location(in: self)
if openButton.contains(touchLocation) {
displayInventoryDialog()
}
}
}
//MARK: - InventoryDialogDelegate Methods
extension GameScene: InventoryDialogDelegate {
func close() {
//at this point you could update any GUI nesc. based on what happened in your dialog
backgroundBlocker.removeFromParent()
inventoryDialog?.removeFromParent()
gameLayer.isPaused = false
}
}
我想为我的 SpriteKit 游戏建立一个清单。为此,当我按下暂停按钮时,我想转到另一个代表我的库存的 SKScene 文件。我的问题是,当我从我的 InventoryScene 转换回我的 GameScene 时,GameScene 会加载全新的。这是我的 GameScene class:
的转换代码func loadInventory(){
if !transitionInProgress{
transitionInProgress = true
if let scene = InventoryScene(fileNamed: "Inventory"){
Globals.InventoryGlobals.levelBeforeSwitch = currentLevel
scene.scaleMode = .aspectFill
let transition = SKTransition.push(with: .down, duration: 0.5)
self.view?.presentScene(scene, transition: transition)
}
}
}
使用此代码,我将转到我的 InventoryScene。
现在在我的 InventoryScene 中,我想返回到我的 GameScene 中:
func loadLevel(level: String){
if !transitionInProgress{
transitionInProgress = true
if let scene = GameScene(fileNamed: level){
scene.currentLevel = level
scene.scaleMode = .aspectFill
let transition = SKTransition.doorsOpenHorizontal(withDuration: 1)
self.view?.presentScene(scene, transition: transition)
}
}
}
过渡正常,但我的问题是 GameScene 加载全新,这显然是因为我实例化了一个新的 GameScene。所以当玩家在关卡中间然后转到 Inventory 并返回 GameScene 时,玩家回到了关卡的开头。如果我从 Inventory 回到场景,我想像以前一样成为 GameScene(玩家位置、敌人生命值等)
有人知道我该怎么做吗?
留住你的场景很简单,你只需要留住强引用的场景就可以了
在你的ViewController
中,就像存储一个变量一样简单
class ViewController : UIViewController
{
var gameScene = GameScene(fileNamed:"GameScene")
}
现在,只要您的视图控制器处于活动状态,您的场景就会处于活动状态。
要访问它,您只需要找到一种方法告诉 MenuScene 您的视图控制器在哪里,然后呈现场景。
class ViewController : UIViewController
{
var gameScene = GameScene(fileNamed:"GameScene")
lazy var skView : SKView = self.view as! SKView
func gotoMenu()
{
let menu = MenuScene(fileNamed"MenuScene")
menu.viewController = self
skView.presentScene(menu)
}
}
class MenuScene : SKScene
{
var viewController : ViewController!
func returnToGame()
{
view.presentScene(viewcontroller.gameScene)
}
}
但是,如果您不想一直使用自定义 SKScene classes,使用视图控制器,或者宁愿依赖组件,为什么没有方便的方法呢?回到场景
好吧,我的朋友,这就是 userData
发挥作用的地方
class GameScene : SKScene
{
func gotoMenu()
{
let menu = MenuScene(fileNamed:"MenuScene")
menu.userData = menu.userData ?? ["":Any]()
menu.userData["backToScene"] = self
view.presentScene(menu)
}
}
class MenuScene : SKScene
{
func returnToGame()
{
guard let userData = userData, let scene = userData["backToScene"] as? SKScene
view.presentScene(scene)
}
}
由于我们将其保留在用户数据中,因此我们现在可以在我们可以访问菜单场景的任何地方呈现旧场景。
userData
也很擅长转移库存,当然我会创建一个 class 来管理库存,并通过 userData
现在,要创建一个覆盖当前场景的菜单,就像在场景中应用一个新节点一样简单。
您甚至可以使用单独的 SKS 文件来布置您的菜单,并叠加它:
class GameScene : SKScene
{
let menu = MenuScene(fileNamed:"MenuScene")
func overlayMenu()
{
scene.addChild(menu) //You probably want to add an SKCameraNode, and add it to the camera instead
}
override func update(currentTime: CFTimeInterval)
{
if menu.parent != nil
{
menu.update(currentTime:currentTime) //do this only when you need to have a constant update call, be sure to include additional functionality like `didFinishUpdate` in the approprate functions when needed
}
}
}
当然现在是开发什么叫worldNode
的好时机,也可以称为gameNode
从本质上讲,这个节点是包含所有游戏元素的节点。 这允许您添加可以暂停游戏的覆盖节点。
您的场景层次结构如下所示:
SKScene
--worldNode
----属于游戏的所有节点
--menuNode
----属于菜单的所有节点
现在任何时候,menu 都可以将 worldNode 的 isPaused 状态设置为 true,允许游戏暂停并仍然让您能够与 menuNode 进行交互
这正是您想要的。
class GameScene: SKScene {
class InventoryManager: UIView {
override init(frame: CGRect) {
super.init(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
//here is where you set up anything you want to display inside the view
self.backgroundColor = UIColor.green
let Button = UIButton()
Button.frame = CGRect(x: 0, y: 0, width: 40, height: 20)
Button.backgroundColor = UIColor.red
let TouchView = UIView()
TouchView.frame = self.frame
Button.center.x = TouchView.center.x
Button.center.y = TouchView.center.y
TouchView.backgroundColor = UIColor.brown
self.addSubview(TouchView)
//make sure you add any objects that you want to the TouchView and not to the self
TouchView.addSubview(Button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var InventoryView = InventoryManager()
var Node = SKSpriteNode()
override func didMove(to view: SKView) {
//this keeps the view centered in the screen
InventoryView.center.x = (self.view?.center.x)!
InventoryView.center.y = (self.view?.center.y)!
Node = SKSpriteNode(color: UIColor.blue, size: CGSize(width: 40, height: 40))
Node.position = CGPoint(x: self.frame.size.width / 3, y: self.frame.size.height / 3)
self.addChild(Node)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if !(self.view?.frame.contains(location))! {
InventoryView.removeFromSuperview()
}
if Node.contains(location) && InventoryView.isDescendant(of: self.view!) {
self.view?.addSubview(InventoryView)
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
单击蓝色按钮再次将其添加到场景中,单击其他任何地方将其从场景中删除
我在我的 Spritekit 游戏中一直进行中途停留 windows,它不一定像您想的那么复杂。以下是如何在不离开场景的情况下在 Spritekit 中完成所有操作。
为您的 InventoryDialog 创建一个新的 class,它是 SKSpriteNode 的子class。
import SpriteKit
protocol InventoryDialogDelegate: class {
func close()
}
class InventoryDialog: SKSpriteNode {
private var closeButton: SKSpriteNode!
weak var delegate: InventoryDialogDelegate?
init(size: CGSize) {
super.init(texture: nil, color: .clear, size: size)
name = "inventoryDialog"
//do some background design work here
let background = SKSpriteNode(color: .white, size: self.size)
background.zPosition = 1
addChild(background)
closeButton = SKSpriteNode(texture: SKTexture(imageNamed: "closeButton"))
closeButton.position = CGPoint(x: self.size.width / 2 - closeButton.size.width / 2, y: self.size.height / 2 - closeButton.size.height / 2)
closeButton.zPosition = 2
addChild(closeButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if closeButton.contains(touchLocation) {
close()
}
}
func close() {
self.delegate?.close()
}
}
在您的 GameScene 文件中
class GameScene: SKScene {
var inventoryDialog: InventoryDialog!
var openButton: SKSpriteNode!
override func didMove(to view: SKView) {
openButton = SKSpriteNode(texture: SKTexture(imageNamed: "openButton"))
openButton.position = CGPoint(x: self.size.width / 2 - closeButton.size.width / 2, y: self.size.height / 2 - closeButton.size.height / 2)
openButton.zPosition = 2
addChild(openButton)
}
func displayInventoryDialog() {
backgroundBlocker = SKSpriteNode(imageNamed: "background3")
backgroundBlocker.size = self.size
backgroundBlocker.zPosition = 4999
addChild(backgroundBlocker)
inventoryDialog = InventoryDialog(size: CGSize(width: 500, height: 800))
inventoryDialog.delegate = self
inventoryDialog.zPosition = 5000
addChild(inventoryDialog)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//pause any action that you don't want running while the dialog is open
gameLayer.isPaused = true
let touch = touches.first
let touchLocation = touch!.location(in: self)
if openButton.contains(touchLocation) {
displayInventoryDialog()
}
}
}
//MARK: - InventoryDialogDelegate Methods
extension GameScene: InventoryDialogDelegate {
func close() {
//at this point you could update any GUI nesc. based on what happened in your dialog
backgroundBlocker.removeFromParent()
inventoryDialog?.removeFromParent()
gameLayer.isPaused = false
}
}