【UnityではじめるVR開発!】VRに適したUI設計(3/3)

投稿者:

VRならではのUI設計・実装や、視線やコントローラを使用した操作の実装を全3回に分けて紹介していきます。プログラミング初心者の方もOKです!
第1回の記事を読んでいない方は コチラ から始めてください!
第2回の記事は コチラ です!

第3回の内容は「VRに適したUI設計〜後編〜」です。

 

フェードの実装

3-0. 事前準備

本記事は第2回「VRに適したUI設計〜前編〜」の続きの内容となっています。
第2回の記事をまだ読んでいない方は コチラ を先に読んでください。

 

3-1. はじめに

VR Samples デモScene をどれでもいいので実行してみると「アプリの起動したタイミング」と「ボタンを押す」と真っ黒にフェードアウトするのが確認出来ると思います。これはVRアプリではよく用いられているエフェクト処理になります。

実は、VR空間での画面切り替えは非常に厄介です。何もせずに突然画面が切り替わると、人は不快感を感じ、それがVR酔いに繋がります。
なので、VR上で画面が切り替わるときは映像に連続性をもたせて段階的に画面を切り替えるような処理が必要になります。

そこで今回は「画面のスタート時」と「ボタンが押されたとき」にフェードする処理を追加したいと思います。
ついでに、ボタンが押されたら新しいシーンに遷移するようにしたいと思います。

 

3-2. フェード処理を追加

では実際にフェード処理を追加してみましょう。

 

上記の画像のとおり、方法としては単純で黒い画像をカメラの前で表示し、段階的にアルファ値を下げて透過しているだけになります。
MainCamera / VRCameraUI / FadePanel があるのを確認してください。今回はこのオブジェクトを使ってフェード処理を実装していきます。

フェード処理を行うスクリプトを自作しても良いですが、既に VR Samples に高機能なものが用意されているのでそれを使いたいと思います。

Assets / VRSampleScenes / Scripts / Utils にある VRCameraFade.csMainCamera にアタッチします。
そして、Inspectorの設定項目を下記の画像のように設定してください。

 

では、 VRCameraFade.cs の中身をチェックしてみましょう。

/// <summary>
/// VRCameraFade.
/// @copyright Copyright © 2017 Unity Technologies.
/// @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
/// </summary>
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Audio;
using UnityEngine.SceneManagement;
namespace VRStandardAssets.Utils {
public class VRCameraFade : MonoBehaviour {
public event Action OnFadeComplete;
[SerializeField] private Image m_FadeImage;
[SerializeField] private AudioMixerSnapshot m_DefaultSnapshot;
[SerializeField] private AudioMixerSnapshot m_FadedSnapshot;
[SerializeField] private Color m_FadeColor = Color.black;
[SerializeField] private float m_FadeDuration = 2.0f;
[SerializeField] private bool m_FadeInOnSceneLoad = false;
[SerializeField] private bool m_FadeInOnStart = false;
private bool m_IsFading;
private float m_FadeStartTime;
private Color m_FadeOutColor;
public bool IsFading { get { return m_IsFading; } }
private void Awake() {
SceneManager.sceneLoaded += HandleSceneLoaded;
m_FadeOutColor = new Color(m_FadeColor.r, m_FadeColor.g, m_FadeColor.b, 0f);
m_FadeImage.enabled = true;
}
private void Start() {
if (m_FadeInOnStart) {
m_FadeImage.color = m_FadeColor;
FadeIn(true);
}
}
private void HandleSceneLoaded(Scene scene, LoadSceneMode loadSceneMode) {
if (m_FadeInOnSceneLoad) {
m_FadeImage.color = m_FadeColor;
FadeIn(true);
}
}
// フェード終了
public void FadeOut(bool fadeAudio) {
FadeOut(m_FadeDuration, fadeAudio);
}
public void FadeOut(float duration, bool fadeAudio) {
if (m_IsFading) {
return;
}
StartCoroutine(BeginFade(m_FadeOutColor, m_FadeColor, duration));
if(m_FadedSnapshot && fadeAudio) {
m_FadedSnapshot.TransitionTo (duration);
}
}
// フェード開始
public void FadeIn(bool fadeAudio) {
FadeIn(m_FadeDuration, fadeAudio);
}
public void FadeIn(float duration, bool fadeAudio) {
if (m_IsFading) {
return;
}
StartCoroutine(BeginFade(m_FadeColor, m_FadeOutColor, duration));
if(m_DefaultSnapshot && fadeAudio) {
m_DefaultSnapshot.TransitionTo (duration);
}
}
public IEnumerator BeginFadeOut (bool fadeAudio) {
if(m_FadedSnapshot && fadeAudio) {
m_FadedSnapshot.TransitionTo (m_FadeDuration);
}
yield return StartCoroutine(BeginFade(m_FadeOutColor, m_FadeColor, m_FadeDuration));
}
public IEnumerator BeginFadeOut(float duration, bool fadeAudio) {
if(m_FadedSnapshot && fadeAudio) {
m_FadedSnapshot.TransitionTo (duration);
}
yield return StartCoroutine(BeginFade(m_FadeOutColor, m_FadeColor, duration));
}
public IEnumerator BeginFadeIn (bool fadeAudio) {
if(m_DefaultSnapshot && fadeAudio) {
m_DefaultSnapshot.TransitionTo (m_FadeDuration);
}
yield return StartCoroutine(BeginFade(m_FadeColor, m_FadeOutColor, m_FadeDuration));
}
public IEnumerator BeginFadeIn(float duration, bool fadeAudio) {
if(m_DefaultSnapshot && fadeAudio) {
m_DefaultSnapshot.TransitionTo (duration);
}
yield return StartCoroutine(BeginFade(m_FadeColor, m_FadeOutColor, duration));
}
private IEnumerator BeginFade(Color startCol, Color endCol, float duration) {
m_IsFading = true;
float timer = 0f;
while (timer <= duration) {
// フェードを実際に行なっている部分
// 第三引数を使って第一引数と第二引数の間の色を線形補間
m_FadeImage.color = Color.Lerp(startCol, endCol, timer / duration);
timer += Time.deltaTime;
yield return null;
}
m_IsFading = false;
if (OnFadeComplete != null) {
OnFadeComplete();
}
}
// シーン終了時に呼び出し、登録したイベントを解除する
void OnDestroy() {
SceneManager.sceneLoaded -= HandleSceneLoaded;
}
}
}

 

