昨日はタッチ処理の実装を途中まで行った。
今日はその続き。
タッチ処理の実装
さて、肝心のタッチ処理の実装は、次のような感じ。
public class BoardNode: SKSpriteNode { // 省略 private var touch: UITouch! private var previousSelectedSquare: SquareNode! private var currentSelectedSquare: SquareNode! // 省略 public override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) { self.touch = touches.first as! UITouch let location = self.touch.locationInNode(self) let touchedSquare = self.findSquareAtPoint(location) if self.touchedSquareIsValid(touchedSquare) { self.currentSelectedSquare = touchedSquare self.clearPreviousSelectedSquareIfNeeded() self.currentSelectedSquare.changeStatusTo(.HighLighted, withTurn: self.board.turn) } else { self.clearPreviousSelectedSquareIfNeeded() } } public override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) { self.clearCurrentSelectedSquareIfNeeded() if self.touch != nil && touches.contains(touch) { let location = self.touch.locationInNode(self) let touchedSquare = self.findSquareAtPoint(location) if self.touchedSquareIsValid(touchedSquare) { self.currentSelectedSquare = touchedSquare self.clearPreviousSelectedSquareIfNeeded() self.currentSelectedSquare.changeStatusTo(.HighLighted, withTurn: self.board.turn) } else { self.clearPreviousSelectedSquareIfNeeded() } } } public override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) { self.clearCurrentSelectedSquareIfNeeded() if self.touch != nil && touches.contains(touch) { let location = self.touch.locationInNode(self) let touchedSquare = self.findSquareAtPoint(location) if self.touchedSquareIsValid(touchedSquare) { self.currentSelectedSquare = touchedSquare if self.previousSelectedSquare == nil || self.previousSelectedSquare != self.currentSelectedSquare { self.previousSelectedSquare = self.currentSelectedSquare self.currentSelectedSquare = nil self.previousSelectedSquare.changeStatusTo(.HighLighted, withTurn: self.board.turn) } else { let row = self.currentSelectedSquare.row let col = self.currentSelectedSquare.col println("confirmed! [row: \(row), col: \(col)]") self.lighDown() self.currentSelectedSquare.changeStatusTo(.Lighted, withTurn: self.board.turn) if self.board.isPlayable(row, col) { self.play(row, col) } else if self.board.isChangeable(row, col) { self.change(row, col) } self.lightUpEnableSquares() self.previousSelectedSquare = nil self.currentSelectedSquare = nil } } else { self.clearPreviousSelectedSquareIfNeeded() } } } private func findSquareAtPoint(location: CGPoint) -> SquareNode! { for node in self.nodesAtPoint(location) { switch node { case let square as SquareNode: return square default: break } } return nil } private func touchedSquareIsValid(touchedSquare: SquareNode!) -> Bool { if touchedSquare == nil { return false } let row = touchedSquare.row let col = touchedSquare.col if self.board.isPlayable(row, col) || self.board.isChangeable(row, col) { return true } else { return false } } private func clearCurrentSelectedSquareIfNeeded() { if self.currentSelectedSquare != nil { self.currentSelectedSquare.changeStatusTo(.Lighted, withTurn: self.board.turn) self.currentSelectedSquare = nil } } private func clearPreviousSelectedSquareIfNeeded() { if (self.previousSelectedSquare != nil && self.currentSelectedSquare == nil) || (self.previousSelectedSquare != nil && self.previousSelectedSquare != self.currentSelectedSquare) { self.previousSelectedSquare.changeStatusTo(.Lighted, withTurn: self.board.turn) self.previousSelectedSquare = nil } } }
ホントはちゃんとデリゲートとかを定義すべきなんだけど、とりあえずやっつけで、プレイだけ出来るようにしてある。
ただし、パスには未対応。
ゲームの終了判定とかもしてない。
(この辺りは、デリゲート先でコントロールすべき)
一つポイントを挙げるなら、clearCurrentSelectedSquareIfNeeded()というprivateメソッドを、touchesMoved()とtouchesEnded()の先頭で呼んでいるということ。
これの意味するところは、タッチが移動した(あるいは確定した)ときには最新のタッチを利用して、それ以前のタッチは無効にするということ。
こうすることで、ロジックが簡単になっている。
しかし、こうやって改めてみると、オプショナル型だらけだよね(^^;
オプショナル型と通常型を分けることに、どれくらい意味があるのか・・・
タッチ処理の動作確認
実装したタッチ処理の動作確認をするために、コントローラを修正。
//============================== // YWF //------------------------------ // GameViewController.swift //============================== import UIKit import SpriteKit class GameViewController: UIViewController { @IBOutlet weak var skView: SKView! override func viewDidLoad() { BoardNode.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() self.skView.presentScene(scene) } // hide status bar. override func prefersStatusBarHidden() -> Bool { return true } }
これで実行すると、それっぽくプレイ出来る。
ただ、実際にやってみると分かるのだけど、2回タッチしないといけないというのは割と面倒くさい。
一度タッチしてから、思い直して別の場所をタッチするということは、あまりない感じ。
なので、タッチ1回で選択を確定させてしまって、ミスタッチした場合などはUndoでやり直すようにした方が、いい気がする。
こういった「実際に触ってみた感じ」をフィードバックしていく開発の方が、最初からガチガチに設計してしまうよりも、ずっといいものが作れると思う。
今日はここまで!