- 投稿日:2019-04-15T23:35:19+09:00
Unity2018にアップデートしたらVisualStudioCommunity2017でエラーだらけになった件
- 投稿日:2019-04-15T22:40:51+09:00
UnityのWebGLで外部JavaScriptライブラリを使う
概要
UnityのWebGLプラットフォームで、FirebaseやStripeといった外部サービスのJavaScriptライブラリを使いたかったので、そのときに必要だった手順をまとめました。
WebGLTemplatesの用意
公式ページにあるように
Assets/WebGLTemplates/テンプレート名/
に以下のファイルを作成します。
使いたいライブラリも記述します。index.html<!DOCTYPE html> <html lang="en-us"> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Unity WebGL Player | %UNITY_WEB_NAME%</title> <!--使いたいライブラリを追加--> <script src="%UNITY_WEBGL_LOADER_URL%"></script> <script> var gameInstance = UnityLoader.instantiate("gameContainer", "%UNITY_WEBGL_BUILD_URL%"); </script> </head> <body> <div id="gameContainer" style="width: %UNITY_WIDTH%px; height: %UNITY_HEIGHT%px; margin: auto"></div> </body> </html>C#←→JavaScriptの連携について
C#とブラウザのJavaScriptと連携する方法も公式ページにあります。
軽く手順説明します。C#→JavaScript
Assets/Plugins/
以下にこのようなファイルを作成します。test.jslibmergeInto(LibraryManager.library, { Hello: function () { //外部ライブラリを使った処理 }, });そして以下のようなコードを書き、WebGLでビルドすると
C#からtest.jslibのHello関数を通して、外部ライブラリの処理呼ぶことができます。NewBehaviourScript.csusing UnityEngine; using System.Runtime.InteropServices; public class NewBehaviourScript : MonoBehaviour { [DllImport("__Internal")] private static extern void Hello(); void Start() { Hello(); } }JavaScript→C#
話が脱線しますがこちらも
WebGLTemplatesのindex.htmlでこのように書きます。index.html<!DOCTYPE html> <html lang="en-us"> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Unity WebGL Player | %UNITY_WEB_NAME%</title> <script src="%UNITY_WEBGL_LOADER_URL%"></script> <script> var gameInstance = UnityLoader.instantiate("gameContainer", "%UNITY_WEBGL_BUILD_URL%"); gameInstance.SendMessage('MyGameObject', 'MyFunction', 5); </script> </head> <body> <div id="gameContainer" style="width: %UNITY_WIDTH%px; height: %UNITY_HEIGHT%px; margin: auto"></div> </body> </html>公式ページに明記されてないと思いますが、以下のように書く必要がありました。
gameInstance.SendMessage('MyGameObject', 'MyFunction', 5)ちょっと工夫
jslibにコードを書いて動作確認をするには、毎回プロジェクトをビルドする必要があります。(よね?)
結構時間がかかって大変だったので、私はWebGLTemplatesにJavaScriptファイルを作成して
ビルド後に一緒に吐き出されるそのファイルを編集して確認していました。また、index.htmlにボタンを追加し、jslibからは実行せずに、そのボタンによってJavaScriptの処理が実行されるようにしました
動作的には
- シーンに応じてC#→jslibでボタンを有効化無効化 (タイトル画面でログインボタンを有効にするとか)
- ボタンでJavaScriptを実行
- JavaScriptの処理結果をC#に渡す
という感じです。
記事を書いたときの環境
Unity 2018 3.8f1
- 投稿日:2019-04-15T21:54:14+09:00
[Unity] Post Processing Stackで MinAttribute のエラーが出るようになった時の対処
MinDrawer.cs 内の MinAttribute を UnityEngine.PostProcessing.MinAttribute に変更することでエラーを回避できます。
MinDrawer.csusing UnityEngine; using UnityEngine.PostProcessing; namespace UnityEditor.PostProcessing { [CustomPropertyDrawer(typeof(UnityEngine.PostProcessing.MinAttribute))] sealed class MinDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { UnityEngine.PostProcessing.MinAttribute attribute = (UnityEngine.PostProcessing.MinAttribute)base.attribute; if (property.propertyType == SerializedPropertyType.Integer) { int v = EditorGUI.IntField(position, label, property.intValue); property.intValue = (int)Mathf.Max(v, attribute.min); } else if (property.propertyType == SerializedPropertyType.Float) { float v = EditorGUI.FloatField(position, label, property.floatValue); property.floatValue = Mathf.Max(v, attribute.min); } else { EditorGUI.LabelField(position, label.text, "Use Min with float or int."); } } } }ただ、現在はPostProcessingStack2.xがあるので、新しくプロジェクトをつくるのであればそちらを使用したほうが良いでしょう。
- 投稿日:2019-04-15T21:54:14+09:00
[Unity] Post Processing Stack で MinAttribute のエラーが出るようになった時の対処
Post Processing Stack をUnity2018.3以降でビルドすると、以下のエラーが出るようになりました。
Assets\PostProcessing\Editor\PropertyDrawers\MinDrawer.cs(6,34): error CS0104: 'MinAttribute' is an ambiguous reference between 'UnityEngine.PostProcessing.MinAttribute' and 'UnityEngine.MinAttribute'MinDrawer.cs 内の MinAttribute を UnityEngine.PostProcessing.MinAttribute に変更することでエラーを回避できます。
MinDrawer.csusing UnityEngine; using UnityEngine.PostProcessing; namespace UnityEditor.PostProcessing { [CustomPropertyDrawer(typeof(UnityEngine.PostProcessing.MinAttribute))] sealed class MinDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { UnityEngine.PostProcessing.MinAttribute attribute = (UnityEngine.PostProcessing.MinAttribute)base.attribute; if (property.propertyType == SerializedPropertyType.Integer) { int v = EditorGUI.IntField(position, label, property.intValue); property.intValue = (int)Mathf.Max(v, attribute.min); } else if (property.propertyType == SerializedPropertyType.Float) { float v = EditorGUI.FloatField(position, label, property.floatValue); property.floatValue = Mathf.Max(v, attribute.min); } else { EditorGUI.LabelField(position, label.text, "Use Min with float or int."); } } } }ただ、現在はPostProcessingStack2.xがあるので、新しくプロジェクトをつくるのであればそちらを使用したほうが良いでしょう。
- 投稿日:2019-04-15T21:41:58+09:00
Unity、アウトラインを出してみる【Shader : 3】
はじめに
この記事は連載記事です。
記事を読むにあたってレンダリングパイプラインでどのような処理を行っているのか?を知っている事は前提になります。
非常に分かりやすい資料がありますので、レンダリングパイプラインを知らない方は読む前にご覧ください。Unity道場 2019.2 シェーダを書けるプログラマになろう #1 シェーダを理解しよう
Unity道場 2019.2 シェーダを書けるプログラマになろう #2 GPUの神秘アーカイブ
Unity、Shaderことはじめ【Shader : 0】
Unity、UnlitShader/textureのコードを追いかける【Shader : 1】
Unity、UVScroll(UVの処理)をやってみる【Shader : 2】導入
ちょうど最近、技術書典がありレイマーチングの季節になりました。
なので、「ジュリア集合とかマンデルブロ集合を書いていきます」といいたい所ですが、この記事書いてる人は「フラクタル?アニメじゃないの?」という感じなので、地に足ついて基本的なShaderの書き方をまとめてゆきます。アウトライン概要
感覚的には「1オブジェクトあたり1回のレンダリング」ですが、実際の所複数レンダリングできるみたいです。
ポピュラーな例?として、トゥーンレンダリングの輪郭線なんかがそれに該当します。トゥーンレンダリングの輪郭線の前に…オブジェクトの裏面を描画するケースについて話します。
おそらく、メジャーな例ではSkydomeが該当すると思います。
球体のオブジェクトがSkydomeで、球体の中が表・球体の外が裏側となっています。
この状態でSkydomeの中にカメラを入れて描画すると、球体は空のように機能します。
プラネタリウムも同じ仕組みというと分かりやすそう?
そして、Skydomeの中にもう1つ球体を置くとこのようになります。
Skydome側は表からは遮るものがないので、中のオブジェクトがレンダリングできます。
さらに、Skydomeのテクスチャを黒にしてみると…
球体に黒い輪郭がついてるように見えます。
これが(リアルタイムレンダリングで)トゥーンシェーダーで輪郭線をレンダリングする仕組みです。プリレンダの場合、ちゃんと輪郭線がどこなのか?のシュミレーションを行っている。複雑な形状だと輪郭を誤検知する事もある。
今回の場合、Skydomeと中の球体を2つ用意したわけですが、1つのオブジェクトでも「頂点を外側に拡大→裏面をレンダリングする→テクスチャの色ではなく輪郭線にしたい色で塗りつぶす」そして、そのあとに通常通り表面をレンダリングするという事をShader側でやってやれば、輪郭線が表示できるわけです。
というわけで「輪郭線のあるトゥーンシェーダーは2回分(2パスに分けて)レンダリングしている」というわけです。複数Passの書き方
MultiPath.shaderShader "MultiPath" { SubShader { Tags { "RenderType"="Opaque" } LOD 100 // 1回目のレンダリング Pass { //裏面 Cull Front CGPROGRAM //(略) ENDCG } // 2回目のレンダリング Pass { //表面 Cull Back CGPROGRAM // (略) ENDCG } // 3回目、4回目以降は下に Pass{} を増やしてゆく } }単純に
Pass{}
でくくってCGPROGRAM
とENDCG
の間に追加のレンダリング内容を書けば、複数回のパスでレンダリングされます。レンダリングは上から順に行われます。上記のサンプルはよくあるパターンを想定して、1回目のパスでは裏面を。2回目のパスで表面をレンダリングしてますが、2回表面のパスを描く事もできますし、3回目以降も下に続けてゆけば書けます。
(その分、処理が増えますが)
Cull Back
とCull Front
はカメラの向きを基準に表を描画するか、裏を描画するか?のプロパティです。
表面に関しては、省略しても問題ないですが、2パスある場合はあえて明示しておくと分かりやすいので書きました。
ShaderLab :Culling と Depth TestingUnlitShaderにアウトラインの表示を追加したSample
UnlitOutLine.shaderShader "Unlit/UnlitOutLine" { Properties { _MainTex ("Texture", 2D) = "white" {} _LineColor("Line Color", Color) = (1, 1, 1, 1) _LineWidth("Line Width", Range(0.001, 0.2)) = 0.01 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 //Line Pass { //Back Side Cull Front CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float _LineWidth; fixed4 _LineColor; v2f vert (appdata v) { //offset方向の計算 float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal)); float2 offset = TransformViewToProjection(normal.xy); //OutLineとしてレンダリングされるポリゴン v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.vertex.xy = o.vertex.xy + offset * _LineWidth; UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = _LineColor; UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } //Unlit Pass { //Surface Cull Back CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } }解説
UnlitOutLine.shaderstruct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float _LineWidth; fixed4 _LineColor; v2f vert (appdata v) { //offset方向の計算 float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal)); float2 offset = TransformViewToProjection(normal.xy); //OutLineとしてレンダリングされるポリゴン v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.vertex.xy = o.vertex.xy + offset * _LineWidth; UNITY_TRANSFER_FOG(o,o.vertex); return o; }アウトラインのレンダリングでポイントとなるのは、アウトラインを描画すべき裏面の大きさを計算しているバーテクスシェーダーです。
そこさえわかれば、線の色に工夫しなくてよいなら、ピクセルシェーダーはプロパティで指定されたカラーを渡すだけなので簡単です。まずここの行。
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));v.normal はモデル原点を基準とした法線方向なので、これをカメラから見た時の法線方向に変換する必要があります。
そこで、(float3x3)UNITY_MATRIX_IT_MV (モデルビュー行列の逆行列の転置行列) を mul()関数で掛けると、カメラから見た法線ベクトルが求まるそうです。※mul()は掛け算、HLSLでは行列の掛け算で演算子を使わないらしい。
※UNITY_MATRIX_IT_MVを掛けると何故、カメラから見た時の状態になるのか?はそもそも行列とベクトルを理解する必要があります(なんもわからん…)float2 offset = TransformViewToProjection(normal.xy);次の行で、3次元ベクトルの法線情報を、2次元ベクトルに変換しています。
TransformViewToProjection()
の関数がよしなにやってくれるそうですが、この関数はUnityCG.cginc
のライブラリに定義されています。
UnityEditorにコンパイルされてるので、何もせずとも使えますが、ライブラリの中身を見たければダウンロードする必要があります。Unity ダウンロード アーカイブ
※ドロップダウンから「ビルトインシェーダー」を選択してダウンロードし、その中にある「CGIncludes」の .cginc を読むと、関数の中身が分かる。v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.vertex.xy = o.vertex.xy + offset * _LineWidth;そして、最後はいつもどおりの処理で、オブジェクト座標をクリップ座標に変換。
クリップ座標に「offsetで求めたベクトル(外側の方向)×ラインの太さ(ベクトルの長さ)」
を足してやることで、実際のモデルよりも大きくなった場合のメッシュが求まります。あとは、フラグメントシェーダーに渡してやって、そこのメッシュを塗った後…
2パス目でUnlitShaderをレンダリングするとアウトラインのあるUnlitShaderになります。おわり
行列ちゃんとやろう…Unityなんもわからん…
次
まだ
- 投稿日:2019-04-15T21:05:57+09:00
プライバシーポリシー
プライバシーポリシー
・ユーザー登録は必要ありません。
・個人情報に類する情報を明示的に収集・利用していません。
・組み込んでいるライブラリが個人情報を利用していません。
- 投稿日:2019-04-15T19:21:38+09:00
[Unity] 複数のアセットから参照されているアセットを見つけるエディタ拡張
概要
Projectビューで選択中のアセットから共通して参照されているアセットを参照されている数が多い順にログに出力するスクリプト。
using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; public static class AssetUtility { [MenuItem("Assets/GetCommonDependencies")] public static void GetCommonDependenciesInSelection() { var assetPaths = Selection.assetGUIDs.Select(guid => AssetDatabase.GUIDToAssetPath(guid)).Distinct(); GetCommonDependencies(assetPaths); } public static void GetCommonDependencies(IEnumerable<string> assetPaths) { var dependencies = new List<string[]>(); foreach (var assetPath in assetPaths) { dependencies.Add(AssetDatabase.GetDependencies(assetPath, true)); } var assetPathCounts = new Dictionary<string, int>(); foreach (var temp in dependencies) { foreach (var assetPath in temp) { int count; assetPathCounts.TryGetValue(assetPath, out count); assetPathCounts[assetPath] = count + 1; } } foreach (var kvp in assetPathCounts.OrderByDescending(kvp => kvp.Value)) { if (kvp.Value > 1) { Debug.Log($"{kvp.Key}: {kvp.Value} references"); } } } }
- 投稿日:2019-04-15T18:44:57+09:00
アーカイブ化したmp3をデコードしよう【Unity】
UnityでMP3をちょっとトリッキーに扱いたい
Unityくんは優秀なので、wwwで外部のサウンドファイルを指定すれば勝手にデコードしてオーディオクリップにしてくれます。
https://docs.unity3d.com/ja/2018.2/ScriptReference/WWW.GetAudioClip.html
しかし……
「スタンドアロン(パソコン)でもMP3使いたいな~」とか
「アーカイブ化したファイルから読み込ませたいんだよね」とか
Unityくんは、そんな隙間ニーズには対応してくれていないのです。
(MP3はこないだまで有料規格だったしね……色々あるんだろうね)という訳で、自力でどうにかしましょう。
Windows用アプリケーションを作る場合
NAudioという偉大なモノがあるので楽勝ですね。
先達たちに感謝。https://github.com/naudio/NAudio
https://so-zou.jp/software/tech/programming/c-sharp/media/audio/naudio/Windows用アプリケーションではない場合
NAudio! おまえはクロスプラットフォームではないのだね……。
https://github.com/naudio/NAudio/issues/184
https://stackoverflow.com/questions/6186559/will-naudio-support-mac-osxということで、私はC#で書かれたプラットフォームに依存しないMP3デコーダーの情報を探してネットの海を彷徨うことになりました。
※しかし、全然情報なくてビックリだね。同じニーズは割とあると思うんだけど、検索しても日本語の情報はゼロ、英語でも検索からは全然情報が見つからず。わざわざ同じ苦労する人がいないように記事書いてます。そして、最終的に辿り着いたのがコイツ。
https://github.com/naudio/NLayerコイツをNAudioと一緒に使いましょう。まぁ、NAudioの説明をよく読んでれば探し回る必要もなかったんだけど……。
https://github.com/naudio/NAudio/blob/master/Docs/ConvertMp3ToWav.md
NLayer is a fully managed MP3 decoder, meaning it can run on any version of Windows (or indeed any .NET platform).
……ちゃんと書いてたね。NLayerを使えば『.NETが使える環境なら何でもいける(or indeed any .NET platform)』って。見落としてました。.NET(Mono)はクロスプラットフォームなので、この組み合わせで動きます。情報捜索で潰れた私の土曜日は何だったんや……。
具体的な手順
1 NAudioとNLayerの関数を使えるようにしよう
NAudioとNLayerをGitHubから[Clone or download]→[Download ZIP]でダウンロード。ZIPを解凍したら中身をそのままプロジェクトのAssetフォルダ内のどこかにどーん。(もちろん常識的な人はAssetフォルダ内にScriptフォルダを作ってるだろうから、整理整頓のためにもその中に入れよう)。これでNAudioやNLayerの名前空間を呼び出せばいつでも使えるようになった!
※この手のC#ソースを複数使ってるとソースごとのAssemblyInfo.csとかの記述が二重に被ってエラー出たりするので、出たエラーに応じて片方を消しましょう。2 関数を使ってデコードしよう!
mp3形式のStreamをwav形式のbyte配列に変換する関数
private byte[] Mp3ToWav(Stream st) { using (MemoryStream ms = new MemoryStream()) { using (var reader = new NAudio.Wave.Mp3FileReader(st, wf => new NLayer.NAudioSupport.Mp3FrameDecompressor(wf))) { if (reader.WaveFormat.Encoding == NAudio.Wave.WaveFormatEncoding.IeeeFloat) { NAudio.Wave.WaveFloatTo16Provider w32to16 = new NAudio.Wave.WaveFloatTo16Provider(reader); byte[] tmp=new byte[10000]; w32to16.Read(tmp,0,10000); NAudio.Wave.WaveFileWriter.WriteWavFileToStream(ms, w32to16); } if (reader.WaveFormat.Encoding == NAudio.Wave.WaveFormatEncoding.Pcm) { NAudio.Wave.WaveFileWriter.WriteWavFileToStream(ms, reader); } } return ms.ToArray(); } }……せや、wavのbyte配列をAudioClipにする方法説明してなかった。
https://github.com/deadlyfingers/UnityWav
コイツを使いましょう。例の如く(手順1を参照)AssetフォルダにDLしたファイルをぶち込めばOK。
で、上の関数とUnityWavを組み合わせてmp3ファイルのStreamをAudioClipに変換する関数
public AudioClip Mp3ToAudioClip(Stream st) { return WavUtility.ToAudioClip (Mp3ToWav(stream)); }できあがり。
- 投稿日:2019-04-15T16:26:41+09:00
Untiyなで初め Unityってなんじゃろな編
Unityとは?
Unityはゲームエンジンと呼ばれるジャンルのソフトウェアで、ゲームの開発を効率的に行うことのできるツール。
ゲームエンジンとして有名なものはUnityの他にUnreal Engine、Cry Engine辺りだろうか?
RPGツクールとか吉里吉里なんかも一応ゲームエンジンなのかな...?2Dゲームも3Dゲームも、パソコンのゲームもスマホのゲームもVRのゲームも、3DCG動画も。
高機能で柔軟なUnityは様々なものを作られるのに利用されている。代表例を上げるなら出不精の人も500kmほど歩くようになるポケモンGO、インディー発の超人気タイトルCuphead。
ゲーム実況者御用達の恐怖の森、シノアリス...などなど。最近では大企業のスマホゲーなんかでも見受けらる。
Unityのいいところ
- インストールがありえんほど楽
- すべてがGameObject、Componentの関係で一貫性があり、適当に扱ってもとりあえず動く
- Microsoft製のレトロな構文ながらもモダンな書き方もできる言語、C#を使う
- 初心者も熟練者も多く、日本語での情報がめちゃくちゃ多い
- よくできたComponentが多く、扱いやすい
- Assetが無料、有料問わずAsset Store上で多数公開されており、素材には困らない
Unityのよくないところ
- 完成したゲームがちょっと重たい
- オブジェクト同士が複雑に絡み合いがち。不具合の修正に時間がかかることがある。
- Unityでいろいろなことが簡単にできるあまりクオリティを高望みしてしまい、なかなか作品が完成しない
- キーコンフィグの実装がめんどくさい。標準ではゲーム起動前にUnityのダイアログを用いて行う。ダサい。
良くないところも意識していれば大きな問題にはならない。
それほど素敵なツールだってことだ。お言葉の説明
GameObject
GameObjectはUnityで表示されるキャラクターや障害物、UIなどを含めたすべてのもののこと。
GameObjectという存在によってUnity上ではなんでも同じように扱うことができる。Component
GameObjectに対して機能や特殊効果を追加したいときに使うものがComponent。
音を出したり、重力を計算したり、エフェクトを出したり、アニメーションさせたり、当たり判定をつけたり、光らせたり...
星の数ほどComponentがあるので、やりたいことがきっと実現できるはず。C#
Javaみたいな構文なのに、Kotlinみたいなモダンな書き方もできるMicrosoft製の言語。
オートプロパティや式形式のif文、foreach文も型推論などと機能が豊富で書いていて楽しい。Unityの他にWindowsフォームアプリケーションや、Xamarinなどで使われているらしい。
インストールしてみよう
インストーラのダウンロード
こちらからインストーラをダウンロードしよう。
Download - Unity
Unity を選択 + ダウンロード
を選ぶ。Unityには3エディションあるが、今回は無料のPersonalを利用する。
もし貴方が神ゲーの作者で収益をたくさん受け取っている場合はProやPlusを購読する必要があるので、心配な人は収益を確認してこよう。
有料のUnityにはデバッグツールや、素材が付属しているらしい。
その他にもゲーム起動時に表示されるUnityのロゴを消したりできる。無料のPersonalでもUnityロゴの表示がされる以外の機能制限が特にあるわけではないのでありがたく使わせてもらおう。
規約に同意して、ココをクリックするとインストーラがダウンロードされる!!
インストーラによるインストール
インストーラを起動して、規約を読み、ポチポチと進めていくとこんな画面になる。
ここにチェックを入れることでUnity本体と、ゲームを実行可能な形式として書き出すときに必要なツールと、開発環境のVisual Studioをインストールすることができる。
Build Support
は必要に応じてチェックを入れておこう。
チェックを入れ忘れても後から簡単に追加できるのでとりあえず何にもチェックを入れなくても大丈夫。Visual Studioにはデバッグツールも揃っており、高機能なIDEではあるが...
Windows向けの様々なビルドツールのせいでかなりストレージが圧迫されるし、インストールに長い時間を要するので、飲めるコーヒーが大量にあり、時間に余裕のある場合はチェックを入れてもいいかもしれない。ストレージに余裕がなかったり、時間が惜しい人はVisual Studio Codeでいいんじゃないかな。
チェックを入れ、適当にプチプチ進めていくとインストールが始まるのでコーヒーでもガブ飲みしながらゆっくり待とう。
Unityのみのインストールでもそこそこ時間がかかる。そしてそこそこ容量も食われる。インストールが済んだな?
それではUnityの沼へとハマっていこう!!次 -> 執筆中...
- 投稿日:2019-04-15T07:32:02+09:00
【Unity】ベジュ曲線のテレポート移動を一から作ってみる (1) for Oculus Go
はじめに
VRアプリでよく見かけるコントローラーから放物線を描くレイを飛ばし、レイが床に当たったポイントに移動する移動方法があります。この移動手段でよく使われているレイの放物線はベジュ曲線と呼ばれています。Unityでベジュ曲線を使ったテレポートを一から実装してみようと思います。
今回HMDはOculus Goを使います。また、Oculus GoアプリをUnityで開発できるようにするための設定周りは説明しません。お持ちでない方は、適当な3Dオブジェクト(見た目的に、カプセルの形がおすすめ)を擬似的にコントローラーとすることで進めてください。
この記事ではベジュ曲線の原理と共にレイを飛ばすところまでの実装方法を説明します。
ベジュー曲線とは
wikipediaの説明によると、
ベジェ曲線(ベジェきょくせん、Bézier Curve)またはベジエ曲線とは、N 個の制御点から得られる N − 1 次曲線である
この説明ではさっぱりわからないですね。
簡単に言うと、制御点と呼ばれるものから作られる滑らかな曲線のことです。
ここでは2次元ベジュ曲線を利用するので、2次ベジュ曲線について説明します。
2次ベジュ曲線を作るにはまず、制御点と呼ばれる点を3点用意します。隣り合う制御点同士を繋いだ直線を引きます。制御点同士を繋いだ直線を時間とともに徐々に進む点を作ります。先ほど作った徐々に進む点同士を繋いだ直線を引きます。先ほど作った直線を時間とともに徐々に進む点を作ります。最後に作った点の軌跡が2次元ベジュ曲線になります。
さらに制御点を増やし、同じ手順で曲線を作ると、3次ベジュ曲線、4次ベジュ曲線
と高次のベジュ曲線を描くことができます。高次になればなるほど、計算量は増えます。プレイヤーの準備
まず、y座標0の位置に、適当に床を作成しておいて下さい。
Assetストアから、Oculus Integrationをインポートして下さい。
Projectの
Oculus → VR → Prefabs → OVRCameraRig
、Oculus → VR → Prefabs → TrackedRemote
を使います。
OVRCameraRig
をHierarchyに配置し、OVRCameraRig
の子要素のLeftHandAnchor
、RightHandAnchor
にTrackedRemote
をアタッチしてください。床より2mほどあげてあげるとちょうど良い高さになります。アタッチした
TrackedRemote
のController
の項目はInspectorから正しい方(Left or Right)の値にセットしてください。これで、ビルドするとVR空間にコントローラーが動くだけのアプリができます。
コントローラーの位置・傾きから制御点を算出する
Projectsで
teleportController.cs
を作成し、TrackedRemote
にアタッチしてください。2次ベジュ曲線を作るためには、3点の制御点が必要です。3点(2次ベジュ曲線の黒色の点)を開始点(
p0
)、経過点(p1)
、終着点(p2
)とします。制御点を算出するために、2つのパラメーターを用意します。
distance
とdropHeight
です。
distance
は移動距離の補正に使用します。
dropHeight
は開始点より終着点をどれだけ下げるか調整する値です。開始点は、(
コントローラーの位置
)になります。
経過点は、(開始点
) + (コントローラーの向き
) * (distance/2
) になります。
終着点は、(開始点
) + (コントローラーの向き
) * (distance
) で算出された点のYを(開始点のY - dropHeight
)にした点になります。下のレイのようなイメージです。
これを、コードに落としこむと、
Vector3 p0 = pointer.transform.position; Vector3 p1 = pointer.transform.position + pointer.transform.forward * distance/2; Vector3 p2 = pointer.transform.position + pointer.transform.forward * distance; p2.y = p0.y - dropHeight;制御点から生まれる点を算出する
3点の制御点を作ったので、制御点同士を結んだ直線上を動く点(2次ベジュ曲線の赤色の点)を算出します。2点は
b01
、b12
とします。今回、本来表現すべきベジュ曲線の軌跡の何点かを取得し、直線で繋げることで擬似的にベジュ曲線に見せる形で再現します。
その分割点の数をn
とします。
i
点目のb01
は、(p0 * (n-1-i) / (n-1)
) + (p1 * (i / (n-1))
)になります。
i
点目のb12
は、(p1 * (n-1-i) / (n-1)
) + (p2 * (i / (n-1))
)になります。※
i
は0から始まります。
b01
、b12
ができたので、2点を結んだ直線上を動く点(2次ベジュ曲線の黄色の点)を算出します。その点はb123
とします。
この点を直線で繋げることで、ベジュ曲線が完成します。
i
点目のb123
は、(b01 * (n-1-i) / (n-1)
) + (b12 * (i / (n-1))
)になります。
b123
を直線で繋げた時のレイは下のようなイメージになります。いい感じに、ベジュ曲線が描けてきました。
これを、コードに落とし込むと、
for(int i = 0; i < n; i++) { float amp = ((float)i/(float)(n-1)); Vector3 b01 = Vector3.Lerp(p0, p1, amp); Vector3 b12 = Vector3.Lerp(p1, p2, amp); Vector3 b012 = Vector3.Lerp(b01, b12, amp); }※
Lerp
は知らなければ、各自調べてください。ベジュ曲線を物体に衝突した段階で止める
このままでは、ベージュ曲線は物体と通過して表示されてしまいます。そのため、物体に衝突したかチェックし、衝突した段階で表示を止める必要があります。
2点の
b123
を繋げた直線上に、衝突する物体があるかどうかをチェックさせることで、これを実現します。2の位置情報を与えると、直線上に物体があるかどうかチェックしてくれる便利な関数がUnityには存在します。
Physics.Linecast(point1, point2, out hit)※
hit
には衝突したCollide
の情報が入ります。これを利用して、衝突した先の点を全て衝突した位置にします。
衝突した際のレイは下のようなイメージになります。
これをコードに落とし込むと、
Vector3 _b012 = p0; for(int i = 0; i < n; i++) { float amp = ((float)i/(float)(n-1)); Vector3 b01 = Vector3.Lerp(p0, p1, amp); Vector3 b12 = Vector3.Lerp(p1, p2, amp); Vector3 b012 = Vector3.Lerp(b01, b12, amp); RaycastHit hit; if(Physics.Linecast(_b012, b012, out hit)) { Vector3 hitPoint = hit.point; for(int i2 = i; i2 < n; i2++) { Debug.Log("plot hitPoint"); } break; } else { Debug.Log("plot b012"); _b012 = b012; } }LineRendererを利用してコードを完成させる
InitLine()
でLineRenderer
コンポーネントをアタッチしたGameObject
を作成します。
Draw()
で、これまで説明してきた内容を実行し、線を描いています。※
LineRenderer
の使い方は各自調べてください。using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; public class TeleportController : MonoBehaviour { // [SerializeField] GameObject pointer; [SerializeField] Material material; [SerializeField] float distance = 10; [SerializeField] float dropHeight = 5; [SerializeField] int positionCount = 10; [SerializeField] float width = 0.1f; GameObject line; LineRenderer lRend; void Start() { pointer = this.gameObject; InitLine(); } void Update() { Draw(); } void InitLine() { line = new GameObject("Line"); line.transform.parent = pointer.transform; lRend = line.AddComponent<LineRenderer>(); lRend.receiveShadows = false; lRend.shadowCastingMode = ShadowCastingMode.Off; lRend.loop = false; lRend.positionCount = positionCount; lRend.startWidth = width; lRend.endWidth = width; lRend.material = material; } void Draw() { Vector3 p0 = pointer.transform.position; Vector3 p1 = pointer.transform.position + pointer.transform.forward * distance/2; Vector3 p2 = pointer.transform.position + pointer.transform.forward * distance; p2.y = p0.y - dropHeight; Vector3 _b012 = p0; for(int i = 0; i < positionCount; i++) { float amp = ((float)i/(float)(positionCount-1)); Vector3 b01 = Vector3.Lerp(p0, p1, amp); Vector3 b12 = Vector3.Lerp(p1, p2, amp); Vector3 b012 = Vector3.Lerp(b01, b12, amp); RaycastHit hit; if(Physics.Linecast(_b012, b012, out hit)) { Debug.Log("hit!!"); Vector3 hitPoint = hit.point; for(int i2 = i; i2 < positionCount; i2++) { lRend.SetPosition(i2, hitPoint); } break; } else { lRend.SetPosition(i, b012); _b012 = b012; } } } }Oculus Goのコントローラーでレイの制御をできるようにする
ここから先は、Oculus Go固有の対応なので、持ってない方は読む必要はありませんん。
現状だと、
LeftHandAnchor
、RightHandAnchor
のTrackedRemote
両方からレイが出てしまうので、どちら側(L or R)でコントローラーを持っているか判定し、持っている方のTrackedRemote
のみレイを表示させるようにします。まずは、使用するOculus IntegrationのAPIを説明します。
OVRInput.Controller
型でどちらが側(L or R)のコントローラーか設定させます。OVRInput.IsControllerConnected(mController)
コントローラーがアクティブかどうかチェックできます。OVRInput.Controller mController; bool controllerConnected = OVRInput.IsControllerConnected(mController);
OVRInput.GetDown([ボタン])
でボタンが押された時の検知を、OVRInput.GetUp([ボタン])
でボタンを離した時の検知を拾えます。ボタンの部分はたくさんあるので、OVRInput(公式サイト)をみてください。
ここではトリガーを使うので、
OVRInput.Button.PrimaryIndexTrigger
です。if(OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) { Debug.Log("Down Trigger"); } if(OVRInput.GetUp(OVRInput.Button.PrimaryIndexTrigger)) { Debug.Log("Up Trigger"); }今説明したAPIを利用して、コントローラーがアクティブかつトリガーが引かれている時のみレイを飛ばすようにします。
activeDraw
を用意することで、無駄にベジュ曲線の計算をさせることを防いでいます。
トリガーを押したとき、離したときそれぞれ別の関数を呼び、関数のなかでレイのアクティブ化、activeDraw
の切り替えをさせています。[SerializeField] OVRInput.Controller mController; bool activeDraw = false; ... void Update() { bool controllerConnected = OVRInput.IsControllerConnected(mController); if(!controllerConnected) { return; } else { if(OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) { OnTriggerDown(); } if(OVRInput.GetUp(OVRInput.Button.PrimaryIndexTrigger)) { OnTriggerUp(); } if(activeDraw) { Draw(); } } } void OnTriggerDown() { activeDraw = true; line.SetActive(true); } void OnTriggerUp() { activeDraw = false; line.SetActive(false); }トリガーを引けばレイが出るようになりました。
さいごに
次は、テレポートできるエリアの設定とトリガーを離したときテレポートできるようにします。
- 投稿日:2019-04-15T07:32:02+09:00
【Unity】ベジェ曲線のテレポート移動を一から作ってみる (1) for Oculus Go
はじめに
VRアプリでよく見かけるコントローラーから放物線を描くレイを飛ばし、レイが床に当たったポイントに移動する移動方法があります。この移動手段でよく使われているレイの放物線はベジェ曲線と呼ばれています。Unityでベジェ曲線を使ったテレポートを一から実装してみようと思います。
今回HMDはOculus Goを使います。また、Oculus GoアプリをUnityで開発できるようにするための設定周りは説明しません。お持ちでない方は、適当な3Dオブジェクト(見た目的に、カプセルの形がおすすめ)を擬似的にコントローラーとすることで進めてください。
この記事ではベジェ曲線の原理と共にレイを飛ばすところまでの実装方法を説明します。
ベジェ曲線とは
wikipediaの説明によると、
ベジェ曲線(ベジェきょくせん、Bézier Curve)またはベジエ曲線とは、N 個の制御点から得られる N − 1 次曲線である
この説明ではさっぱりわからないですね。
簡単に言うと、制御点と呼ばれるものから作られる滑らかな曲線のことです。
ここでは2次元ベジェ曲線を利用するので、2次ベジェ曲線について説明します。
2次ベジェ曲線を作るにはまず、制御点と呼ばれる点を3点用意します。隣り合う制御点同士を繋いだ直線を引きます。制御点同士を繋いだ直線を時間とともに徐々に進む点を作ります。先ほど作った徐々に進む点同士を繋いだ直線を引きます。先ほど作った直線を時間とともに徐々に進む点を作ります。最後に作った点の軌跡が2次元ベジェ曲線になります。
さらに制御点を増やし、同じ手順で曲線を作ると、3次ベジェ曲線、4次ベジェ曲線
と高次のベジェ曲線を描くことができます。高次になればなるほど、計算量は増えます。プレイヤーの準備
まず、y座標0の位置に、適当に床を作成しておいて下さい。
Assetストアから、Oculus Integrationをインポートして下さい。
Projectの
Oculus → VR → Prefabs → OVRCameraRig
、Oculus → VR → Prefabs → TrackedRemote
を使います。
OVRCameraRig
をHierarchyに配置し、OVRCameraRig
の子要素のLeftHandAnchor
、RightHandAnchor
にTrackedRemote
をアタッチしてください。床より2mほどあげてあげるとちょうど良い高さになります。アタッチした
TrackedRemote
のController
の項目はInspectorから正しい方(Left or Right)の値にセットしてください。これで、ビルドするとVR空間にコントローラーが動くだけのアプリができます。
コントローラーの位置・傾きから制御点を算出する
Projectsで
teleportController.cs
を作成し、TrackedRemote
にアタッチしてください。2次ベジェ曲線を作るためには、3点の制御点が必要です。3点(2次ベジェ曲線の黒色の点)を開始点(
p0
)、経過点(p1)
、終着点(p2
)とします。制御点を算出するために、2つのパラメーターを用意します。
distance
とdropHeight
です。
distance
は移動距離の補正に使用します。
dropHeight
は開始点より終着点をどれだけ下げるか調整する値です。開始点は、(
コントローラーの位置
)になります。
経過点は、(開始点
) + (コントローラーの向き
) * (distance/2
) になります。
終着点は、(開始点
) + (コントローラーの向き
) * (distance
) で算出された点のYを(開始点のY - dropHeight
)にした点になります。下のレイのようなイメージです。
これを、コードに落としこむと、
Vector3 p0 = pointer.transform.position; Vector3 p1 = pointer.transform.position + pointer.transform.forward * distance/2; Vector3 p2 = pointer.transform.position + pointer.transform.forward * distance; p2.y = p0.y - dropHeight;制御点から生まれる点を算出する
3点の制御点を作ったので、制御点同士を結んだ直線上を動く点(2次ベジェ曲線の赤色の点)を算出します。2点は
b01
、b12
とします。今回、本来表現すべきベジェ曲線の軌跡の何点かを取得し、直線で繋げることで擬似的にベジェ曲線に見せる形で再現します。
その分割点の数をn
とします。
i
点目のb01
は、(p0 * (n-1-i) / (n-1)
) + (p1 * (i / (n-1))
)になります。
i
点目のb12
は、(p1 * (n-1-i) / (n-1)
) + (p2 * (i / (n-1))
)になります。※
i
は0から始まります。
b01
、b12
ができたので、2点を結んだ直線上を動く点(2次ベジェ曲線の黄色の点)を算出します。その点はb123
とします。
この点を直線で繋げることで、ベジェ曲線が完成します。
i
点目のb123
は、(b01 * (n-1-i) / (n-1)
) + (b12 * (i / (n-1))
)になります。
b123
を直線で繋げた時のレイは下のようなイメージになります。いい感じに、ベジェ曲線が描けてきました。
これを、コードに落とし込むと、
for(int i = 0; i < n; i++) { float amp = ((float)i/(float)(n-1)); Vector3 b01 = Vector3.Lerp(p0, p1, amp); Vector3 b12 = Vector3.Lerp(p1, p2, amp); Vector3 b012 = Vector3.Lerp(b01, b12, amp); }※
Lerp
は知らなければ、各自調べてください。ベジェ曲線を物体に衝突した段階で止める
このままでは、ベージュ曲線は物体と通過して表示されてしまいます。そのため、物体に衝突したかチェックし、衝突した段階で表示を止める必要があります。
2点の
b123
を繋げた直線上に、衝突する物体があるかどうかをチェックさせることで、これを実現します。2の位置情報を与えると、直線上に物体があるかどうかチェックしてくれる便利な関数がUnityには存在します。
Physics.Linecast(point1, point2, out hit)※
hit
には衝突したCollide
の情報が入ります。これを利用して、衝突した先の点を全て衝突した位置にします。
衝突した際のレイは下のようなイメージになります。
これをコードに落とし込むと、
Vector3 _b012 = p0; for(int i = 0; i < n; i++) { float amp = ((float)i/(float)(n-1)); Vector3 b01 = Vector3.Lerp(p0, p1, amp); Vector3 b12 = Vector3.Lerp(p1, p2, amp); Vector3 b012 = Vector3.Lerp(b01, b12, amp); RaycastHit hit; if(Physics.Linecast(_b012, b012, out hit)) { Vector3 hitPoint = hit.point; for(int i2 = i; i2 < n; i2++) { Debug.Log("plot hitPoint"); } break; } else { Debug.Log("plot b012"); _b012 = b012; } }LineRendererを利用してコードを完成させる
InitLine()
でLineRenderer
コンポーネントをアタッチしたGameObject
を作成します。
Draw()
で、これまで説明してきた内容を実行し、線を描いています。※
LineRenderer
の使い方は各自調べてください。using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; public class TeleportController : MonoBehaviour { // [SerializeField] GameObject pointer; [SerializeField] Material material; [SerializeField] float distance = 10; [SerializeField] float dropHeight = 5; [SerializeField] int positionCount = 10; [SerializeField] float width = 0.1f; GameObject line; LineRenderer lRend; void Start() { pointer = this.gameObject; InitLine(); } void Update() { Draw(); } void InitLine() { line = new GameObject("Line"); line.transform.parent = pointer.transform; lRend = line.AddComponent<LineRenderer>(); lRend.receiveShadows = false; lRend.shadowCastingMode = ShadowCastingMode.Off; lRend.loop = false; lRend.positionCount = positionCount; lRend.startWidth = width; lRend.endWidth = width; lRend.material = material; } void Draw() { Vector3 p0 = pointer.transform.position; Vector3 p1 = pointer.transform.position + pointer.transform.forward * distance/2; Vector3 p2 = pointer.transform.position + pointer.transform.forward * distance; p2.y = p0.y - dropHeight; Vector3 _b012 = p0; for(int i = 0; i < positionCount; i++) { float amp = ((float)i/(float)(positionCount-1)); Vector3 b01 = Vector3.Lerp(p0, p1, amp); Vector3 b12 = Vector3.Lerp(p1, p2, amp); Vector3 b012 = Vector3.Lerp(b01, b12, amp); RaycastHit hit; if(Physics.Linecast(_b012, b012, out hit)) { Debug.Log("hit!!"); Vector3 hitPoint = hit.point; for(int i2 = i; i2 < positionCount; i2++) { lRend.SetPosition(i2, hitPoint); } break; } else { lRend.SetPosition(i, b012); _b012 = b012; } } } }Oculus Goのコントローラーでレイの制御をできるようにする
ここから先は、Oculus Go固有の対応なので、持ってない方は読む必要はありませんん。
現状だと、
LeftHandAnchor
、RightHandAnchor
のTrackedRemote
両方からレイが出てしまうので、どちら側(L or R)でコントローラーを持っているか判定し、持っている方のTrackedRemote
のみレイを表示させるようにします。まずは、使用するOculus IntegrationのAPIを説明します。
OVRInput.Controller
型でどちらが側(L or R)のコントローラーか設定させます。OVRInput.IsControllerConnected(mController)
コントローラーがアクティブかどうかチェックできます。OVRInput.Controller mController; bool controllerConnected = OVRInput.IsControllerConnected(mController);
OVRInput.GetDown([ボタン])
でボタンが押された時の検知を、OVRInput.GetUp([ボタン])
でボタンを離した時の検知を拾えます。ボタンの部分はたくさんあるので、OVRInput(公式サイト)をみてください。
ここではトリガーを使うので、
OVRInput.Button.PrimaryIndexTrigger
です。if(OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) { Debug.Log("Down Trigger"); } if(OVRInput.GetUp(OVRInput.Button.PrimaryIndexTrigger)) { Debug.Log("Up Trigger"); }今説明したAPIを利用して、コントローラーがアクティブかつトリガーが引かれている時のみレイを飛ばすようにします。
activeDraw
を用意することで、無駄にベジェ曲線の計算をさせることを防いでいます。
トリガーを押したとき、離したときそれぞれ別の関数を呼び、関数のなかでレイのアクティブ化、activeDraw
の切り替えをさせています。[SerializeField] OVRInput.Controller mController; bool activeDraw = false; ... void Update() { bool controllerConnected = OVRInput.IsControllerConnected(mController); if(!controllerConnected) { return; } else { if(OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) { OnTriggerDown(); } if(OVRInput.GetUp(OVRInput.Button.PrimaryIndexTrigger)) { OnTriggerUp(); } if(activeDraw) { Draw(); } } } void OnTriggerDown() { activeDraw = true; line.SetActive(true); } void OnTriggerUp() { activeDraw = false; line.SetActive(false); }トリガーを引けばレイが出るようになりました。
さいごに
次は、テレポートできるエリアの設定とトリガーを離したときテレポートできるようにします。