【Swift Playgrounds】ARを作るPART.2「簡単なシーン」を読み解く

# この記事は何か？
[前回](https://qiita.com/chino_tweet/items/2bfd3bf8354f38342414)からの続きです。
実行環境などに変更はありません。
この投稿では、「簡単なシーン」ページのコードを解説します。

# コードを読み解く
このページにも、ふたつのファイルが存在します。　
- メインファイル
- SharedCode（以前のページと同じもの）

ここでは、メインファイルを主に扱います。

## メインファイル
まず、行っていること全体を挙げます。
- 変数に3Dモデルを割り当てる
- シーンに3Dモデルを追加する
- シーン開始時の演出
- カメラのイベントを指定する

大まかには以上の4つが行われています。
全体のコードを見たあと、ひとつずつ解説します。

````Swift:メインファイル
// いくつかのモデルを作成します。
var raincloud = Model.raincloud
var label = Model.text("雨の日 (´･_･`)", elevation: 10.cm)
var cactus = Model.cactus

// シーンに複数のモデルを追加します。
scene.add([raincloud, label, cactus])

// シーンが開始するときに、サウンドを再生して雨雲から雨を降らせます。
scene.setOnStartHandler {
    scene.applyColor(scheme: .cool)
    raincloud.play(.soundscapeLoop, loops: true)
    raincloud.animate(.rain)
    raincloud.run(action: .scaleTo(3, duration: 5))
}

// カメラがサボテンから8cm以内に近づいたとき、太陽を昇らせます!
scene.camera.when(cactus, isWithin: 8.cm) {
    scene.applyColor(scheme: .hot)
    raincloud.remove()
    label.text = "晴れの日! (^_^)"
    let sun = Model.sun
    scene.place(sun, at: cactus.location)
    sun.run(group: [.moveBy(x: 0, y: 20.cm, z: 0, duration: 5), .scaleBy(5, duration: 10)])
    sun.animate()
}
````

## 変数の宣言
以下に挙げる3つのモデルを生成しています。
- raincloud（雨雲）
- label（テキスト）
- cactus（サボテン）

````swift:いくつかのモデルを作成します。
var raincloud = Model.raincloud
var label = Model.text("雨の日 (´･_･`)", elevation: 10.cm)
var cactus = Model.cactus
````

ここで気になるのは、`raincloud`と`cactus`モデルを作成しているコードがメソッドではなく、プロパティである点です。
`Model`型の`raincloud`プロパティが3Dモデルを返すという記述になっています。
一方、`label`モデルは`Model`型の`text(_:elevation:)`メソッドによって作成されています。
いずれにしても、オブジェクトを作成するためにイニシャライザが呼び出されていません。
Swift Playgrounds特有のアプローチに見えます。

これらのモデルは、`scene`オブジェクトの`add()`メソッドによってAR空間に配置されます。
なお、`label`モデルは、サーフェスのタップした面から10cm高いところに表示されます。

````swift:シーンに複数のモデルを追加します。
scene.add([raincloud, label, cactus])
````

`add()`メソッドを使用しているので、ページを実行したあと検出したサーフェスをタップすることで、任意の場所にモデルを配置できます。
パラメータがモデルの配列になっているので、連続的にシーンに配置できます。
ここでは、3回タップすることになります。

## シーン開始時のハンドラ
ページを実行すると、サーフェスの検出が始まります。
サーフェスをタップして、すべてのモデルが配置されると「シーンを開始」ボタンを押せるようになります。
ここでは、シーン開始後に以下の演出が始まるようにしています。
- シーン全体を冷淡な雰囲気にする
- BGMを鳴らす
- 雨雲から雨を降らせる
- 雨雲を徐々に大きくする

````swift:シーン開始時の演出。
scene.setOnStartHandler {
    scene.applyColor(scheme: .cool)    // シーン全体の雰囲気
    raincloud.play(.soundscapeLoop, loops: true)    // BGM
    raincloud.animate(.rain)    // 雨雲から雨が降る
    raincloud.run(action: .scaleTo(3, duration: 5))    // 雨雲が大きくなる
}
````

シーンに追加したモデルごとに様々な挙動を指定できるので、いろいろ試してみると面白そうです。
どのモデルにも、`play(_:loops:)`メソッド、`animate()`メソッド、`run(action:)メソッドがあるようです。
これらを駆使すれば、独創的なシーンを構築できるはずです。

## カメラのイベント
シーンにインタラクティブな効果をもたらします。
ここでは、ユーザーがiPadを持って3Dモデルに接近した時、アクションを行っています。
iPadのカメラとサボテンの距離が8cm以内になると、アクションが実行されます。

````swift:カメラがサボテンに近づいたら、太陽を配置する
scene.camera.when(cactus, isWithin: 8.cm) {
    scene.applyColor(scheme: .hot)
    raincloud.remove()
    label.text = "晴れの日! (^_^)"

    let sun = Model.sun
    scene.place(sun, at: cactus.location)
    sun.run(group: [.moveBy(x: 0, y: 20.cm, z: 0, duration: 5),
                    .scaleBy(5, duration: 10)])
    sun.animate()
}
````

イベントは、シーンにおけるカメラの場所からトリガーされます。
`camera`オブジェクトの`when(_:isWithin:do:)`メソッドによって実装されており、最後のパラメータはクロージャです。
ここでは、後付けクロージャ形式で記述されています。

クロージャでは、`remove()`メソッドと`place(_:at:)`メソッドによって、雨雲モデルから太陽モデルへの変化を実装しています。
また、`run(group:)`メソッドによって、太陽が上昇しつつ、大きくなる演出も行われています。
パラメータに、動作のコレクションを渡すことで、アクションを同時に実行できるようです。
ここでは、`removeBy(x:y:z:duration:)`メソッドと`scaleBy(_:duration:)`メソッドが列挙されています。

# コードを読んでわかったこと

- 3Dモデルの作成は、イニシャライザを使っていない
- テキストの3Dモデルは、`text(_:elevation)`メソッド
- シーンに複数のモデルを配置したい場合は、`add()`メソッドにモデルの配列を渡す
- カメラによるイベントをハンドリングできる
- `run(group:)`メソッドで、複数のアクションを同時に実行できる

# さらに...
このブックの次のページ「APIの概要」は[こちら](https://qiita.com/chino_tweet/items/221c268de2137b90eef0)で解説します。