そういえば、UnityのMathfって

クラスじゃないんですね。

と、何故わざわざこんな事を書くかというと、実は拡張メソッドを入れられなかったからでした。
少し前にMathfに拡張メソッドを入れようとしたのですが、スクリプトを入れてもなぜか一向にメソッドが出てきませんでした。
(後日、ただの勘違いだとわかりましたが(汗))

ちなみに、拡張メソッドの書き方はこんな感じ。

//拡張メソッドの一例(Vector3の場合)
public static class Vector3Extend
{
public static void ExtendMethodSample(this Vector3 self, Vector3 a, vector3 b)
{
//(2番目以降の引数は、自由に決められる)
//書きたい処理
}
}


で、小一時間悩んだ挙句、Mathfのソースを見てみると
Unity_Vector3.png

「これ、構造体だったのか。。。」

クラスじゃないから拡張メソッドを入れられないのかと盛大に勘違いしていましたが、
結局、拡張メソッドはインスタンスからしか呼び出せない仕様になっているのを忘れていただけでした...orz

なので、Mathfに拡張メソッドを入れたい場合は、下のように使います。

//Mathfの拡張メソッド
public static class MathfExtend
{
public static void Sample(this Mathf self)
{
//書きたい処理
}
}

//...

//実際の処理
Mathf math = new Math();
mathf.Sample();


結局、拡張メソッドを使う場合はインスタンスを作らないといけないのに、いつものMathf構造体(のメソッド)と同じようにクラスから直接呼び出そうとして参照されないだけでした...orz
そんなわけで、皆様も気を付けましょう(たぶん当方だけでしょうが(^_^;))

あ、Mathfがクラスではないのがちょっと意外でした。

テーマ : ゲーム開発
ジャンル : コンピュータ

覚えておくと(たぶん)便利な素敵設定たち(主にUnityのインスペクタ関連)

どうも、Reveです。
今回もUnity関係の備忘録兼Tipsです。
(それにしても、最近の投稿がUnityばっかりですね(^-^;)。もう少し別の分野でも記事が書きたい...。)

Unityでは、インスペクタと呼ばれる3DCGなどオブジェクトの設定を確認・変更できるパネルがあります。
(インスペクタの画像)

これが少し工夫をすることで、自作のスクリプト内にあるフィールド(変数)の数値をIDE(統合開発環境)を開かずに編集できたり、使い勝手をよくすることができます。

インスペクタを拡張する理由】
ですが、何故インスペクタ上で変数を変えられるようにするのか。
一言でいえば、ちょっとした調整がすぐにできてとても便利だからです。

わざわざ「あのキャラクターの体力値をもう少し大きくしたい」といった小さな調整で、IDEからキャラに関するスクリプトを開いて数値を変えるのは時間も浪費しますし面倒です(最近のVisual Studioが特に重いもので...。まあ便利だから使いますがw)。

特に長いコードになると、変数を探す作業も時間がかかってしまい、ミスを犯す可能性も高くなるので、GUI上で変更する方がメリットが多いのです。

また、作成した後にUnityエディタ上でテストプレイなどをしてもらい、その場で値を調整してもらうといったことも可能なので、
プログラマでない人との共同作業にもうってつけなのです(さすがにコードを読ませて修正してもらうのはほぼ不可能なので)。

