2015年7月7日火曜日

Spriteがタッチされたら処理を行う

やりたいこと

spriteをボタンとして扱いたい。

やり方

SKSpriteNodeはUIButton的な機能は持ってないので、touchBegan()の中でtouch箇所がボタン用スプライトの位置かを判定し、処理をすることになる。ちょっとめんどくせえ。
スプライトにはあらかじめ任意のnameを付けておけば複数のボタンに対処できる。

Swift4版

以下はボタン用スプライトをタッチしたらParticleを表示するもの。
ボタンにはあらかじめ"fireButton"というnameが設定されている。

//fireButtonがタッチされたらparticle処理して終了
if let touch = touches.first as UITouch? {
    let location = touch.location(in: self)
    //本とかだと.firstでやるように書かれてるが、途中から反応しなくなることがあるので、
    //.lastにしたほうがいい。原因は不明。これで支障ないのでこれでいい。
    if self.nodes(at: location).last?.name == "fireButton" {
        //パーティクル
        let particleSpark = SKEmitterNode(fileNamed: "SparkParticle.sks")
        particleSpark.position = CGPoint(x: 0, y: 0)
        particleSpark.numParticlesToEmit = 400
        self.scene?.addChild(particleSpark)
        
        return
    }

}

注意点

Particleをいっぱい表示させてたところ、途中からボタンが反応しなくなった。
参考書などでは
if self.nodes(at: location).first?.name == "fireButton" { }
となってたのだが、どうやら.firstの中身がParticleになってしまい、nameが付いていないのではじかれてしまったのだ。
これは.last?にすることで回避できたが、なぜ途中から変わっちゃうんだろう? Particleって勝手に消えてくれるはずだよね? 画面見てもわからないが、不可視要素としてボタンに引っかかってるんだろうか?

Swift2.1版(古いけど参考までに残しとく)

Spriteがタッチされたら何か反応示す場合、Spriteに名前を付けておけば、addChildしてある親SceneのtouchBeganメソッドから、nodeAtPointメソッドでタッチされたノードを得ることができる。
そしてそれの.nameを調べればどのSpriteがタッチされたか判断できる。

以下は、「powerGauge」という名前をつけたSpriteがタッチされたら、バカみたいなメッセージをprintlnするというもの。
touchedNode?.nameをprintltすると「Optional("powerGauge")」というメッセージがコンソールに出るが、if文で判定する際は「powerGauge」だけでいい。

    //タッチ検出
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        let touch = touches.first as! UITouch //Swift1.2から.first
        let location = touch.locationInNode(self)
        
        let str = selectSpriteForTouch(location)
        println("タッチ開始 \(str)")
    }
    
    
    override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
        let touch = touches.first as! UITouch //Swift1.2から.first
        let location = touch.locationInNode(self)
        
        let str = selectSpriteForTouch(location)
        println("タッチ終了 \(str)")
    }
    
    override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
        let touch = touches.first as! UITouch
        let location = touch.locationInNode(self)
        
        if let touchedNode = self.nodeAtPoint(location) as? SKSpriteNode {
            touchedNode.position = location
        }
    }

    //タッチしたSpriteNodenameを得る
    func selectSpriteForTouch(location:CGPoint) -> String {
        let touchedNode = self.nodeAtPoint(location) as? SKSpriteNode
        println("タッチされたSprite: \(touchedNode?.name)")
        let str:String = (touchedNode?.name ?? "にる")
        if touchedNode?.name == "powerGauge" {
            println("パワーゲージだよ~~~ん")
        }
        return str
    }

touchesMoved中の
if let toucheNode = self.nodeAtPoint(location) asSKSpriteNode {
            toucheNode.position = location
        }
は、Swift独特の書き方で、右辺の値がnilじゃなかったらtouchedNodeに値を代入して { }内の処理を行うというもの。
値がnilだったらif文の { }が飛ばされるので、nilによる例外エラーとかが起きない仕組みだ。

selectSpriteForTouch中の
let str:String = (touchedNode?.name ?? "にる")
は、Swiftが原則的にnilを変数に入れられないため、touchedNode.nameがnilだった場合は「にる」をstrに入れよというデフォルト値の設定。

optional関係はまだ不慣れなため、もっといい書き方があると思うけど。

Spriteのカスタムクラスを作った場合でも、いちいちdelegateとか使わなくてもタッチされたSpriteを特定できるので、コードが簡単になる。
(delegate使ったほうが楽な場合もあると思うけどね)

0 件のコメント:

コメントを投稿