- 投稿日:2019-02-11T23:28:57+09:00
ポスト Uniduino に何を使おうか
Uniduinoを愛用していたけど、開発終わったっぽく、どうしようかなーと最近悩み中。
https://portutility.com/ja/
あたりが、ガチなシリアルポートアセットで見た感じとてもよさげだが、1万円コースなので、試すの購入躊躇してる...酔った勢いでポチった暁には、結果を改めて書こうと思う。
Raspberry Piとかやるときも問題なさそうではあるし、組み込んでいい感じに使えるなら、いいなぁ。
https://qiita.com/saitotak/items/e2006423a40bb89cb653
- 投稿日:2019-02-11T22:52:05+09:00
VR180 Mesh Projection Box の技術解説
はじめに
大分以前に VR180 ビデオの Metadata の一部である "Mesh Projection Box" のパーサーとそれに基づいて Unity の Mesh を生成するコードを書いてたのですが、これの解説記事書いてなかったなーと思ったので書いてみることにしました。
VR180 (VR ビデオ) の Metadata
VR ビデオには規定の Metadata を設定する必要があります。
従来の 360 度全天球では "Spherical Video RFC" でしたが VR180 に合わせて (?) V2 の規格ができました。 Mirage Camera はこの規格に沿ったファイルで MP4 を記録しています。
VR ビデオの Metadata の一番の目的は どのような形状の映像に変形 (配置) して記録しているか の情報を定義するものです。これがないと結局はただの 2D の映像ということになり、通常のパースの映像とは大分異なる、ある種とても見づらい映像で表示することになります (あるいは決め打ちかユーザー側に個別に設定してもらう) 。
- 記録している映像の投影方式は? (equirectanglur, fisheye etc.)
- ステレオか? ステレオなら side-by-side は左右分割か、上下分割か。左右の目の割り当ては?
これらの情報を元にプレイヤーはレンダリング時に適切な変形を行うことで記録時の想定に沿った形で表示ができるわけです。
V2 では多くの情報を MP4 フォーマットに準拠した Box 形式で格納するようになったのが V1 との大きな違いです。このうち、映像の投影方式の定義が 3 種類存在します。
Cubemap Projection Box
環境マッピングをする際のおなじみの方式。自分を中心として 6 方向の平面にそれぞれの視野角 90 度のパース映像を配置するやり方。
Equirectangular Projection Box
デファクトスタンダードとなっている "正距円筒図法 (equirectangular)" で記録されている場合のその詳細情報を定義しているものです。 VR (パノラマ) 映像を扱う場合、一度は必ず見るはずの形式です。
なぜこの方式がよく使われるかというと、コンピューター上でレンダリングする際に都合がよいため (UV 座標が緯度経度に対応しているので取り扱いが容易) というのが主な理由のようです。
Mesh Projection Box
最後の一つが今回の目的である Mesh Projection Box です。これは驚くことに Mesh 情報 (頂点座標、 UV 座標、インデックス) がそのまま記録されています。 Mesh に映像を投影 (projection) するわけです。
この形式を採用したのはおそらく あらゆる投影方式を表現できるから ではないかと思います。 cubemap や equirectangular ももちろん表現可能ですが、 これらはよく使われている (ほとんどの場合は equirectanglar ではないかと) ので特例扱いしたのではないかと思います。
equirectangular 以外では魚眼レンズ (fish eye) 形式になるかと思います。
fish eye 形式についてはこちらの記事で実例を解説しています。この記事では 3D 空間から fish eye へのレンダリングを実装していますが、この逆 (fish eye から対応する Mesh 情報を構築する) ももちろん可能ではあります。
しかし一言で fish eye と言っても投影方式が複数ありますし、同じ投影方式でも視野角が異なったりするため equirectangular と比べてパラメーターが複雑になりがちです。つまり
- 様々な投影方式を仕様で定義する必要がある。 → 未知の方式が出てきた時に困る
- 実装側は仕様に定義されている投影方式に合わせて処理を個別に実装必要がある。
ということがあるのですが、これを Mesh で定義することにより "Mesh からレンダリングする" という統一的な手法で対応できることになって、仕様定義側も実装側も単一の定義で対応できるというメリットが得られます。 equirectanglar も結局球体の内側にテクスチャーマッピングしたものをレンダリングする、というパターンが多いわけでその場合はやる事同じなわけですし。
デメリットとしては
- データー量がとても大きい。圧縮もできますがそれでも大きい。投影方式と視野角だけだったら 2 項目。
- 論理的にどのような投影方式かがわからない。頂点情報だけで判断するのは難しいでしょう。
前者は映像データー本体と比べれば大きいわけでもないので気にする必要はないかもですが、後者は知りたい事もあると思うのでつけてくれてもよかったのではないかなあと思いました。
Mesh Projection Box は圧縮もしたりするのでリアルタイムに作るには重たい処理になりそうですが、カメラの場合はこれはレンズ特性を表すものなので符号化されたバイナリーデーターをファーム内に保持して撮影時にはそれをコピーしているだけと思います。よって撮影時には特に負荷はないと思います。読み込み時もストリームにつき一つなので、初期化時に作ってあとは使いまわしとなりこちらも特に負荷はないでしょう。
VR180 Mesh Projection Box Parser の概要
"VR180 Mesh Projection Box Parser" は主に 2 つの機能があって
- MP4 の "Mesh Projection Box" を扱いやすい状態にパース (デシリアライズ) する。
- パースしたデーターを元に Unity の Mesh を構築する。
パース処理は Unity に依存していない C# コードなのでそのまま流用可能なはずです。
また、あくまで Mesh Projection Box のパースが主目的でパノラマ動画の再生を目的としていません (動作確認のため、副次的にそういう実装はしていますが) 。パノラマ動画再生を目的とするなら equirectangular や fisheye の投影対応も必要でしょう。
MeshProjectionBox クラス
MP4 の MeshProjectionBox を表すクラスです。
MeshProjectionBox では先頭の "encoding_four_cc" に符号化方式の fourcc が書き込まれていて、非圧縮か deflate 圧縮かのどちらかです。 .NET では好都合な事に deflate の実装がされている (DefalteStream クラス) のでこれを利用して復号します。
MeshProjectionBox の中は MeshBox が 1 つ以上存在します。
MeshBox クラス
MeshProjectionBox 内の MeshBox の内容を表すクラスです。項目は仕様通りにそのまま仕分けしています。加工は何もしません。
public struct MeshBoxVertex { public int x_index_delta; public int y_index_delta; public int z_index_delta; public int u_index_delta; public int v_index_delta; } public enum MeshBoxIndexType : int { Triangles = 0, TriangleStrip = 1, TriangleFan = 2 } public class MeshBoxVertexList { public int texture_id; public MeshBoxIndexType index_type; public int[] index_as_delta; } public class MeshBox { public float[] coordinates; public MeshBoxVertex[] verticies; public MeshBoxVertexList[] vertex_lists; }MeshBox は一般的な Mesh そのもので
- 頂点リスト (頂点座標と UV)
- インデックスリスト
のセットとなっています。
頂点座標と UV 座標は coordinate という浮動小数点値リストのインデックスで表現されています。通常は球体になるはずで、球体であれば同じ値が頻出する可能性があると考えられるので要素数の削減につながります。さらにインデックス値も直接保持するのではなく、直前インデックス値との相対値となっています (これは理由がよくわからない。相対範囲を絞っているわけでもないのでデーター量削減になってないように思います) 。
インデックスリストも頂点リストと同じように直前のインデックス値との相対値です。
インデックス相対値はその総数に応じて値のビット幅を決定することにより適切なデーター量になるようにしています。 (ccsb, vcsb)
VR180Mesh クラス
MeshProjectionBox から Unity の Mesh を生成するクラスです。このクラスは Unity 依存です。現実装ではいくつか決め打ちで書いているので対処をした方がよいかもしれません。MeshBox の内容を元に Unity 用の頂点リストとインデックスリストに作り変えます。
課題
カメラの姿勢情報に対応したかったのですが、 "Projection Header Box" や "Camera Motion Metadata Track" に有意なデーターが確認できすに対応ができていません。
おわりに
Mirage Camera は画質的には正直今一つなところはありますが、お手軽に VR180 Video の撮影ができるのでなかなかよいカメラではあると思います。
実際のところ VR180 Creator 等を通して equirectangular に変換した方が扱い的にはしやすいかもしれませんが、それはそれとして生データーでの扱いをできるようにしておくのもよいかなと思います。理屈的に画質劣化もないわけですし。
- 投稿日:2019-02-11T21:10:13+09:00
オブジェクトをジグザグに動かしてみる
はじめに
スーパーマリオ・ソニック等アクションゲームに代表されるものに、物体・地形が動くものがほぼまちがいなくある。
その中でも基本となる上下に動くものを今回作成すると同時に、2種類の方法を用いて挙動の違いを調べてみる。方法1 Mathf.Sinメソッド
3座標のうち
・2座標を動かす:円運動
・1座標を動かす:ジグザグpublic class CubeLR : MonoBehaviour { float x; //三角関数の数値設定 float speed = 3f; //スピードの数値設 float radius = 0.1f; //半径の設定 // Update is called once per frame void Update () { x = radius *Mathf.Sin(Time.time * speed); //三角関数による動きの設定。 //X座標のみ三角関数による動きの設定を反映 transform.position = new Vector3(x+transform.position.x,transform.position.y,transform.position.z); } }方法2 PingPongメソッド。
PingPong(Time.time(移動スピード),3(移動距離));
public class test : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { transform.position = new Vector3(Mathf.PingPong(Time.time,3),transform.position.y,transform.position.z); } }結果
青Cube : PingPong
緑Cube : Mathf.Sin感想
両方ともほぼ同じ動きになるが、PingPongは速度、距離の設定がとても簡単である。
わざわざMathf.Sin関数を使うメリットは今のところ感じられません。
- 投稿日:2019-02-11T17:28:00+09:00
【Unity】InputFieldの入力中に判定しない拡張Inputクラス(ExclusiveInput)
概要
Input
クラスのGetKeyDown
などはuGUIのInputField
にテキストを入力中でも判定がされてしまいますが、多くの場合は意図しない入力だと思います。そこで、テキスト入力中には入力判定を返さない拡張クラスを作成しました。gist: ExclusiveInput.cs
使い方
Input
クラスの代わりにExclusiveInput
経由でGetKeyDown
などを呼び出してください。void Update() { if (ExclusiveInput.GetKeyDown(KeyCode.A)) { // hoge } if (ExclusiveInput.GetKeyDown("a")) { // hoge } }対応メソッド
bool anyKeyDown
bool anyKey
float GetAxis(string axisName)
float GetAxisRaw(string axisName)
bool GetKey(string name)
bool GetKey(KeyCode key)
bool GetKeyDown(string name)
bool GetKeyDown(KeyCode key)
bool GetKeyUp(KeyCode key)
bool GetKeyUp(string name)
必要であれば適宜追加してください。
コード
EventSystem
クラスで現在フォーカス中のゲームオブジェクトを取得することが出来ます。そこでそのオブジェクトがInputField
であればtrue
を返すIsFocusedOnInputField
を実装し、各入力メソッドの前で判定を入れています。public static bool IsFocusedOnInputField { get { return EventSystem.current?.currentSelectedGameObject?.GetComponent<InputField>() != null; } }コード全文はこちらです。(gist: ExclusiveInput.cs)
ExclusiveInput.csusing UnityEngine.EventSystems; using UnityEngine.UI; namespace UnityEngine { public class ExclusiveInput { public static bool IsFocusedOnInputField { get { return EventSystem.current?.currentSelectedGameObject?.GetComponent<InputField>() != null; } } // // Summary: // Returns true the first frame the user hits any key or mouse button. (Read Only) public static bool anyKeyDown { get { return !IsFocusedOnInputField && Input.anyKeyDown; } } // // Summary: // Is any key or mouse button currently held down? (Read Only) public static bool anyKey { get { return !IsFocusedOnInputField && Input.anyKey; } } // // Summary: // Returns the value of the virtual axis identified by axisName. // // Parameters: // axisName: public static float GetAxis(string axisName) { return IsFocusedOnInputField ? 0 : Input.GetAxis(axisName); } // // Summary: // Returns the value of the virtual axis identified by axisName with no smoothing // filtering applied. // // Parameters: // axisName: public static float GetAxisRaw(string axisName) { return IsFocusedOnInputField ? 0 : Input.GetAxisRaw(axisName); } // // Summary: // Returns true while the user holds down the key identified by name. // // Parameters: // name: public static bool GetKey(string name) { return !IsFocusedOnInputField && Input.GetKey(name); } // // Summary: // Returns true while the user holds down the key identified by the key KeyCode // enum parameter. // // Parameters: // key: public static bool GetKey(KeyCode key) { return !IsFocusedOnInputField && Input.GetKey(key); } // // Summary: // Returns true during the frame the user starts pressing down the key identified // by name. // // Parameters: // name: public static bool GetKeyDown(string name) { return !IsFocusedOnInputField && Input.GetKeyDown(name); } // // Summary: // Returns true during the frame the user starts pressing down the key identified // by the key KeyCode enum parameter. // // Parameters: // key: public static bool GetKeyDown(KeyCode key) { return !IsFocusedOnInputField && Input.GetKeyDown(key); } // // Summary: // Returns true during the frame the user releases the key identified by the key // KeyCode enum parameter. // // Parameters: // key: public static bool GetKeyUp(KeyCode key) { return !IsFocusedOnInputField && Input.GetKeyUp(key); } // // Summary: // Returns true during the frame the user releases the key identified by name. // // Parameters: // name: public static bool GetKeyUp(string name) { return !IsFocusedOnInputField && Input.GetKeyUp(name); } } }
- 投稿日:2019-02-11T17:12:16+09:00
Resources.LoadのBestPracticesの検証(3,複数回読んでみる
Resources.LoadのBestPracticesの検証(2,使用している状態で消してみる
の続き
UnityのAddComponentのつくり上、
いろんなところで複数回よばれ、いろんなところで解放
が呼ばれるはずなので軽く
2個生成して2個削除したときどうなるかを見てみる。今回は前回のソースに
protected override void OnLoadComplete(Object _object) { ~ Debug.Log("OnLoadComplete:" + _object.GetInstanceID() + ":" + _object.GetHashCode()); }を出力してロードしたものは
違うものなのか?同じものなのか?
を見るGameObject2個生成コンソール出力
GameObject2個生成プロファイラー
GameObject1個目解放
GameObject2個目解放
画像においては複数Resources.Loadを呼んでも読み込まれた実態は1つのように見える
解放については1個目の開放ではTextureSprite両方とも使われているので解放に失敗
2個目でSpriteは解放できたがTextureは解放できずと前回と同じ結果になった。途中結果
最善の方法は使わないこと
について取得したら解放するとソースに書いても意図とした取得、解放にはならず
メモリ管理が困難になる
になる
まともに使うためにはかなり無駄な労力を費やしそうなのが見えてきた。
単純なだけにソースレビューするだけでなく実機確認及びProfiler確認までしないと
この部分の問題を発見することは非常に困難と見える。Loadした分Unloadすればよいという挙動ではないのでちゃんと解放するには難儀しそうな印象。
実はResources.UnloadはResources.Loadで得たUnityEngine.Objectを引数指定すれば
解放されるものではないでは?ということも含め検証を進めていくとしよう。解放についての検証はまた次回
- 投稿日:2019-02-11T16:53:18+09:00
Resources.LoadのBestPracticesの検証(2,使用している状態で消してみる
Resources.LoadのBestPracticesの検証(1,ロードとアンロード
の続き
前回のResourcesLoader.csを継承して実際に呼んだものを
描画した状態で消したらどうかを見てみる
今回使用するソースはResourcesLoadSprite.csusing UnityEngine; public class ResourcesLoadSprite : ResourcesLoader { [SerializeField, Header("表示するSpriteRendererを指定")] private SpriteRenderer TargetRenderer; protected override void SetLoadObjectType() { LoadObjectType = typeof(Sprite); } protected override void OnLoadComplete(Object _object) { base.OnLoadComplete(_object); TargetRenderer.sprite = _object as Sprite; } }ざっくり挙動は
0.SetLoadObjectTypeはResource.Loadが型を指定しないと一部情報が飛ぶので引数で与えているデータ型
1.ロードが完了したら指定した描画コンポーネントにSpriteを渡す。前回と比べて
実際に描画している状態
でよりアプリ内で使われているのに
近い状態にしてみた。Texture2Dは使用されているため解放できなかったのはわかるが
Sprite情報は解放されてしまった。
- 投稿日:2019-02-11T16:29:20+09:00
Resources.LoadのBestPracticesの検証(1,ロードとアンロード
今回のUnityバージョン
Unity 2018.3.5f1
https://unity3d.com/jp/learn/tutorials/topics/best-practices/resources-folder
Unity公式のチュートリアルより要約
・最善の方法は使わないこと
・メモリ管理が困難になる
・アセットの管理が非常に難しくなる
・配信機能の低下
・プロトタイプを迅速に制作するには優れているが、製品版では排除する必要がある
・リソースフォルダ以下のAssetはシリアライズされる次の場合は便利に使える
・プロジェクトの存続期間を通して一般的に必要
・メモリ集約型ではない
・パッチを適用する傾向がない、またはプラットフォームやデバイス間で差がない
・最小限のブートストラップに使用とあるがなにより
・最善の方法は使わないこと
とはどういうことなのか?簡単に下記ソースコードを書いて検証してみた
ResourcesLoader.csusing System.Collections; using UnityEngine; public class ResourcesLoader : MonoBehaviour { protected System.Type LoadObjectType; [SerializeField, Header("ResourcesLoadのPathを指定")] private string ResourcesLoadPath; private UnityEngine.Object Asset; private void Awake() { SetLoadObjectType(); } private IEnumerator Start() { var request = Resources.LoadAsync(ResourcesLoadPath, LoadObjectType); yield return request; Asset = request.asset; OnLoadComplete(Asset); } private void OnDestroy() { if (Asset != null) { Resources.UnloadAsset(Asset); Asset = null; } } protected virtual void SetLoadObjectType() { LoadObjectType = typeof(Object); } protected virtual void OnLoadComplete(Object _object) { ; } }ざっくりどんな挙動をするかというと
1.GameObjectが生成されたらResourcesLoadPathに設定されているPathでリソースの非同期読み込みを行う
2.ロードが完了したらOnLoadCompleteが呼ばれる
3.GameObjectが削除されたらResources.UnloadAssetを呼ぶで検証の前にカメラだけがあるSceneにおいてAssetがどうなってるか
ProfilerでMemoryの項目をDetailed表示でAssetのSampleを取っておく
適当にGameObjectを作ってAddComponentし、test.pngを読み込んだのが
PrefabやらTransformやらMonoBehaviourなど増えているがLoadに関係するもののみ開いておく
そしてGameObjectを削除したら
画像データのロードにおいてはSprite情報は解放されず
Texture本体のみ解放されてしまったように見える。ソースコードではリソースのAssetを読んで消したから関連するものは
消したつもりだったが完全に消し去ることはできなかったようだ。
- 投稿日:2019-02-11T16:06:56+09:00
Subscribe内でInputクラスを使ってはいけない
- 投稿日:2019-02-11T16:06:56+09:00
Subscribe内でInputクラスを使ってはいけないケース
//追記(2019/2/11 17時頃)
設定を変更したところ、問題なく動作しました。
そのため改めて書き直しました。内容
Observable.Internal内でScheduler.ThreadPoolと書き、
Subscribe内でInputクラスを用いるとデッドロックを起こします。実際のコード
Test.csObservable.Interval(System.TimeSpan.FromSeconds(1), Scheduler.ThreadPool).Take(10).Subscribe(t => { Debug.Log(t); if(Input.anyKey) { Debug.Log("Push!"); } });出力結果
0 //これ以降何も出力されない補足
Audio系クラスでも同様の事象が起きます。
- 投稿日:2019-02-11T13:11:11+09:00
インスペクタの表示を動的に変更する
インスペクタのプロパティ変更に応じて、表示内容を動的に変更する。
チェックボックスの有無で、各設定の表示/非表示が切り替わるだけの、簡単なサンプル
HogeEditor.csusing UnityEngine; public class HogeObject : MonoBehaviour { public bool EnableShadow = false; public ShadowSetteing Setteing = new ShadowSetteing(); [System.Serializable] public class ShadowSetteing { public Color EffectColor; public Vector2 Distance; public bool UseAlpha; } }
- Editor側
HogeObjectEditor.csusing UnityEditor; [CustomEditor(typeof(HogeObject))] public class HogeEditor : Editor { private HogeObject _target; private void Awake() { _target = target as HogeObject; } public override void OnInspectorGUI() { _target.EnableShadow = EditorGUILayout.ToggleLeft("EnableShadow", _target.EnableShadow); if (_target.EnableShadow) { EditorGUILayout.LabelField("影の設定"); _target.Setteing.EffectColor = EditorGUILayout.ColorField("色", _target.Setteing.EffectColor); _target.Setteing.Distance = EditorGUILayout.Vector2Field("距離", _target.Setteing.Distance); _target.Setteing.UseAlpha = EditorGUILayout.Toggle("透過", _target.Setteing.UseAlpha); } } }
- 投稿日:2019-02-11T03:32:30+09:00
HoloLens:開発環境の準備から3Dオブジェクトを表示するまで
はじめに
HoloLensで任意の3Dオブジェクトを表示します。
コーディングは一切発生しません。開発環境の準備
- Windows 10
- Visual Studio 2017
- Unity 2017.4.17f1
- Mixed Reality Toolkit(Unity 2017.4.30)
自分はMacを使っているため、仮想マシン上(VMWare Fusion)に開発環境を作りました。
Mixed Reality Toolkitをダウンロード
https://github.com/Microsoft/MixedRealityToolkit-Unity
以下をダウンロードします。
- HoloToolkit-Unity-2017.4.3.0.unitypackage
- HoloToolkit-Unity-Examples-2017.4.3.0.unitypackage
Unityでアプリを作る
Unityでプロジェクトを作成
- Project Name
- 任意の名前
- Location
- 任意のパス
- Template
- 3D
Ctrl + sでUnityファイルを作成&保存します。
Mixed Reality Toolkitをインポート
Mixed Reality Toolkitの設定を行う
3Dオブジェクトを配置する
Z=1.5で正面の1.5m先にCubeが表示されます。
Scaleは適度なサイズに調整してください。ビルドする
「Add Open Scenes」をクリックしてSceneを追加して、「Unity C# Projects」にチェックをいれます。
「Build」をクリックして、VSSのプロジェクトを出力するフォルダを選択します。
ビルドが完了すると、VSSのプロジェクトが出力されます。
HoloLensにアプリをデプロイする
- 投稿日:2019-02-11T02:52:34+09:00
【Unity】キャラクターを弾ませる(ARKit)
■環境
⭐️Mac OS Mojave バージョン10.14
⭐️Unity 2018.2.15f1
⭐️Mac Bookキャラクターを弾ませる(ARKit)
実際に動かした動画はこちら↓↓
https://twitter.com/nonnonkapibara/status/1094640606683385856
?Unity ARKit?
— non (@nonnonkapibara) 2019年2月10日
床を(shadowPlaneMaterialを使って)透明に設定して、
?ARKit?で弾ませてみたよぉ
(●^o^●)v
赤いボールと青いボールはマテリアルの?メタリック?設定にしてみた。#Unity #Unity3d #ARKit pic.twitter.com/IL9CDNU6If
画像が多いとQiitaの容量オーバーで画像が貼り付けられなくなるので
詳細は、「はてなブログ」に記載してます↓
かぴばらさんの覚書ブログ nonkapibara
- 投稿日:2019-02-11T02:14:41+09:00
【Unity】キャラクターを弾ませる
環境メモ
⭐️Mac OS Mojave バージョン10.14
⭐️Unity 2018.2.15f1キャラクターを弾ませる
実際に動かした動画はこちら↓↓
https://twitter.com/nonnonkapibara/status/1094639157761036288
BlenderからUnityへキャラクターを取り込んで
— non (@nonnonkapibara) 2019年2月10日
❤️マテリアル?をいろいろ変更してみたよぉ。
そしてUnityで✨弾ませてみた✨
(=^0^=)v#Unity #Unity3d pic.twitter.com/uPNPiD19I3
画像が多いとQiitaの容量オーバーで画像が貼り付けられなくなるので
詳細は、「はてなブログ」に記載してます↓
かぴばらさんの覚書ブログ nonkapibara
- 投稿日:2019-02-11T00:35:25+09:00
unityの2D UFO tutorialメモ
unityの公式チュートリアルである2D UFO tutorialをやったのでメモをまとめておきます。自分が戸惑ったところについて出来るだけ詳しく説明しているためかなり冗長です。コードも分かりやすさ優先で全部貼ってますのでめちゃめちゃ冗長です。
元のチュートリアルはこちら
https://unity3d.com/jp/learn/tutorials/s/2d-ufo-tutorialプロジェクトを作る
unityを起動するとプロジェクトを新規に開始するか、以前のプロジェクトから再開するかを選ぶメニューが出ます。今回は新規プロジェクトですのでNewから2D Templateを選択し、プロジェクト名を適当に付けたらCreate Projectです。
unityの基礎知識
unityでは場面毎にsceneをつくり、その中にゲーム進行上必要なすべてを入れておきます。今回のように場面切り替えのないゲームではsceneは1つだけです(つまりこのチュートリアルでは場面切り替えのやり方は分かりません)。
sceneはカメラとオブジェクトから成ります。多分はじめてunityを触る人はカメラの扱いに戸惑うと思います。その点、2Dゲームはカメラの機能が限られているので入りやすいかも知れません。sceneの中身はすべて画像左上のHierarchyに表示されます。
カメラおよびオブジェクトの挙動は画像右上のInspectorから操作するか、C#またはJavaのプログラムに記載するかします。大枠をInspectorで決めて、細かくはプログラムを書くイメージです。このチュートリアルでも何度かプログラムを書きます。
プログラムはそれぞれのオブジェクトに貼り付ける事になります。バラバラのプログラムの連携はコード内に別のオブジェクトを定義しておいて、Inspectorから相手オブジェクトを指定することで可能です(今回のチュートリアルでも3回ほどやります)。また、時間や衝突などのイベントは共有されているのでそれらを使って実行順などをあまり気にせずに書くことができます(めちゃめちゃ楽です)。
画像左下にあるAssetは過去に自分で作った素材やAsset Storeで提供されている素材の置き場です。中央のSceneとGameはどちらもゲーム画面を表示するパネルですが、Game実際にカメラに写る画面を表示するのに対し、Sceneは作業用のパネルになっていて拡大縮小などができます。
どのパネルも場所を移動できるようになっていますのでやりやすいように移動して使いましょう。
チュートリアルに使うAssetをゲットする
WindowからAsset StoreをクリックしてAsset Storeを開きます。
2D UFOで検索するとほしいAssetがすぐ見つかりますのでImportボタンをクリックします。
「上書きするかも」と言われますが気にしなくて良いと思います
全部Importするんじゃなく一部だけImportすることも出来ます。今回は全部ImportしたいのでAllをクリックしてからImportボタンを押します。
Importが完了するとAssetパネルにダウンロードしてきたAssetが表示されます。
できました背景を配置する
AssetのSpritesからBackgroundをdragし、Hierarchyにdropすると、Gameパネル、Sceneパネルともタイル地の背景画像が表示されます。Spritesは画像のAssetのことで、オブジェクトとしての情報を画像ファイルに追加したものです。自前の画像ファイルを使うときもSpriteに必ず貼って使います(貼らないと使えません)。Sceneパネルはマウスのスクロールホイールで拡大縮小ができますので適当な大きさになるように調節しておきましょう。
UFOを配置して表示順を変更
同様にAssetのSpritesからUFOをdrag & dropしてもUFOが表示されません。これは表示順が背景の方が上になってしまっているからで、隠れてUFOが見えなくなっています。これではゲームになりませんので表示順を変更します。
表示順はInspectorのSorting Layerから選択できます。下にいくほど後から描画されますので一番下は常に表示されます。UFOをPlayerに、BackgroundをBackgroundに変更すると無事UFOが表示されました。UFOのオブジェクト名をLayer名と対応させる為にPlayerに変更しておきます。
また、UFOがタイル目よりちょっと大きいようなので、Scaleを(0.75, 0.75, 0)に変更してタイルと合うようにしておきます。カメラの縮尺を調整する
Main CameraをハイライトするとInspectorからカメラの設定を変更することが出来ます。カメラ自体は2Dと3Dで共通のようですが、ProjectionをOrthographicに設定すると奥行きが無視されるようになるので2D表示になる、という事のようです。
まず、カメラの表示範囲を示すInspectorのSizeを16.5に変更します。Gameパネルで背景全体がちょうど画面に収まるようになりました。続いてオブジェクトのない部分の背景色をBackgroudをクリックして変更します。チュートリアルではRGB = (32,32,32)の暗い灰色に指定しています。
UFOの動きを設定する
unityは物理演算用のテンプレートをたくさん用意しています。このチュートリアルではその中でも一番基本的なRigid Body 2Dを使ってUFOの動きを設定していきます。rigid bodyは剛体ですから、柔らかく曲がったり凹んだりしない、変形のない物体という事でしょうね。
PlayerをハイライトしてInspectorの一番下にあるAdd Componentをクリック、2D PhysicsからRigid Body 2Dを選択して追加します。追加できたらunityの画面上側にある右向きの黒い三角形をクリックしてゲームをテストプレイしてみましょう。
UFOが一直線に下の方に落っこちていったと思います。これはデフォルトで重力が下向き掛かる設定になってるからですが、真上から見ているのに重力で横に動いて行ったら変ですので、InspectorのGravity Scaleを0に変更しておきます。テストプレイすると動かなくなっている筈です。
続いて、カーソル操作でUFOが動くようにしていきます。ここからはいよいよC# Scriptを書いていきます。PlayerをハイライトしてInspectorの一番下にあるAdd Componentをクリック、New Scriptを選択して新しいScriptを追加します。Script名はPlayerControllerとしておきます。Scriptは自動的にAssetパネルの末尾に追加されますが、後で分からなくならないようフォルダにまとめておきます。
AssetパネルにあるPlayerControllerをダブルクリックするとC#を開けるEDITERが開きます。windows環境だとunityインストール時にVisual Studioをインストールしているんじゃないかと思います。開いてすぐは以下のような感じになっている筈です。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }Start()はゲーム開始時に1度だけ実行される関数ですので初期化に使います。Update()はゲーム動作の最小単位である1フレームに1度実行されます。ちなみに1フレームが何秒なのかは負荷次第で変わっちゃうようです。書式はC#ですが、オブジェクトの多くがunity独自のものになりますので、慣れるまではunityのdocumentとにらめっこしながらの作業になるのではないでしょうか。
https://docs.unity3d.com/ja/2018.1/Manual/class-Rigidbody2D.htmlとりあえず動かしてみます。剛体がどう動くかはRigidbody2Dがやってくれますので、カーソルの入力をInput.GetAxis()で取得し、2次元ベクトルとして定義したmovementに格納してrb2dに渡します。AddForceは加速度運動の外力を入力としているのでUFOがふわふわと慣性運動してくれる筈です。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { private Rigidbody2D rb2d; void Start() { rb2d = GetComponent<Rigidbody2D>(); } void Update() { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); Vector2 movement = new Vector2(moveHorizontal, moveVertical); rb2d.AddForce(movement); } }上手く動いたでしょうか。動かない場合、コードが間違っていてcompileが通っていない可能性があります。compileエラーの場合、ScriptのInspectorにその旨が表示されます。
また、これでは動きが遅すぎてイマイチです。ですので速度をちょっと上げてみます。ここではpublic float speed;を定義しておいてrb2d.AddForce()に放り込むのをmovement * speedに変更します。speedを100にしたら100倍早く動く筈です。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public float speed; private Rigidbody2D rb2d; void Start() { rb2d = GetComponent<Rigidbody2D>(); } void Update() { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); Vector2 movement = new Vector2(moveHorizontal, moveVertical); rb2d.AddForce(movement * speed); } }ところであれれ、speedの値を入力しないまま使ってますね。普通のC#コードだと実行時にエラーになる筈です。でも大丈夫。unityはScriptのpublic変数をInspectorから指定できるようになってるので、そっちで指定するつもりでこういう書きかたをしているようです。100にして速すぎるのを見てひと笑いした後、10ぐらいにしておきます。
壁を設定する
さて、UFOはそれっぽく動くようになりました。ですが壁がないとどこまでも飛んでいってしまってゲーム的にイマイチですから、色の違う背景のところでぶつかって止まるようにしましょう。
衝突のためのPhysics EngineとしてCollider Componentが用意されています。衝突判定を付けたいオブジェクトの双方(今回はUFOと壁)にColliderを付けて大きさを指定しておくとぶつかったら止まるようになります(簡単!)。
Colliderは形がいくつかありますが、UFOは丸いのでCircle Colliderを使います。PlayerのInspectorからAdd ComponentでPhysics 2DのCircle Collider 2Dを追加しましょう。sceneパネルに緑色の線で衝突判定の範囲が表示されてますが、例によってUFOのサイズとはズレてますのでRadiusを2.15に変更してUFOの外径とぴったり合わせます。
続いて四角い壁をBox Collider 2Dで作ります。BackgroundをハイライトしてInspectorからAdd ComponentでPhysics 2DのBox Collider 2Dを追加します。Sizeを(3.3, 31.64)、Offsetを(14.3, 0)とするとちょうど右側の壁が作れます。作ったBox ColliderのInspector右上にある歯車マークをクリックしてCopy Componentを選択してから、Paste Component as Newを3回繰り返して合計4つのColliderを作り
Size(3.3, 31.64), Offset(-14.3, 0)
Size(31.64, 3.3), Offset(0, 14.3)
Size(31.64, 3.3), Offset(0, -14.3)
の3つを設定すると4方の壁が完成です。
ゴロゴロ~カメラがUFOを追うようにする
一番簡単な方法はMain CameraをPlayerの子オブジェクトにすることだそうです。確かに簡単なんですが、今回はPlayerが回転するのでカメラが一緒に回ってしまってイマイチです。
なので今回はコードを書きます。Main CameraのInspectorのAdd ComponentからNew Scriptを追加、エディターで開きます。public GameObject player;というのはUFOのことです。後で紐付ける操作をすると別のオブジェクトの情報を拾えるようになります。ここではUFOの位置とカメラの位置(つまり自分の位置)の差を初期値として、UFOが動いた分カメラを移動するようにしています。Start()内で初期値を指定しているのはInspectorで調整した位置を初期とできるようにして、いちいちコードを直さなくて良いようにしているんだと思います。using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraController : MonoBehaviour { public GameObject player; private Vector3 offset; void Start() { offset = transform.position - player.transform.position; } void LateUpdate() { transform.position = player.transform.position + offset; } }最後に、Main Cameraをハイライトした状態でHierarchyのPlayerをdragし、InspectorのScriptにあるPlayerのところにdropして紐づけを行います。これをやらないとScriptがpublic GameObject playerが誰のことなのか分からなくてエラーになっちゃうので忘れずにやっておきます。
実行するとカメラがUFOに追従するようになってる筈です。金塊のGameObjectを作る
ProjectのSpritesからPickupをdragして、Hierarchyにドロップします。HierarchyのPickupをハイライトしてInspectorのSprite RendererのSorting LayerをPickupに変更して背景より手前、UFOより奥に見えるようにします。通常だとUFOと重なっていて見えないと思いますのでPositionを適当に変更して見える場所まで動かします。
金塊を回転させる
金塊画像を貼り付けたgameObjectであるPickupを回転させてゲーム画面に動きを出します。Pickupをハイライトして、InspectorのAdd ComponentからScriptを追加して以下のように記入します。updateが掛かる度に回転のz軸を45×Time.deltaTime回すという書き方です。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Rotator : MonoBehaviour { void Update() { transform.Rotate(new Vector3(0, 0, 45) * Time.deltaTime); } }これでゲームを実行すると金塊がグルグルまわるようになります。
作った金塊をprefabに登録してから複製する
Hierarchyに作ってあるPickupをdrag & dropでProjectビューに持っていくとprefabになります。この時点で作ったprefabのPickupとHierarchyのPickupは紐付いているので、この後でHierarchyのPickupを複製したらすべてprefabのPickupの変更が反映されるようになります(便利)。
あとはHierarchyのPickupをハイライトした状態にして、ctrl-dで複製し、位置を移動して金塊を配置していきます。たくさんあるのでフォルダを追加して入れておくと良いでしょう。
金塊を回収する
UFOが金塊に接触したら金塊を回収するようにします。衝突の検出にはCollider 2Dを使うようです。衝突を検出したいオブジェクトの両方にColliderが必要ですが、PlayerはすでにPhysics、Collider 2Dを貼り付け済みですので、今回はPickupに付けるだけです。
PrefabのPickupをダブルクリックしてInspectorを開き、Add ComponentでPhysics Circle Collider 2Dを追加します。金塊はUFOより小さいので、Radiusは0.94にします。
これで衝突が検出されるようになったので、続いて衝突したら金塊が消えるようにします。Playerに貼ってあるPlayerControllerスクリプトにOnTriggerEnter2D()イベントを利用して衝突対象のタグがPickupであるときに相手のgameObjectを消す一節を追記します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public float speed; private Rigidbody2D rb2d; void Start() { rb2d = GetComponent<Rigidbody2D> (); } void FixedUpdate() { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); Vector2 movement = new Vector2(moveHorizontal, moveVertical); rb2d.AddForce(movement * speed); } private void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("PickUp")) { other.gameObject.SetActive(false); } } }これでいけるかと思ったらまだのようで、ゲームをプレイすると金塊にぶつかると壁にぶつかったようにUFOが止まってしまいます。これはColliderが衝突したものとして扱ってしまう為であるようです。PickupのCircle Collider 2DのIs Triggerのチェックを入れる(trueにする)と衝突フラグがトリガーとして使われるようになって、ようやく期待通り金塊が消えてくれるようになります。
動かない金塊をStaticにする
unityはColliderのあるオブジェクトにrigidbodyが付いてない場合はdynamic rigidbodyとして取り扱ってしまうそうです。dynamicだと毎フレーム物理演算をやってしまう為、動かない金塊をdynamic扱いしていると無駄な負荷が掛かります。そこで金塊のPrefabにRigidbody 2DをAdd Componentして、gravityをゼロにしてから、Rigidbody 2DのBody TypeをKinematicに変更しておきます。動作は変わりませんが処理が軽くなった筈。
金塊をゲットした数を表示する
まず、PlayerControllerに金塊をいくつゲットしたか取得する変数を作ります。private int count;を定義しておいて、void Start()でゲーム開始時にcount = 0;して、金塊に接触して消すときについでにcount ++;するように変更します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public float speed; private Rigidbody2D rb2d; private int count; void Start() { rb2d = GetComponent<Rigidbody2D> (); count = 0; } void FixedUpdate() { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); Vector2 movement = new Vector2(moveHorizontal, moveVertical); rb2d.AddForce(movement * speed); } private void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("PickUp")) { other.gameObject.SetActive(false); count++; } } }続いて表示するためのTextを作ります。HierarchyのCreateボタンからUI、Textと選択していくとTextオブジェクトが追加されます。
追加されたらまず文字色を黄色に変更します。InspectorのText内にあるColorをクリックし、表示されたカラーウィンドウのRGBにそれぞれ255, 255, 0を入力すると文字色が黄色に変更されます。
続いて、位置を画面左上に文字の位置を移動します。Anchor Presetの左上をctrl + altを押しながらクリックすると一度に左上に移動させることができます。
このTextオブジェクトにPlayerControllerで取得したcountを表示するようにします。using UnityEngine.UI;でUI用のライブラリを読み込み、public Text countText;を定義してTextオブジェクトを受け取れるように宣言し、countが更新される度にcountTextに金塊の数を文字列で書き込むように追記します。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerController : MonoBehaviour { public float speed; public Text CountText; private Rigidbody2D rb2d; private int count; void Start() { rb2d = GetComponent<Rigidbody2D>(); count = 0; SetCountText(); } void FixedUpdate() { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); Vector2 movement = new Vector2(moveHorizontal, moveVertical); rb2d.AddForce(movement * speed); } private void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("PickUp")) { other.gameObject.SetActive(false); count++; SetCountText(); } } void SetCountText() { CountText.text = "Count: " + count.ToString(); } }最後にHierarchyのPlayerをハイライトした状態でInspectorのPlayer Controller (Script)に表示されているCount Textの右の枠にHierarchyのCount Textをdrag & dropすると、先程public Text CountText;で宣言したCountTextがいまdrag & dropしたCount Textであるという事になります。
勝利メッセージを表示する
先程と同様にUI、Textを作成して、WinTextとrenameします。PlayerControllerにcountが金塊の数と同じになったらWinTextを「You Win!」と表示するように変更します。以下のコードでは金塊10個として書いています。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerController : MonoBehaviour { public float speed; public Text CountText; public Text winText; private Rigidbody2D rb2d; private int count; void Start() { rb2d = GetComponent<Rigidbody2D>(); count = 0; SetCountText(); winText.text = ""; } void FixedUpdate() { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); Vector2 movement = new Vector2(moveHorizontal, moveVertical); rb2d.AddForce(movement * speed); } private void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("PickUp")) { other.gameObject.SetActive(false); count++; SetCountText(); } } void SetCountText() { CountText.text = "Count: " + count.ToString(); if (count >= 10) { winText.text = "You Win!"; } } }先程と同様にHierarchyのPlayerをハイライトした状態でInspectorのPlayer Controller (Script)に表示されているWin Textの右の枠にHierarchyのCount Textをdrag & dropすると、先程public Text winText;で宣言したwinTextがいまdrag & dropしたWin Textであるという事になります。
Windowsアプリをビルドする
FileからBuild Settingsを開き、PC, Mac & Linux Standaloneを選択してから、Add Open Scenesをクリックして現在のsceneを取り込みます。Buildボタンをクリックしてフォルダを指定するとBuild結果がフォルダ内に出力されて終了です。
フォルダに生成されたWindows用exeファイルを実行してみます。
ウィンドウサイズを選択してPlayボタンを押すとゲームがはじまります。
やったね
レッツエンジョイunity!!
- 投稿日:2019-02-11T00:22:29+09:00
VSCodeで c# のバージョンを変える方法
使用エディタ
visual studio code
問題
visual studio code
で C#6 のままで作業している場合、
例えば以下のプロパティの get ステートメントはエラーになる。適当なプロパティ例public int ID { get => this.id; }エラー文Feature 'expression body property accessor' is not available in C# 6. Please use language version 7.0 or greater. [Assembly-CSharp]要は
language version
というのを 7.0以上にしなさいということのようだ。※ ちなみに、この記述のドキュメントはこちら
https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members#property-set-statements対応方法
[プロジェクト名].csproj
の中の<LangVersion>6</LangVersion>
を7
以上にしてあげればOK。<PropertyGroup> <LangVersion>7</LangVersion> </PropertyGroup>これでC#7.0の文を書いてもエラーが出なくなった。
ちなみにUnity
だと、デフォルトでAssembly-CSharp.csproj
というファイル名だと思う。