いものやま。

雑多な知識の寄せ集め

変種オセロのスタート画面を作ってみた。(その5)

昨日は設定関係を完成させた。

今日はスタート画面からゲーム画面へ遷移できるようにしていく。

ゲーム画面への遷移

ゲーム画面へ遷移できるようにするために、スタート画面をPlayButtonNodeObserverプロトコルに準拠するようにする。

//==============================
// YWF
//------------------------------
// StartScene.swift
//==============================

import SpriteKit
import Security

public class StartScene: SKScene, PlayButtonNodeObserver {
  // 省略
  
  public func playButtonIsSelected(button: PlayButtonNode) {
    let config = Config.getInstance()
    
    let humanStatus: PieceNode.Status
    let computerStatus: Board.Status
    switch config.firstMove {
    case .Random:
      var buf = UnsafeMutablePointer<UInt8>.alloc(1)
      SecRandomCopyBytes(kSecRandomDefault, 1, buf)
      let randomValue = buf.memory
      if randomValue % 2 == 0 {
        humanStatus = .Bad
        computerStatus = .Good
      } else {
        humanStatus = .Good
        computerStatus = .Bad
      }
    case .Human:
      humanStatus = .Bad
      computerStatus = .Good
    case .Computer:
      humanStatus = .Good
      computerStatus = .Bad
    }
    
    let computer: Player
    let computerName: String
    switch config.computerLevel {
    case .Easy:
      computer = RandomCom()
      computerName = "Computer - Easy"
    case .Normal:
      computer = AlphaBetaCom(status: computerStatus, depth: 3)
      computerName = "Computer - Normal"
    case .Hard:
      computer = AlphaBetaCom(status: computerStatus, depth: 5)
      computerName = "Computer - Hard"
    }
    
    let scene = GameScene(size: self.size,
                          userName: "You", userStatus: humanStatus,
                          opponentName: computerName, opponentPlayer: computer)
    
    let transition = SKTransition.fadeWithDuration(NSTimeInterval(1.0))
    self.view?.presentScene(scene, transition: transition)
  }

  // 省略
}

Securityをインポートしているのは、乱数を得るため。
このあたりについては、変種オセロをSwiftに移植してみた。(その10) - いものやま。を参照。

やっているのは、設定オブジェクトから設定を取り出して、それにしたがってゲーム画面を作成して表示させるということ。
このとき、トランジションも指定するようにしてある。

オブザーバの解放

さて、画面の遷移自体は上のコードでオシマイなんだけど、このときにちょっと気をつけないといけないのが、メモリリークが発生しないようにすること。
SwiftではObjective-Cと同様に参照カウンタ方式を使っているので、参照がループしてしまっていると、メモリが解放されなくなってしまう。

これまでのコードで、静的に参照がループしているところはないのだけど、一つ例外となるのが、オブザーバ。
各ボタンからオブザーバへの参照で、参照のループが出来てしまう場合がある。(そして、実際それでループが出来ている)
そこで、SceneがViewから取り除かれるときに、各オブザーバへの参照をボタンから取り除いてやる必要がある。

まず、各ボタンのオブザーバを解放する処理。

//==============================
// YWF
//------------------------------
// LabelButtonNode.swift
//==============================

import SpriteKit

public class LabelButtonNode: SKLabelNode {
  // 省略
  
  public func removeAllObservers() {
    self.observers.removeAll()
  }
  
  // 省略
}
//==============================
// YWF
//------------------------------
// PlayButtonNode.swift
//==============================

import SpriteKit

public class PlayButtonNode: SKSpriteNode {
  // 省略
  
  public func removeAllObservers() {
    self.observers.removeAll()
  }
  
  // 省略
}

そして、ConfigNodeの各ボタンからオブザーバを解放する処理。

//==============================
// YWF
//------------------------------
// ConfigNode.swift
//==============================

import SpriteKit

public class ConfigNode: SKSpriteNode, LabelButtonNodeObserver {
  // 省略
  
  private var subjectLabelButtonNodes: [LabelButtonNode]
  
  public init() {
    // 省略
    
    self.subjectLabelButtonNodes = [LabelButtonNode]()
    
    let texture = ConfigNode.textures["ConfigBack"]!
    super.init(texture: texture, color: SKColor.whiteColor(), size: texture.size())
    
    /* Labels */
    for setting in ConfigNode.labelButtonNodeSettings {
      let label = LabelButtonNode(name: setting.label, fontSize: setting.fontSize)
      label.position.x = setting.x
      label.position.y = setting.y
      label.horizontalAlignmentMode = setting.align
      if setting.computerLevel != nil {
        self.subjectLabelButtonNodes.append(label)
        label.addObserver(self)
        self.computerLevel[label] = setting.computerLevel!
      } else if setting.firstMove != nil {
        self.subjectLabelButtonNodes.append(label)
        label.addObserver(self)
        self.firstMove[label] = setting.firstMove!
      } else {
        label.userInteractionEnabled = false
      }
      addChild(label)
    }

    // 省略
  }

  // 省略
  
  public func removeFromSubjectLabelNodes() {
    for labelButtonNode in self.subjectLabelButtonNodes {
      labelButtonNode.removeObserver(self)
    }
  }
  
  // 省略
}

変種オセロのスタート画面を作ってみた。(その4) - いものやま。で言及したとおり、処理を設定に置き換えて、処理のコードをシンプルにしてあるので、何箇所も修正をする必要がなくなってる。

あとは、これらを呼び出せるように、StartSceneを修正する。

//==============================
// YWF
//------------------------------
// StartScene.swift
//==============================

import SpriteKit
import Security

public class StartScene: SKScene, PlayButtonNodeObserver {
  // 省略
  
  private var configNode: ConfigNode!
  private var playButton: PlayButtonNode!
  private var ruleButton: LabelButtonNode!
  
  public override init(size: CGSize) {
    // 省略
    // 各ノードへの参照をプロパティに保持するように修正
  }

  // 省略

  public override func willMoveFromView(view: SKView) {
    self.releaseObservers()
  }
  
  private func releaseObservers() {
    self.configNode.removeFromSubjectLabelNodes()
    self.playButton.removeAllObservers()
    self.ruleButton.removeAllObservers()
  }
}

これでスタート画面からゲーム画面への遷移はOK。
明日は逆に、ゲーム画面からスタート画面への遷移を実装していく。

今日はここまで!