tanaka's Programming Memo

プログラミングについてのメモ。

Definitive Stylized Waterで海る

f:id:am1tanaka:20190731111605p:plain
Definitive Stylized Waterで海る

夏といえば海!ということで、少し古いモバイル端末でも動作する水面を描いてくれるアセットDefinitive Stylized Waterの記事です。

Definitive Stylized Water - Asset Store

この記事は、Unity アセット真夏のアドベントカレンダー 2019 Summer!の4日目の記事です。

目次

アセットの概要

Definitive Stylized Waterはトゥーン調の水面を描画することができるアセットです。VoxelorerBirdのマップ画面の水面を描画したくて購入しました。水深に応じて色を変化させたり、水面の波紋を描いたりしてくれます。

f:id:am1tanaka:20190731111513g:plain
水面が動きます

UnityAssetStoreJapanさんの紹介ツイート。

この手のものだとStylized Water Shaderの方が実績があるような印象がありましたが、OpenGL ES2に対応しているのでこちらの方を選びました。うちにあったかなり古いAndroid4.2.2のSH-08Eでも動きましたし、速度も綺麗さもいい感じでお気に入りのアセットです^^

設定の仕方や問題の解決策、設定の概要などをご紹介します。

動作条件

  • AssetStoreの記載ではUnity5.4.5以降対応。Unity 2017 & 2018も動作確認済みとなっています
    • Unity2019.1.12でも動作しました
  • モバイル版は、OpenGL ES2以降に対応です
  • カメラのProjection設定がOrthographicだと動作しません

標準版とモバイル版

フル機能の標準版に加えて、機能制限をして機能が弱いモバイル端末でも動作するモバイル版が同梱されています。設定項目の違いは以下のような感じです。

f:id:am1tanaka:20190731121424p:plain
モバイル版と標準版の設定項目

御覧の通り、右の標準版の方が設定は豊富です。主な違いは以下の通りです。

  • 水面下のオブジェクトが揺らいで見えるかの有無(Opacity)
  • 水面の波による変形の有無(WAVES)
  • 鏡面反射(REFLECTIONS)の有無

標準版がDeferredレンダリングで、モバイル版がForwardレンダリングです。Deferredレンダリングに対応しているモバイル端末であれば標準版も動作します。両方組み込んでおいて、実行時に機能を確認してどちらを使うかを選択するのもありかなと考えています。

iPhoneSEでもアセット付属の標準版サンプルが60fpsで動きましたので、iOS向けなら標準版で問題なさそうです。モバイル版はちょっと弱いAndroid用という感じです。

水面を作る

自分のプロジェクトにDefinitive Stylized Waterをインポートして、水面を表示します。このブログの操作はUnity2019.1.12で行っています。別のバージョンの場合は操作位置が異なる場合があります。

  • 対応するバージョンのUnityで、水面を表示したいプロジェクトを新規に作成するか読み込みます
  • Asset StoreでDefinitive Stylized Waterを購入、ダウンロードしてインポートします

Definitive Stylized Water - Asset Store

  • カメラのProjection設定はPerspectiveにしてください。Orthographic(平行投影モード。遠近感がなくなる主に2D用の設定)には非対応です
    • Orthographicを利用していた場合は、Perspectiveに変更して視野角を小さくして対応するとよいでしょう

f:id:am1tanaka:20190731230549p:plain
ProjectionはPerspectiveに

  • 必要に応じて地形を配置します。以下の例では、Definitive Stylized Waterに付属の灯台のモデル(Lighthouseプレハブ)を座標0, 0, 0の位置に配置しました

f:id:am1tanaka:20190719153423p:plain
海底を配置

  • 水面プレハブをシーンに配置します。標準版はStylizedWaterShaderフォルダーの直下にあるWaterPlaneです。モバイル版はStylizedWaterShader > MobileVersionフォルダー内のMobileWaterPlaneです。どちらか利用したい方をHierarchyウィンドウかSceneウィンドウにドラッグ&ドロップします

f:id:am1tanaka:20190731230912p:plain
水面プレハブの場所

f:id:am1tanaka:20190719153924p:plain
Mobile版の水面を設定

水面が真っ白になった時の対処

この段階で、水面が真っ白になる可能性があります。

f:id:am1tanaka:20190801191259p:plain
水面が真っ白

考えられる原因はすでに述べていますが、以下の二つです。

  • CameraRendering Pathが、水面シェーダーと合っていない
  • CameraProjectionが、Orthographicになっている

Sceneウィンドウでは、Isoモードでも白くなります。

標準版での水面が真っ白になった時の対処

標準版は、Rendering PathDeferredである必要があります。手っ取り早いのはカメラで設定することです。

  • Main Cameraを選択して、Inspectorウィンドウで以下を確認して、必要なら設定します
    • ProjectionPerspective
    • Rendering PathDeferred

f:id:am1tanaka:20190801194800p:plain
標準版のCameraの設定

まだ白いかも知れませんが、構わずPlayしてみてください。

f:id:am1tanaka:20190801194922p:plain
標準版の水面

以上で設定完了です。

モバイル版での水面が真っ白になった時の対処

モバイル版は、Rendering PathForwardである必要があります。カメラにアタッチするためのスクリプトがあるのでひと手間増えます。

  • Main Cameraを選択して、Inspectorウィンドウで以下を確認して、必要なら設定します
    • ProjectionPerspective
    • Rendering PathForward

f:id:am1tanaka:20190801195123p:plain
モバイル版のカメラの設定

  • StylizedWaterShaderフォルダーの中のCodeフォルダーに入っているEnableCameraDepthInForwardスクリプトを、カメラにドラッグ&ドロップしてアタッチします

f:id:am1tanaka:20190801195406p:plain
カメラにスクリプトをアタッチ

まだ白いかも知れませんが、構わずPlayしてみてください。

f:id:am1tanaka:20190801195450p:plain
モバイル版の水面

以上で設定完了です。

Sceneウィンドウの水面が白い

Sceneウィンドウの水面が白いままだった場合、Isoモードになっているものと思われます。右上のGizmoIsoになっていると思いますので、クリックしてPerspに変更してください。

f:id:am1tanaka:20190801194420p:plain
SceneウィンドウのカメラがIsoモードになっていても白くなる

以上で設定完了ですが、水面は白いままの場合があります。設定したらとりあえずPlayしてみてください。設定ができていればPlayすれば正しく描画されます。

水面の位置はPosition、水面の大きさはScaleで調整すればOKです。

モバイル版の設定

モバイル版の設定概要です。標準版の設定概要はこちら

水面の設定はマテリアルで調整できます。MobileWaterPlaneオブジェクトのMesh Rendererに設定されているマテリアルがそうです。

f:id:am1tanaka:20190801200442p:plain
水面用マテリアル

モバイル版と標準版の水面シェーダーが設定されているマテリアルは以下の位置にあります。必要に応じて複製して、水面にアタッチして利用するとよいでしょう。

f:id:am1tanaka:20190801200343p:plain
水面の設定をするためのマテリアル

Color0, 1, 2

Color0, 1, 2は、水深ごとの色味です。以下はColor0の変更例で、水深0の部分の色です。

f:id:am1tanaka:20190801201751g:plain
Color0, 1, 2

Color1Gradient1の水深の色、Color2Gradient2より深いところの色です。

Gradient1, 2

Color0, 1, 2と組み合わせて使う設定です。変更に応じて色が適用される深さが変わっています。マイナスになると無効になります。

f:id:am1tanaka:20190801202446g:plain
Gradient

FresnelColor

Fresnelと書いてフレネルです。理科で習ったのではないかと思いますが(カリキュラム的に習わないコースもあるかもですが)、定義はwikiより以下の通りです。

屈折率が異なる物質間の界面に入射すると、一部は反射し、一部は透過(屈折)する。このふるまいを記述するのがフレネルの式である。 フレネルの式 - Wikipedia

といいつつ、このアセットではカメラから遠い水面の色を変化させる設定になっています。フォグの水面版といった感じです。

f:id:am1tanaka:20190801203800g:plain
Fresnel

Color2だと深い部分全体の色が設定されますが、Fresnelの方は遠方の水面の色が変わっているのが観察できると思います。

FresnelExp

フレネルの距離です。値を小さくすればより近くからフレネルの色が適用され始めます。負の値が設定できますが、色味が明らかにおかしくなります^^;

f:id:am1tanaka:20190801204211g:plain
FresnelExp

Foam

フォーム=泡の設定です。水面や浅いところの白い筋の模様の設定です。MainとSecondaryの2種類を設定することで、立体感のある揺らぎが演出されます。以下、MainFoamの設定です。

f:id:am1tanaka:20190801212349g:plain
MainFoam

SecondaryFoamも基本的に同様です。

Textureは水面を表現するための画像データと思われます。ここをいじる必要はありません。

モバイル版の設定は以上です。

標準版の設定

標準版についてはオフィシャルの動画が分かりやすいです。

youtu.be

設定の概要は以下の通りです。

COLOR GRADIENT

水深に応じた色や、水面の設定を行う項目です。後ろに(*)が書いてある項目は、モバイル版にはない機能です。

  • COLOR GRADIENT 色の勾配
    • 左の3つの色がモバイル版のColor0, 1, 2、右の2つのスライドバーがGradientに該当します
  • Fresnel フレネル
    • 左の色がFresnelColor、右のスライドバーがFresnelExpに該当します
  • Light Color Intensity(*) ライトの色の強度
    • 水の色が、シーンのライトの色の影響を受ける度合いです。0にするとDirectional Lightの色や角度による光の具合の影響を受けなくなります
  • Specular Intensity(*) 反射光の強度
    • 太陽などの光の反射の強さです。0にすると光の反射がなくなり、1にすると強く反射します(Directional Lightが水面に反射して、カメラにうつる位置にないと影響が分からない可能性があります)
  • Roughness(*) 粗さ
    • 光の反射の拡散度合いです。Roughness0だとDirectional Lightの位置による太陽がはっきりと描画されます。1にするとぼんやりと拡散します
  • Water Opacity(*) 水の不透明度
    • この下のOpacity Depthとセットで利用して、水中のものが屈折して見える度合いと深さを設定します
    • 1にすると、Opacity Depthの値に関わらず、水中の屈折が見えなくなります
    • 0にすると、Opacity Depthで設定されている深さまで、水中の屈折が見えるようになります
  • Opacity Depth(*) 透明度の深さ
    • この値の深さまで、水中のものが歪んで描画されます

