GKState: Why is StateMachine == nil on delegate call? - swift

I'm developing a SpriteKit Game using the GKStateMachine for transitioning between multiple scenes. I'm using a class that holds an instance of the StateMachine, which has multiple states. The states themselves have access to the current SKView through an initializer property. The States are responsible for presenting scenes, so they have a scene property, which will be presented on the didEnter-Method.
Now I have a MainMenuState, which has a scene with 3 buttons. For passing the button events to the State, I wrote a custom delegate. The MainMenuState implements the delegate protocol and sets the scene's delegate property to 'self'. So when a user hits a button the action is forwarded to a delegate method. I wanted to use the delegate method to transition to the next state (e.g. SettingsState). However, when I try to access the GKStates StateMachine property within the delegate func it's always nil.
I think that I have a design problem here and I don't know how to solve it.
The Code of the MainMenuState
import Foundation
import GameplayKit
class MainMenuState : GKState, MenuSceneDelegate {
var view: SKView
var scene: GKScene?
init(view: SKView) {
self.view = view;
self.scene = GKScene(fileNamed: "MenuScene")
super.init()
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is MultiplayerHostState.Type ||
stateClass is MultiplayerSearchState.Type ||
stateClass is SettingsState.Type
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = self.scene {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! MenuScene? {
// Set delegate
sceneNode.menuDelegate = self
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
sceneNode.size = view.bounds.size
// Present the scene
if let view = self.view as SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
}
func hostGameClicked() {
if let stateMachine = self.stateMachine {
stateMachine.enter(MultiplayerHostState.self)
}
}
func joinGameClicked() {
if let stateMachine = self.stateMachine {
stateMachine.enter(MultiplayerSearchState.self)
}
}
func settingsClicked() {
// <-- Here the StateMachine is nil -->
if let stateMachine = self.stateMachine {
stateMachine.enter(SettingsState.self)
}
}
}

After some research I found out this behavior was caused by Swifts ARC System.
The class holding the reference of the state machine was only declared within a func of the overall ViewController. So after existing the func, the class and state machine were deallocated.
I solved it within the View Controller:
class ViewController {
var classWithStateMachine: ClassWithStateMachine?
func initializeClassWithStateMachine {
self.classWithStateMachine = ClassWithStateMachine()
}
}
This code snippet is just a demonstration of the concept, no real code.

Related

How to position SKTilemap Scene in the middle of screen in Swift

I have created a TilemapScene (CKTilemapScene.sks) in Swift 5. This is basically to lay a tile map background of my game project. Refer to the second screenshot below.
Then, in my main swift code, I load all the background with the standard codes.
But, somehow the tile map is not centered. Refer to the first screenshot below . But, at the bottom left of the screen. I tried to play with anchor point. That doesn't help. Did I set anything wrong?
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "CKTilemapScene") {
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .landscape
//.allButUpsideDown
} else {
return .all
}
}
Set the position of SKScene to the SKView's center.
override func viewDidLoad() {
super.viewDidLoad()
if let view = view as? SKView {
if let scene = SKScene(fileNamed: "CKTilemapScene") {
scene.position = view.center
view.presentScene(scene)
}
}
//...
}

Class to protocol conversation in swift

