UIの間でエフェクトの行き来をする方法について、技術部平山が書いてみます。実行して動かしてみると良いかと思います。ドラッグするとカメラが動き、タップしたりキーを叩いたりするとエフェクトが出ます。エフェクトが出てからカメラを動かしても正しい位置に飛んでいくことをご確認ください。コードも同様にgithubに置いてあります。いうスタイルは良く見られます。弊社東京プリズンはそのスタイルです。いうゲームでは、たまに3Dと2Dを行き来するような表現が欲しくなることがあります。バラバラに表示をすると、原因と結果の結びつきがわかりにくくなります。何かを飛ばすわけです。動きの向きによって、原因と結果の関係が直感的に理解できます。その表現に使っています。飛ばしたくなることもあるでしょう。倒した敵からお金や宝箱が出て、それが画面上部のお金や宝箱のカウンタに飛んでいったりします。うまく使えばわかりやすい表現にできるかと思います。いう効果を実装するにはどうすればいいのでしょうか。全部3D世界に置く一番理屈として簡単なのは、全てを3D世界に置くことです。カメラに対して固定した位置に、毎フレーム設置しなおします。カメラが1m上へ上がれば、UIも1m上へ動かすわけです。すれば止まっているように見えます。動かないように見えますが、車、そして車の中の人間と一緒に動いているわけです。カメラを複数置くことによる性能の無駄がありません。全てを3D世界に置けるので、頻繁に行き来がある場合には最もシンプルになります。3D的な演出も制限なく行えます。一味違ったUIを作るには良い方法でしょう。UnityEngine.UIの便利機能の恩恵を受けられません。そこまではしたくない、というケースもあるでしょう。この場合は、UnityEngine.UIで作り、ワールド座標でCanvasを置くだけで済ますこともできます。カメラの子にcanvasを置けば相対位置が保たれますので、手間もさほど増えません。東京プリズンの場合はこの方法を選択しませんでした。後からそこをいじるのが手間だったからです。UI要素をいくつかのシーンに分けて作ることも簡単です。カメラの子にCanvasを置く必要がないからです。シーンの数だけ「カメラの座標を参照して毎フレーム正しい位置に配置し直す」処理を行うのも面倒でしょう。2D世界と3D世界を分けたままやるというわけで、東京プリズンではカメラを分けたままでやることとしました。これには大きく分けて二つの方法があります。3D世界の座標系で配置し、3Dのカメラで写します。「2D要素が仮に3D世界にあるとしたらどこにあるか」を計算します。その座標から出発して3D世界の座標系で動かします。3Dのカメラで描画する、つまりPerspectiveのカメラで描画するので、遠近法のケアは不要です。遠くに行けば勝手に小さくなります。後者の場合、飛ばすものはUnityEngine.UIで作り、2D用のカメラで写します。3D世界の座標系で動かします。Transformに入れるパラメータを計算します。「遠くに行くほど小さい」を手動で実現します。2Dのカメラは正射影(Orthographic)なので、遠くに行っても小さくならないからです。UI要素との描画順を制御したい場合はこちらが必要です。今回のサンプルでは両方の実装を用意しました。サンプルの構造をまず説明にあたって、サンプルの構造に簡単に触れさせてください。シーンのオブジェクト木構造は以下のようになっています。Sampleなるシーンの中に、3Dと2Dというオブジェクトが存在しています。「3D世界に置くエフェクト」を写します。2Dの下にもCamera2Dがあり、Canvasと「2D世界に置くエフェクト」を写します。2D世界のカメラはZを100ほどずらしてあります。Sampleクラスだけです。今後示すコードは全てこの中にあるものとなります。3Dの物を2Dに合わせて置くサンプルでは、UIからキャラクターに向かって飛ぶエフェクトは、3D世界にあります。ここからキャラクターへ向かって飛んでいきます。おおまかに言えば以下のような処理をしています。distance);}内容を箇条書きにすると以下のようになります。開始点にあるUIの2D世界におけるワールド座標を得るこのスクリーン座標を得るこのスクリーン座標を3D世界におけるレイに変換するレイ上の一点を選ぶ2D世界におけるワールド座標の取得Transform.positionで取れます。2D世界におけるワールド座標→スクリーン座標2Dカメラと3Dカメラをつなぐものは、スクリーン座標です。カメラをまたぐ時は基本スクリーン座標を経由します。「2Dカメラ上で画面のここに写る。いう順序で計算するわけです。3Dカメラから見る時には使えません。2DカメラがZ+100されていることを思い出しましょう。(カメラが一つであればこういうややこしさがありません)。スクリーン座標→3D世界におけるワールド座標系のレイスクリーン座標からワールド座標に変換するには、一旦レイを経由します。「画面上のある点」は実際には点でなく直線だからです。まっすぐな棒を目の前に前を向けて持てば、点に見えます。カメラからどれだけの距離にある点なのか、は定まりません。これを自分で決めてやる必要があります。使うカメラにはご注意ください。2Dのカメラを使えば「2D世界におけるワールド座標系のレイ」が得られます。サンプルのGUI上でも実行しながら調整できます。あることに注意してください。カメラからこのベクトルを伸ばした所に求めるべきUI要素があります。カメラが向いている方向のベクトルはCamera.transform.forwardです。UIが乗っている平面までの距離は、上述のuiPlaneDistanceFromCameraです。図中の「UI要素」と「カメラ」の距離が問題になります。コサインがわかれば求まります。uiPlaneDistanceFromCameraです。B.magnitude)という関係があるので、ここから求まりますね。cosine;で距離が求まります。あとはカメラ座標にray.directionをこの長さだけ伸ばしたものを足すだけです。次に、座標を補間します。t);サンプルのコードでは_useBezierなる変数で分岐していますが、elseの方だけ見てください。何があっても指定した時間で到着するようにしています。遠いほど時間がかかるようにしたければ別の計算が必要で、しかも結構面倒です。用途では、指定時間で必ず到着する補間の方が楽でしょう。補間係数tは0から1の間である必要があるので以下のように求めます。ベジエの計算をしています。これを用いて軌道を作るわけです。飛ぶよりはいい感じで、東京プリズンでもこうしています。加速感が欲しいので、tを二乗することで最初をゆっくりにし、後半を加速させています。試されると良いかと思います。Transformにセットあとは、エフェクトのTransform.positionにできた座標をつっこみます。サンプルでは事故らないことを重視してpositionを設定しています。2Dの物を3D的に置くでは本題の「面倒くさい方」に行きましょう。UIよりも手前に表示したいのです。なると2D層で描画する必要があります。描画順をいじるためだけにカメラを増やしたくはありません。この方法で実装されています。コードはSample.UpdateEffect2D()のあたりで、おおまかには以下のような処理をしています。目標点のUIの3D世界における座標を得る開始点と目標点の間を補間する補間する補間後の3D世界におけるワールド座標をスクリーン座標に変換するスクリーン座標を2D世界におけるワールド座標系のレイに変換するレイの始点をエフェクトのpositionに設定する。カメラからの「Z方向距離」を求める。距離からスケールを計算するスケールを設定する補間までは同じですが、出てきた3D世界におけるワールド座標を、2D世界に持っていかねばなりません。そこの処理がひどく面倒くさいわけです。補間が終わったところから説明しましょう。スクリーン座標にしましょう。使うカメラは3Dのカメラです。カメラを間違えると動きません。スクリーン座標を、2D世界におけるワールド座標系のレイに変換するスクリーン座標はCamera.ScreenPointToRay()でレイに変換できます。使うカメラは2Dです。これで2D世界のワールド座標を得ます。ScreenPointToRay()で得られるray.directionは全て(0,0,1)です。確認してみると良いでしょう。レイ上のどこを取ってもx,yは変わらず、画面上の位置は同じになります。Ray.originを使うのが簡単でしょう。これをエフェクトのTransform.positionに設定します。このあたりのコードは以下のようになります。ray.origin;遠近法を自力でやる2D(正射影)の場合遠くに行っても勝手には小さくなりません。3D世界にあるように見せかけるなら、この拡大縮小の処理を手動でやる必要があります。拡大率の計算ですが、普通の3D描画において、拡大率はカメラからの「Z方向距離」で決まります。普通の距離ではありません。問題になるのはZだけです。今問題にしている「z方向距離」は100です。座標系(=カメラのローカル座標系)に変換して、そのzを見る必要があります。だいたいこんなコードでそれができます。そのzがz方向距離となります。簡単に求めることができます。今はUI要素の位置がわかっているので、カメラとUI要素の距離は計算できます。書いた方の線の長さです。例によってコサインがわかれば出ますね。cosine;カメラからエフェクトのワールド座標に向かうベクトルcamToPosの長さ(magnitude)が、「普通の距離」です。コサインを求めて乗ずればZ距離が出ます。ここで、camToPos.magnitudeが分子と分母の両方にあるので省けますね。コードの方が慣れてる方が多そうなので、コードにしてから変形してみました。あとは、z方向距離からスケールを求めます。スケールを1にするようにしています。scale);めでたく計算が完了しました。明記されていないので割っています(下の方の例を見るに、長さ1を前提としているように見えるが...)。おわりにいかがでしょうか。日常的にこういう処理を書く気にはなれないように思います。いうことがわかりました。今回のサンプルを作るにあたって少々手こずりました。定期的にベクトルを使う仕事をして、腕が錆びつかないようにしたいものです。技術部に5日間のインターンに参加させていただいた高...2019-06-19Raycasterを自作してデバグUIをUGUI非依存にしてみたこんにちは。以前デバグUIのライブラリを作っ...2019-02-21端末性能を測るWebGLアプリ作ってみたこんにちは。この記事では、雑にベンチマーク...2019-02-15補間関数で作るアニメーションプログラムで動きを作ることは結構あります。(1)月別アーカイブ検索スターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。