FOAM 泡

モバイルの設定とほぼ同じです。SpeedIntensityなど、微調整用のパラメーターが増えています。

Always Visibleにチェックを入れると、Intensityの設定に関わらず、水面に縞模様を描画します。トゥーン感を出す場合に有効そうです。

f:id:am1tanaka:20190531201520p:plain
Secondary FoamのAlways Visible

WAVES(*) 波

モバイル版にない設定です。水面に波を発生させます。

  • Amplitude 振幅
    • 波の幅です
  • Speed
    • 波が進む速度です
  • Intensity
    • 波の高さです
  • Direction
    • 波の方向です
  • Vertex Offset
    • チェックを入れると、実際に水面の高さが変化して波打つようになります
    • チェックを外すと、水面の高さは変わらず、光や屈折の描画の変化のみで波を表します

REFLECTIONS(*) 反射

モバイル版にはない設定です。空や水上のオブジェクトを水面に映すかを設定できます。

  • Real time reflections
    • チェックを外すと、水面に水上のオブジェクトが映らなくなります
  • Intensity
    • 映り込みの度合いです
    • 0だと無効になります
  • Wave Distortion 波によるねじれ
    • Waveの動きでどれぐらい映り込みの像をゆがめるかの設定
    • WaveIntensityと関連するようです。1にすると自然に動く感じがします
  • Turbulence Distortion 乱流によるねじれ
    • 波より細かい水面の凹凸を描画する強さを設定します。大きい値にすると映り込みが強くなります
  • Turbulence Scale 乱流のスケール
    • 水面の凹凸のスケールです。値を大きくすると、凹凸が細かくなります

TEXTURES

水面の動きや泡のテクスチャーです。泡のテクスチャーを自前で用意して差し替えると、水面の模様が変えられます。

水面のエフェクト

VFXフォルダーにいくつかエフェクトが入っています。これも何気に便利です。

FoamParticles

泡立っている感じのエフェクトです。

f:id:am1tanaka:20190801224546g:plain
FoamParticles

FoamUp

水しぶきのエフェクトです。

f:id:am1tanaka:20190801224821g:plain
FoamUp

RadialSplash

放射状に広がる波紋です。

f:id:am1tanaka:20190801225130g:plain
RadialSplash

SmallSplash

小さいものが落ちた時のようなしぶきと波紋です。

f:id:am1tanaka:20190801225340g:plain
SmallSplash

WaterSplash

しぶきと波紋が一緒に出現するエフェクトです。

f:id:am1tanaka:20190801225605g:plain
WaterSplash

川などを作る

付属の水面プレハブは平面ですが、ProBuilderなどで川となるメッシュを作成して水面マテリアルなどをアタッチすれば、傾斜のある水面を作ることができます。

f:id:am1tanaka:20190801230534p:plain
川用のメッシュ

背景に乗せるとこんな感じになります。

f:id:am1tanaka:20190801230755p:plain

モバイル版の場合はStylizedWaterMobileマテリアルを設定するだけでOKです。標準版を使う場合は、StylizedWaterマテリアルをアタッチした上で、Codeフォルダー内のWaterReflectionスクリプトをアタッチして鏡面反射に対応させます。

f:id:am1tanaka:20190801230923p:plain
WaterReflection

※水が流れ落ちているエフェクトは、Unity公式のUnity Particle Pack - Asset Storeのものを利用しています。

まとめ

Definitive Stylized Waterの設置方法や設定、付属のエフェクト、平面以外のメッシュを水面にする方法をご紹介しました。トゥーン系のポップな水面を作るのに適していて、少々古いAndroid端末でも動作するのが嬉しいです。標準版であれば、リアルな水面を作ることも可能です。波も生成できるので楽しいです。

暑い夏に、ナイスな海を作ってみてはいかがでしょうか。

関連リンク

Definitive Stylized Waterのアセットストア

Definitive Stylized Water - Asset Store

Definitive Stylized Waterの紹介動画

youtu.be

Definitive Stylized Waterの紹介ツイート

滝の部分に使ったパーティクルアセット

assetstore.unity.com

Photon BoltでPhysicsを利用する(ver1.2.9)

f:id:am1tanaka:20181129195310g:plain
作例

こちら( Photon BoltでPhysicsのボールは動くのか - tanaka's Programming Memo )の記事の内容が古くなっていたので、2019/7/28現在の最新場である1.2.9に対応させて書き直しました。

目次

Photon Boltとは

マルチプレイヤーゲームのネットワーク部分を担当してくれるUnity用のネットワークエンジンです(ビジュアルスクリプティングのBoltの話ではございません!!Exit Gamesが提供しているネットワークエンジンの一つで、最近流行っている人が集まって対戦するようなリアルタイムネットワークゲームを開発するのに向いています。

詳しくは以下の公式サイトや、アセットストアをどうぞ。

www.photonengine.com

注意点!

Photon Boltの最大の注意点の一つは、現時点でWebGLに対応していない!!ということだと思います。1週間ゲームジャムとかでは使えませんのでご注意を!PUNの方は使えます。

目的

学園祭のような人が集まる場所で、同じ教室内のLAN環境で、エアホッケーのネットワーク対戦ゲームを動かしたいというのが今回の出発点です。ビルドターゲットはWindowsmacなどのPCです。最初のマッチング時にPhotonクラウドを通す必要があるのでインターネット接続は必要です。

エアホッケーならRigidbodyvelocityを同期できれば簡単に作れそうです。しかし、Photon Boltにはvelocityを同期するためのState設定がありません。そこで、Commandを使うことで対応させました。その構築手順です。

前提

はじめようを読んで、Photon Bolt Freeのインストールと概要を確認しておくのが望ましいです。

ブログではUnity2018.4.4f12019.1.5で動作確認しました。ブログ執筆時のPhoton BoltのUnityのサポートバージョンは2017.4.26以降です。

考え方

Photon BoltでPC間で同期したいデータは、Stateを作って、それにデータを割り当てます。そこにRigidbodyvelocityとかを同期する設定があればいいのですがありません。UNetにはあったので油断していました。

Photon Boltでは、Commandsを使って、クライアントからホストに操作情報を送らせて、サーバーでまとめて制御した結果をクライアントに戻して反映させる、という仕組みがあります。ざっくりこんな感じ。

f:id:am1tanaka:20181129142906p:plain
(public) RollerBallBoltChart | Sketchboard

1カ所でまとめて物理シミュレーションを行うので操作に対する反応は少し遅れますが、全てのプレイヤーの動きを一致させることができます。

今回は、CommandsでマウスのX-Yの移動量を送信して、プレイヤーの座標を結果(Result)として返すようにしました。

プロジェクトを作成

まずはProjectを作成します。作業中、指示通りにやっているのに動かない場合は、Unityを閉じてプロジェクトを開き直すとか、Unityが2017.4以降かを確認してください。

アカウントの入手とApp IDの取得

Sign In | Photon Engineを開いて、Photonアカウントの作成と、Bolt用のアプリを作成して、App IDを生成してください。

フィールドを読み込む

作例用のプレイヤーやフィールドのオブジェクトを用意しました。まずはそれらを組み込みます。

  • 新規にPhotonBoltPhysicsなどの名前でUnityのプロジェクトを作成してください。Unityのバージョンは2017.4以降の安定版がよいでしょう。このブログではUnity2018.4.4を使っています
  • こちらを開いてください
  • 最新版のPhotonBoltPhysics@v?.?.?.unitypackageをクリックしてダウンロードします
  • ダウンロードが完了したら、UnityのProjectウィンドウにドラッグ&ドロップして、インポートします

以上で、ゲーム用のオブジェクトが読み込まれます。Scenesフォルダー内のGameシーンをダブルクリックすると、フィールドとプレイヤー2つ、ボールが確認できます。

f:id:am1tanaka:20190728141043p:plain
Gameシーン

Photon Boltの初期設定

  • Asset StoreからPhoton Bolt Freeをダウンロードしてインポートします

Photon Boltは、同時接続20接続(マッチング後にサーバーを利用しなくなったアカウントも含みます)などの制約以内であれば、無料で利用できます。開発中や、今回のような試用の場合はFree版でいけます。

エラーが出るので、直すために設定をします。

  • Boltメニューがあるか確認します。見当たらなかったら、Unityを閉じてプロジェクトを開きなおします
  • BoltメニューからWizardを選んで、ウィンドウが開いたらNextをクリックします
  • PhotonのWebページで作成したApp IDをコピーして、Photon Bolt App ID or Email欄に入力します
  • 変なリージョンに行かれると接続ができなかったりしたので、テスト時はRegion[jp] Japan :: Tokyoにしておきます
  • NAT Punchthrough Enabledはチェックを入れたままにしておきます
  • 以上選択したらNext

f:id:am1tanaka:20181129125930p:plain

  • Sampleを有効にすると、公式のサンプルを見ることができます。今回は不要なので、そのままNextをクリックします
  • Doneをクリックします
  • Bolt Setup Completeダイアログが表示されます。コンパイルが必要なので、Yesをクリックします

以上でエラーが解消されます。エラーがまだ表示されていたら、ConsoleウィンドウのClearボタンをクリックすれば消えます。

Menuシーンの作成

サーバーとクライアントのどちらでログインするかを決めるためのボタンとサーバー接続のコードを実装したMenuシーンを作成します。

  • デフォルトで作成されるSampleSceneがあったら、名前をMenuに変更します
    • Unity2017だとシーンはないので、New Sceneで新しいシーンを作成して、名前をMenuにします
  • 作成したMenuシーンをダブルクリックして切り替えます
  • HierarchyウィンドウのMain Cameraをクリックして選択します
  • InspectorウィンドウのAdd Componentをクリックして、New scriptで新しいスクリプトを作成して、名前をMenuにします
  • 作成したMenuスクリプトをダブルクリックして開きます
  • 以下のスクリプトに置き換えます。Photon BoltアセットのGettingStartedサンプルに含まれているMenu.csを元にして、シーン名をGameに変更したものです
using System;
using System.Collections;
using System.Collections.Generic;
using Bolt.Matchmaking;
using UdpKit;
using UnityEngine;

namespace Bolt.Samples.GettingStarted
{
    public class Menu : Bolt.GlobalEventListener
    {
        bool ShowGui = true;

        private void Awake()
        {
            Application.targetFrameRate = 60;
            BoltLauncher.SetUdpPlatform(new PhotonPlatform());
        }

        void OnGUI()
        {
            if (!ShowGui) { return; }

            GUILayout.BeginArea(new Rect(10, 10, Screen.width - 20, Screen.height - 20));

            if (GUILayout.Button("Start Server", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)))
            {
                // START SERVER
                BoltLauncher.StartServer();
            }

            if (GUILayout.Button("Start Client", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)))
            {
                // START CLIENT
                BoltLauncher.StartClient();
            }

            GUILayout.EndArea();
        }

        public override void BoltStartBegin()
        {
            ShowGui = false;
        }

        public override void BoltStartDone()
        {
            if (BoltNetwork.IsServer)
            {
                string matchName = Guid.NewGuid().ToString();

                BoltMatchmaking.CreateSession(
                 sessionID: matchName,
                 sceneToLoad: "Game"  // 元のファイルからここを変更
                );
            }
        }

        public override void SessionListUpdated(Map<Guid, UdpSession> sessionList)
        {
            Debug.LogFormat("Session list updated: {0} total sessions", sessionList.Count);

            foreach (var session in sessionList)
            {
                UdpSession photonSession = session.Value as UdpSession;

                if (photonSession.Source == UdpSessionSource.Photon)
                {
                    BoltNetwork.Connect(photonSession);
                }
            }
        }
    }
}