BeginFade() は引数として、Inspectorで設定した色情報の StartCol と、その色からアルファ値をゼロにした endCol 。そして同じくInspectorで設定した duration が渡されてます。 これはフェードアウトするまでの時間になります。

Color.Learp() ですが、第三引数を使って第一引数と第二引数の間の色を線形補間して返します。
つまり、第三引数が0のときは第一引数の色に、第三引数が1のときは第二引数の色を返します。
つまり、この部分でフェードが行われています。
Unity – スクリプトリファレンス: Color.Lerp

 

3-3. シーンの遷移にフェードを利用

まず、シーンを遷移させるための 新規Scene を作成しましょう。
次に、MainCamera の直下に Assets / VRSampleScenes / Prefabs にある VRCameraUI を設置します。
さらに、MainCamera に Assets / VRSampleScenes / Scripts / Utils にある VRCameraFade.cs をアタッチし、下記の画像のように設定してください。

 

UnityRemoteを使っている方は MainCameraCameraController.cs をアタッチして isRemote にチェックを入れておきましょう。

そして忘れずに、Build Settings > Scenes in Build に新規作成したシーンを追加してください!

 

このままでは味気ないので全天球画像を貼り付けてグルグル見れるようにしたいと思います。

Flicker VR から 全天球画像(.jpg) をダウンロードしましょう。
ダウンロードした 全天球画像 を Asstes の直下にドロップアンドドラッグしましょう。
その画像の Inspector の設定項目の TextureShapeCube に変更して、下にある Apply ボタンを押下してください。

 

また、Asstes の直下に 空のMaterial(右クリックして Create > Material)を作成してください。
その Material の Inspector の設定項目の ShaderSkybox / Cubemap に変更しましょう。
するとその下の設定項目に Cubemap(HDR) が表示されるので、先ほど用意した全天球画像を選択しましょう。

 

最後に Window / Lighting / SettingsEnvironment / SkyboxMaterial を先ほど作った Material に変更しましょう。

 

では全天球画像を貼り付けた Scene を保存して、今まで作業を行なっていた Scene に戻ります。
そして UIManager.cs を下記のように更新しましょう。

using UnityEngine;
using System.Collections;
using VRStandardAssets.Utils;
// 追加 ①
using UnityEngine.SceneManagement;
public class UIManager : MonoBehaviour {
[SerializeField] private Reticle m_Reticle;
[SerializeField] private SelectionRadial m_Radial;
[SerializeField] private UIFader m_HowToUseFader;
[SerializeField] private SelectionSlider m_HowToUseSlider;
// 追加 ②
[SerializeField] private string sceneName;
private IEnumerator Start () {
m_Reticle.Show ();
m_Radial.Hide ();
yield return StartCoroutine (m_HowToUseFader.InteruptAndFadeIn ());
yield return StartCoroutine (m_HowToUseSlider.WaitForBarToFill ());
yield return StartCoroutine (m_HowToUseFader.InteruptAndFadeOut ());
// 追加 ③
if (sceneName != null) {
SceneManager.LoadScene (sceneName, LoadSceneMode.Single);
}
}
}
view raw UIManager.cs hosted with ❤ by GitHub

 

UIManager.cs の変更を保存した後、Inspectorで先程作成したシーン名を sceneName に入力します。

 

ここまで出来たら実行してみましょう。下記のように遷移したら完成です!

 

 

まとめ

お疲れ様でした!

実際にVR開発を行なってみて、どのように感じましたか?
意外と簡単だと思いませんか?

確かに1つ1つの機能を深く理解するのは大変かもしれませんが、
VR Samples の中から必要なパーツを取り出して組み合わせることでVRコンテンツを作ることができます!

来月は Google VR SDK の使い方をまとめる予定です。
興味がある方はぜひそちらも御覧ください!
ではまた〜!

 

ps. 皆様の声
Q. 後編の内容、前編に比べて少なくない?
A. 仕様です!(第1回:2682文字、第2回:7447文字、第3回:2750文字)

 

 

【10/7(土) スタート!】VRプロフェッショナルアカデミー第2期募集中!

VRアカデミーに入学して本格的にVR開発を学んでみませんか?

 

☆オススメ☆ VRエンジニアコース【エキスパート】

高度なプログラミング技術を持つエンジニアが、
VRコンテンツをつくるための知識と技術を習得するためのコースです。
VRを構成する基本要素のコントロールシステムやUIをはじめ、SpatialSoundやShaderなど、コンテンツを開発する上でニーズの高い技術もまとめて学習することで、様々なVRコンテンツ開発に対応出来るエンジニアを目指します。
更にUnityでのAR開発についても代表的なツールを用いた開発方法を学ぶことが出来ます。

VR Professional Academy | 日本初のVR/AR専門の学校 – VRエンジニアコース【エキスパート】

 

詳しく説明を聞きたい方は…

無料個別説明会 in 中目黒VRプライベートサロン ☆参加者限定の割引特典あり☆ 申し込みはコチラ