読者です 読者をやめる 読者になる 読者になる

いものやま。

雑多な知識の寄せ集め

「BirdHead」のUIを作ってみた。(その9)

昨日はマイナス点一覧に必要な部品の実装を行った。

今日はそれらを使ってマイナス点一覧を実装していく。

MinusPointInfoNode

さっそくコードを。

//==============================
// BirdHead
//------------------------------
// MinusPointInfoNode.swift
//==============================

import SpriteKit

class MinusPointInfoNode: SKNode {
  // 続く

クラス定数

最初はクラス定数。

  // 続き

  private static let actionDuration = NSTimeInterval(0.3)
  private static let margin: CGFloat = 20.0
  private static let titleFontSize: CGFloat = 56.0
  private static let nameFontSize: CGFloat = 48.0

  // 続く

特に説明が必要なものもなく。

プロパティとイニシャライザ

続いて、プロパティとイニシャライザ。

  // 続き
  
  private let frameNode: SKShapeNode
  let okButtonNode: ButtonNode
  let exitButtonNode: ButtonNode
  private let minusCardListNodes: [MinusCardListNode]
  
  private let actionQueue: ActionQueue
  
  override var frame: CGRect {
    var frame = self.frameNode.frame
    frame.origin.x += self.position.x
    frame.origin.y += self.position.y
    return frame
  }
  
  init(size: CGSize) {
    self.frameNode = SKShapeNode(rectOfSize: size)
    self.frameNode.fillColor = SKColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.9)
    self.frameNode.lineWidth = 0.0
    
    let titleNode = SKLabelNode(text: "Minus Point")
    titleNode.fontColor = SKColor.blackColor()
    titleNode.fontSize = MinusPointInfoNode.titleFontSize
    titleNode.horizontalAlignmentMode = .Left
    titleNode.verticalAlignmentMode = .Top
    
    let nameNodes = [
      SKLabelNode(text: "You"),
      SKLabelNode(text: "Left Player"),
      SKLabelNode(text: "Opposite Player"),
      SKLabelNode(text: "Right Player"),
    ]
    var nameNodesHeight: CGFloat = 0.0
    for nameNode in nameNodes {
      nameNode.fontColor = SKColor.blackColor()
      nameNode.fontSize = MinusPointInfoNode.nameFontSize
      nameNode.horizontalAlignmentMode = .Left
      nameNode.verticalAlignmentMode = .Top
      nameNodesHeight += nameNode.frame.height
    }
    
    self.okButtonNode = ButtonNode(texture: SKTexture(imageNamed: "OKButton"))
    self.exitButtonNode = ButtonNode(texture: SKTexture(imageNamed: "ExitButton"))
    self.okButtonNode.alpha = 0.9
    self.exitButtonNode.alpha = 0.9
    
    let minusCardListNodesHeight = (size.height - titleNode.frame.height
                                      - nameNodesHeight - self.okButtonNode.frame.height
                                      - MinusPointInfoNode.margin*CGFloat(nameNodes.count + 3))
    let minusCardListNodeHeight = min(CardNode.size.height, minusCardListNodesHeight/CGFloat(nameNodes.count))
    let minusCardListNodeSize = CGSize(width: size.width - MinusPointInfoNode.margin*2.0,
                                       height: minusCardListNodeHeight)
    self.minusCardListNodes = [
      MinusCardListNode(size: minusCardListNodeSize),
      MinusCardListNode(size: minusCardListNodeSize),
      MinusCardListNode(size: minusCardListNodeSize),
      MinusCardListNode(size: minusCardListNodeSize),
    ]
    
    self.actionQueue = ActionQueue()
    
    super.init()
    
    self.addChild(self.frameNode)
    
    let upperLeftPoint = CGPoint(x: -self.frameNode.frame.width/2.0 + MinusPointInfoNode.margin,
                                 y: self.frameNode.frame.height/2.0 - MinusPointInfoNode.margin)
    titleNode.position = upperLeftPoint
    self.addChild(titleNode)
    