Playすると、以下のようにメニューが表示されます。

f:id:am1tanaka:20190728162702p:plain
Menu画面

まだ設定などが足りないので、ボタンを押してもエラーになります。Playを停止して設定を続けます。

ビルド設定

チャプター1 | Photon Engineを参考にビルド設定をします。

  • FileメニューからBuild Settingsを選択します
  • ProjectメニューからMenuシーンをドラッグして、Scenes In Build欄にドロップします
  • 同様に、Gameシーンもドラッグ&ドラッグでScenes In Build欄に追加します

f:id:am1tanaka:20190728161910p:plain
ビルドシーン

  • Player Settings...ボタンをクリックします
  • Resolution and Presentation欄をクリックして開いて、以下を設定します
    • Fullscreen ModeWindowsに設定
    • Default Screen Width640に設定
    • Default Screen Height360に設定
    • Run In Backgroundにチェックを入れます
    • Display Resolution DialogDISABLEDにします

以上は開発用の設定です。開発が完了したら適宜変更してください。

  • Build Settingsウィンドウを閉じます
  • Boltメニューから、Compile Assemblyを選択します。これをやらないと、シーンがBoltに認識されないので動きません

以上設定したらPlayして、Start Serverボタンをクリックしてください。Gameシーンに切り替わります。

f:id:am1tanaka:20190728164339p:plain
Gameシーン

Gameシーンの設定を全くしていないので操作はできません。停止して、Gameシーンの実装をします。

Gameシーンを作る

ネットワークに接続して、Gameシーンに切り替えることができたので、Gameシーン本体の実装を始めます。

Stateの作成

Boltでネットワークに登場させるゲームオブジェクトは、同期するデータを定義したStateを持ちます。今回はTransformだけ同期します。そのためのTransformStateを作成します。

  • BoltメニューからAssetsを選択します
  • Bolt Assetsウィンドウの何もない部分を右クリックして、New Stateを選択します
  • 以下の通り設定します

f:id:am1tanaka:20190728165116p:plain
TransformStateの作成

  • 設定したら、Bolt Editorウィンドウは閉じます

利用するプロパティはTransform型のTransformだけです。

SpaceはデフォルトがLocalだったのでそのままにしました。今回は使っていませんが、StateCommandなどの設定にCompression(圧縮)の項目があって、そこで値の範囲を制限して送信するデータ量を減らすことができます。SpaceLocalにしておくことで、設定した範囲を越える移動は親のオブジェクト側で行うような工夫ができそうです。

捕捉: クライアントの動きを滑らかにする設定(補間とNetwork Rate)

Smoothing AlgorithmNoneのままにしました。Interpolation(内挿)とExterpolation(外挿)の違いは以下にあります。

Interpolation vs. Extrapolation | Photon Engine

ざっくりと、Interpolationは過去のデータを利用して動きを滑らかに見せます。過去の場所の間に補間するので、突飛な場所に移動することはありません。ただし、少し古い座標に表示されることになります。

Exterpolationは過去の動きの傾向から、次の座標を予想して動かします。レスポンスは速いですが、予想が外れた場合はいたことがない座標に移動してしまいます。

今回のようにマウス操作&Physicsベースだと、動きの予想がしずらいのでExterpolationでの予測精度があまりよくありませんでした。また、Interpolationは遅延が気になります。

ということで、Noneのままにして補間はしないことにしました。そのままではガタついて見えてしまいますが、これはStateの送受信の頻度が原因です。最後の方の設定の調整のところで調整します。

Commandの作成

今回の肝であるクライアントからマウスの移動量を送り、座標を返してもらうためのCommandを作成します。

  • Bolt Assetsウィンドウの余白を右クリックして、New Commandを選択します
  • 以下のように設定します

f:id:am1tanaka:20190728165856p:plain
RollerBallBoltCommandの作成

  • 以上設定したら、Bolt Editorウィンドウと、Bolt Assetsウィンドウを閉じます

入力のMouseVector型で、動かす時に楽をするためにXZで送ることにしました。

結果はXYZ全て利用するVector型で、そのままローカル座標に放り込みます。効果がよくわかりませんでしたが、なんとなく奇麗に動いている気がするのでSmooth Corrections(スムーズに補正する)にチェックを入れました。あとは以前の設定にもあったのでTeleport Threshold10を設定しました。

ボールプレハブの作成

ゲームオブジェクトをネット対応させます。ネットワーク上で共有するオブジェクトにはBoltEntityスクリプトをアタッチして、ネットワークに接続した時にBoltNetwork.Instantiate()で生成します。そして、Stateのデータをオブジェクトのプロパティーと関連付けます。

  • Projectウィンドウから、Gameシーンをダブルクリックしてシーンを切り替えます
  • ProjectウィンドウのRollerBallBolt > Prefabsフォルダーを開いて、Ballプレハブをクリックして選択します
  • InspectorウィンドウでOpen Prefabボタンをクリックします(Open Prefabボタンがない場合はそのまま編集します)
  • InspectorウィンドウのAdd Componentボタンをクリックして、Bolt Entinyを検索などして見つけてアタッチします

アタッチした直後はエラーになります。以下を設定します。

  • Ballプレハブを再び開いて、State欄をITransformStateにします

f:id:am1tanaka:20190728170730p:plain
Stateを設定

  • BoltメニューからCompile Assemblyを選択してコンパイルします

以上でエラーがなくなります。

  • その他の設定も確認して、以下のようにします。TagBallを予め割り当てています

f:id:am1tanaka:20181129195533p:plain
Ball Inspector

  • InspectorウィンドウからAdd Component > New scriptを選択して、Ballという名前のスクリプトを作成します
  • Ball.csは以下の通りです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace PhotonBoltPhysics
{
    public class Ball : Bolt.EntityBehaviour<ITransformState>
    {
        /// <summary>
        /// transformをIPlayerStateのTranformに割り当てます。
        /// </summary>
        public override void Attached()
        {
            state.SetTransforms(state.Transform, transform);
        }
    }
}

アタッチされた時にtransformStateTransformに割り当てているだけです。

プレイヤーの作成

プレイヤーも同様にネットワーク対応させていきます。

以下は設定済みです。

  • Player1Player2とも、TagPlayer
  • Sphere Colliderをアタッチして、良い場所と大きさに調整
  • Rigidbodyをアタッチして、Use Gravityを外し、Collision DetectionContinuous DynamicFreeze PositionYにチェック、Freeze Rotationを全てチェック

ネットワーク対応させるために、以下を設定します。

  • ProjectウィンドウからPlayer1プレハブをクリックして選択します
  • InspectorウィンドウにOpen Prefabボタンがあったらクリックします
  • InspectorウィンドウのAdd Componentボタンをクリックして、Bolt Entityを選択してアタッチします
  • InspectorウィンドウのStateITransformStateに設定します
  • BoltメニューからCompile Assemblyを選択して実行したら、Player1プレハブを開き直します
  • 新規スクリプトを作成して、Playerの名前にします
  • Player.csスクリプトの中身は以下の通りです。
using System.Collections;
using System.Collections.Generic;
using Bolt;
using UnityEngine;

namespace PhotonBoltPhysics
{

    public class Player : Bolt.EntityBehaviour<ITransformState>
    {

        [Tooltip("送られてきた操作を速度に変換するレート。Mouse XとMouse Yは-1~1に正規化されているので、動作環境の解像度は考えなくて構いません。"), SerializeField]
        float input2Speed = 20f;

        Rigidbody rb;

        float _x;
        float _y;

        #region System Loop

        private void Awake()
        {
            rb = GetComponent<Rigidbody>();
        }

