LoginSignup
14
20

More than 5 years have passed since last update.

SwiftのPlaygroundが、インタラクションに対応したというので、数字をタッチして消すゲームを作ってみた

Last updated at Posted at 2016-04-07

・はじめに

Playgroundが新たにインタラクションに対応したということで、Playgroundだけで完結するゲームを作ってみました。
実は、最初にミニゲームを作り貯めて公開していこう!ぐらいの気持ちだったので、あまり考えずにPlaygroundを使えば、公開もスムーズにできるかな?と作り始めたのですが、いざ始めてみると、「あれ?」、「あれれ?」が続いたので、これを書き記しておきたいと思います。

Spritekitの物理シミュレーションなどは、動作を確認したことがあったので、てっきり、タッチアクションもできるものかと思っていましたが、Xcode7.2以下のバージョンできないことが、作成の途中でわかりました。

数字をタッチして順々に消していく、どこかで見たことのあるゲームです。
最初に、以下をPlaygroundにて作りました。

・ゲームの見た目

数字のボタンをランダムに並べる。見た目の作業が終わりました。
cap1.png

↓タッチアクションのないプログラム↓
・Playground プラットフォームをiOSとします。
・シーンサイズ 320×568pt
・画像ファイル rect.png 50×50px
(下部にあります。rect.pngとリネームして、Resourceフォルダにドラッグ&ドロップしてください。)

MyPlayground1.playground
import SpriteKit
import XCPlayground

let scene = SKScene(size: CGSize(width: 320, height: 568))
let view = SKView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
view.presentScene(scene)
view.showsFPS = true
view.showsPhysics = true
XCPlaygroundPage.currentPage.liveView = view

var minValue = 1
var nums = [Int](1...25)
var randNums = [Int]()

for _ in nums {
  let randIdx = Int(arc4random_uniform(UInt32(nums.count)))
  randNums.append(nums[randIdx])
  nums.removeAtIndex(randIdx)
}

let offset = CGPointMake(40, 450)
var count = 0

for y in 0...4 {
  for x in 0...4 {
    let numSprite = SKSpriteNode(imageNamed: "rect")
    numSprite.name = "numSprite\(randNums[count])"
    numSprite.position.x = numSprite.position.x + (CGFloat(x) % 5) * (numSprite.size.width + 10) + offset.x
    numSprite.position.y = numSprite.position.y - (CGFloat(y) % 5) * (numSprite.size.height + 10) + offset.y
    numSprite.zPosition = 0
    scene.addChild(numSprite)

    let numLabel = SKLabelNode(fontNamed:"Futura-CondensedExtraBold")
    numLabel.text = "\(randNums[count])"
    numLabel.name = "numLabel\(randNums[count])"
    numLabel.verticalAlignmentMode = .Center
    numLabel.fontSize = 36
    numLabel.fontColor = SKColor(red: (255/255.0), green: (40/255.0), blue: (160/255.0), alpha: 1.0)
    numLabel.zPosition = 1
    numSprite.addChild(numLabel)

    count += 1
  }
}

ここまでは、良かったのですが...

・Playgroundのインタラクションについて

そして、タッチするには、どうしたらいいのか?と思って、ググってみると...以下検索結果。

Stack Overflow:How to get user interaction events in playground with Swift

そこには、1年前に同じことを考えていた人がいたようで、その答えに「ユーザーインタラクションは、Playgroundではできないよ。(ソース: WWDC)」(意訳)とありました。「あれ?」これダメじゃんと思いつつ、よくよく、最新のコメントを見たところ、「今のPlaygroundは、インタラクションに対応していて、Xcode 7.3からスタートしているよ」(意訳)と書いてありました。今のXcodeのバージョンっていくつ? Xcode 7.3っていつから?って思いながら、確認してみると、現在インストールしているバージョンで、つい最近バージョンアップしたやつでした。「あれれ?」出来るようになっている。しかも、最近じゃないですか!これは、ラッキー。ということで、一旦、ゲームを離れて、Playgroundのインタラクションに関する情報をここでも調査しました。

Apple:Interactive Playgrounds デモサンプルもある。
Qiita:Xcode7.3のPlaygroundはついにインタラクションに対応! UIKitを動かしている。
Github:Sprite-Kit-Collisions-Playground 1ファイルで記述されている。touchesbegan関数は組み込まれていませんでしたが、ほぼこれを参考にしています。

これらの情報を参考にして、touchesbegan関数を使うには、Sceneを作ってSKSceneを継承し、XcodeのProjectのようにtouchesbegan関数をoverrideすれば使えるという事がわかりました。以下プログラムです。

↓touchesbegan関数を組み込んだ最小のプログラム↓
・Playground Xcode7.3以上、プラットフォームをiOSとします。
・シーンサイズ 320×568pt

MyPlayground2.Playground
import SpriteKit
import XCPlayground

class Scene: SKScene{

  override func didMoveToView(view: SKView) {
    size = CGSize(width: 320, height: 568)

  }
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for touch in touches {
      let location = touch.locationInNode(self)


    }
  }

}

let scene = Scene()
let view = SKView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
view.presentScene(scene)
XCPlaygroundPage.currentPage.liveView = view

・ゲームとして統合してみる。

タッチができます。
cap2.png

