昨日はカードの表示を実装した。
今日はデッキの表示を実装していく。
デッキとフレーム
デッキは、単純に考えると中央にカードを表示するだけなんだけど、実際には各プレイヤーにカードを配る必要があるので、もうちょい工夫が必要。
具体的には、デッキの他に「フレーム」という枠を用意しておいて、カードを配る場合、そのフレームの外へカードを配るようにする。
DeckNode
ということで、デッキの表示の実装。
DeckNodeはSKNodeを継承して実装した。
//============================== // BirdHead //------------------------------ // DeckNode.swift //============================== import SpriteKit class DeckNode: SKNode { // 続く
カードを配る方向、クラス定数
まずはカードを配る方向を列挙型として定義し、アニメーションの時間をクラス定数として定義した。
// 続き enum DealDirection { case Top case Right case Bottom case Left } private static let actionDuration = NSTimeInterval(0.3) // 続く
プロパティとイニシャライザ
次はプロパティとイニシャライザ。
// 続き private let deckPosition: CGPoint private let frameSize: CGSize private let frameNode: SKShapeNode private let dummyCardNode: CardNode private let actionQueue: NSOperationQueue init(deckPosition: CGPoint, frameSize: CGSize) { self.deckPosition = deckPosition self.frameSize = frameSize self.frameNode = SKShapeNode(rectOfSize: self.frameSize) self.frameNode.lineWidth = 0.0 self.dummyCardNode = try! CardNode.get(2, withFaceUp: false) self.dummyCardNode.position = self.deckPosition self.actionQueue = NSOperationQueue() self.actionQueue.maxConcurrentOperationCount = 1 super.init() self.addChild(self.frameNode) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 続く
説明が必要なところとして、DeckNode#dummyCardNodeがありそう。
これは、「次に配られるカード」のダミー。
ビューでは、実際にモデル側でデッキの並びがどうなっているのかは知らない(し、知る必要もない)けど、
- まだ山札が残っている→次のカードの裏側が見える
- もう山札が残っていない→次のカードはない
ので、まだ山札が残っている場合には、次のカードの裏側の代わりとして、ダミーのカードの裏側を表示するようにする。
それと、DeckNode#actionQueueというのは、SpriteKitでアクションを確実に1つずつ実行する方法について。 - いものやま。で説明した、アクションを確実に1つずつ実行するためのオペレーションキュー。
カードを配る準備
次は、カードを配る準備。
// 続き func readyToDeal(completion: (() -> Void)! = nil) { if self.dummyCardNode.parent == nil { self.actionQueue.addOperationWithBlock { self.actionQueue.suspended = true NSOperationQueue.mainQueue().addOperationWithBlock { self.dummyCardNode.alpha = 0.0 self.insertChild(self.dummyCardNode, atIndex: 0) let fadeInAction = SKAction.fadeInWithDuration(DeckNode.actionDuration) self.dummyCardNode.runAction(fadeInAction) { self.actionQueue.suspended = false } } } } if completion != nil { self.actionQueue.addOperationWithBlock { NSOperationQueue.mainQueue().addOperationWithBlock { completion() } } } } // 続く
ダミーカードが表示されていない(=ダミーカードの親ノードが存在しない)場合には、ダミーカードをノードに追加して、フェードインさせるようにしている。
そして、コンプリーションが指定されていた場合、フェードインが完全に終わった後にコンプリーションが実行されるようにしている。
カードの分配
最後にカードの分配。
// 続き func deal(card: Int, direction: DeckNode.DealDirection, deckRemain: Bool, completion: ((CardNode) -> Void)! = nil) { let cardNode = try! CardNode.get(card, withFaceUp: false) self.actionQueue.addOperationWithBlock { self.actionQueue.suspended = true NSOperationQueue.mainQueue().addOperationWithBlock { cardNode.position = self.deckPosition self.addChild(cardNode) if !deckRemain { self.dummyCardNode.removeFromParent() } let moveAction: SKAction switch direction { case .Top: let y = self.frameSize.height/2.0 + CardNode.size.height moveAction = SKAction.moveToY(y, duration: DeckNode.actionDuration) case .Right: let x = self.frameSize.width/2.0 + CardNode.size.width moveAction = SKAction.moveToX(x, duration: DeckNode.actionDuration) case .Bottom: let y = -self.frameSize.height/2.0 - CardNode.size.height moveAction = SKAction.moveToY(y, duration: DeckNode.actionDuration) case .Left: let x = -self.frameSize.width/2.0 - CardNode.size.width moveAction = SKAction.moveToX(x, duration: DeckNode.actionDuration) } let rotateAction = SKAction.rotateByAngle(CGFloat(M_PI), duration: DeckNode.actionDuration) let dealAction = SKAction.group([moveAction, rotateAction]) cardNode.runAction(dealAction) { self.actionQueue.suspended = false } } } if completion != nil { self.actionQueue.addOperationWithBlock { NSOperationQueue.mainQueue().addOperationWithBlock { completion(cardNode) } } } } }
配るように指定されたカードのノードを生成し、それを指定された方向へ180度回転させながら、フレームの外側に出るまで移動するアニメーションを実行する。
ここでもし、デッキの残りがない場合、ダミーのカードは取り除くようにしている。(そうしないと、デッキの残りがないにもかかわらず、まだデッキに残りがあるかのように見えてしまう)
そして、コンプリーションが指定されていた場合、そのアニメーションが終わった後でコンプリーションが実行されるようにしてある。
なお、このコンプリーションは配られたカードを引数として呼び出されるので、ここで配られたカードを手札に入れる処理などを行うことになる。
今日はここまで!