        private void Update()
        {
            PollMouse();
        }

        #endregion System Loop

        #region Bolt Events

        /// <summary>
        /// transformをIPlayerStateのTranformに割り当てます。
        /// </summary>
        public override void Attached()
        {
            state.SetTransforms(state.Transform, transform);
        }

        /// <summary>
        /// 操作権を持つプレイヤー(Player1はホスト、
        /// Player2はクライアント)で呼び出されます。
        /// 操作をBoltEntityに渡します。
        /// </summary>
        public override void SimulateController()
        {
            IRollerBallBoltCommandInput input = RollerBallBoltCommand.Create();

            Vector3 data = new Vector3(_x, 0, _y);
            input.Mouse = data;

            entity.QueueInput(input);
        }

        /// <summary>
        /// オブジェクトのオーナーで呼び出されます。
        /// これはPlayer1, 2ともにホストで呼び出されます。
        /// 入力を受け取って動かします。
        /// Player2からは、クライアントに結果を送信します。
        /// </summary>
        /// <param name="command">送られてきた操作コマンド</param>
        /// <param name="resetState">操作権を持っていたらtrue</param>
        public override void ExecuteCommand(Command command, bool resetState)
        {
            RollerBallBoltCommand cmd = (RollerBallBoltCommand)command;

            if (resetState)
            {
                // Player2。送られてきたコマンドのデータを反映させます
                transform.localPosition = cmd.Result.Position;
            }
            else
            {
                // 入力を使ってオブジェクトを動かします
                rb.velocity = cmd.Input.Mouse * input2Speed;

                // ホストとクライアントの双方で呼び出されます
                // 現在の座標を送信します
                cmd.Result.Position = transform.localPosition;

            }
        }

        #endregion Bolt Events

        #region My Methods

        void PollMouse()
        {
            _x = Input.GetAxis("Mouse X");
            _y = Input.GetAxis("Mouse Y");
        }

        #endregion My Methods

    }
}

Advencedチュートリアルをベースに改造したものです。ポイントは以下の通りです。

  • Bolt.EntityBehaviour<ITransformState>を継承します
  • Update()でマウスの移動量を記録しておきます
  • アタッチされた時に、transformStateTransformに割り当てるコードをAttached()メソッドに実装します
  • SimulateController()は、操作をシミュレートする際にBoltから呼び出されるメソッドです。ここで、Update()で記録しておいたマウスの移動量をRollerBallBoltCommandInputMouseに設定して、QueueInput()で登録します
  • ExecuteCommand(command, resetState)に、コマンドを実行するための処理を実装します。commandは送受信するためのRollerBallBoltCommandインスタンスresetStateはサーバーかクライアントのどちらで動いているかを表していて、trueの時はコントローラー(クライアント)側の処理、つまり、送られてきた結果を自分のローカル座標に反映させます。falseの時は、オーナー(サーバー)側の処理で、QueueInput()で蓄えられた入力データを速度に反映させて、オブジェクトを動かし、結果をcommandに渡します

Player2の作成

同様にPlayer2も設定します。以下、ざっとした手順です。

  • Player2プレハブを開きます
  • Bolt Entityをアタッチ
  • 作成済みのPlayerスクリプトをアタッチ
  • StateITransformStateに設定
  • BoltメニューからCompile Assemblyを選択

以上でプレイヤーの設定完了です。

フィールド

枠や地面などのフィールドは、ネットワークで共有する必要はないのでそのまま配置しておけばOKです。

f:id:am1tanaka:20181129203141p:plain

オブジェクトを配置する機能の作成

公式のチュートリアルでは、ランダムに座標を設定したりしてスクリプト側で座標を作っていましたが、最初から登場させたい場所にゲームオブジェクトを配置して、そこにInstantiateした方が楽だろうということで作った機能です。

  • 新規に空のGame Objectを作成して、NetworkSceneManagerなどの名前にします
  • 新しいスクリプトを作成して、NetworkSceneManagerなどの名前にします

スクリプトは以下の通りです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace PhotonBoltPhysics
{
    public struct OBJECT_RECORD
    {
        public int id;
        public Vector3 position;
    }

    public class NetworkSceneManager : MonoBehaviour
    {
        public static NetworkSceneManager Instance
        {
            get;
            private set;
        }

        public List<OBJECT_RECORD> playerPositions {
            get;
            private set;
        }
        public OBJECT_RECORD ballPosition
        {
            get;
            private set;
        }

        private void Awake()
        {
            Instance = this;

            playerPositions = new List<OBJECT_RECORD>();

            GameObject[] gos = GameObject.FindGameObjectsWithTag("Player");
            OBJECT_RECORD or;
            foreach(GameObject go in gos)
            {
                or.id = go.GetComponent<BoltEntity>().PrefabId.Value;
                or.position = go.transform.position;
                playerPositions.Add(or);
                Destroy(go);
            }

            GameObject ball = GameObject.FindGameObjectWithTag("Ball");
            or.id = ball.GetComponent<BoltEntity>().PrefabId.Value;
            or.position = ball.transform.position;
            ballPosition = or;
            Destroy(ball);
        }

        /// <summary>
        /// BoltEntity.prefabId.Valueを受け取って、該当する
        /// プレハブIDで記録された座標を返します。
        /// </summary>
        /// <param name="id">プレハブID</param>
        /// <returns>座標を返します</returns>
        public Vector3 GetPlayerPosition(int id)
        {
            foreach(OBJECT_RECORD or in playerPositions)
            {
                if (or.id == id)
                {
                    return or.position;
                }
            }
            return Vector3.zero;
        }
    }
}

Awake()で、PlayerBallのタグが付いたオブジェクトを検索して、プレハブIDと座標を記録したらDestroyします。サーバーでオブジェクトを生成する際に、プレハブIDで記録しておいた座標を検索して、その座標にゲームオブジェクトを配置しています。

ネットワーク管理スクリプト

最後に、シーンが始まった時に、プレイヤーやボールを生成するためのオブジェクトとスクリプトです。これは、ゲームオブジェクトにはアタッチしません。新規にスクリプトを作成したら、PhotonBoltPhysicsServerCallbacksなどの名前にします。コードは以下の通りです。

using UnityEngine;

namespace PhotonBoltPhysics
{
    [BoltGlobalBehaviour(BoltNetworkModes.Server, "Game")]
    public class PhotonBoltPhysicsServerCallbacks : Bolt.GlobalEventListener
    {
        /// <summary>
        /// このプログラムがシーンを読み込んだ時の処理
        /// </summary>
        /// <param name="map"></param>
        public override void SceneLoadLocalDone(string map)
        {
            // プレイヤー1を生成して、操作を担当します
            BoltEntity be = BoltNetwork.Instantiate(BoltPrefabs.Player1);
            be.transform.position = NetworkSceneManager.Instance.GetPlayerPosition(be.PrefabId.Value);
            be.TakeControl();

            // ボールを作ります
            be = BoltNetwork.Instantiate(BoltPrefabs.Ball);
            be.transform.position = NetworkSceneManager.Instance.ballPosition.position;
        }

        /// <summary>
        /// クライアントがシーンを読み込んだ時に報告されるコールバック
        /// リモート用のプレイヤーを生成
        /// </summary>
        /// <param name="connection"></param>
        public override void SceneLoadRemoteDone(BoltConnection connection)
        {
            // プレイヤー2を生成して、操作を接続先に任せます
            BoltEntity be = BoltNetwork.Instantiate(BoltPrefabs.Player2);
            be.transform.position = NetworkSceneManager.Instance.GetPlayerPosition(be.PrefabId.Value);
            be.AssignControl(connection);
        }
    }
}

クラスは、Bolt.GlobalEventListenerを継承します。

    // :
    [BoltGlobalBehaviour(BoltNetworkModes.Server, "Game")]

上記の属性により、このクラスはサーバー、かつ、Gameシーンのみで実行されます。

SceneLoadLocalDone()は、サーバーのGameシーンが準備できた時にサーバーで実行するコードです。Player1Ballを生成して、Player1はサーバー自身が制御するのでTakeControl()を呼び出します。

SceneLoadRemoteDone()は、クライアントのGameシーンが準備できた時にサーバーで実行するコードです。Player2を生成して、操作はクライアントが担当するので、AssignControl()で接続先(connection)に割り当てます。

制作過程は、おおよそ以上の通りです。

動作確認

Photon Boltでは、開発を楽にするために実行形式のファイルを自動的にビルドして、ホストとクライアントで起動してくれる機能があります。それを使ってGameシーンをネットワークで実行してみます。

  • BoltメニューからScenesを選択します
  • Bolt Scenesウィンドウで、Clients欄を1にします。これで、クライアントアプリが1つ起動するようになります
  • Gameと書いてある欄の右のDebug Startをクリックします
  • Saveするかの確認ダイアログが表示されたら、Saveをクリックします

f:id:am1tanaka:20181128160121p:plain

ビルドが始まって、しばらく待つとアプリが1つ起動します。Unityでサーバー、クライアントアプリでクライアントが動きます。マウスを動かすと、アクティブなウィンドウのプレイヤーが操作できます。

f:id:am1tanaka:20190728185734p:plain
完成!!

設定の調整

Photon Boltの設定がデフォルトのままなので、クライアント側の動きがガタガタで、オブジェクトに雷マークが表示されています。これらを直すための設定は以下の通りです。

  • Windowメニューから、Bolt > Settingsを選択します
  • Network Rate1にします
    • これで、状態の同期を1フレームごとに行うようになるので、クライアントの動きがスムーズになります
    • ネット環境によっては、動きがおかしくなることがあるので、その時は2にしたり3に戻したり調整してみてください
  • 必要に応じて、Miscellanceous欄の以下のチェックを外します
    • Show Debug Info
      • オブジェクトに表示されるBoltアイコンが消えます
    • Show Help Buttons
      • ヘルプボタンを非表示にします
    • Visible By Default
      • 情報ウィンドウをデフォルトで非表示にします

