新しい VR 作品を作り始めました。この間まで春だったので、桜をモチーフに何か現実とは違う雰囲気を感じて楽しめるものを目指して開発を始めます。
今週は下記の進捗がありました。
- 桜の舞うシーンの作成
- 特定の花弁をキャッチするインタラクションの作成
- 上記を気持ちよくできるサウンドの実装(Android の音声再生レイテンシを低減)
- 上記を気持ちよくできるアニメーションの実装
- Ambience を感じるための BGM の選定
Windows(Oculus Rift) 版のダウンロードはこちらから
詳細は以下
桜の舞うシーンの作成
ふわりふわりと桜が落ちてくるシーンを作成します。この花弁、1枚1枚丸みがあります。ペラくないです。しかもちょっと向こうが透けていてたまに美しい。
どこからともなく出てきて、落ちていくと闇へ消えていきます。簡単ですが、なかなか美しい。
特定の花弁をキャッチするインタラクションの作成
自分の周りをぐるぐると回り続ける花弁を作成します。視界の中央に円を表示し、この中に入った花弁を円の中にキャッチできるようにします。
5枚キャッチすると…?
上記を気持ちよくできるサウンドの実装(Android の音声再生レイテンシを低減)
キャッチした瞬間に音を鳴らすようにします。サウンドは Asset Store でよさげなのを探します。
Operation Sounds Free – Unity Asset Store
こいつを普通に Audio Source で鳴らそうとしたのですが、耳で聞いてわかるほどの遅延があり、正直しっくり来ません。これだから Android はクs…と文句を垂れても仕方がないので解決策を探します。幸いにも、おなじみテラシュールブログさんが公開されている低遅延音声再生プラグイン(GitHub)が見事に機能してくれたため、レイテンシ問題は無事解決しました。
上記を気持ちよくできるアニメーションの実装
ただ円の中に花弁が入っていって音がなるだけでも気持ちが良いものですが、せっかくなのでクオリティを上げるべく、キャッチ時にビジュアルエフェクトがかかるようにします。
パーティクルシステムで単発動作のアニメーションを作り、桜のキャッチと同時に動くようにします。
実際被ってみてみるとチープ感が否めないので、どうすれば良い感じになるかはこれから研究。
Ambience を感じるための BGM の選定
空間を感じるのには音が非常に重要です。私が「ゼルダの伝説 時のオカリナ」を愛してやまないのも、アレはマップごとに適切なリバーブ効果のかかった適切な質感の音楽でその世界に入り込んでいるんだという感覚を強く想起させる、要するに没入感が桁違いに素晴らしいためです。
あのような芸術のレベルには届きませんが、下記の条件で BGM を探しました。
- 空間の広がりが感じられる講堂系のリバーブ
- 曲調は無彩色的な…キャラクターの強くないマイナースケールのもの
- ベースはストリングスか環境音のようなものでゆったりとしたもの
- ピアノの高音を低い頻度で響かせ、リバーブの効果を上げている
そんなにぴったり来るものはないだろうな…後で作ろうかな…と思っていたものの、なんと無料で割と近い物があったので仮採用しました。
Fantasy Ambient Music Pack – Unity Asset Store
でもあんまりピッタシじゃないので、結局後で1曲書くことになりそう。来週もぼちぼち進めよう。
んで、折角なので現在の進捗をプレイできる形で公開します。
Windows(Oculus Rift) 版のダウンロードはこちらから
Oculus Software 1.3 で動作確認しています。Rift も Software も無くてもただの平面ゲームとして動作はします。でも操作はできません。
蛇足:書いたコード
FPS を計測するやつを除けば、2つの C# コードを書きました。クソコードも置いておけば見返した時に「アッ…」ってなるノスタルジーが待ってますからね!
花弁を回すやつ
1 2 3 4 5 6 7 8 9 10 11 |
using UnityEngine; using System.Collections; public class PetalController : MonoBehaviour { public Quaternion rot; void Update () { transform.rotation = transform.rotation * rot; } } |
花弁キャッチ周り
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
using UnityEngine; using System.Collections; public class PetalCatcher : MonoBehaviour { Ray ray; RaycastHit hit; GameObject Ring; GameObject RotationRoot; GameObject petal; int petalNumber = -1; ParticleSystem particle; Color RingColor; Vector3 PrevFrame; float diff; public Vector3[] PetalPosition; int soundID1; void Start () { //Ring色処理の初期化 Ring = GameObject.Find ("Ring"); PrevFrame = Ring.transform.position; //Ringの表示を遅らせる RingColor = Ring.GetComponent<SpriteRenderer> ().color; RingColor.a = 0; Ring.GetComponent<SpriteRenderer> ().color = RingColor; StartCoroutine (RingRepresenter ()); //回転のベースとなる GameObject を取得 RotationRoot = GameObject.Find ("RotationRoot"); //サウンドの初期化 soundID1 = AudioCenter.loadSound ("Pause_003-edit"); //パーティクルの取得 particle = GameObject.Find ("Particle System Right").GetComponent<ParticleSystem> (); } IEnumerator RingRepresenter () { yield return new WaitForSeconds (7); while (RingColor.a < 1) { RingColor.a += 0.01f; Ring.GetComponent<SpriteRenderer> ().color = RingColor; yield return null; } StartCoroutine (UpdateCoroutine ()); yield break; } IEnumerator UpdateCoroutine () { while (true) { //PetalCatcher ray = new Ray (transform.position, transform.forward); if (Physics.Raycast (ray, out hit)) { if (hit.collider.tag == "Petal") { petal = GameObject.Find (hit.collider.name); if (petal.transform.parent != RotationRoot.transform && petalNumber < 4) { ++petalNumber; petal.transform.parent = RotationRoot.transform; //RotationRoot の子オブジェクトに petal.transform.localEulerAngles += new Vector3 (0, 72 * petalNumber, 0); petal.transform.localPosition = PetalPosition [petalNumber]; petal.transform.localScale = new Vector3 (4, 4, 4); AudioCenter.playSound (soundID1); particle.Play (); if (petalNumber == 4) { StartCoroutine (NextScene ()); } } } } //ここからRingの色処理 diff = Vector3.Distance (PrevFrame, Ring.transform.position); if (RingColor.a > 0) { RingColor.a -= diff * 0.75f; } if (RingColor.a < 1) { RingColor.a += 0.01f; } Ring.GetComponent<SpriteRenderer> ().color = RingColor; PrevFrame = Ring.transform.position; yield return null; } } IEnumerator NextScene () { OVRScreenFade sf = GetComponent<OVRScreenFade> (); StartCoroutine (sf.FadeOut ()); yield return new WaitForSeconds (sf.fadeOutTime); UnityEngine.SceneManagement.SceneManager.LoadScene ("CherryBlossom"); } void Update () { Debug.Log (petalNumber); } } |