Unity における「カメラ」という考え方
Tags: unity3d
Published: 2020-01-23 15:46:09

※この記事は 2014年08月08日 に Qiita に投稿したものです。

---


この内容はきっと他の 3D 系開発ツールでも有用だと思いますが、タイトルを「Unity」と限定したのは、私が cocos2d と Unity しか使った経験がないゲーム開発初級者だからです。許せ。

## カメラの「存在」

通常、私たちはテレビやモニター、あるいは携帯型ゲーム機やらスマホなど専用のディスプレイにゲーム画面を映し出して遊びます。私たちにとっては目に見えている画面=ゲームですが、 3D のゲームでは、開発者が用意した 3D 空間の一部しか画面に映りません（どうにか全部表示しても遊びにくくなるだけですよね）。そのため、プレイヤーがゲーム空間を把握する必要のあるゲームでは、主観視点で歩き回れるようにするなり、マップを回転させるなりしてプレイヤーの手助けをします。こうして、私たちは平面のディスプレイを通して 3D のゲーム空間を覗き込みます。このとき、ディスプレイに映し出される光景を切り出す役を担うのが「カメラ」です。 3D ゲームではカメラはプレイヤーの目となり、また手にもなります。手にもなります。大事なことなので （ry

まあ言われてみれば「そんなの当然じゃないの？」と思われるかもしれませんが、 3D ゲームの開発にも映像撮影にも縁のなかった私は、普段から 3D のゲームで遊んでいても「自分はカメラを通してゲーム内のオブジェクトを見ている」という考え方をしなかったので、カメラの使い道になかなか気が回りませんでした。 Unity の入門書は何冊か読んだんですけど、読んだ中ではカメラについてのまとまった解説は見当たらなかったんで、覚えたことをまとめておきます。

## ゲーム空間とディスプレイのギャップを埋めるカメラ

例えば、クリックされたオブジェクトを選択状態にしたいとします。クリックされたディスプレイ上の位置（二次元座標）を取得する API はもちろんあります。ですがその座標でゲーム空間内のオブジェクトを指し示せるのかというと……そうもいきません。オブジェクトがどうディスプレイに映し出されようとも、それはあくまでカメラの視点です。ゲーム空間におけるオブジェクトの位置（三次元座標）とディスプレイ上の位置（二次元座標）はまったくの別物です。「ディスプレイ上の座標 (x, y) に表示されてるオブジェクトを取得したいんだけどさあ」と思っても、オブジェクトたちにすれば「私たちがディスプレイにどう映ってるかなんて知りませんよ。カメラさんに聞いてください」と答えるしかありません。

```
ゲーム空間（三次元） <-> カメラ <-> ディスプレイ（二次元） <-> プレイヤーの目と手（入力デバイス）
```

具体的な解決方法は後述しますが、このようにゲーム空間とディスプレイの間には座標のギャップがあります。というか間にカメラが入ってるんだから当然と言えば当然ですが、私たちが普段使っている入力デバイス（マウスとかゲーム用のコントローラーとか）も三次元座標を手軽に伝達できるようにはなっていません。ということは、ゲーム空間をディスプレイに表示するのとは逆の方向 -- ディスプレイ上で指定した位置をゲーム空間に関連付ける手段も必要になります。というか間にカメラが入ってるせいでギャップが発生するんだから、これもカメラが解決方法を提供してくれます。

ちなみにディスプレイ上の座標を「スクリーン座標 (screen point/position) 」、ゲーム空間内の座標を「世界座標、ワールド座標 (world point/position) 」と言います。 “ScreenToXXX” や “WorldToXXX” などの名前の API は、これらの座標を変換します。


## 光線を放つカメラ

カメラの API には、指定した座標に向かって光線を出すメソッドがあります。例えばこうすると、クリックされたゲーム空間内の座標（スクリーン座標じゃないのかって？ とりあえずそういうものだと思ってください）に向かって人には見えない怪光線をカメラから打ち出します。ビビっと。


```c#
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
```

“Ray” とは「光線」の意です。初めてこのコードを見たときには「何の役に立つんだこんなもの？」と思ったのですが、ちょっと想像してみてください。カメラから出力された光線は、途中で何らかのオブジェクトにぶつかります。そのオブジェクトを「プレイヤーがクリックした（い）オブジェクト」とみなせば、「プレイヤーがクリックしたスクリーン座標から、ゲーム空間内のオブジェクトを指定する」ができるようになるわけです。光線と衝突したオブジェクトを検出するコードは探せば出てきますので、そちらをどうぞ。