f:id:am1tanaka:20190728185925p:plain
本当に完成!!

まとめ

Physicsで制御するゲームオブジェクトを、Photon Boltを使ってネットワーク上で共有することができました。

UNetに比べると手順はやや多い印象ですが、動いたあとの安心感があります。サービス停止の心配ないし...。

エアホッケーブロック崩しピンボール、コインプッシャーなど、Physicsを使って簡単に作れるタイプのゲームをネットワーク対応させるのに、今回の手法は有用と思います。

WebGLが使えたらなぁ...と願いつつ。

最後の最後に: Physicsの同期について

Photon Unity Networking(PUN)では、PhysicsのRigidbodyのvelocityを同期するためのクラスPhotonRigidbodyViewがあります。

Photon Unity Networking: PhotonRigidbodyView Class Reference

Photon Boltでも、Statevelocity用のVector3を追加して速度の同期を試みることはできます。ただ、このAPIリファレンスの解説にある通り、Rigidbodyのvelocityを同期するだけではネットワーク間のオブジェクトの動きが変わってしまう可能性があります。

今回のように、物体の衝突による動きが直接ゲーム性に関わる場合、PC間の挙動の違いでボールの飛ぶ方向が変わるのは困ります。座標を送って補正したとしても、動きが不自然になることが予想されます。そのため、Commandsで操作を送ってサーバーがまとめて処理する方式にしました。

Rigidbodyのvelocityを使えば、各PC上で座標計算ができるので動きを綺麗にすることができます。弾などの単純な軌道で、衝突時に跳ね返る必要がないようなオブジェクトの場合は、Rigidbodyのvelocityを同期する手法は使えると思います。どのような動きをさせたいかで採用する技術が変化してくるので、研究のし甲斐があると思います。今回と同じ内容のものを、PUNのvelocity同期で実装したらどうなるかも見てみたいところです。

参考URL

www.photonengine.com

assetstore.unity.com

CameraPlayのChromaticalの効果を出し続ける

CameraPlayは画面を揺らしたり、波紋を広げたりと、印象的な演出をするのにとても助かるアセットです。PostProcessingと組み合わせると素晴らしいです。

f:id:am1tanaka:20190711202054p:plain
CameraPlay - Chromatical

assetstore.unity.com (面倒なのでアフィリエイトは設定してません^^;)

使える演出の数々はUnity AssetStoreまとめさんやコガネブログさんの記事をご覧ください。

www.asset-sale.net

baba-s.hatenablog.com

1週間ゲームジャムで投稿した跳ね玉で利用したところ演出を評価してくださる方が増えまして、YOKETORU改2019でも利用して同様なお声を頂戴しました。効果を実感しています。

そんなCameraPlayですが、思った通りの設定がなかったりします。例えば古いモニターのようなかっこいい画面にできるChromaticalの使い方は以下の2通りです。

// time秒間で、演出して消える
CameraPlay.Chromatical(float time);

// 3秒間で、演出して消える
CameraPlay.Chromatical();

公式リファレンス

演出する秒数を変えられはしますが、シーン中ずっと効果を持続させるための設定がありません。今回はエンディングで利用したのですが、最初から最後までずっとこれになって欲しいのです。ということで改造したのでやりかたをご紹介します。

目次

準備

実行環境は以下の通りです。

  • Windows10
  • Unity2019.1.5
  • Visual Studio 2019 Community Edition
  • CameraPlay 1.3.2

空の3Dプロジェクトを作成して、Camera Playをインポートしました。Camera Play > Example > ExampleSceneシーンにサンプルシーンがあるので、それをもとに動作環境を構築しました。

  • Camear_GUIを無効にして、新しい空のゲームオブジェクトを作成して、ChromaticalTestというスクリプトを作成してアタッチしました。以下のような感じになります

f:id:am1tanaka:20190711133710p:plain
動作環境の作成

まずは現状のまま動作させてみます。

  • 作成したChromaticalTestスクリプトをダブルクリックしてエディターで開きます
  • Start()メソッドを以下のようにします
    void Start()
    {
        CameraPlay.Chromatical();        
    }

Playすると、3秒間で効果が表れて消えるのが確認できます。

f:id:am1tanaka:20190711202702g:plain
Chromaticalデフォルト動作

使い方はめちゃくちゃ簡単です。

効果を維持するための機能を追加する

CameraPlay_ChromaticalクラスのプロパティーTimer0.5にすれば、常時効果が常に続くようになります。解説は後述しています。もともとはprivateなので、これをpublicにして公開します。

  • ProjectウィンドウからCamera Play > Scriptsフォルダーを開いて、CameraPlay_Chromaticalをダブルクリックして、エディターで開きます
  • 20行目付近のTimerの宣言を、以下のようにpublicに変更します
// 20:
    [HideInInspector] public float Timer = 1f;

あとは利用する側で、Chromaticalを発動させたい間、上記のTimer0.5f-Time.deltaTimeを代入し続ければOKです。

  • 作成したChromaticalTestスクリプトをエディターで開きます
  • 以下のようにします
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChromaticalTest : MonoBehaviour
{
    CameraPlay_Chromatical CP = null;

    void Update()
    {
        if (CP == null)
        {
            CameraPlay.Chromatical();
            CP = CameraPlay.CurrentCamera.GetComponent<CameraPlay_Chromatical>();
        }
        if (CP != null)
        {
            CP.Timer = 0.5f - Time.deltaTime;
        }
    }
}

以上で完了です。Playすると、常にChromaticalがかかり続けます。

f:id:am1tanaka:20190711203020g:plain
ずっとChromaticalが持続

参考 CameraPlay_Chromaticalクラスを読む

参考までに、改造に至った手順を書いておきます。

まずは、どのようなコードで動いているのか覗いてみます。

  • Visual Studioで開いているChromaticalTestスクリプトに追加したコードのChromatical()の部分を右クリックして、定義へ移動を選びます
  • CameraPlayソースコードが開いて、Chromatical()メソッドの中身が確認できます。やっているのは以下の処理です
    • CurrentCameraが無効な時、現在のメインカメラを設定
    • カメラにCameraPlay_Chromaticalコンポーネントをアタッチ
    • アタッチしたCameraPlay_ChromaticalインスタンスDurationプロパティに、演出する秒数(デフォルトなら3)を代入

以上です。あとは、CameraPlay_Chromaticalクラス側が演出をしているということになるので、クラスの中身を見てみます。

  • どれでもよいので、ソースコード上のCameraPlay_Chromaticalの部分を右クリックして、定義へ移動を選びます
  • CameraPlay_Chromaticalクラスのファイルが開くので目を通します
  • OnRenderImage()メソッド内で演出の管理をしているのが確認できます

ざっくりと、以下のような感じです。

  1. シェーダーが有効か(nullじゃない)を確認
  2. TimeXは1から始めて、経過秒を足して、100を超えたら0に戻しています。シェーダーに渡しているようですが、他のパラメーターに関与してないので、ひとまず無視
  3. Timerは、経過時間です。開始直後が0Duration秒経過すると1になるようになっています
  4. _Fadeは、Timerをそのまま代入しているだけです
  5. _Fade0から1に変化する間に、fresultにアニメーションカーブを利用して、滑らかに0から1、そしてまた1から0と変化する値を代入しています
  6. fresultの値を、フェードのパラメーターとしてシェーダーに渡しています

以上から、常にfresult1になるようにすればよいことが分かります。

方法としては、常時Chromaticalとなる別のクラスを新設することが考えられますが、面倒なので単にCameraPlay_ChromaticalTimerを公開して、そこに値を代入する方法を採りました。

まとめ

Chromaticalをずっと適用できるようになりました。CameraPlayは綺麗に作られているのでソースコードを追いやすいと思います。表現の幅を拡張する一助になれば幸いです!

参考URL

Unity2019のWebGLビルドのエラー対策

f:id:am1tanaka:20190628215704p:plain

UnityのWebGLビルドではあれこれやられていましたが、ついに最後の砦を攻略できました。既知の問題はニムサイトさんに詳しいです。

nimushiki.com

日本語が含まれず、保存先が自分のドキュメント下で、最近のUnityであれば大体は問題ないのですが、学校の半数以上のマシンでビルドが通っていませんでした。エラーはil2cpp.exeがなんちゃらかんちゃら、というやつです。これの原因と対処方法です。

(2019/6/30 エラーの例を追記)

目次

エラーメッセージ

この問題で発生するエラーは以下のようなものです。この後にまだ2つほど続きます。冒頭のエラーに以下のようなil2cpp.exeが含まれていたら、この問題である可能性があります。

Failed running C:\Program Files\Unity\Hub\Editor\2019.1.5f1\Editor\Data\il2cpp/build/il2cpp.exe --convert-to-cpp --dotnetprofile="unityaot" --compile-cpp --libil2cpp-static --platform="WebGL" --architecture="EmscriptenJavaScript" --configuration="Release" --outputpath="C:\Users\student\Documents\webgl\Assets /../Temp/StagingArea/Data\Native\build.bc" --cachedirectory="C:\Users\student\Documents\webgl\Assets..\Library/il2cpp_cache" --compiler-flags="-Oz -DIL2CPP_EXCEPTION_DISABLED=1 " --emit-method-map --additional-libraries=(中略) --profiler-report --map-file-parser="C:/Program Files/Unity/Hub/Editor/2019.1.5f1/Editor/Data/Tools/MapFileParser/MapFileParser.exe" --directory=C:/Users/student/Documents/webgl/Temp/StagingArea/Data/Managed --generatedcppdir=C:/Users/student/Documents/webgl/Temp/StagingArea/Data/il2cppOutput

原因

最初にWebGLビルドをする際に、以下のようなnode.exeがブロックされているという警告が表示されます。

f:id:am1tanaka:20190628212408p:plain
node.exeをブロックしたとの警告

