昨日はアクションを実行するためのコードに関してリファクタリングを実施した。
今日はマイナス点一覧に使う部品を実装していく。
必要なもの
マイナス点一覧を表示するときに、必要になるものをリストアップしてみると、以下。
- ボタン
- テキスト(プレイヤー名など)
- マイナス点となるカードのリスト
ここで、テキストはSKLabelNodeを使えばいいので、残りの2つについて実装していく。
ButtonNode
まずはボタンから。
これは簡単で、YWFのときと同じ感じで実装。
//============================== // BirdHead //------------------------------ // ButtonNode.swift //============================== import SpriteKit class ButtonNode: SKSpriteNode { weak var buttonNodeDelegate: ButtonNodeDelegate? init(texture: SKTexture?) { self.buttonNodeDelegate = nil super.init(texture: texture, color: SKColor.clearColor(), size: (texture?.size())!) self.userInteractionEnabled = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { let touch = touches.first! let location = touch.locationInNode(self.parent!) if self.containsPoint(location) { self.buttonNodeDelegate?.buttonNodeIsSelected(self) } } } protocol ButtonNodeDelegate: class { func buttonNodeIsSelected(buttonNode: ButtonNode) }
ただ、オブザーバパターンを使ったYWFのときと違って、デリゲートを使っている。
理由は、どうせデリゲート先となるのはコントローラ(となるシーン)だけだからw
一つ、デリゲートを使う場合のSwiftでのベストプラクティスとして、デリゲート先のオブジェクトに対する参照は、弱い参照にして、オプショナル型にしておく、というのがある。
こうしておくと、参照が循環してしまうのを防げ、また、オプショナルチェーン?.
を使うことで簡単にメソッドを呼び出すことが出来る。
MinusCardListNode
続いて、マイナス点となるカードのリスト。
//============================== // BirdHead //------------------------------ // MinusCardListNode.swift //============================== import SpriteKit class MinusCardListNode: SKNode { // 続く
クラス定数
まずはクラス定数から。
// 続き private static let actionDuration = NSTimeInterval(0.3) private static let margin: CGFloat = 10.0 private static let fontSize: CGFloat = 48.0 private static let pointFormat: String = "%d pt" // 続く
マージンは、カードの間をどれくらい開けるのかという値。
あと、フォントサイズとテキストのフォーマットを定義しているけど、これは、マイナス点のカードのリストと一緒に、分かりやすいようにマイナス点の合計も表示するためのもの。
プロパティとイニシャライザ
次はプロパティとイニシャライザ。
// 続き private(set) var point: Int private let scale: CGFloat private let frameNode: SKShapeNode private var cardNodes: [CardNode] private let pointLabelNode: SKLabelNode 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.point = 0 self.scale = min(1.0, size.height / CardNode.size.height) self.frameNode = SKShapeNode(rectOfSize: size) self.frameNode.lineWidth = 0.0 self.cardNodes = [CardNode]() self.pointLabelNode = SKLabelNode(text: String(format: MinusCardListNode.pointFormat, self.point)) self.pointLabelNode.fontColor = SKColor.blackColor() self.pointLabelNode.fontSize = MinusCardListNode.fontSize*self.scale self.pointLabelNode.horizontalAlignmentMode = .Right self.pointLabelNode.verticalAlignmentMode = .Center self.actionQueue = ActionQueue() super.init() self.addChild(self.frameNode) self.pointLabelNode.position.x = self.frameNode.frame.width/2.0 self.addChild(self.pointLabelNode) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 続く
プロパティでスケールを用意しているのは、PlayAreaNodeと同様に、カードがフレームに収まるようにするため。
もっとも、幅は十分あるので、高さだけチェックをするようにしている。
それと、マイナス点の合計を表示するためのラベルノードも用意してある。
一つ、注目して欲しいのは、昨日作ったActionQueueを使っているところ。
これを使ってアクションを登録する様子は、以下で。
カードの追加
さて、カードの追加。
マイナス点となるカードを追加し、マイナス点の合計を表示しているラベルを更新する。
// 続き func addCardNode(cardNode: CardNode, completion: (() -> Void)! = nil) { self.actionQueue.addActionBlock { executor in cardNode.xScale = self.scale cardNode.yScale = self.scale let x = (-self.frameNode.frame.width/2.0 + (CardNode.size.width*self.scale + MinusCardListNode.margin)*CGFloat(self.cardNodes.count) + CardNode.size.width*self.scale/2.0) cardNode.position = CGPoint(x: x, y: MinusCardListNode.margin) cardNode.alpha = 0.0 self.addChild(cardNode) self.cardNodes.append(cardNode) let moveAction = SKAction.moveByX(0.0, y: -MinusCardListNode.margin, duration: MinusCardListNode.actionDuration) let fadeInAction = SKAction.fadeInWithDuration(MinusCardListNode.actionDuration) let addAction = SKAction.group([moveAction, fadeInAction]) executor.executeAction(addAction, forNode: cardNode) self.point += cardNode.card let fadeOutAction = SKAction.fadeOutWithDuration(MinusCardListNode.actionDuration/2.0) executor.executeAction(fadeOutAction, forNode: self.pointLabelNode) { self.pointLabelNode.text = String(format: MinusCardListNode.pointFormat, self.point) let fadeInAction = SKAction.fadeInWithDuration(MinusCardListNode.actionDuration/2.0) executor.executeAction(fadeInAction, forNode: self.pointLabelNode) } } if completion != nil { self.actionQueue.addBlock(completion) } } }
見ての通り、キューを一時停止したり再開させたりする処理を書く必要はなく、メインキューに投げる処理を書く必要もなくなっている。
肝心の処理は、カードを追加する位置を計算して、その位置にカードをスッと差し込むようなアクションを実行している。
(具体的には、フェードインと、上→下の移動を組合せたアクションを実行している)
このとき、単にSKNode#runAction(_: SKAction)を使うのではなく、ActionQueue.Executor#executeAction(_: SKAction, forNode: SKNode)を使っているのがポイント。
こうすることで、終わりを待つ必要のある処理の数を自前で管理する必要がなくなっている。
また、マイナス点の合計を表示しているラベルについても、一度フェードアウトさせ、表示を更新し、再度フェードインで表示させるとしている。
これで部品は用意できたので、明日はこれらを組合せて、マイナス点一覧を実装していく。
今日はここまで!