    let buttonPositionY = (-self.frameNode.frame.height/2.0 + MinusPointInfoNode.margin
                             + self.okButtonNode.frame.height/2.0)
    self.okButtonNode.position = CGPoint(x: 0.0, y: buttonPositionY)
    self.exitButtonNode.position = CGPoint(x: (self.frameNode.frame.width/2.0 - self.exitButtonNode.frame.width/2.0
                                                 - MinusPointInfoNode.margin),
                                           y: buttonPositionY)
    self.addChild(self.okButtonNode)
    self.addChild(self.exitButtonNode)
    
    var namePosition = CGPoint(x: upperLeftPoint.x,
                               y: upperLeftPoint.y - titleNode.frame.height - MinusPointInfoNode.margin)
    for i in 0..<nameNodes.count {
      nameNodes[i].position = namePosition
      self.addChild(nameNodes[i])
      
      let linePoints = [
        CGPoint(x: upperLeftPoint.x, y: namePosition.y - nameNodes[i].frame.height),
        CGPoint(x: -upperLeftPoint.x, y: namePosition.y - nameNodes[i].frame.height),
      ]
      let lineNode = SKShapeNode(points: UnsafeMutablePointer(linePoints), count: linePoints.count)
      lineNode.strokeColor = SKColor.blackColor()
      self.addChild(lineNode)
      
      self.minusCardListNodes[i].position = CGPoint(x: 0.0,
                                                    y: (namePosition.y - nameNodes[i].frame.height
                                                          - self.minusCardListNodes[i].frame.height/2.0
                                                          - lineNode.lineWidth))
      self.addChild(self.minusCardListNodes[i])
      
      namePosition.y += -nameNodes[i].frame.height - self.minusCardListNodes[i].frame.height - MinusPointInfoNode.margin
    }
    
    self.alpha = 0.0
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  // 続く

ちょっと長い・・・けど、難しいことは特にしていなくて、レイアウトを計算して部品を配置していってるだけ。

表示/非表示の切り替え

次は表示/非表示の切り替え。

これは、普段は隠しておいて、必要なときに表示し、OKボタンが押されたら再び隠す、といったことが出来るようにするため。

  // 続き
  
  func show(completion: (() -> Void)! = nil) {
    self.actionQueue.addActionBlock { executor in
      let fadeInAction = SKAction.fadeInWithDuration(MinusPointInfoNode.actionDuration)
      executor.executeAction(fadeInAction, forNode: self)
    }
    
    if completion != nil {
      self.actionQueue.addBlock(completion)
    }
  }
  
  func hide(completion: (() -> Void)! = nil) {
    self.actionQueue.addActionBlock { executor in
      let fadeOutAction = SKAction.fadeOutWithDuration(MinusPointInfoNode.actionDuration)
      executor.executeAction(fadeOutAction, forNode: self)
    }
    
    if completion != nil {
      self.actionQueue.addBlock(completion)
    }
  }

  // 続く

これも特に難しいことはなくて、ActionQueueを使って、フェードイン/フェードアウトを実行しているだけ。

マイナス点カードの追加

最後にマイナス点カードの追加。

  // 続き
  
  func addCard(card: Int, forPlayers playerIndices: [Int],
               completion: (() -> Void)! = nil)
  {
    self.actionQueue.addActionBlock { executor in
      for playerIndex in playerIndices {
        let cardNode = try! CardNode.get(card, withFaceUp: true)
        executor.startAction()
        self.minusCardListNodes[playerIndex].addCardNode(cardNode) {
          executor.endAction()
        }
      }
    }
    
    if completion != nil {
      self.actionQueue.addBlock(completion)
    }
  }
}

マイナス点を受け取ったプレイヤーのマイナス点カードリストにカードを追加している。

なお、ActionQueue.Executor.executeAction(_: SKAction, forNode: SKNode)は使えないので、ActionQueue.Executor#startAction()とActionQueue.Executor.endAction()を使って、直接コントロールを行っている。

マイナス点一覧の見た目

なお、マイナス点一覧の見た目は、以下のような感じ。

f:id:yamaimo0625:20151114062632p:plain:h600

なかなかいい感じでしょ?

もちろん、こうした表示を行うにはシーンの実装をしていかないといけないので、それについてはまた今度。

今日はここまで!