この時に、うっかりキャンセルをクリックしたのが原因です。これにより、node.exeはブロックするものとWindowsが理解してしまうため、それ以降のビルドは無条件にエラーになっていたのでした。

ということで、手動でnode.exeの動作を許可すれば解決します!!

確認したバージョン

  • Unity2019.1.5
  • Windows10

解決手順

操作には、管理者権限が必要です。

  • Windowsメニューから設定を開きます

f:id:am1tanaka:20190628211857p:plain
設定を開く

  • 更新とセキュリティをクリックします

f:id:am1tanaka:20190628212603p:plain
更新とセキュリティをクリック

  • Windowsセキュリティをクリックします

f:id:am1tanaka:20190628212705p:plain
Windowsセキュリティ

f:id:am1tanaka:20190628212755p:plain
ファイアウォールとネットワーク保護

f:id:am1tanaka:20190628212841p:plain
ファイアウォールによるアプリケーションの許可

  • 設定の変更ボタンをクリックします

f:id:am1tanaka:20190628213034p:plain
設定の変更

  • Nキーを押して、Node.jsの設定を見つかった場合
    • 詳細をクリックします

f:id:am1tanaka:20190628214132p:plain
詳細を確認

  • 対象のUnityのバージョンのフォルダーかを確認します。以下のようになっていれば、Unity2019.1.5でWebGLビルドが成功するはずです

f:id:am1tanaka:20190628214201p:plain
パスを確認

  • 対象のUnityフォルダーのNode.jsの前のチェックが外れていたら、チェックしてください

f:id:am1tanaka:20190628214352p:plain
チェック

Node.jsの設定があった場合、ここまでの設定でビルドが通るようになります。

Node.jsの設定がなかったり、パスが別のバージョンのものしかなかったりする場合は、続けて設定します。

Node.jsの設定がない場合

  • 別のアプリの許可ボタンをクリックします

f:id:am1tanaka:20190628213124p:plain
別のアプリの許可

  • 参照をクリックします

f:id:am1tanaka:20190628213833p:plain
アプリの追加の参照

  • WebGLビルドで使いたいUnityのインストールフォルダー内のnode.exeを指定します。例えば、デフォルトの場所にインストールしたUnity2019.1.5を指定したい場合は以下の通りです
    • C:\Program Files\Unity\Hub\Editor\2019.1.5f1\Editor\Data\Tools\nodejs\node.exe
  • 追加ボタンをクリックします

以上で完了です。以下のようにNode.jsの設定が追加されていればOKです。

f:id:am1tanaka:20190628213953p:plain
登録完了

この設定をしてもエラーが出る場合

既知の問題が原因と思われます。以下を確認してみてください。

  • Unityのプロジェクトが、日本語を含むパスの下にあったら、全て半角英数のみのパスに移動する
  • Unityのプロジェクト内に、日本語ファイルがあったら、全て半角英数にリネームする
  • Unityのプロジェクトが、自分のドキュメントフォルダーの外にあったら、ドキュメントフォルダーの下に移動する
  • Unityを一度閉じて、プロジェクトを開き直してビルドしなおす(結構これで成功することがあります)

なんだかんだで日本語パスと日本語ファイル名が一番のネックです。PC用ビルドなどは通ってしまうので、安心しているとWebGLでコケますのでご注意を!!

参考URL

nimushiki.com

STYLYを利用してHTC VIVEでVR

HTC Viveで3Dモデルを表示して、うろうろ見回るというのをSTYLYでやってみました。手順をざっと残しておきます。

目次

事前調査

STYLYに行き着くまでに調べた候補です。

STYLY

styly.cc

今回利用したサービスです。VRコンテンツをWebブラウザーで作成して公開できるサービスで、デザイナー向けということでUnityなどを介さなくてもWebブラウザーとツールで完結できるのが優秀です。HMDで空間を編集するツールや、作ったVR空間をVR表示するビュアーなどあります。Unityからシーンをアップロードするのも簡単にできて、ビュアーではワープ移動が最初から実装されているので本当に手軽に使えます。

VR Chat

www.vrchat.net

VR空間にアバターを表示して、ほかのプレイヤーと交流できるサービスです。Unityを使ってワールドの構築などが可能です。Unityを介する必要があることと、今回はアバターとかチャットが不要だったので保留にしました。

vrcworld.wiki.fc2.com

SteamVR Plugin

assetstore.unity.com

Steamから出ているUnityのVRプラグインです。様々な有効なサンプルが含まれているアセットです。既存のサンプルに表示したいモデルをインポートすればいけそうでしたが、シーンを整理したりするのが面倒そうだったので保留にしました。

VR Samples

assetstore.unity.com

Unity公式のVRのサンプル集です。4つのミニゲームによるVRの作例といった感じで、ちょっと今回やりたいことと違う方向性だったので保留にしました。

STYLYのざっとした流れ

デザイナー向けのサービスで、手軽にモデルの読み込みとシーン内のワープなどの基本動作ができるため、STYLYでやることにしました。Unityからシーンのアップロードも可能なので、Unityに読み込めるモデルであれば持っていくことができます。FBXとVRMのモデル、どちらも手軽に動かせました。

  1. STYLYのサイトでアカウントを作成してログイン
  2. VR空間の作成はWebブラウザーでできます。Google ChromeSafariMicrosoft Edgeに対応。Firefoxは現状では非対応の模様
    • Unityにアセットをインポートすれば、自作のプレハブやシーンをアップロードして、簡単にSTYLYのシーンに配置できます
  3. VR空間をHMDで表示するにはビュアーをダウンロードします。以下の3種類あります

ビュアーを起動して、STYLYのアカウントでサインインすると作成したシーンを表示できます。

現時点では、Oculus GoやQuestには非対応のようです。こちらの方法でいけるかも? → Oculus QuestでSteamVRのゲームを動かす(Riftcat/ALVR/VirtualDesktop) | VRまにあっくす!

HMDに表示するまでの流れ

詳しくは公式サイトということで、ざっくりHTC Viveで表示するまでの手順です。

サインアップとログイン

  • STYLY - VR creation platformを開いて、Sign upします
    • スクリーン名とメールアドレス、パスワードを設定するだけです
  • SIGN UPをクリックすると、一番下にLog in hereと小さくあるので、クリックしてログインします

アプリのダウンロード

ログインすると以下のようなページが表示されます。

f:id:am1tanaka:20190614091837p:plain
Get VR Client

  • HMDで表示してみたいのでGet VR Clientをクリックします

表示されている機種以外のもので利用したい場合は、Other downloadsをクリックして対応するものを選択してください。

  • Steamのページが開きます。2019/6/14現在、まだβ版だったので早期アクセス扱いになります。ページをスクロールさせて「STYLYを使用」の無料をクリックします
  • 指示に従って操作して、インストールを完了させます
  • インストールが完了したら、Steamメニューをクリックして、STYLYをクリックして起動します

f:id:am1tanaka:20190614092743p:plain
STYLYを起動

問題がなければ、VRコンテンツの一覧が表示されます。何かウィンドウが表示されたら、それぞれ対応してください。

  • アプリの更新があると、SteamVR Updateのウィンドウが表示されます。その場合は、DismissをクリックすればOKです
  • Bluetoothのエラーが表示されたら、Bluetooth Settingsをクリックして、Update bluetooth driverをクリックして、ドライバーを更新します
  • 他にも何かエラーが表示されたら、対応してください

HMDに表示してみる

インストールしたSTYLYアプリを起動すると、公開されているVRコンテンツが表示されます。

f:id:am1tanaka:20190620131704p:plain
STYLY VRホーム画面

なんでもよいので気になったものをクリックすると、HMDに表示されて、見まわしたり、ウォークスルーできます。

コントローラーの1と2で、親指のところの丸いやつの操作割り当てが違います。

f:id:am1tanaka:20190620132303j:plain
親指のところの丸いやつ

一方はスクリーンショット撮影、もう一方でワープ移動ができます。

モデルの読み込み

styly.cc

上記に詳しくありますが、以下が利用できます。

  • Google Poly の3Dモデル
  • Sky and Groundsから、背景の空と、地面を選んで入れることができます
  • 3D Modelsから、STYLYが提供する標準モデルを入れることができます。モデルやパーティクルもあります
  • Filtersから、画面の色味を設定するフィルターを選べます
  • Uploadsから、自作モデルをアップロードして利用できます。対応フォーマットは以下の通りです
  • Unityからアップロードするなら、VRMなどのUnityで読み込めるモデルも利用できます
  • アップロードしたことがあるモデルは、My Modelsから再利用できます
  • その他、画像、BGM、動画、PDFなどをインポートできます

自作モデルをアップしてみる

STYLYのWebエディターからできます。

以上で、WebブラウザーにSTYLY Studioが起動します。

  • Create Sceneタブをクリックします
  • Titleを入力して、CREATEボタンをクリックします
  • 初期画面は以下のような感じで、平行光(Directional Light)、デフォルトの地面、デフォルトの空が読み込まれています

f:id:am1tanaka:20190620133649p:plain
デフォルトのシーン

  • 左上のメニューからAssetsをクリックします

f:id:am1tanaka:20190620133743p:plain
Assetsボタン

  • 何を読み込むかを選びます。3Dモデルなら3D Modelをクリックします
  • 以下のいずれかを選びます。自分でモデルをアップロードしたり、以前アップロードしたモデルを利用したい場合はMy Models & Uploadをクリックします
    • Poly 3D Models
      • Google Polyに公開されているモデルを読み込みます
    • Sky and Grounds
      • スカイボックスや地面を読み込みます
    • 3D Models
      • STYLY謹製のパーティクルやモデルを読み込みます
    • My Models & Upload
      • 自分で作成したモデルをアップロードしたり、以前アップロードしたモデルを読み込みます
  • タイトルを入力して、Selectボタンでファイルを選択したら、Uploadボタンを押してモデルをアップロードします
  • アップロードが完了したら、My Modelsに追加されるので、クリックして選択するとシーンに配置されます

