2017年11月13日月曜日

SpriteKit でボールを壁に当てて無限に反射させる

概要

SpriteKit の CGVector は指定方向に力を与えることができます
今回はノードに対して力を加え動かし壁に衝突したら永遠に反射しつづけるものを作成してみました

環境

  • macOS X 10.13.1
  • Xcode 9.1 (9B55)

コード

import SpriteKit

class GameScene: SKScene {

    var ball = SKSpriteNode()

    override func didMove(to view: SKView) {
        self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
        self.physicsBody?.affectedByGravity = false
        self.physicsBody?.isDynamic = false
        self.physicsBody?.linearDamping = 0
        self.physicsBody?.friction = 0
        self.physicsBody?.restitution = 1

        ball = self.childNode(withName: "ball") as! SKSpriteNode
        ball.physicsBody = SKPhysicsBody(texture: ball.texture!, size: ball.size)
        ball.physicsBody?.affectedByGravity = false
        ball.physicsBody?.linearDamping = 0
        ball.physicsBody?.restitution = 1
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchUp(atPoint: t.location(in: self)) }
    }

    func touchUp(atPoint pos : CGPoint) {
        print(abs(pos.x - ball.position.x))
        if (abs(pos.x - ball.position.x) <= 200 && abs(pos.y - ball.position.y) <= 200) {
            let vector = CGVector(dx: (pos.x - ball.position.x) / 10, dy: (pos.y - ball.position.y) / 10)
            ball.physicsBody?.applyImpulse(vector)
        }
    }
}

説明

.sks ファイルでは中央に 30x30 の円上のノードを 1 つ用意しています
それを childNode(withName:) で読み込んでいます
物理エンジンはコード上で設定します

今回壁に反射してもスピードが減衰しない角度が変わらないようにする必要があります
それを実現するための必須プロパティは以下の通りです

ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.restitution = 1

重力の影響を受けないようにして直線移動時の減衰率を 0 にして反発率を 1 にすることで反射時にも減衰しないようにします
加えて衝突する壁にも以下の設定が必須となります

self.physicsBody?.affectedByGravity = false
self.physicsBody?.isDynamic = false
self.physicsBody?.linearDamping = 0
self.physicsBody?.friction = 0
self.physicsBody?.restitution = 1

重力の影響と衝突の影響を受けないようにします
そして直線の減衰と反発時の減衰が発生しないようにします
ボールの設定と違って friction = 0 も設定します
これは摩擦係数の値で 1 に近いほど摩擦が多くなります
なので 0 にすることで壁に衝突したときの減衰をなくしています

今回はタップした方向に対してボールに力を与えます
ボールとタップした距離が 200 以下の場合にタップした座標への距離の 1/10 の力をボールに加え移動させています
なぜ 1/10 にしているかというと距離分力を加えるとものすごいスピードでボールが動き出してしまいます
ある程度の速度なら問題ないですが速度が早すぎると物理エンジンの当たり判定がうまく動作しないケースがあったので速度を抑えるために 1/10 にしています
本当は速度を一定にするためにタップした座標との方向はそのままで距離を一手にするような計算処理を入れればいいのですがとりあえず今回はさっくり動かしたかったので 1/10 にしました

CGVector の情報はノードに対して applyImpulse を設定することで割り当てることができます

動作確認

実行すると以下のように動作すると思います
vector_sample1.gif

最後に

SpriteKit で CGVector を使って外部から力を加えてみました
SpriteKit では重力以外の力も加えることができます
今回の手法は主にビーム光線やブロック崩しなどに使われます

結構使える技なので覚えておいて損はないかなと思います

0 件のコメント:

コメントを投稿