この記事は何か?
iPadアプリケーションSwift Playgroundsは、Swiftプログラミングのスキルを習得できます。
「ブック」と呼ばれるコンテンツを実践することで、Swiftプログラミングやコンピュータの基礎から応用までを学びます。
そして、習得したスキルからオリジナルのコンテンツを開発できるテンプレートのブックがあり、「ARを作る」では拡張現実コンテンツを簡単に構築できます。
この記事では、「ARを作る」の最初のページである「スタート地点」を解説します。
「ARを作る」テンプレートから、独自の拡張現実空間を構築してiPadで遊ぶことができます。

実行環境
- iPad Pro 11.5インチ(2018モデル)
- iOS 13.2.3
- Swift Playgrounds(バージョン3.1)
- ARを作る(Swift 5.1)
ブックの構成
「ARを作る」ブックは、次の4つのページで構成されています。
また、それぞれのページには共通のSharedCode
というファイルがあります。メインファイルからSharedCode
に定義された機能を利用できます。
メインファイル
最初のページなので、メインファイルのコードは極めてシンプルです。
以下の作業を行なっています。
- レモンを作って、シーンに追加する
let lemon = Model.lemon
scene.add(lemon)
ページを実行すると、iPadのカメラが平面(サーフェス)の検出を開始します。
平面を検出すると、任意の箇所をタップしてレモンを配置できます。
さらに、「シーンを開始する」ボタンをタップすることで、コードで指定した挙動が動き出します。
このファイルにコードを自由に追記することで、独自のAR空間を構築できます。
SharedCodeファイル
このファイルは、すべてのステージに含まれています。
コメントにもあるように、このファイルに加えた編集内容は「ARを作る」ブックに含まれる全てのページに反映されます。
以下、ソースコードを少しずつ、分解してみていきます。
// 共有コード
// このファイルに書いたコードは、このプレイグラウンドブックのすべてのページで利用できます。
import SceneKit
public var scene = Scene()
public var allModels: [ImaginaryNode] = [Model.alarmClock, Model.arrow, Model.cactus, Model.ear, Model.eye, Model.flyingBug, Model.foot, Model.hand, Model.lemon, Model.lightbulb, Model.lightning, Model.moon, Model.nose, Model.mouth, Model.raincloud, Model.snail, Model.star, Model.sun]
public var origin = Point(x: 0, y: 0, z: 0)
var xOffset = -48.cm
var modelsPlacedCount = 0
// allModels配列のすべてのモデルをシーンの原点の周囲に配置します。
public func placeAllModels() {
let model = allModels[modelsPlacedCount]
scene.place(model, at: Point(x: origin.x - xOffset, y: origin.y, z: origin.z - 30.cm))
model.animate()
xOffset += 8.cm
if modelsPlacedCount + 1 < allModels.count {
modelsPlacedCount += 1
model.play(.ring, loops: false, completion: placeAllModels)
}
}
準備
まず、3D空間を扱うためのフレームワークSceneKit
を導入しています。
そのあと、シーンのオブジェクトを生成して、変数scene
に割り当てています。
import SceneKit
public var scene = Scene()
主要な変数の定義
ここで定義している変数オブジェクトは、以下の4つです。
- allModels(ARコンテンツとして配置できる3Dモデルのコレクション)
- origin(3D空間の原点)
- xOffset(原点からX軸方向への移動距離)
- modelsPlacedCount(配置した3Dモデルの総数)
public var allModels: [ImaginaryNode] = [Model.alarmClock,
Model.arrow,
Model.cactus,
Model.ear,
Model.eye,
Model.flyingBug,
Model.foot,
Model.hand,
Model.lemon,
Model.lightbulb,
Model.lightning,
Model.moon,
Model.nose,
Model.mouth,
Model.raincloud,
Model.snail,
Model.star,
Model.sun]
public var origin = Point(x: 0, y: 0, z: 0)
var xOffset = -48.cm
var modelsPlacedCount = 0
3Dモデルには、生き物から身体の一部、天体に置物などいろいろ用意されています。種類ごとに整理しておくと...
- arrow(矢印)
- alarmClock(目覚し時計)
- lightbulb(電球)
- star(星)
- sun(太陽)
- moon(月)
- lightning(稲妻)
- raincloud(雨雲)
- ear(耳)
- eye(目)
- foot(足)
- hand(手のひら)
- nose(鼻)
- mouth(口)
- cactus(サボテン)
- lemon(レモン)
- snail(カタツムリ)
- flyingBug(空飛ぶ虫)
placeAllModels()関数の定義
この関数を実行すると、allModels
配列のすべての3Dモデルをシーンの原点の周囲に配置します。
public func placeAllModels() {
let model = allModels[modelsPlacedCount]
scene.place(model, at: Point(x: origin.x - xOffset,
y: origin.y,
z: origin.z - 30.cm))
model.animate()
xOffset += 8.cm
if modelsPlacedCount + 1 < allModels.count {
modelsPlacedCount += 1
model.play(.ring, loops: false, completion: placeAllModels)
}
}
もっと詳しくコードを見てみます。
最初に、モデルのコレクションから指定したインデックスのモデルをひとつだけ取り出して、定数model
に割り当てています。
let model = allModels[modelsPlacedCount]
ここでインデックスとして使用されているmodelPlacedCount
は、シーンに配置済みの3Dモデルの総数です。既定値は0
でした。
つまり、初めてplaceAllModels()
関数が呼び出された時点では、定数model
にModel.alarmClock
(レモン)が割り当てられるはずです。
次に、place(_:at:)
メソッドを使って、この3Dモデルをシーンに追加します。
さらに、配置した3Dモデルのanimate()
メソッドを呼び出して、それぞれ特有の動作をさせています。
scene.place(model, at: Point(x: origin.x - xOffset,
y: origin.y,
z: origin.z - 30.cm))
model.animate()
なお、3Dモデルは3次元座標が指定されています。
実行したiPadから見て、X軸方向に48
cm、Z軸方向に30
cm移動した地点です。
ここからは、モデルを配置した後のコードです。
X軸方向のオフセットである変数xOffset
を8
cmだけ増分しています。
xOffset += 8.cm
既定値が-48
cmだったので、この時点では-40
cmに変化します。
もう一度、placeAllModels()が呼び出されると、3Dモデルは8
cmだけ隣に配置されるはずです。
最後に、モデルのコレクションの全てをシーンに追加しています。
if modelsPlacedCount + 1 < allModels.count {
modelsPlacedCount += 1
model.play(.ring, loops: false, completion: placeAllModels)
}
ここでは、__配置した3Dモデルの数__と__コレクションにあるモデルの総数__を比較しています。
allModels.count
に達していない場合、modelsPlacedCount
を+1
します。
modelsPlacedCount += 1
model.play(.ring, loops: false, completion: placeAllModels)
play(_:loops:completion:)
メソッドにある最後のパラメータcompletion:
に注目します。
このパラメータには、placeAllModels
メソッドが渡されています。
つまり、コレクションにあるすべてのモデルがシーンに追加されるまで、placeAllModels()
関数が呼び出されることになります。
したがって、メインファイルでplaceAllModels()
メソッドを一度実行するだけで、シーンには用意されたすべてのモデルが配置できます。
placeAllModels()関数を使ってみる
既存コードをコメントアウトして、placeAllModels()
関数を呼び出します。
// let lemon = Model.lemon
//scene.add(lemon)
placeAllModels()
ページを実行すると、iPadから見て右から左に向かって、3Dモデルが配置されていきます。
サーフェスを検出したり、モデルの配置場所をタップすることはありません。
3Dモデルが追加される高さは、ちょうどiPadと同じくらいです。
「リン〜、リン〜、...」というリズムに合わせて、空間にひとつずつ3dモデルが出現します。
これはplay(_:loops:completion:)
メソッドの挙動です。
completion:
部分が完了ハンドラになっていることがわかります。
コードを読んでわかったこと
「ARを作る」ブックのガイダンスにような内容になっていることが感じ取れました。
また、シーンについては次のようなベクトルがあることがわかりました。
- X軸: 画面左へ + <---> - 画面右へ
- Y軸: 画面上へ + <---> - 画面下へ
- Z軸: 画面前へ + <---> - 画面奥へ
このブックは、少なくとも以下の機能が利用できることがわかりました。
Scene型オブジェクト
-
add(_:)
メソッド(検出した平面上の任意の場所にモデルを配置する) -
place(_:at:)
メソッド(コードで指定した場所にモデルを配置する)
Model型オブジェクト
-
animate()
メソッド -
play(_:loops:completion:)
メソッド
さらに
続きのページ「簡単なシーン」の解説はこちら