f:id:am1tanaka:20190620134437p:plain
モデルの読み込み完了

以上でシーンを作成して、そこにモデルを読み込みました。保存は自動的に行われるので、特に操作はありません。

あとはSTYLYビュアーを起動して、自分のアカウントでSign inするとシーンが一覧に表示されるようになるので、選択すればHMDで見ることができます。

アップロードしたモデルやシーンはデフォルトでは非公開

アップロードしたモデルが素材として公開されてしまうか心配でしたが、原則として非公開でした。

現在の仕様では、アップロードされたモデルはアップロードしたアカウント以外からは利用されることはありません。また、内部的に利用しているモデル一位識別子も予測不可能で十分長い文字列になっています。

(STYLY よくあるご質問(Q&A) | STYLY 「アップロードしたMy Modelsを削除するにはどうしたらよいですか」より)

一度アップロードしたモデルは削除できないとありますが、My Modelsから削除できました。名前変更しないと同じ名前のモデルがアップされちゃうので邪魔になるんですよね。これは助かります。

シーンも公開の設定をしない限り、プライベートになります。安心して使えます。

f:id:am1tanaka:20190620134710p:plain
シーンはデフォルトではPrivate

Unityとの連携

UnityからSTYLYにアセットをアップロードする方法 | STYLYより抜粋です。

  • 2019/6/14時点では、Unity2017.4.xに対応しています。
  • アップロードできるのはモデル、パーティクル、ライト、シェーダー、PlayMaker(2019年3月現在、1.8.xに対応)を用いたアセットです
  • スクリプトは反映されないので、何らかのインターラクションを付けたい場合はPlayMakerが必要です。PlayMakerの基本アクションはほぼ利用できるとのことです

簡単なFPS的なものは作れるようです。詳しくは以下参照。

Playmaker情報まとめ | STYLY

シーンをアップロードしてみる

  • こちらに従ってUnity2017.4.xをインストールして、プラグインをインポート、APIキーの設定などを行います
  • カメラ座標を0, 1.5, -5に設定(STYLYビュアーでは起動時にプレイヤーの位置を0, 0, -5に配置します。目の高さ分、上になるので、Yは1.5ぐらいで設定すればおおよそSTYLYビュアーの起動時の状態になります)
  • 適当にシーンを作成

f:id:am1tanaka:20190614102845p:plain
Sample Scene

  • シーンを保存
  • Projectウィンドウから保存したシーンを右クリックして、STYLY > Upload prefab or scene to STYLYをクリック

以上でアップロードが開始します。ビルドをしてからのアップロードになるので、やや時間がかかります。Upload successed.と表示されたら完了です。

アップロードしたシーンをHMDで表示する

Unityでアップロードしたシーンはモデル扱いになるため、STYLY Studioでシーンに配置する必要があります。

  • STYLY Studioを開いて、Sign inします
  • 新しくシーンを作成するか、モデルを読み込みたいシーンをクリックして、シーンを開きます
  • 左上のメニューのAssetsをクリックします

f:id:am1tanaka:20190614104000p:plain
Assetsをクリック

  • 3D Model > My Models & Uploadをクリックします
  • アップロードしたシーンがあるので、クリックします
  • デフォルトでSTYLYの地面とSkyboxが作成されます。不要な場合は[Ground]と[Skybox]の右の歯車をクリックして、ごみ箱アイコンをクリックして消します

編集が完了したら、STYLYビュアーを起動して、Log inしてシーンを呼び出せば見ることができます。

STYLYビュアー実行中に編集した場合

STYLYビュアーでHMD実行中に、WebブラウザーのSTYLY Studioでシーンを編集することができます。編集した内容はその都度サーバーにアップされますが、STYLYビュアーには自動的に反映されません。再読み込み操作でもダメなようです。STYLYビュアーで一度Homeに戻ってから、シーンを開き直してください。

まとめ

以上で、今回やりたかった自作のモデルをVR空間に表示して、その中をワープ移動するということができました。自分で作った3Dワールドやオブジェクト、VRoidで作ったキャラクターを手軽に表示できるのでオススメです。

トリガーを引いたら大砲が発射されたり、何かをきっかけにキャラクターをアニメーションさせるにはPlayMakerが必要になります。そのあたりのカスタマイズ性が上がってくるとさらに強力になってきそうです。

参考URL

styly.cc

assetstore.unity.com

vr-maniacs.com

SpriteでBloomするSpriteGlow

みんな大好きBloomをスプライトやUIでもできるようにしてくれるSpriteGlowというアセットがあります。

github.com

アセットストアではなくGitHubでMITライセンスで公開されています。

あっという間にできますので設定方法をまとめておきます。

目次

手法

このアセットは、スプライトの画像の周りにBloom用のHDRの縁を描画します。Bloom効果自体は、PostProcessing StackやMK Glow Freeといった既存のポストプロセス機能を利用するという経済的(?)な手法です。

下準備

Unity2018.2.20での作業手順を示します。

組み込みたいプロジェクトをUnityで開きます。ここでは、ユニティちゃん 2Dデータのコインを光らせてみたいと思います。同様に作業をする場合は、UNITY-CHAN! OFFICIAL WEBSITEからユニティちゃん 2Dデータをダウンロードして、プロジェクトにインポートしておいてください。

インポートが完了したら、UnityChan2D/Demo/Scenes/World 1-1シーンをダブルクリックして開いておきます。

ポストプロセスの設定

PostProcessing StackV2やMK Glow Freeをプロジェクトにインポートします。Bloomだけ使うならMK Glow Freeの方が手順が楽です。Vignetteなど他のエフェクトも使いたい場合はPostProcessing Stack V2にするとよいでしょう。

MK Glow Freeの場合

MK Glow Freeはこちらからインポートできます。

インポートしたら、Main CameraにMKGlowスクリプトをアタッチしておきます

f:id:am1tanaka:20190612143100p:plain
MKGlowスクリプトをアタッチ

これ以降の調整は、SpriteGlowを組み込んだあとに行います。

PostProcessing Stack V2

PostProcessing Stackについてはこちらに手順をまとめています。

記事に従ってBloomが動作するように設定してください。

SpriteGlowを設定する

SpriteGlowパッケージをインポートする

GitHubリポジトリーを開いて、必要なパッケージをダウンロードしてインポートします。

  • 以下を開きます

github.com

  • README.mdの最初の方にありますSpriteGlow.unitypackageのリンクをクリックして、パッケージをダウンロードします
  • ダウンロードしたSpriteGlow.unitypackageをドラッグして、UnityのProjectウィンドウにドロップして、インポートします

光らせたいオブジェクトへの設定

光らせたいオブジェクトに設定を加えます

  • Sceneウィンドウで光らせたいコインをクリックして選択します

f:id:am1tanaka:20190612143802p:plain
光らせたいオブジェクトを選択

  • ProjectウィンドウからSpriteGlowフォルダーを開いて、SpriteGlowEffectスクリプトをドラッグして、Inspectorウィンドウにドロップして、アタッチします

f:id:am1tanaka:20190612144039p:plain
スクリプトをアタッチ

以上で、スプライトの周りに白い枠が描画されるようになります。

f:id:am1tanaka:20190612144133p:plain
Glowが描画される

あとは、Bloomの効果が発揮されるようにHDR設定をします。

HDR設定

  • HierarchyウィンドウからMain Cameraをクリックして選択します
  • InspectorウィンドウのCameraコンポーネントにあるAllow HDR設定にチェックを入れます

f:id:am1tanaka:20190612144341p:plain
CameraのHDR設定

この段階で光った場合は、調整作業に進んでください。光ってない場合はGraphicsのHDRを有効にします。

  • Editメニューをクリックして開いて、Project Settings > Graphicsを選択します
  • InspectorウィンドウのUse Defaultsのチェックを外します
  • Use HDR設定が3か所あるので、チェックを入れて有効にします

f:id:am1tanaka:20190612144913p:plain
HDR設定

以上で輝き始めます。MK Glow Freeの場合は以下のようになりました。

f:id:am1tanaka:20190612144451p:plain
光った(けど光り過ぎ)

あとはちょうどよい光り方になるように調整します。

MK Glow Freeの光り方を調整する

MK Glow Freeは、Post Processing Stackに比べて強めに光る感じです。MKGlow自体の調整を加えておきます。

  • Hierarchyウィンドウから、Main Cameraをクリックして選択
  • InspectorウィンドウのMK Glowコンポーネントから、Intensityの値を小さくします。0.25ぐらいがちょうどよさそうでした

f:id:am1tanaka:20190612145654p:plain
MKGlowの調整

f:id:am1tanaka:20190612145734p:plain
光るアイテム

他のパラメーターも変更して、何が変わるか確認してみてください。

オブジェクト側の調整

オブジェクト側でも光り方を調整できます。

  • 調整したコインをクリックして選択します
  • InspectorウィンドウのSprite Glowコンポーネントで、光り方を調整できます

f:id:am1tanaka:20190612145913p:plain
Sprite Glowスクリプト

まとめ

以上です。PostProcessの設定に慣れていれば、あっという間に使えると思います。HDRだけ加えてくれるというのがスマートですね。お手軽にBloomが使えて、Bloom好きには堪らないアセットです。

記事の中の画像はUnityちゃん2Dデータを利用しました(コインがプレハブじゃなかったのは誤算でした^^;)。

ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています

参考URL

Unityで簡易にBGMと効果音を鳴らす

UnityでささっとBGMや効果音を鳴らす方法です。

目次

前提

Unityは多機能なので、フェードイン&フェードアウトや3Dサウンド、シーンを跨いだ曲の再生、AudioMixerへの対応など、様々なオーディオ効果を実装できます。しかし、そのためには相応の学習コストがかかるため、特に初学者だとかなり時間が必要になるでしょう。それらは後回しにして、先にゲームを完成させたいところです。