I have this heavy basicVC Class subclass of UIViewController that I am trying to convert as vcprotocol.
It's the basicVC that is doing all work like god class. Which I would like to break into as vcProtocol.
I am trying to do is a separation of concern. Not all of the ViewControllers need to show the Alert View or Network not connected msg.
For example, I have indicatorView that I create in protocol extension as computed property. No error warning but no indicator is being shown. When I try to debug and do po acticvityIndicator I get the following error, which indicates that activityIndicator was never allocated.
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x5a1012de027).
The process has been returned to the state before expression evaluation.
Code snippet:
protocol vcProtocol {
var activityIndicator: UIActivityIndicatorView { get }
}
protocol extension:
extension vcProtocol where Self: UIViewController {
var activityIndicator: UIActivityIndicatorView {
let indicator = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.gray)
indicator.hidesWhenStopped = true
indicator.style = .whiteLarge
indicator.color = .red
indicator.backgroundColor = UIColor.gray
indicator.translatesAutoresizingMaskIntoConstraints = false
return indicator
}
func showLoadingIndicator() {
activityIndicator.startAnimating()
activityIndicator.isHidden = false
}
func hideLoadingIndicator() {
activityIndicator.stopAnimating()
activityIndicator.isHidden = true
}
}
I am not able to wrap my head around how to solve this. as I can only have computed properties in the protocol.
so I have them as get only properties.
my plan is to use the protocol extension to provide a default implementation.
Any thoughts on how to solve this problem.
This activityIndicator is a computed property, so every time you reference the computed property, that get block will be called. The net effect is that, as written, each time you reference activityIndicator, you’re going to get a new instance of UIActivityIndicatorView, which is obviously not your intent.
Consider your showLoadingIndicator:
func showLoadingIndicator() {
activityIndicator.startAnimating()
activityIndicator.isHidden = false
}
The first line (with startAnimating) will return a new UIActivityIndicatorView and the second line (with isHidden) will return yet another. And neither of these will be the same one that you presumably added to your subview.
This activityIndicator really should be instantiated once and only once. Unfortunately, you can’t define the stored property in an extension, so there are a few approaches:
You can let the UIViewController declare the stored property and just define methods method to configure, show, and hide it:
protocol LoadingIndicatorProtocol: class {
var loadingActivityIndicator: UIActivityIndicatorView? { get set }
}
extension LoadingIndicatorProtocol where Self: UIViewController {
func addLoadingIndicator() {
let indicator = UIActivityIndicatorView(style: .gray)
indicator.hidesWhenStopped = true
indicator.style = .whiteLarge
indicator.color = .red
indicator.backgroundColor = .gray
indicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(indicator)
// you might want to add the constraints here, too
loadingActivityIndicator = indicator
}
func showLoadingIndicator() {
loadingActivityIndicator?.startAnimating()
loadingActivityIndicator?.isHidden = false
}
func hideLoadingIndicator() {
loadingActivityIndicator?.stopAnimating()
loadingActivityIndicator?.isHidden = true
}
}
And then a UIViewController subclass simply has to define its own ivar for the activityIndicator, e.g.
class ViewController: UIViewController, LoadingIndicatorProtocol {
var loadingActivityIndicator: UIActivityIndicatorView?
override viewDidLoad() {
super.viewDidLoad()
addLoadingIndicator()
}
...
}
The other approach is to use associated objects via objc_getAssociatedObject and objc_setAssociatedObject, to achieve stored property behavior:
protocol LoadingIndicatorProtocol {
var loadingActivityIndicator: UIActivityIndicatorView { get }
}
private var associatedObjectKey = 0
extension LoadingIndicatorProtocol {
var loadingActivityIndicator: UIActivityIndicatorView {
if let indicatorView = objc_getAssociatedObject(self, &associatedObjectKey) as? UIActivityIndicatorView {
return indicatorView
}
let indicator = UIActivityIndicatorView(style: .gray)
indicator.hidesWhenStopped = true
indicator.style = .whiteLarge
indicator.color = .red
indicator.backgroundColor = .gray
indicator.translatesAutoresizingMaskIntoConstraints = false
objc_setAssociatedObject(self, &associatedObjectKey, indicator, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return indicator
}
func showLoadingIndicator() {
loadingActivityIndicator.startAnimating()
loadingActivityIndicator.isHidden = false
}
func hideLoadingIndicator() {
loadingActivityIndicator.stopAnimating()
loadingActivityIndicator.isHidden = true
}
}

how to access entity from code - gameplay kit

I have a node I added in the scene. Also in the scene I give it a component to bounce. The component looks like this:
class BounceComponent: GKComponent, ComponentSetupProt {
var bounce: SKAction?
var node: SKSpriteNode?
#GKInspectable var diff: CGFloat = 0.2
#GKInspectable var startScale: CGFloat = 1
#GKInspectable var duration: TimeInterval = 0.5
override init() {
super.init()
comps.append(self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setup() {
print("setup")
node = entity?.component(ofType: GKSKNodeComponent.self)!.node as? SKSpriteNode
setupBounce()
}
func setupBounce() {
...
}
func performBounce() {
print("performing")
node?.run(bounce!)
}
}//
In the didMove function on my scene file, it calls the components setup(). This works fine. I'm trying to call the function performBounce() when I click on the button...
if (play?.contains(pos))! {
print("test")
if let _ = play?.entity {
print("we have an entity")
}
play?.entity?.component(ofType: BounceComponent.self)?.performBounce()
}
When I click, the only thing it prints is "test" and the entity is nil. I was under the assumption that when you add a node to the editor, it also sets up the entity for that node, so I'm not sure why it is nil. Curious if anyone could shed some light on this?
Thanks for any help!
The entity on the SKSpriteNode is a weak reference, you need to make sure you retain your entities from your gameplaykit object
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = GKScene(fileNamed: "Main") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as? Main {
sceneNode.entities = scene.entities // add this
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.showsDrawCount = true
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
class Main: Base {
var entities = [GKEntity]() // add this

How can an existing protocol implemented by a delegate be extended with a var?

I've tried many combinations and the problem still remains. I can't figure out why Swift won't compile the following code. I've used multiple variations (using 'where' to constraint protocol, moving setter & getter inside the protocol, etc...) Still no luck. Can you see where the problem is?
// GameScene.swift
import SpriteKit
extension SKSceneDelegate { // adding 'where Self: Game'
// causes err to moves somewhere else
var playerDirection: PlayerDirection { get set } // doesn't like this!
}
class GameScene: SKScene {
override func keyDown(theEvent: NSEvent) {
switch (theEvent.keyCode) {
case 123:
delegate!.playerDirection = .Left;
case 124:
delegate!.playerDirection = .Right;
default:
break
}
}
}
// SomeGame.swift
import Foundation
import SpriteKit
class Game: NSObject, SKSceneDelegate {
var _playerDirection: PlayerDirection = .None
// moving that code to the protocol, compiler can't find _playerDirection
var playerDirection: PlayerDirection {
set {
_playerDirection = newValue
}
get {
return _playerDirection
}
}
lazy var scene: GameScene = {
let scene = GameScene(size: CGSizeMake(CGFloat(100), CGFloat(100)))
scene.delegate = self
return scene
}()
func update(currentTime: NSTimeInterval, forScene scene: SKScene) {
}
}
// PlayerControlComponent.swift
import Foundation
enum PlayerDirection {
case None, Left, Right, Down, Up
}
I think you're approaching your problem from the wrong angle. It looks like what you want is to be able to access playerDirection from the keyDown() function in GameScene. Instead of attempting to make playerDirection a property of the SKSceneDelegate protocol, you should probably be checking to see if the delegate property in GameScene is a Game and, if it is, casting delegate to Game so that the playerDirection property becomes available to you.
You can do that very easily with an if let and the as? operator like this:
override func keyDown(theEvent: NSEvent) {
if let game = delegate as? Game {
switch (theEvent.keyCode) {
case 123:
game.playerDirection = .Left;
case 124:
game.playerDirection = .Right;
default:
break
}
}
}
This is doubly nice because now you're also checking to make sure that delegate actually exists before using it. Forcibly unwrapping it, like you were doing before, could cause a runtime exception if delegate isn't set when before that function is called.

error in viewcontroller (lldb) when presenting another view

when I try to present an uiactivityviewcontroller or just another viewcontroller, I get an lldb error without any further explanation
EDITED:
Game starts with GameMenuScene, when play is clicked it will move to GameScene:
class GameMenuScene: SKScene {
weak var weakGameVC: GameViewController?
if nodeAtPoint.name == "play"{
NSUserDefaults.standardUserDefaults().setInteger(score+1, forKey: "currentLevel")
NSUserDefaults.standardUserDefaults().synchronize()
self.removeAllActions()
self.removeAllChildren()
var scene1:SKScene = GameScene(size: self.size)
scene1.weakGameVC = self ##Updated : error: SKSCene does not have a member named "weakGameVC"
self.view?.presentScene(scene1)
}
}
GameScene:
Here is the GameViewController(as you can see, first scene is GameMenuScene:
import UIKit
import SpriteKit
import AVFoundation
import Social
class GameViewController: UIViewController, UITextFieldDelegate{
weak var weakGameVC: GameViewController? ##Updated
var player:SKSpriteNode = SKSpriteNode()
var scene:GameMenuScene!
override func viewDidLoad() {
super.viewDidLoad()
let skView = view as SKView
skView.multipleTouchEnabled = false
scene = GameMenuScene(size: skView.bounds.size)
scene.weakGameVC = self ##Updated
//scene.scaleMode = SKSceneScaleMode.ResizeFill
skView.showsFPS = true
skView.showsNodeCount = true
skView.presentScene(scene)
}
func shareButton() {
var myShare = "aa"
let activityVC:UIActivityViewController = UIActivityViewController(activityItems: ["aa"], applicationActivities: nil)
presentViewController(activityVC, animated: true, completion: nil)
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
And here the GameScene, where the gameplay is:
import SpriteKit
import Social
class GameScene: SKScene, SKPhysicsContactDelegate {
weak var weakGameVC: GameViewController?
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location: CGPoint! = touch.locationInNode(self)
let nodeAtPoint = self.nodeAtPoint(location)
if (nodeAtPoint.name != nil) {
if nodeAtPoint.name == "share"{
println("1")
if let gvc = weakGameVC {
println("2")
gvc.shareButton()
}
}
}
}
}
}
It doesn't work because in touchBegan method you create new object GameViewController:
var gameViewController = GameViewController()
The gameViewController is not added to the view hierarchy (it's not presented) and after that you call shareButton() method which wants to present another view (share view) on not presented view (game view).
The solution should be that you handle the touch (touchesBegan) in the same class where you have shareButton() by calling shareButton() on the self not gameViewController.shareButton().
Or create delegate method from scene to view controller, if touch happened in scene call delegate method and handle it in view controller to present the share view
// EXTENDED - Solution with weak reference to game view controller.
In GameScene add weak reference to gameVC:
weak var weakGameVC: GameViewController?
and later in the same file:
if nodeAtPoint.name == "share" {
if let gvc = weakGameVC {
gvc.shareButton()
}
}
In GameViewController file after you create game scene you have to add something like that:
gameScene.weakGameVC = self