↓数字をタッチして消すゲーム↓
・Playground Xcode7.3以上、プラットフォームをiOSとします。
・シーンサイズ 320×568pt
・画像ファイル rect.png 50×50px
(下部にあります。rect.pngとリネームして、Resourceフォルダにドラッグ&ドロップしてください。)

内容はコメントにて解説してあります。

touchNum.playground
import SpriteKit
import XCPlayground //画面を表示するためにインポートが必要

class Scene: SKScene{ //ここでSceneクラスを作っているのだけど、SKSceneを継承する事で、touchesBegan()関数が利用できる。

  var minValue = 1//ボタンをタッチし、消えたボタン個数を数える。

  override func didMoveToView(view: SKView) {
    size = CGSize(width: 320, height: 568) //320×568ptのsceneを作る。

    var nums = [Int](1...25) //1から25の配列を作る。
    var randNums = [Int]() //空の配列を

    //----1から25をランダムに配列へ格納----
    //numsで作った配列を要素分取り出して、ランダムに空の配列randNumsに入れ替える。この時に、添字をランダムにして、それを元に入れ替えている。
    for _ in nums {
      let randIdx = Int(arc4random_uniform(UInt32(nums.count)))//ランダムを作る。
      randNums.append(nums[randIdx]) //ランダムな添字を使ってrandNums配列に追加
      nums.removeAtIndex(randIdx) //numsからそのランダム添字を使って配列を削除。これを繰り返す。
    }

    //----ボタンの生成----
    let offset = CGPointMake(40, 450)//Spritekitは、原点がviewの左下なるので、これを上部位置に来るよう調整。
    var count = 0//下記のループの総計をカウント
    //ボタンのタイリングを行う。
    for y in 0...4 { //x座標
      for x in 0...4 { //y座標
        let numSprite = SKSpriteNode(imageNamed: "rect") //数字の下のボタン(スプライトノード)
        numSprite.name = "numSprite\(randNums[count])"
        numSprite.position.x = numSprite.position.x + (CGFloat(x) % 5) * (numSprite.size.width + 10) + offset.x
        numSprite.position.y = numSprite.position.y - (CGFloat(y) % 5) * (numSprite.size.height + 10) + offset.y
        numSprite.zPosition = 0 //奥行き
        addChild(numSprite)

        let numLabel = SKLabelNode(fontNamed:"Futura-CondensedExtraBold") //数字のラベルノード
        numLabel.text = "\(randNums[count])"
        numLabel.name = "numLabel\(randNums[count])"
        numLabel.verticalAlignmentMode = .Center
        numLabel.fontSize = 36
        numLabel.fontColor = SKColor(red: (255/255.0), green: (40/255.0), blue: (160/255.0), alpha: 1.0)
        numLabel.zPosition = 1 //奥行き
        numSprite.addChild(numLabel)

        count += 1 //yとxが繰り返されるたびにカウントアップ
      }
    }
  }

  //----終了処理----
  //25個ボタンを押し終えた時の処理。文字が出てくるだけ。
  func win() {
    if minValue > 25{//minValueが25より多くなったら、処理を行う。
      let winLavel = SKLabelNode(fontNamed:"Futura-CondensedExtraBold")
      winLavel.text = "You did it!"
      winLavel.position = CGPointMake(frame.width / 2, frame.height / 2)
      winLavel.fontSize = 100
      winLavel.fontColor = SKColor(red: (255/255.0), green: (40/255.0), blue: (160/255.0), alpha: 1.0)
      addChild(winLavel)
      winLavel.runAction(SKAction.scaleTo(0.5, duration: 1)) //文字の表示アニメーション
    }
  }

  //----タッチ処理----
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for touch in touches {
      let location = touch.locationInNode(self)//タッチした座標をlocationに代入。
      let node:SKNode! = self.nodeAtPoint(location)//そのlocationから、そこにあるノードをnodeに代入。

      //ボタンの背景スプライトである"numSprite\(minValue)"か数字のラベルノードである"numLabel\(minValue)"のどちらかのノードネームを押したら、if文内部を実行。
      if node.name == "numSprite\(minValue)" || node.name == "numLabel\(minValue)"{
        childNodeWithName("numSprite\(minValue)")!.runAction(SKAction.fadeAlphaTo(0.0, duration: 0.5)) //ボタン(数字ごと)が、消えるアニメーション
        minValue += 1 //タッチしたボタンが消えたら1追加。
        win() //26になったらwin()を実行。
      }
    }
  }
}

let scene = Scene() //Sceneクラスを生成
let view = SKView(frame: CGRect(x: 0, y: 0, width: 320, height: 568)) //viewを320×568Pt
view.presentScene(scene) //sceneの表示
view.showsFPS = true //FPSの表示
//view.showsPhysics = true //物理体の枠が見える。
XCPlaygroundPage.currentPage.liveView = view //ライブビューの表示

・最後に

海外のサイトで、Playgroundファイルを確認してみると、ドキュメント方式で記述しているものを多く見ました。Playgroundのドキュメント方式+インタラクションの組み合わせでプログラム自体や数学(三角関数等)を解説したら、非常にわかり易い解説となると思います。今回の件で、今後の普及次第ではあると思いますが、プログラマーだけに留まらず、子供の教育、プレゼン、その他、多方面で使えるのではないか?とPlaygroundに秘めたる可能性を感じました。

・画像

rect.png
rect.png 50×50px
(rect.pngとリネームして、Resourceフォルダにドラッグ&ドロップしてください。)

14
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
20