そこで、BGMと効果音を鳴らすだけに機能を絞って、手っ取り早く実装する手順をまとめます。機能は以下の通りです。

  • BGM
    • シーンごとに、設定したBGMを再生します
    • BGMはループ再生させることも、させないこともできます
    • 同じシーン内で曲は変えません
    • シーンを跨いだBGMの再生には対応しません
  • SE
    • 1つのオブジェクトですべての効果音を再生します
    • 3Dサウンドは使いません

プロジェクトの準備

Unity2019.1.5での実装例です。

  • Unityでプロジェクトを新規に作るか、音を鳴らしたいプロジェクトを開きます
  • 利用したい音源データ(oggmp3wavなど)を用意したら、Projectウィンドウの任意のフォルダーにドラッグ&ドロップしてインポートします
    • BGMをAudios/BGMフォルダー、SEをAudios/SEフォルダーにまとめた場合、以下のような感じになります

f:id:am1tanaka:20190610214221p:plain
音源データのインポート

音データの種類

Unityでは色々な音声データが利用できます(オーディオファイル - Unity マニュアル)。それらのうち、主に利用するのはoggmp3wavの3種類かと思います。

oggmp3は録音したデータを圧縮したもので、やや音が劣化しますが容量が小さくなります。両者の違いはそれほどないのでどちらを選んでも構いません。

(参考: oggとMP3の違いは何でしょうか?持ってるmp3の機械には対応してるみた... - Yahoo!知恵袋)

wavは録音したデータをそのまま保存する形式です。同じ条件で録音した場合、容量が大きくなる代わりに音質はよくなります。ハイエンドな高音質な環境向けの場合、wavを選ぶのも手です。ただし、WebGL環境だとwav形式の音にノイズが入る場合があります。特別な狙いがないのであれば、BGMも効果音もoggmp3の方がよいでしょう。

wavファイルは、以下のサイトなどでoggmp3に変換できます。

Load Typeについて

読み込んだ音声データを選択した時にInspectorに表示される設定です。効果音やゲーム中のBGMの場合はデフォルトのままにします。

タイトル画面やゲームオーバーなど、処理が多少重くなっても構わないようなシーンでは、Load TypeStreamingに変更すると音が鳴り始めるのが早くなる(はず)で、よさげです。

f:id:am1tanaka:20190610182854p:plain
タイトルやゲームオーバー用の音源はStreamingにするとよいかも

BGMを鳴らす

BGMを鳴らします。

  • HierarchyウィンドウのCreateをクリックして、Create Emptyをクリックして空のゲームオブジェクトを作成して、名前をTinyAudioなどにします
  • InspectorウィンドウのAdd Componentをクリックして、Audio > Audio Sourceをクリックします
  • Projectウィンドウから、そのシーンで鳴らしたいBGMの音源ファイルをドラッグして、InspectorウィンドウのAudio Clip欄にドロップします

f:id:am1tanaka:20190610185243p:plain
音源データをAudio Clip欄にドラッグ&ドロップ

  • BGMをループ再生したい場合は、InspectorウィンドウのLoop欄にチェックを入れます

f:id:am1tanaka:20190610185400p:plain
Loop設定

以上で設定完了です。UnityをPlayするとBGMが再生されます。BGMはこれでOKです。

効果音を鳴らすためのクラスを作る

効果音はTinyAudioというクラスを作成して、スクリプトから呼び出して鳴らすようにします。

  • Hierarchyウィンドウから先ほど作成したTinyAudioオブジェクトをクリックして選択します
  • InspectorウィンドウのAdd Componentをクリックして、New scriptをクリックして、TinyAudioなどの名前でスクリプトを作ります
    • スクリプトは、Scriptsフォルダーなどを作って、そこにまとめておくのがオススメです
  • Projectウィンドウから作成したTinyAudioスクリプトをダブルクリックしてエディターで開きます
  • 以下のコードを入力、あるいはコピペします
using UnityEngine;

public class TinyAudio : MonoBehaviour
{
    public static TinyAudio Instance;

    /// <summary>
    /// seListに設定する効果音の種類を以下に定義します。
    /// </summary>
    public enum SE
    {
        CLICK,
        HIT,
        MAGIC
    }

    [Tooltip("効果音のAudio Clipを、SEの列挙子と同じ順番で設定してください。"), SerializeField]
    AudioClip[] seList;

    AudioSource audioSource;

    private void Awake()
    {
        Instance = this;
        audioSource = GetComponent<AudioSource>();
    }

    /// <summary>
    /// SEで指定した効果音を再生します。
    /// </summary>
    /// <param name="se">鳴らしたい効果音</param>
    public static void PlaySE(SE se)
    {
        Instance.audioSource.PlayOneShot(Instance.seList[(int)se]);
    }
}
  • 上書き保存したら、Unityに切り替えます
  • HierarchyウィンドウからTinyAudioオブジェクトをクリックして選択します
  • Projectウィンドウから効果音のファイルをドラッグして、InspectorウィンドウのSe List欄にドロップします
    • 設定する順番は、TinyAudioスクリプトSE列挙子に対応付けていきます。CLICKはクリック音、HITは攻撃音、MAGICは魔法のような音ということで、その順番に設定します

f:id:am1tanaka:20190610190734p:plain
AudioClipを設定

効果音を鳴らす

スクリプトからTinyAudio.PlaySE(TinyAudio.SE.CLICK);のように呼び出せば指定した効果音が鳴ります。TinyAudio.SE.CLICKは、TinyAudio.csの10行目付近に宣言してあります。SE列挙子の最初(0番目)なので、Se ListElement 0に設定した音が鳴ります。

数字キーで効果音を鳴らす機能を実装しましょう。

  • HierarchyウィンドウのCreateをクリックして、Create Emptyをクリックして、空のゲームオブジェクトを作成して、SETestなどの名前にします
  • InspectorウィンドウのAdd Componentをクリックして、New scriptを選択して、SETestなどの名前にします
    • SETestスクリプトは、Scriptsフォルダーに移動しておくとよいです
  • SETestスクリプトをダブルクリックして、以下のコードにします
using UnityEngine;

public class SETest : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            TinyAudio.PlaySE(TinyAudio.SE.CLICK);
        }
        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            TinyAudio.PlaySE(TinyAudio.SE.HIT);
        }
        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            TinyAudio.PlaySE(TinyAudio.SE.MAGIC);
        }
    }
}

以上で保存して、Unityに切り替えてPlayします。数字キーの123でそれぞれ効果音が鳴ります。

数字キーを連打すると、効果音が同時に鳴ることが確認できます。今回のように音に3D空間の効果を使わない場合は、1つのAudio SourceでBGMも効果音も鳴らせます。

シーン切り替えに対応する

他のシーンでもBGMを鳴らせるようにします。

  • HierarchyウィンドウからTinyAudioオブジェクトをドラッグして、Projectウィンドウにドロップしてプレハブ化します
    • プレハブは、Prefabsなどのフォルダーを作ってまとめておくのがおススメです
  • ついでにSETestオブジェクトのドラッグ&ドロップしてプレハブ化しておきます
  • [Ctrl]+[S]キーでシーンを保存します
  • 新しいシーンを作るか、既存の別のシーンに切り替えます
  • Projectウィンドウから、TinyAudioプレハブをドラッグして、Hierarchyウィンドウにドロップします
  • 必要に応じて、ProjectウィンドウからSETestプレハブをドラッグして、Hierarchyウィンドウにドロップします(効果音の再生テストが不要な場合は不要です)
  • HierarchyウィンドウのTinyAudioオブジェクトをクリックして選択します
  • 新しいシーン用のBGMをProjectウィンドウからドラッグして、InspectorウィンドウのAudioClip欄にドロップします

f:id:am1tanaka:20190610214835p:plain
BGMの音源データを設定

  • BGMをループさせたいかどうかに応じて、InspectorウィンドウのLoop欄のチェックをつけるか、外します

f:id:am1tanaka:20190610185400p:plain
ループ設定

以上で完了です。PlayするとBGMが鳴ります。SETestオブジェクトを設定してあれば、数字キーの123で効果音が鳴ります。

シーンを切り替えたら、そのシーンに設定したBGMが鳴り始めます。

効果音の増やし方、あるいは変更の仕方

効果音を増やしたり、内容を変更したい場合、TinyAudioクラスのSE列挙子の宣言を変更して、それに合わせてTinyAudioオブジェクトに設定する効果音リストを更新します。

  • ProjectウィンドウからTinyAudioスクリプトをダブルクリックして、エディターで開きます
  • 使いたい効果音を全て列挙子SEに定義します。例えば食べる効果音を増やしたいなら、EATなどの名前で定義を追加します

f:id:am1tanaka:20190610222908p:plain
列挙子を増やす

  • 上書き保存したらUnityに切り替えます
  • ProjectウィンドウのTinyAudioプレハブをクリックして選択します

f:id:am1tanaka:20190610223041p:plain
TinyAudioプレハブを選択する

  • Projectウィンドウから追加したい音データをドラッグして、InspectorウィンドウのSe List欄にドロップするか、Sizeを増やして該当するスロットにドロップします
    • 以下、matchstick-put-fire1を追加している例です

f:id:am1tanaka:20190610223359p:plain
音を追加

以上で完了です。あとはスクリプトからTinyAudio.PlaySE(TinyAudio.SE.EAT);のように呼び出すことで、新しく追加した音を鳴らすことができます。

まとめ

シーン切り替え時に効果音が途切れたり、BGMがシーンを跨げないなど気になる部分はありますが、一先ずBGMと効果音を鳴らすことができました。2DゲームやUIのシステム音など、鳴っている場所の影響を受けない音はここで紹介した以下のような方法が効率よいです。

  • BGMは、Audio SourceAudio Clip欄に音データを設定して再生
  • SEは、PlayOneShot()で再生

3D対応させたり、最初に記載したような多様な機能を実装したい場合は以下のブログなどが役に立つと思います。

kan-kikuchi.hatenablog.com

qiita.com

参考URL