いものやま。

雑多な知識の寄せ集め

変種オセロのUIを作ってみた。(その11)

ちょっと間が空いたけど、続き。

今日はUndoなどを行うためのボタンを作っていく。

ボタンの描画

ということで、まずはボタンの描画から。
SKSpriteNodeを継承して、ButtonNodeを作る。

//==============================
// YWF
//------------------------------
// ButtonNode.swift
//==============================

import SpriteKit

public class ButtonNode: SKSpriteNode {
  public enum Type {
    case Undo
    case Pass
    case Config
    case Exit
  }
  
  private static var textures = [String: SKTexture]()
  
  public class func loadAssets() {
    let atlas = SKTextureAtlas(named: "Button")
    let names = ["UndoButton", "PassButton", "ConfigButton", "ExitButton",
                 "UndoButtonDisabled", "PassButtonDisabled", "ConfigButtonDisabled", "ExitButtonDisabled"]
    for name in names {
      ButtonNode.textures[name] = atlas.textureNamed(name)
    }
  }
  
  private class func getTextureFor(type: Type, enabled: Bool = true) -> SKTexture {
    var name: String
    switch type {
    case .Undo:
      name = "Undo"
    case .Pass:
      name = "Pass"
    case .Config:
      name = "Config"
    case .Exit:
      name = "Exit"
    }
    name.extend("Button")
    if !enabled {
      name.extend("Disabled")
    }
    return ButtonNode.textures[name]!
  }
  
  public private(set) var type: Type
  public private(set) var enabled: Bool
  
  public init(type: Type, enabled: Bool = true) {
    self.type = type
    self.enabled = enabled
    
    let texture = ButtonNode.getTextureFor(type, enabled: enabled)
    super.init(texture: texture,
               color: SKColor.whiteColor(),
               size: texture.size())
    
    self.userInteractionEnabled = enabled
  }
  
  public required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  public func enable() {
    self.setEnable(true)
  }
  
  public func disable() {
    self.setEnable(false)
  }
  
  private func setEnable(enabled: Bool) {
    if self.enabled != enabled {
      self.enabled = enabled
      self.texture = ButtonNode.getTextureFor(self.type, enabled: self.enabled)
      self.userInteractionEnabled = self.enabled
    }
  }
}

とりあえず描画だけ行いたかったので、実装したのは画像のロードとイニシャライザ、それと有効/無効の切替えだけ。
なので、タッチをされたときの処理はまだ実装していない。
なお、タッチされたときの処理については、SwiftでSetの型パラメータにプロトコルを指定する方法について。 - いものやま。で書いたとおり、オブザーバを登録して、オブザーバに通知を行うことで、外部に委譲させる予定。

ボタンの描画の動作確認

さて、ボタンの描画を確認するために、コントローラを少し修正する。

//==============================
// YWF
//------------------------------
// GameViewController.swift
//==============================

import UIKit
import SpriteKit

class GameViewController: UIViewController {
  @IBOutlet weak var skView: SKView!
  
  override func viewDidLoad() {
    BoardNode.loadAssets()
    ButtonNode.loadAssets()
    PieceNode.loadAssetsAndCreateTemplates()
  }
  
  override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    
    var size = self.view.bounds.size
    if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
      size.height *= 2
      size.width *= 2
    }
  
    let scene = GameScene(size: size)
    scene.scaleMode = .AspectFill
    
    let backgroundTexture = SKTexture(imageNamed: "Background")
    let background = SKSpriteNode(texture: backgroundTexture)
    background.position = CGPoint(x: scene.frame.width/2, y: scene.frame.height/2)
    scene.addChild(background)
    
    var board = Board()    
    let boardNode = BoardNode(board: board)
    background.addChild(boardNode)
    boardNode.lightUpEnableSquares()
    
    let upperButtonPositionY = Int((scene.frame.height + boardNode.calculateAccumulatedFrame().height) / 4)
    let lowerButtonPositionY = Int(-upperButtonPositionY)
    let firstButtonPositionX = Int(scene.frame.width / 2 - 20 - 120 - 20 - 60)
    let secondButtonPositionY = Int(scene.frame.width / 2 - 20 - 60)
    let undoButton = ButtonNode(type: .Undo)
    undoButton.position = CGPoint(x: firstButtonPositionX, y: lowerButtonPositionY)
    let passButton = ButtonNode(type: .Pass, enabled: false)
    passButton.position = CGPoint(x: secondButtonPositionY, y: lowerButtonPositionY)
    let configButton = ButtonNode(type: .Config, enabled: false)
    configButton.position = CGPoint(x: firstButtonPositionX, y: upperButtonPositionY)
    let exitButton = ButtonNode(type: .Exit, enabled: false)
    exitButton.position = CGPoint(x: secondButtonPositionY, y: upperButtonPositionY)
    background.addChild(undoButton)
    background.addChild(passButton)
    background.addChild(configButton)
    background.addChild(exitButton)
    
    self.skView.presentScene(scene)
  }
  
  // hide status bar.
  override func prefersStatusBarHidden() -> Bool {
    return true
  }
}

これを実行させると、次のような感じ。

iPhone 4S
f:id:yamaimo0625:20150803205203p:plain

うん、いい感じ――と思いきや、そう上手くいかないのが開発の難しいところ。

他のデバイスで画面を確認してみると、次のようになっている。

iPad
f:id:yamaimo0625:20150803205257p:plain

iPhone 5
f:id:yamaimo0625:20150803205235p:plain

iPhone 6
f:id:yamaimo0625:20150803205332p:plain

iPhone 6 Plus
f:id:yamaimo0625:20150803205357p:plain

iPadiPhone 5ならまだいいけど、iPhone 6やiPhone 6 Plusになってくると、思った以上にスキマが空いていて、かなり違和感。
一応、ボタンの左側にはプレイヤーの情報を入れる予定だけど、それを入れたとしても、上下のスキマが大きすぎて、よろしくない。

このあたりがiOSでゲームのUIを作るときの難しさの一因になっている気もする。
すなわち、画面のサイズ(もうちょい譲ってアスペクト比)が固定であれば、レイアウトも(比較的)簡単なんだけど、実際にはそうでないので、各画面サイズに適切なレイアウトを用意しなければならなくなっている。
これがドキュメントやデータを扱うアプリであれば、情報を見せる部分のサイズを必要に応じて変えればいいだけなのだけど、ゲームだとそうもいかないので・・・

ただ、iOSはまだマトモな方だと思う。
Androidでの開発を思うと、ゾッとする。

何はともあれ、まずはこのレイアウトの問題を解決していかないと。

今日はここまで!