インスペクタを拡張する手法】
(C#前提です。Javascriptは知らん)

とりあえずフィールドなどに属性(Attribute)をつけるだけ。
また、変数の型によってもインスペクタ上での見え方が変化します。なお標準では、数値や文字を入力するフィールドが出てきます。

あるいは、CustomEditorを駆使してエディタ拡張をすれば独自のインスペクタも作成できるのですが、
長くなってしまうので今回は割愛。

【そもそも属性(Attribute)って?】
プログラムに関するメタデータ、砕けて言えば追加の設定を付けるものです。
詳細は以下のリンク先を参照

- ++C++; // 未確認飛行 C
(C#に関するサイトではトップクラスです)
http://ufcpp.net/study/csharp/sp_attribute.html

使い方も簡単で、フィールド変数やクラスなどの上につけたい属性を書くだけでOK

//...
[SerializeField] //属性(SerializeField:クラス内で宣言した変数が、インスペクタ上に反映)
int sampleField; //この変数がインスペクタ上に表示
//...


【各設定の効果】
では、属性の使い方も分かったところで、早速その効果を見てみましょう。
当方がよく使うものを中心にまとめてみました。

[Attribute]
SerializeField
- 変数の入力欄をインスペクタ上に表示 (public変数にした時と同じ効果)
- privateやprotectedなどでも表示可能
- デフォルト値を代入すると、入力欄にもその値が予め入れられる(変更可)
- Unity標準にはないクラス使う場合は、後述のSerializableと併用
System.Serializable
- クラスをインスペクタ上に表示できるようにする(構造体には使えない)
- (a)対象のクラスにこの属性をつけ、(b)このクラス型で宣言したフィールドにSerializeFieldを与えることで、このクラス内部の変数が表示される(アクセシビリティに注意)
Range
- 変数の設定できる範囲を指定
- 入力がスライダーバー形式に
Multilines(TextArea)
- テキスト(string)の複数行表示が可能(改行が反映される)
- 二者の違いは、右端で折り返すかどうか(TextAreaが折り返す)
Header
- 上にタイトルをつける
- 設定値のカテゴリ分けに便利
Tooltip
- 変数名が書かれた部分にマウスポインタを置くと、その説明文が表示される
- 説明文は引数で記載
RequireComponent(typeof("クラス名"))
- クラス自体に付属させる
- そのクラスに必要なクラスを指定できる
- この属性を付けたクラスをオブジェクトにつける際、必要なクラスがまだない場合は自動で一緒に取り付けてくれる。
- また、この属性付きクラスをつけたまま、要求される別のクラスを外そうとするとエラーメッセージが出る

[型宣言]
配列やリスト
- 要素数と、各要素の値を入力する
- 中の要素の表示/非表示が切り替え可能
bool型
- 入力がチェックボックス形式に
enum(列挙型)
- ドロップダウン形式
- enumを定義して、そのenum型の変数をpublic、またはSerializeField属性にする
Vector3(Vector2)、Rectなど一部のUnity標準クラス
- それぞれのクラスに応じた入力形式に

[そのほか]
UnityEngine.Events.UnityEvent
- 手軽にイベント(何かをきっかけに発生する処理)の実装が可能
- このクラスのフィールドを宣言し、そのフィールドがあるクラス内のメソッドでInvokeメソッドを呼び出せばイベント発生
- イベントの割り当て方は、uGUIでのボタンなどへのイベント設定と同じ方法
- UnityEventへ割り当てられるメソッドには制約があるため、それを配慮して中の処理を与えていく

//...
[SerializeField]
UnityEngine.Events.UnityEvent onSomethingHappen;
//...
void Update(){
if(条件を満たす){
if(onSomethingHappen != null) onSomethingHappen.Invoke(); //割り当てられたメソッドを上から順に処理
}
}


と、上記のAttributeをすべて一つに当てはめるとこうなります。
Attributes_Unity.png

ごちゃごちゃじゃねーかw
まあ、こんな風にいっぺんに盛り込む必要はないのですが、仕様に応じて適したものを使っていけば
ゲーム開発の効率も断然よくなる(ようになるかも)。
この情報が開発の手助けに少しでも慣れば幸いです。

続きを読む

テーマ : ゲーム開発
ジャンル : コンピュータ

Unityで角度制限付きの回転を行う方法

どうも、久しぶりの更新になってしまいました。
今日は備忘録を兼ねたUnityの使い方です。

Unityを使ってゲーム作りをしていると、回転が難しいという話があると思います。
実際、スクリプトでの回転操作はなかなか難しいですし、感覚がうまくつかめないところがあると思います。
特に、角度に制限を付けて回転をさせるのはネットでもなかなか見つからず、実装に苦戦している方も少なくないのではないでしょうか。

そこで、当方の実装方法を(備忘録を兼ねて)公開しようと思います。
あまり良い方法ではないかもしれませんが、もし参考になれば幸いです。

【方法とプログラム】
では、さっそくソースからどうぞ。
なお、ここでは前提として上下左右(XとY軸)の方向のみの回転

//現在の角度
Vector2 rotateAngle;
//制限する角度量
Vector2 rotateLimit;

void Update()
{
//対応するキー(デフォルトではa,s,w,dキー)を押すと、このスクリプトを付けた自身が回転
rotateProcess(new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")), 10f); //2番目の引数は、好きな値で
}

void rotateProcess(Vector2 delta, float speed)
{
//回転する方向と速度を決定
Vector2 rotVector = delta.normalized * speed * Time.deltaTime;

float rotH = getRotateAngle(rotVector.x, ref rotateAngle.x, rotateLimit.x / 2);
float rotV = getRotateAngle(-rotVector.y, ref rotateAngle.y, rotateLimit.y / 2);

//砲塔を回転
transform.rotation = Quaternion.AngleAxis(rotateAngle.x, transform.up) * Quaternion.AngleAxis(rotateAngle.y, transform.right);
//transform.Rotate(rotV, rotH, 0); //こちらは失敗
}

float getRotateAngle(float rotDelta, ref float angle, float rotMax = 90)
{
float result = rotDelta;

//予定の回転角度
float nextRot = angle + rotDelta;
//回転できる最大角度を超えたら
if (nextRot > rotMax)
{
result = rotMax - angle;
}
//回転できる最低角度を超えたら
else if (nextRot < -rotMax)
{
result = -rotMax - angle;
}

//回転後の角度も渡しておく
angle += result;
return result;
}


主な手順は以下の二つです。
1. 今の回転角度を求める
2. 1. で求めた角度をもとに、Quaternion.AngleAxitメソッドで回転させる

まず、1. は自作メソッドのgetRotateAngleから求めています。
引数は、左から (回転させたい角度量)、(回転前の角度)、(最大/最小角の絶対値) となっています。
ここでは、2番目の回転前の角度に、1番目の角度量を足して回転後の角度を求め、それが角度の制限値を超えていたら制限に収まるように回転量を調整しています。

戻り値は回転の角度量ですが、2番目の引数が参照(ref)となっているため、回転後の角度値も同時に求められます(実は、こちらの回転後の角度値を2.で使います)。

続いて2.の処理(自作のrotateProcessメソッド)ですが、回転量をQuaternionでもとめて、transform.rotationに入れています。
Quaternionは苦手な方もいらっしゃるかもしれませんが、Unity(に限らないかもしれませんが)で少し複雑な回転処理をさせようとすると必要になってきます。

とはいえ、今回使うのはQuaternion.AngleAxisメソッドのみです。
このメソッドは、引数に (回転させたい角度値)、(回転軸) を指定します。回転軸はVector3で指定するので、軸の伸びている方向と考えるとわかりやすいかもしれません。
オブジェクトに突き刺す軸を決めて、その軸を基に何度回転させるかといったイメージです。

今回のスクリプトは、上下と左右それぞれの回転をQuaternion.AngleAxisで求め、それをオブジェクトのrotationに代入して回転させます。AngleAxisメソッドで求められる回転は、上下、あるいは左右のみのものですが、実はQuaternion同士を掛け合わせると、二つの回転を反映した回転(Quaternion)が求められます。

そのため、以下の式で回転を求められるのです。
(オブジェクトのrotation) = (上下方向のQuaternion) * (左右方向のQuaternion)

なので、計算した角度とAngleAxisで求めたQuaternion同士を掛け合わせると角度制限を設けた回転が求められます。
今回は0°を中心に左右対称で角度の制限値を与えていますが、getRotateAngleの処理を少し変えれば非対称に角度の制限を設けることも可能です。

続きを読む

テーマ : ゲーム開発
ジャンル : コンピュータ

UnityでOpenCV その2

どうもー、Reveです。
先月は全然ブログを更新できなかったので、今月はちょくちょく記事を上げていきたいですね。
とりあえずUnityでOpenCVの続きです。
目指せ月2桁更新!!

【前回のおさらい】
この記事を参照。
前回は、画像をグレースケールに変換する手法を書いてました。
・グレースケールのMatを用意
・テクスチャ素材をグレースケールにしてコピー


【二値化処理】
今回は、前の記事で行った処理を元に、「二値化処理」というのを施してみましょう。
この二値化処理とは、濃淡のある画像を、一つの閾値を境目に白と黒の階調、つまりモノクロ画像に置き換える処理のことです。大体、元の画像をいったんグレースケール(灰色で濃淡だけを表した画像)にしてからこの処理が施されます。
(参照)
http://ipr20.cs.ehime-u.ac.jp/column/gazo_syori/chapter4.html

さて、なぜ二値化処理をしてモノクロ画像を取得するのかというと、輪郭の抽出や文字の判定処理など後の画像処理が容易になるからです。処理する対象とそれ以外の部分を切り離すのに使われるオーソドックスな処理と言えるでしょう。
(この辺りは、電子回路のアナログとデジタルの話にも繋がる気がします。ちなみに、白黒をどちらにするかという話は正論理か負論理かと考えると、電子回路に詳しい方はわかりやすいかも)

この処理で重要なのは、やはり白黒を分ける閾値の設定で、
様々な研究が行われていろいろな手法が提案されてはいますが、OpenCVでは理論や数式を完璧には覚えてなくてもこれらの手法が使えるよう便利なメソッドが用意されています。

【二値化処理を試そう】
Unityプロジェクトは、前回の記事で作成したものを使います。
まず前回作ったシーンを開き、シーン内にあるGlayScaleというオブジェクトを複製して隣に移動させましょう。
その際は複製したオブジェクトを別名にするとよいでしょう。
(オブジェクトは「Ctrl + D」キーで複製すると便利です)
OpenCVUnity_binarythres1.png

そこで複製したオブジェクトにつけられたGlayScaleScript.csをいったんはずし、新しいC#スクリプトを作ります。
オブジェクトのインスペクタ(Inspector)のボタン「Add Component」で「BinaryThresholdScript」と名前を入力して、新しいスクリプトの生成画面に入るので、「C Sharp」スクリプトにして生成します。

続けて、スクリプトの編集に入ります。
スクリプトの中身は以下の通りです。

using UnityEngine;
using OpenCVForUnity;

public class BinaryThresholdScript : MonoBehaviour {

/// <summary>
/// リソースの読み込み先
/// </summary>
public string texturePath = "test001";

/// <summary>
/// 二値化の閾値
/// </summary>
[SerializeField, Range(0, 256)]
int ThresBinary = 128;

/// <summary>
/// 閾値処理の反転を行うかどうか
/// </summary>
[SerializeField]
bool binInv = false;

// Use this for initialization
void Start ()
{
//テクスチャ画像を読み込む
Texture2D texture_src = Resources.Load(texturePath) as Texture2D;

//テクスチャをMat画像へコピー
Mat origin = new Mat(texture_src.height, texture_src.width, CvType.CV_8UC4);
Utils.texture2DToMat(texture_src, origin);

//画像処理用Mat画像
Mat mat_proc = new Mat(origin.rows(), origin.cols(), CvType.CV_8UC1);
Imgproc.cvtColor(origin, mat_proc, Imgproc.COLOR_RGBA2GRAY);

//------------------(今回追加した処理)--------------------
//2値化処理(反転する場合は Imgproc.THRESH_BINARY_INVを使う)
if (ThresBinary < 0 || ThresBinary > 255)
{
//GaussianCによる適応的閾値処理
//adaptiveMethod: Imgproc.ADAPTIVE_THRESH_MEAN_C と分けて使う
//thresholdType: Imgproc.THRESH_BINARY と分けて使う
int type = (binInv) ? Imgproc.THRESH_BINARY_INV : Imgproc.THRESH_BINARY;
Imgproc.adaptiveThreshold(mat_proc, mat_proc, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, type, 201, 11);

//大津の手法による、単純2値化処理
//Imgproc.THRESH_OTSUを指定すると、thresの値に関係なく自動で閾値を設定する
//Imgproc.threshold(mat_proc, mat_proc, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);
}
else
{
int type = (binInv) ? Imgproc.THRESH_BINARY_INV : Imgproc.THRESH_BINARY;
Imgproc.threshold(mat_proc, mat_proc, ThresBinary, 255, type);
}
//------------------(今回追加した処理)--------------------

//二値化処理したMatをテクスチャに変換
Texture2D texture_out = new Texture2D(mat_proc.cols(), mat_proc.rows(), TextureFormat.RGBA32, false);
Utils.matToTexture2D(mat_proc, texture_out);

//テクスチャの割り当て
GetComponent<Renderer>().material.mainTexture = texture_out;
}
}


御覧のように、前回のようなグレースケール化処理の後に二値化処理を加えていることがわかります。
今回の二値化処理をするには、Imgproc.thresholdメソッドを使います

//元画像(Mat):mat_procを使うとする

//2値化処理メソッド
//引数は、元画像(Mat)、出力先(Mat)、閾値(int)、濃淡の最大値(int)、オプション
//オプションは、値の反転、適応的閾値処理を使うかなどを指定
Imgproc.threshold(mat_proc, mat_proc, ThresBinary, 255, Imgproc.THRESH_BINARY);

//大津の手法による、単純2値化処理
//Imgproc.THRESH_OTSUを指定すると、thresの値に関係なく自動で閾値を設定する
//Imgproc.threshold(mat_proc, mat_proc, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);

//2値化処理(反転する場合は Imgproc.THRESH_BINARY_INVを使う)
//Imgproc.threshold(mat_proc, mat_proc, ThresBinary, 255, Imgproc.THRESH_BINARY_INV);

//GaussianCによる適応的閾値処理
//引数は、元画像(Mat)、出力先(Mat)、濃淡の最大値(int)、適用する手法、オプション、ブロックサイズ(int)、定数(double)
//adaptiveMethod(適用する手法)): Imgproc.ADAPTIVE_THRESH_MEAN_C と分けて使う
//thresholdType(オプション): Imgproc.THRESH_BINARY_INV と分けて使う
//ブロックサイズは偶数だとエラー
//Imgproc.adaptiveThreshold(mat_proc, mat_proc, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 201, 11);

そして、実行してみると下の画像のように、処理前の画像と処理後(グレースケール化)の画像が並べられます。
インスペクタから「Thres Binary」のスライダーを調整すると、処理結果が変わります(実行前に設定すること)。
OpenCVUnity_binarythres2.png

また、勘の良い方ならお気づきかもしれませんが、今回作ったスクリプトは適応的閾値処理にも対応しています。
適応的閾値処理は簡単に言うと、画像のピクセルごとに明るいか暗いかを判別して、それぞれに適した閾値を取ることで抽出したい部分を綺麗に二値化する手法を指します。一定の閾値だと明るい部分や暗い部分などの差異を無視してしまい望ましい結果にならない場合も多いため、この手法が生まれました。

技術的な部分など詳細を知りたい方はこちらを参照。
(参照)
http://schima.hatenablog.com/entry/2013/10/19/085019

では、Unityのプロジェクトをもう一度操作します。
二値化処理用のオブジェクトをもう一つ複製して、今度はThresBinaryの値を最大(256)にしてみましょう。
オブジェクトを横に並べて、再びゲームを実行すると下のような結果になります。
OpenCVUnity_binarythres3.png

やはり手動で二値化を設定する場合と比べて、絵の輪郭がわかりやすくなっています。
ただ、こちらの手法も内部のパラメータ調整は必須なので、場合によっては単純な二値化でもよいかもしれません。
状況に応じて使い分けるとよいでしょう。

ちなみに、UnityとOpenCVでこんなものを作ってみたので、ご覧いただけるとうれしい限りです。

テーマ : ゲーム開発
ジャンル : コンピュータ

ジャイロ非搭載機スマホ用VRキット(Unity)

やべぇ、いつの間にか10月も終わりに近づいている((((;゚Д゚)))))))
今月ほとんど更新してないよ...orz

というわけで、今回は以前作ったアプリ(これとかこれとか)の元になったUnityプロジェクトをパッケージにしてみました。

中身はジャイロセンサがなくてもVR(っぽい)体験ができますよー、というものですが
地磁気センサ(デジタルコンパス)を代わりに使うため、強い磁場を発する場所やものの近くでは正常に動きませんのでご注意。
https://github.com/Revetronique/GeoMagneticVRKit/tree/master

ちなみに、なぜこんなアプリを作ったかというと、
当方の持っているAndroidスマホがジャイロ非搭載という代物だったのでw
これとか

テーマ : ゲーム開発
ジャンル : コンピュータ

プロフィール

Reveちゃん

Author:Reveちゃん
コンビでやってます。
夢担当と技術担当がいます。

大学院卒業 → ロボットベンチャー(漆黒)就職 → 1年で退職 → ベトナムで仕事中(今ここ) → メディアアーティスト(未来☆)

リンクフリーです。

最新記事
最新コメント
月別アーカイブ
カテゴリ
アクセス数
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR