- 投稿日:2019-03-24T18:36:44+09:00
常にカメラを向くオブジェクトをGPUで実装
2年ぶりにQiitaに投稿。
3D空間上で、カメラアングルが変わっても常にこっちを向いていてほしい オブジェクトをつくりたいことがあります。
たとえば、2Dの板にテクスチャを貼り、それを常に正面を向かせることで、一枚絵を3D内で見せる,というテクニック(?)があります。こういう平面を板ポリゴンとかビルボードなどと呼んだりしますね。
この、ビルボードを常にカメラの方へ向ける行為ですが、シェーダで簡単に実装する方法を知ったので紹介してみようと思います。
やりかたはとっても簡単で、頂点シェーダで、クリッピング座標を計算する部分を以下の変えるだけです。
-OUT.vertex = UnityObjectToClipPos(IN.vertex); // 通常 +OUT.vertex = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_MV, float4(0, 0, 0, 1)) + float4(IN.vertex.x, IN.vertex.y, 0, 0)); // 常にカメラを向くなぜこれで常に向きが一定になるのか、すこし解説してみようと思います。
まず、 変更前の、いつもやっている 座標変換の
UnityObjectToClipPosですが、
最適化の分岐を無視するとだいたい以下のような実装になっています。mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));うむ。モデル変換、ビュー変換、プロジェクション変換の行列をそれぞれ掛けているだけですね。
(入力の最後の要素に1.0を入れているのは、 3次元ベクトルを行列で座標変換する際に、移動(translate) も許すようにするおまじない)上で登場する、座標変換のための行列の定数についてすこしだけふりかえってみます。
定数 意味 UNITY_MATRIX_P プロジェクション変換。カメラが映している領域を画面に投影する変換 UNITY_MATRIX_V ビュー変換。ワールド座標から、カメラを原点に置いた場合の座標系への変換 unity_ObjectToWorld いわゆるモデル変換。 ローカル座標からワールド座標への変換。 UNITY_MATRIX_Mという名前じゃないのが不思議(?)これを踏まえて、さきほどの、カメラを向きつつクリッピング座標へ変換するコードを もう一度見てみます。
mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_MV, float4(0, 0, 0, 1)) + float4(IN.vertex.x, IN.vertex.y, 0, 0))
UNITY_MATRIX_MVは、 VとMの行列をあらかじめCPU側で乗算したものです。 一度の描画における変化行列は不定なためです。 なので、実質、以下のコードと同等です。mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, float4(0, 0, 0, 1)) + float4(IN.vertex.x, IN.vertex.y, 0, 0)))つまり、
float(0,0,0.1)に対して MとVの変換をすることで、 オブジェクトのどの頂点の計算もすべてビュー座標系(カメラを原点とした座標系)における原点ということに一旦してしまいます。(すべての頂点が原点にあれば、回転もくそもありません) その後、ビュー座標系における2D上でxとyを足すことで、ビュー座標系内で頂点の位置を合わせます。最後にP変換を被せて完成。シェーダで実装するメリット
最後に、このように向きを変える処理をシェーダで実装するメリットについてです。
- トータルで計算コストが減る
- GPU側の処理はさして増えていない
- CPU側で毎フレームオブジェクトの向きを計算することも可能ですが、Unityの場合はメインスレッドに負荷が集りやすいため、全体としてリーズナブル。
- ゲームを再生しなくても、シーンビューなどすべての表示でこっちを向いてくれる。
- 投稿日:2019-03-24T16:45:05+09:00
Unity uGUIのButtonのイベント(PointerUp, PointerDown)を全てスクリプトから設定する
古い記事です。
==> https://qiita.com/romaroma/items/b632bd8d69271a04c61e
の記事を参考にしてください。個人的な覚書です。
余計な処理もついています。適宜コメントアウトしてください。PointerDownなら、onClick.AddListener()一行で行けるのですが、PointerUpは別途EventTriggerをアタッチして、設定しています。これによって、全てのイベントをスクリプトから設定できるようになります。
関数の引数が複雑になっているのは、引数に別の関数を与える、いわゆるC言語の関数ポインタのようなことをやっているからです。
AddEvent関数の第一引数は、Buttonクラスですが、基底クラスにすれば、別のuGUIコンポーネントでも対応できるはずです。using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; namespace Roma { public class Roma_UI : MonoBehaviour { // Start is called before the first frame update public Dictionary<string, AudioClip> strAudioMap; void Start() { Canvas c = GenCanvasAndEventSystem(); Button b = GenButton(c); AddEvent(b, EventTriggerType.PointerDown, e => { PointerDown(b.gameObject); }); AddEvent(b, EventTriggerType.PointerUp, e => { PointerUp(b.gameObject); }); AudioClip[] musicalSscales = Resources.LoadAll<AudioClip>("NoteSoundData/C4toC6"); strAudioMap = new Dictionary<string, AudioClip>(); for (int i = 0; i < musicalSscales.Length; i++) { AudioClip ac = musicalSscales[i]; string[] token = ac.name.Split('_'); strAudioMap.Add(token[1], ac); print(i + " " + token[1] + " " + token[2]); } } // Update is called once per frame void Update() { } static void AddEvent(Button button, EventTriggerType type, UnityEngine.Events.UnityAction<BaseEventData> call) { EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = type; entry.callback.AddListener(call); button.gameObject.AddComponent<EventTrigger>().triggers.Add(entry); } static Canvas GenCanvasAndEventSystem() { Canvas canvas = new GameObject().AddComponent<Canvas>(); canvas.name = "Roma_Canvas"; canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.gameObject.AddComponent<GraphicRaycaster>(); EventSystem eventSystem = new GameObject().AddComponent<EventSystem>(); eventSystem.gameObject.AddComponent<StandaloneInputModule>(); eventSystem.name = "Roma_EventSystem"; return canvas; } static Button GenButton(Canvas canvas) { Button button = new GameObject().AddComponent<Button>(); button.name = "Roma_Button"; button.targetGraphic = button.gameObject.AddComponent<Image>(); button.GetComponent<RectTransform>().SetParent(canvas.transform); return button; } void PointerUp(GameObject g) { print("up"); } void PointerDown(GameObject g) { print("down"); } } }
- 投稿日:2019-03-24T09:11:05+09:00
Unity初心者やってみた4
前回:https://qiita.com/akagane99/items/dc0291df28f84e192ffd
次回:
一覧:https://qiita.com/akagane99/items/2dd005511a8d50f5634d(前回の続き)
- Unity 5でコインプッシャーゲームを作ろう(中編) | Think IT(シンクイット)
- Unity 5でコインプッシャーゲームを作ろう(後編) | Think IT(シンクイット)
バグ対応:コイン出るとこ壁にめり込む
追加した箇所をコメントアウトしてバグ箇所を特定。
下記箇所でバグ発生。void Update () { /* 追加 * Mathf.Clamp である変数の最小値と最大値を設定することができる。 * 第一引数は設定したい変数、第二引数は最小値、第三引数は最大値である。 * Spawner の移動できるx 座標範囲をleftWallPosition のx 座標から、rightWallPostion のx 座標の範囲にしている。 */ Vector3 currentPosition = this.transform.position; currentPosition.x = Mathf.Clamp(currentPosition.x, ←バグ箇所 leftWallPositionX, rightWallPositionX); this.transform.position = currentPosition; /* 追加ここまで*/ (省略) }Mathf.Clampのマニュアル確認
どうもやりたい事は、コイン出るとこのX軸を、両方の壁のX軸以上に行かないようにしたいみたい。Mathf.Clamp - Unity スクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Mathf.Clamp.htmlpublic static int Clamp (int value, int min, int max);
Debug.logでX軸を確認
Debug.Log("currentPosition.x: " + currentPosition.x); Debug.Log("leftWallPositionX: " + leftWallPositionX); Debug.Log("rightWallPositionX: " + rightWallPositionX);
- Clamp (int value, int min, int max);
- value = currentPosition.x: 0.4
- min = leftWallPositionX: 2.83
- max = rightWallPositionX: -2.9
- min, maxの値を逆にセットしないと機能しない
min, maxの値を逆して修正
void Update () { /* 追加 * Mathf.Clamp である変数の最小値と最大値を設定することができる。 * 第一引数は設定したい変数、第二引数は最小値、第三引数は最大値である。 * Spawner の移動できるx 座標範囲をleftWallPosition のx 座標から、rightWallPostion のx 座標の範囲にしている。 */ Vector3 currentPosition = this.transform.position; // 修正 //currentPosition.x = Mathf.Clamp(currentPosition.x, // leftWallPositionX, // rightWallPositionX); currentPosition.x = Mathf.Clamp(currentPosition.x, rightWallPositionX, leftWallPositionX); this.transform.position = currentPosition; /* 追加ここまで*/ (省略) }上記で修正できた。
どうも壁作るときに逆に
壁の厚みを計算に入れてないので、まだ壁にめり込むけど、壁の外にはでないようになった。
- https://akagane99.github.io/WebGLGames/coin4/
- バグ修正。壁めりこむけど壁むこうに行かなくした
参考リンク
- debug.logの文字連結で利用した
行動予定: 初めの本1冊買う
下記2冊で悩み中。
レビューを参考程度に、amazonの「なか見!検索」で目次チェック
- Unity2018入門 最新開発環境による簡単3D&2Dゲーム制作 (Entertainment&IDEA)
![]()
- Unityの教科書 Unity 2018完全対応版 2D&3Dスマートフォンゲーム入門講座 (Entertainment&IDEA)
![]()
Kindle版と紙版があるけど、紙かなぁ。うーん。
結論
- Unityの教科書 Unity 2018完全対応版 2D&3Dスマートフォンゲーム入門講座のKindle版を購入
- ゲーム企画気になる。C#について書いてある。JavaやPHPやった経験からプログラミング言語のちょいむずでも多分大丈夫だろうと判断。
- Kindle版でプログラミング言語系の本読めるか、使えるか試してみるテスト。PC版のKindleアプリ使って、画面切り替えながら使えばウェブサイトと変わらないはず。
- ああああこの本画像組みだ。そういや検索できないんだった。ぐすん。
C#のTODO表示
書き方
// TODO 壁の厚みを考慮するTODOタスク一覧表示
参考リンク
コメントをタスク一覧に表示させる方法|ソフトウエア開発部(システム開発・システム設計 株式会社アイロベックス|東京都新宿区)
エディタの視点移動
思い通りに動かせないのでマニュアル参照
今後メモする。ここまでできた
いままでの一覧
https://github.com/akagane99/WebGLGames/blob/gh-pages/README.md// 行動予定・行動結果
- 行動予定
- エディタの視点移動メモる
- 別の入門サイトのゲーム作ってみる。そのうち
- 行動結果
- 1.5時間(5.5時間/20時間)
- 投稿日:2019-03-24T03:01:16+09:00
【Unity(C#)】キーボードの同時入力を禁止する
UnityのInput(キーボード入力)は同時入力できてしまう
Input.GetKeyDownを例に話を進めていきます。
こちらの記事にもありますように、
指定したキーを押した瞬間、1フレームだけ呼び出されてtrueを返します。もし同時に押したらどうなるのか検証してみました。
void Update() { if (Input.GetKeyDown(KeyCode.A)) { print("A"); } if (Input.GetKeyDown(KeyCode.S)) { print("S"); } }このように両方が同じように増えました。
キー入力は人間の手で行っているので
厳密に同時(同一フレーム上)に増えているかどうかは読み取れませんが、
前回の記事で発生したバグは
同一フレーム上でキー入力を受け取ってしまったことが原因かと思われます。矢印キーの入力が同時に行われてしまったことでこのようなバグが発生しています。
※正しい挙動は前回の記事を参照ください同時(同一フレーム)入力できるのはヤバい
ヤバいです。
例えば、自動販売機。
ふざけてボタンを同時押ししたことがあるかと思います。(ない?)あれがもし奇跡的なタイミングで両方のボタンをぴったり同時に押した場合、
反応してしまう仕組みだったとしたらどうでしょうか。
一本タダで手に入ってしまいます。それくらいヤバいです。とてもわかり易い例えは置いといて、
同時押しを想定していないのであれば、バグの温床になりかねません。なので、キーボードは片方づつ押せるようになっていてほしいものです。
namespaceを使ってみた
namespaceは
コンポーネントをいっぱい登録して簡単にどこでも呼び出しやすくしました
みたいな理解をしています。(合ってますか?)Unityのスクリプトリファレンスには
大規模な開発にもってこいです みたいなことが書いてありました。
(それ以外に使う理由ってありますか?よく使う方見てたらコメントください)現時点であまり縁がないですが使ってみようと思います。
using UnityEngine; namespace InputKey{ /// <summary> /// 同時入力を禁止する /// </summary> public static class MyInput { static bool isCheck_Input; public static bool MyInputKeyDown(KeyCode key) { if(Input.anyKeyDown == false) isCheck_Input = false; if (isCheck_Input==false) { if (Input.GetKeyDown(key)) { isCheck_Input = true; return true; } } return false; } } }このスクリプトは何にもAdd Componentしなくていいやつっぽい(staticなので)です。
次は使う側です。
using InputKeyを名前空間に追加します。using UnityEngine; using InputKey; public class Test : MonoBehaviour { void Update() { if (MyInput.MyInputKeyDown(KeyCode.A)) { print("A"); } if (MyInput.MyInputKeyDown(KeyCode.S)) { print("S"); } } }これで同時入力(同一フレーム入力)できなくなってました
void Update() { if (Input.GetKeyDown(KeyCode.A)) { print("A"); } else if (Input.GetKeyDown(KeyCode.S)) { print("S"); } }
else if使えば同一フレームを回避できるらしいです!
ただ、1フレームずれただけだと見た目ではあまり変化ないです。もっと厳格な同時押し禁止
MyInputKeyDownにしろ、else ifにしろ、
同時入力が避けられるのは1フレームのみです。キーを押し込んでいる間は他のキーを押せない状態こそ
真の同時押し禁止といえるでしょう。//真の同時押し不可 public static bool MyInputKey(KeyCode key) { if(Input.anyKey == false) isCheck_Input = false; if (isCheck_Input==false) { if (Input.GetKey(key)) { isCheck_Input = true; return true; } } return false; }これで
押しっぱなしでも一回しか反応しない、かつキーを押し込んでいる間は他のキーを押せない状態になりました。これが真の同時押し禁止だーーー!
- 投稿日:2019-03-24T02:17:18+09:00
Unityのコライダー勉強2
前回、Unityのコライダーを勉強しました。
そして今回は こちら の記事を参考に、オブジェクトの位置を入れ替える挙動を、コライダーの判定を使って実装したいと思います。
以下を用意しました。
灰色のCubeの上下左右に白いCubeを配置したものです。
白いCubeには、BoxCollider と Rigidbodyをアタッチしています。本当は灰色CubeそのもののGameObjectに直接BoxColliderを4つ付けたかったのですが、どのコライダーが当たっているか判定することが難しそうでしたので白Cubeを4つ作ります。
↓ColJudge.csの中身です。
using UnityEngine; public class ColJudge : MonoBehaviour { private GameObject parentObj; private GameObject colObj; private bool colFlg = false; private void Start() { parentObj = gameObject.transform.parent.gameObject; } private void Update() { if (colFlg) { if (Input.GetKeyDown(KeyCode.UpArrow) && gameObject.name == "Up") swap(); if (Input.GetKeyDown(KeyCode.DownArrow) && gameObject.name == "Down") swap(); if (Input.GetKeyDown(KeyCode.RightArrow) && gameObject.name == "Right") swap(); if (Input.GetKeyDown(KeyCode.LeftArrow) && gameObject.name == "Left") swap(); } } private void OnTriggerStay(Collider other) { colObj = other.gameObject; colFlg = true; } private void OnTriggerExit(Collider other) { colFlg = false; } private void swap() { Vector3 tmpPos = parentObj.transform.position; parentObj.transform.position = colObj.transform.position; colObj.gameObject.transform.position = tmpPos; } }(オブジェクトの名前を分岐条件に使用しているのがイケていない気がします。。。)
後は、灰色Cubeの周りにBoxcolliderがついているCubeを配置してUnityエディターで実行ボタンを押して動作確認します。
今回はここまでです。
- 投稿日:2019-03-24T01:34:46+09:00
UnityのTest Runnerでusingが効かなかった
Test Runnerでusingが効かなかった
Unityでユニットテストを書くなら、公式の Test Rnner がよいと聞いたので、試したところ、テストコード内の
usingでエラーが発生しました。LeapMotionのSDKを利用したコードだったので、
using Leapを指定したのですが、ご覧の通り赤い波線が表示されました。カーソルを合わせると、ポップアップで警告文が表示されました。
型または名前空間の名前 'Leap' が見つかりませんでした(using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)。と書かれています。
テストコードではない通常のコードからは
Leapが参照できているのに、なぜかテストコードからは参照できていないようです。開発環境
以下の環境で試しました。
- Windows 10 64bit
- Unity 2018.2.21f1
Leapの Assembly Definition が必要だった原因を調査したところ、こちらの記事を見つけました。
Unity 2018.1でTest Runnerの使い方が変わっていた話Assembly Definition が必要ということが書かれています。私の環境も、まさに、これが原因でした。
LeapMotion.asmdef を作る
LeapMotionのフォルダで右クリック → Create → Assenbly Definition を選択すると、
NewAssemblyという名前で入力待機状態になったので、LeapMotionと入力したところ、LeapMotion.asmdefというファイルが生成されました。LeapMotion.asmdef をテストコード側から参照する
これをテストコード側のasmdefファイルから参照するようにします。
私の環境の場合、PlayModeテスト用に
PlayMode.asmdefというファイルを作っていたので、Unity内でそれを選択し、 Inspector → References の右下の + → None (Assembly Definition Asset) の右の 〇 → リストの中からLeapMotionを選択することで、参照設定ができました。これでテストコードの
using Leapのエラーが解消されました。補足:Enable playmode tests for all assemblies
PlayModeでテストしようとすると色々なエラーが出ました。
私の環境では、Enable playmode tests for all assembliesの設定をしたら、解消されました。この設定に関しては、こちらの記事が丁寧でわかりやすかったので、詳しくはご参照ください。
Unity TestRunner(PlayMode)のアセンブリ参照問題さいごに
ユニットテストをしたいだけなのに、 Assembly Definition という設定が必要なのは、
JavaのJUnitやRubyのminitestとかと比べると面倒大変だと感じました。
ただ、Assemblyを定義することで、ビルドの高速化等も可能になるようです。せっかくなので勉強していこうと思います。2019/03/26追記
テストコードのC#ファイルを
Editorという名称のフォルダの配下に設置すれば、 Assembly Definition をしなくても、 Edit Mode で Test Runner を利用できることがわかりました。
ただ、 Play Mode の場合、Assembly Definitionは必要そうです。
- 投稿日:2019-03-24T01:30:38+09:00
オブジェクトにスクリプトをコンポーネントとして追加した際、エラーが発生
経緯
Unityの3DオブジェクトにScriptコンポーネントを追加しようとした際、エラーが発生し追加できなかった。
エラー情報
Can't add script
Can't add script component 'Controller' because the script class cannot be found. Make sure that there are no compile errors and that the file name and class name match.原因
Unity管理のファイル名とプログラムのクラス名が違った。
「右クリック → Create → C# Script」でファイルを作成した際にファイル名を間違えた。
そのためUnity側で再度リネームを行なったが、この時クラス名は修正してくれいないようだ。【エラー発生状態】
ファイル名:Controller.cs
クラス名:public class Controllor : MonoBehaviour【正しい状態】
ファイル名:Controller.cs
クラス名:public class Controller : MonoBehaviour是非自動で修正を反映して欲しいところ。
結論
UnityでScriptのファイル名を変更する際は、クラス名との整合性に気をつけよう。
プログラム開発では当たり前だが、Unityを使っていると少し感覚が違ってくるため。
- 投稿日:2019-03-24T01:30:38+09:00
【Unity】3Dオブジェクトにスクリプトをコンポーネントとして追加した際、エラーが発生した件
経緯
Unityの3DオブジェクトにScriptコンポーネントを追加しようとした際、エラーが発生し追加できなかった。
エラー情報
Can't add script
Can't add script component 'Controller' because the script class cannot be found. Make sure that there are no compile errors and that the file name and class name match.原因
Unity管理のファイル名とプログラムのクラス名が違った。
「右クリック → Create → C# Script」でファイルを作成した際にファイル名を間違えた。
そのためUnity側で再度リネームを行なったが、この時クラス名は修正してくれいないようだ。【エラー発生状態】
ファイル名:Controller.cs
クラス名:public class Controllor : MonoBehaviour【正しい状態】
ファイル名:Controller.cs
クラス名:public class Controller : MonoBehaviour是非自動で修正を反映して欲しいところ。
結論
UnityでScriptのファイル名を変更する際は、クラス名との整合性に気をつけよう。
プログラム開発では当たり前だが、Unityを使っていると少し感覚が違ってくるため。












