いものやま。

雑多な知識の寄せ集め

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

昨日はタッチ処理の実装を途中まで行った。

今日はその続き。

タッチ処理の実装

さて、肝心のタッチ処理の実装は、次のような感じ。

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でやり直すようにした方が、いい気がする。
こういった「実際に触ってみた感じ」をフィードバックしていく開発の方が、最初からガチガチに設計してしまうよりも、ずっといいものが作れると思う。

今日はここまで!