さて今の説明では、最初にゲーム空間内の座標をプレイヤーが指定した前提で進めています。現実には、プレイヤーがクリックできるのはスクリーン座標です。スクリーン座標と世界座標の違いは「 Z 座標（奥行き）の有無」ですね。スクリーン座標は二次元座標ですから Z 座標はありません。ですが、「カメラもオブジェクトの一つである」ことを思い出してください。スクリーン座標がカメラのレンズ上の点であるならば、ここに「カメラからの距離」を加えれば、スクリーン座標を（世界座標を示す）三次元座標に変換できます。上のコードでは「カメラからの距離」を無視してしまっていますが（ゼロ距離とみなしています）、まあ問題が出たら調整すればいいと思います。

前節で「カメラはプレイヤーの手にもなる」とか「ゲーム空間とディスプレイのギャップを埋める」などと書きました。それらの機能の一つがこれです。


## 立体感の有無

カメラによる「空間の見え方」は一つではありません。デフォルトの設定では、カメラは私たちの目に自然に映るような立体感を持った画像にしてくれます。これを「透視投影モード」と言い、カメラの設定項目に "Perspective" とあるのがそうです。

これを、もう一つの設定項目である "Orthographic" にすると、立体感を消し去ったような画像にしてくれます（手元に Unity を動かせる環境があるならさっさと試してみるといいでしょう）。立体感のない、つまり距離感のない画像がどんなものかというと、俯瞰図をイメージすると早いです。

3D 空間を歩き回るゲームなんかだと、俯瞰図って必ず出てくるでしょう？ 簡単なものでは街並みやダンジョンの全体マップだとか、ちょっと形が変わったものだとレーダーも俯瞰図の一種です。立体感を消すだけで多種多様な見せ方ができるのが面白いですね。


## 見せたいものしか映さないカメラ

少し前の立体感の設定の説明で、俯瞰図を例に出しました。全体マップとかレーダーとか、確かに俯瞰図にすれば手軽に実現できそうです。と思って平行投影モードにしてみると、なんだかごちゃごちゃした画面ができてしまいませんでしたか？ 何せすべてのオブジェクトを俯瞰図にしているんですから当然です。求める画面を作るには、余分なオブジェクトを消す……というか、その逆の「見せたいオブジェクトだけを映す」が必要になります。例によって詳細は「レイヤー」とか "Culling Mask" でぐぐってください。

実際にはカメラとオブジェクトの双方に設定が要りますが、「見せたいオブジェクト」と「立体感の有無」の組み合わせは強力です。特に人以外のレイヤーのオブジェクトが映り込んだ映像には誰もが心惹かれるものですよね。……あ、いわゆる心霊なんとかってやつですよ。


## カメラは何台でも用意できる

ここまで書いておいて今更ですが、カメラもオブジェクトの一つであり、複数台のカメラをゲーム空間内に配置できます。これも「そんな当然のことわかってるよ」と思われるでしょうけど、多重度の意識は大事ですよね。

デフォルトではメインカメラ一台のみが用意されています。目まぐるしく視点が切り替わるようなゲームでは、視点ごとにカメラを用意しておいて、必要に応じてカメラを切り替えればよさそうですね。


## カメラを回転させるために覚えておくこと

Unity （ API じゃなくて開発ツールの方）では、カメラの回転は “Rotation” という項目で、 “x” “y” “z” の三つの値で表されます。これをスクリプトで制御しようとすると、 ["Quaternion" （クォータニオン）](http://docs.unity3d.com/ScriptReference/Quaternion.html) という値をカメラの “rotation” プロパティにセットすることになります。クォータニオンにも “x” “y” “z” というプロパティがありますが、これに “Rotation” の項目と同じ値をセットしても思うようには動きません。

スクリプトによるカメラ操作のとっかかりとしては、次の点を押さえておけばいいと思います。まあ私も全然詳しくないです。

- オブジェクトの回転（姿勢）はクォータニオンという概念で表される。
- 開発ツールで設定できる “Rotation" の値とクォータニオンの値は同一ではない。
- “Rotation” の値をクォータニオンに変換するには、 `Quaternion.Euler` 関数を使えばよい。