WWDC 2017 の SceneKit サンプル Fox 2 を調べる その9
Swift ファイルが保存されているフォルダ
Shared には全体で共有する Swift ファイルがあり、
iOS、tvOS、macOS ではプラットフォーム別で AppDelegate.swift と Main.storyboard から呼ばれる GameViewController.swift で構成されている
iOS 版のみ、ヴァーチャルキーボードを使用するため 3つ Swift ファイルが追加されている。
Swift ファイルについて
Shared と 各プラットフォームのフォルダmのファイルをざっくり見てゆく。
Shared
以下の Swift ファイルがある
- Character.swift
- GameController.swift
- Overlay.swift
- BaseComponent.swift
- ChaserComponent.swift
- PlayerComponent.swift
- ScaredComponent.swift
- SimdExtensions.swift
- UI フォルダ内の Button.swift、Slider.swift、Menu.swift
Character.swift
基本的にはプレイヤーキャラ Max 自体の振る舞い設定を全てここで決めており、 GameController.swift でこちらの設定を使用し Max を動かしている。
GameController.swift
シーン全体設定する NSObject のクラス。
各プラットフォームの GameViewController の UIViewController でこのクラスをイニシャライズし、その際に各 Main.storyboard で設定されている SCNView 渡して共通の処理をしている。
Overlay.swift
右上のアイコン類、クリア時のロゴと Max の 2D 画像を画面の UI を SpriteKit の SKScene で設定しており、GameController.swift で SceneKit の overlaySKScene でこちらを設定している。
iOS のヴァーチャルパッドは iOS フォルダの Swift ファイルで設定している。
BaseComponent.swift
GameplayKit の GKComponent を継承したクラスで、敵キャラなどの動作の元となる設定がされている。
目標に沿って動作するエージェントシステムが使用できる GKAgent2D を効率よく使用するため、GKComponent を使用している。
また、このファイルで GKAgent2D の Extension を作成している。
GKAgent2D の他に GKAgent3D があるが、今回は Y 座標に対して、追跡を行わないため 3D の方は使用していない模様。
PlayerComponent.swift
BaseComponent を継承したクラスで、Character.swift でキャラ設定しているクラスを GKComponent で取得している。
ChaserComponent.swift と ScaredComponent.swift ではこちらのクラスからキャラ設定を取得して、近づいたり、離れたりするための位置情報取得と、攻撃の状態を調べているために使用している。
ChaserComponent.swift
ScaredComponent.swift
近づく敵や遠ざかる敵の設定。
基本的にはほぼ同じで GKAgent2D 目的地となる複数の GKGoal を設定したり、
敵キャラの状態の設定や状態ごとの振る舞いを決めている。
状態の判別は GameplayKit の State Machine は使用せず、enum の設定から調べている。
SimdExtensions.swift
SIMD で設定している行列の処理を簡単にする Extension。
基本的には simd_float2、3、4 での == や != のオペレーターや、
simd_float4x4 で 位置情報 xyz 代入したり取得する関数が追加されている。
ちなみに、Fox 2 座標変換はほぼ iOS11 などで追加された SIMD で計算されている。
UI フォルダ内の Button.swift、Slider.swift、Menu.swift
デバッグ用の UI と機能があり、Button.swift ボタンに関する UI。
Slider.swift スライダーの UI。Menu.swift はこれらを表示するものとなっており、Overlay.swift 内で設定されている。
ボタンはカメラの切り替え、スライダは被写界深度の調整を行う。
左上の Max の画像をタップすると出現するはずだが、設定している座標の問題でボタンが表示されず、スライダーは IBAction の設定されていないため使用できない。
iOS
以下の Swift ファイルがある
- ButtonOverlay.swift
- PadOverlay.swift
- ControlOverlay.swift
- GameViewController.swift
- AppDelegate.swift
ButtonOverlay.swift
ヴァーチャルパッドで使用する A、B ボタンの SKNode とタッチイベントを設定している。
GameController.swift でメソッドを実行できる用に ButtonOverlayDelegate が設定されている。
PadOverlay.swift
ヴァーチャルパッドで使用するパッドの SKNode とタッチイベントを設定している。
GameController.swift でメソッドを実行できる用に PadOverlayDelegate が設定されている。
ControlOverlay.swift
ヴァーチャルパッドの部品であるパッド2つとボタン2つを配置している SKNode。
Overlay.swift 内で「#if os( iOS ) ... #endif」を使用し、iOS の場合のみこちらを表示し動作するようにしている。
GameViewController.swift
起点となる UIViewController。 Main.storyboard で view に SCNView が設定されている。
Shared フォルダ GameController の呼び出し時に、この view である SCNView を渡している。
また、iPad の場合は UIView の contentScaleFactor を調べ 1.3 より大きい場合は 1.3 に変更し、preferredFramesPerSecond を 60 にしている。
AppDelegate.swift
通常の AppDelegate.swift。
tvOS
GameViewController.swift
起点となる UIViewController。 Main.storyboard で view に SCNView が設定されている。
Shared フォルダ GameController の呼び出し時に、この view である SCNView を渡している。
AppDelegate.swift
通常の AppDelegate.swift。
macOS
GameViewController.swift
通常の macOS アプリ同様に NSWindow の 起点となる NSViewController。 Main.storyboard の view ではカスタムクラスの GameViewMacOS が呼ばれており SCNView が継承され設定されている。
Shared フォルダ GameController の呼び出し時に、この view である SCNView を渡している。
NSViewController では iOS のヴァーチャルパッドと同様の振る舞いを keyUp, keyDown のキーボード操作に該当する関数で設定している。
カスタムクラスの GameViewMacOS は、ウィンドウのサイズ変更時に Overlay.swift の layout2DOverlay() で UI 領域を再描画したり、viewDidMoveToWindow で NSViewController へサブビューとしてこちらが追加された際に contentsScale を 1 にして Retina 表示ではなくしている。
また、NSViewController ではキーボードイベントがないため、この View のキーイベントを NSViewController の関数にアクセスしている。
AppDelegate.swift
通常の AppDelegate.swift。
以上、ざっくりと Fox 2 のプロジェクトで使用している Swift ファイルの説明をしてみた。
次回、コードの深掘りの前に GameplayKit 概要を見てゆく。
WWDC 2017 の SceneKit サンプル Fox 2 を調べる その8
Fox 2 の概要のおさらい
iOS、tvOS、macOS をサポートしており、MFi 認証が行われているゲームコントローラーをサポート。全体の振る舞いは Shared フォルダの GameController.swift で行われている。
実機で試せていないが、多分 Apple TV はゲームコントローラーしかサポートしていない可能性がある。
また、iOS のみヴァーチャルパッドを使用している。
シーンの構成について
シーンの構成は大きく分けると以下のものになっている
- プレイヤーキャラクターの Max
- 敵キャラクター2体
- 宝石や鍵など取得できるアイテム
- 床や岩場、動く橋、溶岩や溶岩などのパーティクル、仲間が閉じ込められている小屋などの背景
- 遠方のパーティクルや背景画像
- 各カメラとライト
- シーンに対してオーバーレイ表示される UI
以下コードに関する背景、キャラクター、アイテム、カメラについて見てゆく。
背景
前回リソースを見た際に、あったが本来の背景のジオメトリとは別に collision.scn でジオメトリで Physics Body が設定されており、物理シミュレーションの衝突判定を行なっている。
collision.scn には COLL_lava という名のノードがあり、こちらに Max が接触すると音声と尻尾が燃えるアニメーションが再生される。
また、背景にはノードの透明度が 0 となっている物理判定のジオメトリが小屋の鍵穴付近、カメラの視点が変わる各場所に設定されている。
プレイヤーキャラクター Max
歩く、ジャンプ、攻撃、止まった状態でアニメーションが設定されており、攻撃時はその状態を伝える命令がある。
上記のように、溶岩に触れると音声と尻尾が燃えるアニメーション(地上に戻ると消える)、特定の場所に行くと見えないジオメトリの Physics Body により、衝突判定とカメラが変更せれ視点が切り替わったり、アイテムの取得や仲間を助けるなど行われる。
敵に対しての攻撃は、物理シミュレーションではなく GameplayKit 機能内で互いの距離と攻撃状態で調べている。
敵キャラクター
2対おり、Max に近づくものと、遠ざかるものがいる。
「特定の動き、彷徨う、Max を追いかける・遠ざかる」複数の状態を繰り返す、攻撃を受けた時に音や爆発のアニメーションなど行う。 敵の振る舞いは GameplayKit の機能機能内で行われている。
アイテム
宝石と仲間が閉じ込められている小屋の鍵の2つがあり、共に Physics Body が設定されている。
宝石に接触すると Max の動作が止まり、鍵の出現とパーティクル表示、カメラの視点変更し元のカメラに戻る処理、宝石の消滅、UI での宝石の取得表示と内部での値加算処理が行われる。
鍵に接触すると、取得時のパーティクル表示、UI での鍵の取得表示と内部での値加算処理がされる。
カメラ
いくつかのカメラがシーンファイルで設定されており、GameController.swift で振る舞いを決めている。
Max が背景のシーンファイルにあるカメラ視点変更用の Physics Body に触れると対応した設定のカメラへ変更され視点が変わるようになっている。
画像のような透明なジオメトリに接触するとカメラ視点が変わる
GameplayKit の概要や UI 関連に関しては後ほど見ていこうと思う。
次回は、プロジェクト内での Swift ファイルの概要を見てゆく。
WWDC 2017 の SceneKit サンプル Fox 2 を調べる その7
level_scene.scn を調べていく。 シーンにおける操作キャラクター Max、敵キャラクター以外の背景に相当するシーンファイル。
level_scene.scn では設定されているものが多いため、今回は読み飛ばしても構わない。
このシーンにあるジオメトリやパーティクル、物理フィールド以外のもの
ロジック部分である Swift のコードでは、
キャラクターの状態が変わった際に何かが行われるようになっており、
状態の変化を知らせるものはほぼ物理シミュレーションのヒットテストで行われている。
そのため、このシーンでは当たり判定としていくつかの Physics Body が設定されている。
以下、このシーンでのヒットテスト
- アイテムと Max がぶつかり、アイテムを取得する時
- 扉の鍵穴に近づきを扉を開ける時
溶岩に Max が入ってしまった時の判定は collision.scn の COLL_lava で判定し、敵との判定は enemy1、enemy2 の各 GKComponent で行なっている
level_scene.scn の背景と Fog
背景画像に Background_sky.png が設定されており、Image Based Lighting (IBL) では sky_cube.exr が設定されている。
以下の画像では、試しに Physically Based のマテリアルを置いている。マテリアルには背景ではなく、上が青、下が黄色の IBL の色が映り込んでいる。
古い Apple TV では .exr ファイルでの動作が厳しいらしく、tvOS のコード上では png に変更されている。
溶岩が流れている影響で Fog の色はオレンジ、開始距離が 10、終了が 400 となっている。
level_scene.scn のノード
depart
岩場や地面、小屋など背景のジオメトリ。
lava
溶岩のジオメトリ。ジオメトリに対してパーティクルが設定されている。
また、Shader Modifiers でカスタムシェーダーが適応されている。
door
小屋で仲間を閉じ込めている扉。
Max が侵入できないように Static の Physics Body が設定されている。
grass072, Object001, Object002, Object003, Object004
マップ内に配置しているの植物で、シダ植物の葉、その葉と薇(ぜんまい)のような渦を巻きで構成されたものが配置されている。
葉の動きなど Shader Modifiers でカスタムシェーダーが適応されている。
CollectableBig
鍵を表示させるための宝石のアイテム。
Max 接触時にイベントを起こすため Physics Body が設定されている。
また、ジオメトリの両面を透過した際にアーティファクト(表示上のエラー)が出るため、iOS 11 の SceneKit で追加された Dual layar が使用されている、
skybox_copy
depart と lava の外面に筒状のジオメトリが配置されており、星の瞬きのパーティクルが設定されている。
そのため、プレイヤーからの視点は、岩などの背景 > skybox_copy のパーティクル > シーンの背景画像の順で外観が見えることとなる。
DirectLight
全体の光を設定しているディレクショナルライト。
シャドーマップの生成を iOS 11 から追加された Auto Adjust を使用しているため、シャドーマップの細かな設定をしなくても綺麗に描画される。
また、コードの方では遠方のシャドーマップの処理を段階的に低減させるカスケードシャドーマップの設定をしている。
mobilePlatform
小屋の前にある溶岩に浮かぶ移動する6角形の橋?
mobilePlatform ノードの下には、 橋のジオメトリ、橋が動いた時に溶岩の表面の揺れを表示するパーティクルが2つ、 Max が橋に着地した時に溶岩へ落ちないようにコリジョンが設定されている。
key
鍵のアイテム。宝石のアイテム取得後に表示されるため、このノードの透明度は 0 になっている。
鍵のジオメトリに Max 接触時にイベントを起こすため Physics Body が設定されているものと、鍵出現時にパーティクルを表示するためのからのノードがある。
cameraStart_node、cameraGame_node、cameraMountain_node、cameraGameArena_node、camPlatform01_node、camPlatform02_node
親ノードの子のノードにカメラが設置されたものが複数用意されている。 シーン内の ”trigCam_〜” の名前で設定されている Physics Body に Max が接触した際、 特定の場所に移動するカメラ。
trigCam_camLookAt_cameraGameArena
ゲームスタート後ジャンプし段を降りた際、この Physics Body に当たると、cameraGameArena_node のカメラへ切り替わる。
trigCam_camLookAt_cameraMountain
ゲームスタート後ジャンプし段を登った際、この Physics Body に当たると、cameraMountain_node のカメラへ切り替わる。
trigCam_camTrav_camPlatform01
鍵を取る際に岸にあるこの Physics Body に当たると、camPlatform01_node のカメラへ切り替わる。
camPlatform01_node のカメラは視点変更の操作ができなくなる。 そのため、他のカメラの Physics Body に接触するまで視点変更できない。
trigCam_camLookAt_cameraGameArena
宝石の近くに設置されたこの Physics Body に当たると、cameraGameArena_node のカメラへ切り替わる。
岸などで視点変換されたあと移動して戻る際にこちらの視点に変更される。
trigCam_camLookAt_cameraGameArena
宝石の間近に設置されたこの Physics Body に当たると、cameraGameArena_node のカメラへ切り替わる。
岸などで視点変換されたあと移動して戻る際にこちらの視点に変更される。
trigCam_camTrav_camPlatform02
鍵取得後に移動する橋の前にある岸に設置されており、この Physics Body に当たると、camPlatform02_node のカメラへ切り替わる。
camPlatform02_node のカメラは視点変更の操作ができなくなる。 そのため、他のカメラの Physics Body に接触するまで視点変更できない。
trigCam_camTrav_camPlatform01
鍵の場所から宝石の場所に戻る場所に設置され、この Physics Body に当たると、camPlatform01_node のカメラへ切り替わる。
camPlatform01_node のカメラは視点変更の操作ができなくなる。 そのため、他のカメラの Physics Body に接触するまで視点変更できない。
trigCam_camLookAt_cameraGame
宝石のある空間に設置され、この Physics Body に当たると、cameraGame のカメラへ切り替わる。
trigCam_camLookAt_cameraStart
スタート地点に設置され、この Physics Body に当たると、cameraStart のカメラへ切り替わる。
trigCam_camLookAt_cameraGame
動く橋を超えた岸に設置され、この Physics Body に当たると、cameraGame のカメラへ切り替わる。
trigAction_unlockDoor
鍵穴近くに設置される Physics Body。 コードでは当たると扉を開け、仲間の解放、パーティクル、クリア画面を表示する。
friendsBox
用途不明。Static の Physics Body が設定されている
CameraCinematic02
クリア時のカメラ。
CameraCinematic01
宝石取得時に鍵へフォーカスするカメラ。
他のカメラと異なりフォーカスレングスなどが調整されていて、鍵より後ろの背景がボケて表示される。
particles_spores、particles_spores_arena
ステージで舞う火の粉。
particles_volcanoSmoke_v1
煙のパーティクル。
particles_smallJets_v1
溶岩の飛び散りを表現するのパーティクル。
particles_volcanoSmoke_v2
煙のパーティクル。
particles_smallJets_v2
溶岩の飛び散りを表現するのパーティクル。
particles_lock
鍵穴を周りのパーティクル。
field
particles_spores、particles_spores_arena で火の粉となっているパーティクルにターピュランスの Physics Field で揺らぎを与えている。
particles_smallSmoke
隙間から出る煙のパーティクル。
particles_littleSplashs
小さな穴から出る溶岩の飛び散りを表現するのパーティクル。
particles_door
unlock_door.scn のパーティクルを設置する空のノード。
CameraDof0, CameraDof1, CameraDof2
デバッグ用のカメラでそのままでは使えない。
ソースコードをいじることで、これらのカメラを試すボタンが出る。
次回はソースコードの説明をしようとしていたけど、全体の仕組みについて見てゆこうと思う。
WWDC 2017 の SceneKit サンプル Fox 2 を調べる その6
残りのリソースファイルを調べていく。
level_scene.scn は設定されているものが多いので次回に。
collision.scn
プレイヤーキャラ Max との障害物判定を行うためジオメトリ。 ジオメトリは 5 つあるが PhysicsBody の設定は全て同じ。
scene.scn
level_scene.scn、enemy1、enemy2 を参照したファイルで、これが各プラットフォームの GameViewController で呼ばれる。
enemy1、enemy2 の Components には各 GKComponent が設定されている。
enemy1
enemy2
ちなみに、プロジェクト内に GKComponent のクラスが存在していれば、Scene Editor の Components の「+」ボタンで紐づけることができる。
また @GKInspectable の付いた変数は Scene Editor 上でパラメーターを変更できる。
scene.gks
GameplayKit とシーンファイルを紐づけるバイナリの plist ファイルで、
Components の「+」ボタンで自作の GKComponent 紐づけると自動で生成される。
scene.gks の中身
tile.png
使用されていない。多分 WWDC 2017 のデモ用に作成されたファイルの消し忘れだと思われる。
次回は level_scene.scn を見て、その後はソースコードの説明を行う。
WWDC 2017 の SceneKit サンプル Fox 2 を調べる その5
particles フォルダ
シーンで使用するパーティクルシステムを設定しているファイルが入っている。
この中でパーティクルシステムファイルは1つしかなく、他はシーンファイルの中で設定されている。
また、一部パーティクルは背景を設定している level_scene.scn で設定されている。
ファイルの内容
ファイル | 説明 |
---|---|
burn.scn | Max が溶岩に落ちた際に尻尾につける煙パーティクル |
collect-big.scnp | 使用されていない |
collect.scnp | アイテム取得時に表示されるパーティクル |
enemy_explosion.scn | 敵が死んだ時に表示される爆発のパーティクル。ロードされるがシーン上で使われている気配はないような感じ |
key_apparition.scn | 鍵出現の時のパーティクル |
particles_spin.scn | Max が回転して攻撃する時に出る衝撃波のパーティクル |
unlock_door.scn | 鍵を使い、閉じ込められている仲間の扉が開いた時に表示されるパーティクル |
texture フォルダは省くので、
次回は残りのリソースファイルを見てゆく。
WWDC 2017 の SceneKit サンプル Fox 2 を調べる その4
enemy フォルダ
敵キャラクター2体分のリソースが入っている。
2体の敵キャラは ChaserComponent に紐づいたプレイヤーキャラクターに近づいてくるタイプと、ScaredComponent に紐づいたプレイヤーキャラクターから遠ざかるタイプ。
シーンファイルファイル
以下の4つのシーンファイルが存在するが、enemies.scn はこのプロジェクトでは参照されていない。
また、particles に enemy_explosion.scn と同じファイルがある。
- enemies.scn
- enemy_explosion.scn
- enemy1.scn
- enemy2.scn
enemy1.scn
近づいてくるタイプの敵キャラ。ジオメトリの設定しかされていない。
enemy2.scn
遠ざかるタイプの敵キャラ。 baddy_fearfl の下にキャラから放出されるパーティクルとジオメトリ。
その下にゆらめいている炎の fire のジオメトリはマテリアルも異なり、Shader Modifiers 揺らめきのアニメーションが設定されている。
fire のジオメトリにはボーンが設定されているが使用されていない模様。
enemy_explosion.scn
Max の攻撃がヒットした後、敵キャラが死ぬ時に表示される爆発のパーティクル。
次回は particles フォルダ の中身を見てゆく。
WWDC 2017 の SceneKit サンプル Fox 2 を調べる その3
character フォルダ
操作するキャラクター レッサーパンダの Max に関するリソースが入っている。
メインのファイル
max.scn は Max_rootNode を起点に以下のような構成
- シーンの障害物判定に合わせるための Kinetic の PhysicsBody に設定されている collider
- キャラクターアニメーション用ボーンの Bip001
- Max のジオメトリ
- ジャンプ後着地際に表示するパーティクルの起点となる空のノード dustEmitter
- 攻撃する際に表示するパーティクルの起点となる空のノード particles_spin_circle
アニメーション用のファイル
iOS 11, tvOS 11, macOS 10.13 で使用できるようになった SCNAnimation と SCNAnimationPlayer 用のボーンアニメーションが各ファイルにある。
- max_spin.scn 攻撃時のアニメーション
- max_walk.scn 歩行時のアニメーション
- max_idle.scn 操作をしていない時のアニメーション
- max_jump.scn ジャンプボタン押下時のアニメーション
各シーンファイルの構成は以下の画像のようになっている。
max.scn と同じように Max_rootNode の下に Bip001 があり、アニメーションが設定されている。
このボーンアニメーションをプログラムから SCNAnimationPlayer で操作し max.scn に設定されているジオメトリにたいしてスキニングアニメーションをさせる。
そのため、Scene Editor で max_spin.scn などの Bip001 に設定されている Animations の歯車ボタンから.anim で書き出して、max.scn の Bip001 へ読み込むことで動きの確認ができる。
Max のテクスチャファイル
- max_AO.png
- max_diffuse.png
- max_roughness.png
- max_specular.png
基本的な構成はファイル名通りアンビエントオクルージョン、表面色、表面の荒さ、光沢を設定したテクスチャ。 max_specular.png は使用されていない。
- max_diffuseB.png
- max_diffuseC.png
- max_diffuseD.png
他の仲間の色として3色分用意されている。
Roughness と Ambient Occlusion テクスチャのおさらい
Roughness
Roughness は表面の荒さを表し、
テクスチャが黒に近くなる、もしくは値が 0 に近くなる程光沢は小さくシャープになり、
テクスチャが白に近くなる、もしくは値が 1 に近くなる程光沢はぼやけて広範囲になる。
以下の画像では、左のテクスチャをジオメトリに適応している。
色の黒い目や鼻は光沢がシャープで小さく、体などは光沢が荒く広くなっている。
Ambient Occlusion
Ambient Occlusion は物体が遮蔽される部分に影を生成する技術。
形状によっては現実的には存在しない陰ができるため擬似的なものだと考えて欲しい。
今回はテクスチャを使用しており処理的には黒い部分の色を乗算をしている。
以下の画像では左のテクスチャを適応している。本来はアンビエントライト1つしかないので、陰は落ちないがテクスチャが書き込んでいる。
パーティクルシステムファイル
- jump_dust.scn
- smoke.png
jump_dust.scn にパーティクルシステムが設定されておりジャンプ着地時に砂煙のようなパーティクルを出す。
smoke.png はこのパーティクル画像。
ちなみに smoke.png は particles フォルダ内にある Max が溶岩に落ちた際に尻尾につく煙のパーティクルでも使用されている。
その他
max.scn など一部シーンファイルは Scene Editor から Shader Modifiers 経由でシェーダーが設定されている。
こちらはいつかまとめて紹介しようと思う。
次回は enemy フォルダ の中身を見てゆく。