WPFのバグ?的なものを見つけたので報告
内容
Imageの所属を変えても変わっていなかったようで、もろもろのイベントが現在属しているWindow(あるいはCanvas)上で行われず、前のWindow(あるいはCanvas)上で発生していた。
状況
あるオブジェクトの子オブジェクトとしてImageを持たせて、必要に応じてCanvas上に描画するということをしていた。
MainWindowがあって、その上で別の子WindowをnewしてShowDialogしたものの中にあるCanvas上に、上記のImageを描画
↓
子WindowをCloseしたのち、もう一度子WindowをnewしてShowDialogし、同じように上記のImageを描画
↓
ImageのMouseDownイベントを実行したのち、CanvasのMouseMoveイベントで操作を行うようにしていたところ、うまく動作しない(イベントを書いているのはいずれもCanvasのコードの中)
↓
ImageのMouseDownイベント上とCanvasのMouseMoveイベント上でGetHashCodeをしてその値を見比べてみたところ、それぞれ違う値が返ってきていた
↓
さらに調べてみたところ、ImageのMouseDown上でGetHashCodeをした値は初めの子Window上で見た時と変わっていなかった
解決
Imageを持っているオブジェクトに、Imageを再読み込みするという関数を用意し、子WindowのClosingイベントの中で呼び出すようにしたところ上記のバグ?は解決した
結論
これがバグであるかどうかは微妙だけど、Imageを複数の親の間で使いまわしするなってことだろう
クォータービューのゲームのステージを作るエディタをリリースしました
用途
クォータービューという、世界を45度ほどの角度で見たような描画の仕方が、SRPGなどではよく用いられています。
このクォータービューを用いたゲームの多くでは、世界はマス目で区切られており、それぞれのマス目は高さを持っています。
今回作ったのは、そのようなステージを作るためのツールです。
導入
- ダウンロードする
- zipを任意の場所に解凍する
- 「QuarterViewStageMaker.exe」を起動する
出力されるデータ
ステージはJsonで保存されています。
Jsonの中身はこんな感じです。
{ "Name": "Sample", "Width": 4, "Depth": 4, "Squares": [ [ { "Position": { "X": 0.0, "Y": 0.0, "Z": 0.0 }, "Height": 1.0, "Blocks": [ { "Position": { "X": 0.0, "Y": 0.0, "Z": 0.0 }, "Name": "Maptip_1" } ], "Discription": "", "Tag": "" }, ], ] "Discription": "", "ID": 0 }
SerializeはNewtonsoftのJson.NETというライブラリを使っているのでそれを使えばちゃんとDeserializeできます。
その他のライブラリでもたぶん使えると思います。
詳細については以下のページでコードを見ればわかります。
QuarterViewStageMaker/Stage.cs at master · spi8823/QuarterViewStageMaker · GitHub
QuarterViewStageMaker/Square.cs at master · spi8823/QuarterViewStageMaker · GitHub
QuarterViewStageMaker/Block.cs at master · spi8823/QuarterViewStageMaker · GitHub
使い方
プロジェクトを作成
初回起動時、プロジェクト作成ウィンドウが表示されます。
プロジェクトはフォルダ単位で管理されているので、作成するプロジェクトフォルダの名前と、そのルートフォルダを指定して「プロジェクト作成」ボタンを押してプロジェクトを作成します。
マップチップをインポート
使用するマップチップはプロジェクト単位で管理されています。
プロジェクトにマップチップをインポートするために「Project - 素材を読み込む」を実行し、素材ファイル(pngファイル)をインポートしてください。
複数の素材ファイルを一枚の画像にまとめたファイルがある場合は、「Project - 素材を分割して読み込む」を実行すると、そのファイルを32×32の画像に分割してインポートします。
インポートした画像はプロジェクトフォルダの「Maptips」というフォルダに保存されています。
インポートしたマップチップは、画面左側のマップチップ一覧に表示されます。
ここに表示されているマップチップをクリックすることで、そのマップチップを選択することができます。
ステージを作成
画面上部中央、「新規作成」を実行するか、Ctrl-Nを入力するとステージ作成ウィンドウが表示されます。
作成するステージの名前と大きさを入力し、「作成する」を実行し、ステージを作成します。
ステージを編集
ステージを作成するとキャンバスに罫線とともにステージが表示されます。
ステージはマス目に分けられ、それぞれのマス目に乗っている複数のブロックで構成されています。
ブロックの追加
キャンバスを左クリックすることで、その位置に選択中のマップチップに基づくブロックを追加することができます。
ドラッグすることで複数のマス目にブロックを追加することもできます。
ブロックの削除
キャンバスを右クリックすることで、その位置のマス目の一番上のブロックを削除することができます。
ドラッグすることで複数のマス目のブロックを削除することもできます。
マス目の選択
Shiftキーを押しながらキャンバスをクリックすることで、その位置のマス目を選択することができます。
ドラッグすることで複数のマス目を選択することもできます。
また、Shiftキーの代わりにCtrlキーを押しながら同様の操作をすることで、部分選択をすることもできます。
その他
いい感じの機能を用意しました。
表示されてある説明通りの動作をします。
その他の情報
まだまだ開発中なのでうまく動かないところが多々あります。
そういうところを見つけたらコメントとかで報告してくださると非常に助かります。
ご意見・ご要望などもありましたら是非ともお願いします。
UnityでポストエフェクトをGLSLで書く方法
ポストエフェクトとは
ゲーム空間内のオブジェクトを、Vertex ShaderやFragment Shaderを通してスクリーンに描画する画像を作るという処理の後、スクリーンに描画される画像に対してさらに様々な効果を加えることをいう(あってるんかな)。
例えば、画面をぼかしたり、モザイクにしたり、エッジの検出をしたり、ということである。
Unityでは、この処理を実装するために一連の流れを作る必要がある。
ポストエフェクトをCgで書く方法
以下のサイトがわかりやすくてよい
fspace.hatenablog.com
ポストエフェクトをGLSLで書く方法
Shaderにはいくつかの言語があり、Unityで主に使われているのはCgという言語だ。上で紹介した記事のShaderScriptもCgを用いている。
しかし、あえてGLSLで書きたい!!という場合、どのようにして書けばよいのだろうか?探してみたがGLSLでポストエフェクトを書いているサイトは見当たらなかったので、上記の記事をGLSLに簡単に翻訳した。
それが以下である(本当に基本的な事柄のみ書いてある)。
Shader "Custom/PostEffectTestShader" { Properties { _MainTex("Source", 2D) = "" {} } SubShader { ZTest Always Cull Off ZWrite Off Fog{ Mode Off } Pass { GLSLPROGRAM uniform sampler2D _MainTex; #ifdef VERTEX out vec4 coord; void main() { coord = gl_MultiTexCoord0; gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } #endif #ifdef FRAGMENT in vec4 coord; void main() { gl_FragColor = texture2D(_MainTex, vec2(coord)); } #endif ENDGLSL } } }
UnityでGLSLを使う方法そのものについては
GLSL Programming/Unity - Wikibooks, open books for an open world
を参照されたい。
クオータービューとかの2Dゲームを作るときの描画順について(一般論およびUnityでの方法論)
初めに
この記事を開いたということはすでにお困りなのだと思うが、3次元座標を持つ2Dグラフィックのゲームを作る際、困ることの1つに「描画順」がある。
ここ数日間、うまいこといかなくて悩んでいたのだが、やっと望むような形になったので備忘録も兼ねてここに書いておく。
描画順の決め方
早速本題であるが、描画順の決め方というものをズバッと教えてやろう。
描画順に寄与する要素を列挙する
例えば、
- そのオブジェクトがどのマス目にいるのか
- そのオブジェクトの天頂までの高さはいくらか
- そのオブジェクトがマス目内のどの位置にいるのか
- そのオブジェクトが何であるか
みたいな感じである。
描画順に寄与する優先度で並び替える
今回の場合は先ほど列挙したとおりの順番であった。
それぞれの要素について、寄与度を数値化する
例えば、高さならば「何mか」など
ここで、優先度が一番高い要素でない場合は、数値の上限はきちんと定めておかなければならない.。
また、寄与度の数値は整数であったほうがよい(ような気がする)。
寄与度の数値を優先度に応じてオーダーを変えながら足し合わせていく
何が言いたいかというと、例えば、
- 「どのマス目にいるのか」の寄与度が0~99
- 「高さはいくらか」の寄与度が0~9
- 「マス目内のどの位置にいるか」の寄与度が0~999
- 「そのオブジェクトが何であるか」の寄与度が0~9
であった場合、7ケタの数字の特定のケタの部分に特定の寄与度の情報を埋め込んでいくということをする。
具体的に言うと、上から順に寄与度が「81、3、24、5」だったとすると、描画順の最終的な優先度は「8130245」という風にすればよい。
Unityでの実装方法
2Dモードにするやろ?そうするとPositionのZが小さくなるほど手前に表示されるようになる。
だから上の場合、Zに「-8130245」を指定するといい感じになる。値が大きすぎて嫌なんだったら定数で割ってあげればいい。
MoonSharpの紹介 ~ UnityでLuaを使ったイベントスクリプトを書きたい
初めに(飛ばして)
Unityの開発では主にC#が使われている。
C#とはコンパイル言語であり、Rubyなどといったスクリプト言語のようにテキストファイルに直書きしたソースコードを順繰りに実行するということはできず、いったんソースコードをコンパイルしてから実行しなければならない。
コンパイルするためにはメソッドはクラスの下に置かれなければならないし、複数のメソッドを実装するときにはそれらはユニークな名前でなければならない(当たり前ではあるが)。
これらのカッチリとした仕様こそC#のC#たるゆえん(ほんまか)なのだが、時々このカッチリさが煩わしくなってしまう時がある。
そして多分、この記事にたどり着いた人はその辺を憂慮してググってるのだと思うのだけど、この記事ではそんなC#でもスクリプト言語を使える「Lua」というものをUnityで使用する方法について紹介する。
Luaとは(飛ばして)
スクリプト言語であり、C言語などに組み込まれることを想定して作られている。
つまりどういうことかというと、C言語などからスクリプトを読み込んで実行するために使うのだ。
この言語はゲーム開発においてイベントスクリプトを実装するときによく用いられているようで、どっかのゲーム会社の社員募集要項みたいなページにも名前が出ていたと思う。
もともとはCおよびC++向けに作られたものっぽいが、C#でも使えるようにあちこちでライブラリが開発されている。
「Unity Lua」みたいな感じでググると「NLua」や「KeaLua」などいくつかについて紹介されているのだが、これらを使ってみたところどうにも具合が悪かった(そもそも導入がめんどくさかったり、Luaで一番使いたい機能であろうCoroutine.yieldが使えなかったり)。
それでもなんとかUnityに導入して使っていたのだが、最近再び検索してみると、「MoonSharp」というものがあることを知った。
こりゃええやんと思って早速ダウンロードして使ってみたらやっぱりええやんってなったのでそれを今から紹介したいと思う。
MoonSharp
導入方法
- UnityのAssets直下に「Plugins」フォルダを作る。
- MoonSharpの公式ホームページに行って、「Download」を押した先で最新版のZIPファイルをダウンロードする。
- 解凍して、「\interpreter\net35\MoonSharp.Interpreter.dll」をPluginsフォルダに突っ込む。
- 終了!!
なんて簡単なんだ……
使い方
- 「using MoonSharp.Interpreter」する
- Luaのスクリプトを適当にstringで用意する
- Script.RunString(string)する
- 簡単!!!
もっと
そんな欲張りな奴は公式のチュートリアル見るなり適当にググるなりして自分で勉強しやがれ(丸投げ)
そのうち頑張って書くかもしれない……
UnityでJson.NETを使う
導入方法
http://www.newtonsoft.com/jsonへ行って、zipをダウンロードする。
zipファイルを解凍して「Bin\Net20\Newtonsoft.json.dll」をUnityのPluginsフォルダにコピペするだけ。
使い方
using Newtonsoft.Json ... string json = JsonConvert.SerializeObject(object); var object = JsonConvert.DeserializeObject(json);
みたいに普通に使える
string json = JsonConvert.SerializeObject(object, Formatting.Indented);
とかすると整形もしてくれて便利。
ListとかDictionaryとかも使える。
ただし、Vector3をシリアライズしようとしてみたところなんかエラーが出てしまった。
ここは要検証だけどできない理由がわからん……(なんか相互参照してるとか言われている?)。
今更だがこれはpublicなプロパティ(normalとか)もシリアライズしようとしているせいで、シリアライズの設定でうまいことするとうまくいく(うろ覚えで書いてるので詳しいことは勘弁)
独自クラスのシリアライズは普通にできる。
独自クラスのメンバ変数に独自クラスとか独自クラスのListとか配列とかがあっても大丈夫。
UnityでRotation(Quaternion)をうまく使いたい
前置き
Unityは3Dのゲームをゴリゴリ作ることができるように設計されたアレだ。
当然、三次元空間上での操作ができるようにいろいろ用意されている。
その中で根幹を担っているのが、すべてのゲームオブジェクトにくっついてくる"Transform"コンポーネントである。
uGUIが導入された最近では"RectTransform"コンポーネントというものもあるのだが、これとてTransformから継承したクラスに過ぎない。
そんな重要なものなのであるが、それゆえに(私を含む)Unity初心者がつまづきやすいところでもある。
Transformは「Position, Rotation, Size」という3つの項目で物体の3D空間上の状態を保持しているのだが、
なにが分かりにくいかというと、"Rotation"という項目なのである。
Rotationの難点
Rotationという項目は読んで字のごとく物体の回転状態を表している。
インスペクター上で見ると以下のようになっていて、回転が度数法(360度で一周するもの)を使って表示されている。
うむ、じゃあこの物体はX軸に90度、Y軸に30度、Z軸に10度回転しているのだな、と理解しそうになるのだがちょっと待ってほしい。
というかこのページはRotationで困っている人しか見ていないと思うのでもうわかっているとは思うが、そういう理解ではうまく動かないのである。
なぜなら(自分も詳しくないのだが)三次元空間上では物体を回転させるのに3つの情報だけでは無理なのである。
すなわち回転の軸を決めるベクトル(3次元ベクトル)と、どれだけ回転させるか(ただの数字)の情報が必要なのだ。
じゃあどないすんねん・・・、とここまで読んだ人々は嘆息するかもしれない、実際私もそうである。
敵の攻略にはまず敵を知ることから始めねばなるまい。
結論から言うと、Rotationは"Quaternion"という構造体であらわされている。
どういうことかというと以下の通りだ。
transform.rotation = new Vector3(x, y, z); //これは間違い transform.rotation = new Quaternion(x, y, z, w); //これが正しい
これは四元数というもので、ごく簡単に言ってしまうと複素数を拡張したものである。
すなわち実数部分と虚数部分×3という4つのパラメータから成っている数なのだ。
(少し勉強したことのある人なら知っていると思うが、複素数というのは二次元上の回転を表すのに便利なことが知られている。
そして四元数は複素数の拡張なのである。
すなわち三次元上の回転を表すのに便利だ。)
数学的な知識は皆無に等しいので突っ込んだ話はできないが、とにかく回転は4つのパラメータで表さないといけない。それなのにインスペクター上では3つのパラメータしか見えない。
これこそが、そもそもの混乱の原因なのである。
先述のRectTransformにも、見えているパラメータと実際に使われているパラメータが違うことが原因で生じる混乱があり、Unityではこういうことが多々あるので、インスペクターを信じ切ってしまうことはあまりよろしくないのである。
※追記
指摘があったので少し言い直しておくと、回転は一応3次元ベクトルで表すことができる。しかしこのときに注意しないといけないのが3次元ベクトルに対する回転は一意ではないということなのである。どの成分から回転させるかによって結果が変わってしまうのである。したがってインスペクタ上でX軸に何度、Y軸に何度、Z軸に何度と順番に指定した場合と、Z軸に何度、Y軸に何度、X軸に何度と指定した場合では異なる状態となってしまうのだ。
では、これからこのQuaternionとの付き合い方について見ていこう。
理解するな、関数を使え
正直言って人間は三次元空間上にへばりついた下等な生き物にすぎないので3Dのものを3Dとしてそのままとらえることはできない。二つのお目々でできることといえば、せいぜい二次元画像を取得し、それを脳みその中で頑張って処理し、3Dなものとして錯覚させることぐらいなのである。
だから三次元の回転を理解しようとしてはいけない。そもそも無理なのである。
ただ、数学や物理といったすさまじい学問をやっている人たちに限ってはそれを理解していて、数字に落とし込むことに成功している。我々のなすべきことはそういった賢人たちの知恵を借りることなのである。
そしてそういった賢人たちの知恵は、Unityでも「関数」として我々愚人たちが簡単にアクセスできるように用意されている。
Quaternionの使い方
それでも度数法で回転させたい
やっぱりQuaternionなんかめんどくさい、という人はこの項目を見るといい。
Quaternion.Euler
という関数がある。
使い方は至って簡単で、
transform.rotation = Quaternion.Euler(90, 30, 10);
という風に、度数法で表した回転の三次元ベクトルを突っ込んであげればいいのである。
思った通りに動いてくれるとは限らないが、たいていの場合はこれで事足りるだろう。
難しいことは考えずに、とにかくある方向を向かせたい
そういう人はこの項目を見てくれると助かる。
transform.LookAt
という関数がある。
たとえば(10, 20, 30)という座標に対して物体を向かせたいなら
transform.LookAt(10, 20, 30);
としてやればいい。
これで回転を全くしていない初めの状態で、Z軸正の方向を向いていたところが(10, 20, 30)という座標を向いてくれる(伝われ)。
ある軸の周りにいくらか回転させたい
このあたりからQuaternionを使わないとどうしようもなくなってくる。
transform.Rotate
という関数がある
これを使って、例えば(0, 1, 0)というベクトル(すなわちY軸)を軸にして90度回転させたい!と思うなら、
transform.Rotate(new Vector3(0, 1, 0), 90);
としてやればいい。Y軸などだけではなく、(0, 30, 60)といった変なベクトルでも同じことだ。
特にTransformは物体が向いている方向などを
transform.forward //物体が向いている方向 transform.right //物体から見て右側 transform.up //物体から見て上側
という風にVector3で取得することができるので、たとえば戦闘機なんかがスピンするとき(伝われ)には
float angle = 1; transform.Rotate(transform.forward, angle);
とでもしてあげればよいのである。
あるベクトルをある軸で回転させたい
たとえばキャラクターを動かすスクリプトを書きたいとき、InputうんたらとしてHorizontalの入力とVerticalの入力を受け取った時にどうすればよいのか。
キャラクターが常に正面を向いていればそのままpositionにそれらを足してやればいいのだが、そうでない場合そのままでは「キャラクターが向いている方向に対して右」などといったベクトルを取得するにはめんどくさい処理を書かないといけなさそうだ。
そこでQuaternionとVector3との掛け算を使う。
すなわち、(horizontal, vertical)という入力を受け取った時、Y軸に対してangle度回転しているキャラクターの正面に向かってvertical、右側に向かってhorizontal分進ませたいときは、
float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); transform.Translate(Quaternion.AngleAxis(angle, Vector3.up) * new Vector3(horizontal, 0, vertical));
ということができる。
何をしているのかというと「Quaternion.AngleAxis(angle, Vector3.up)」という関数を使って、Y軸にangle度だけ回転させるQuaternionを取得し、それを「new Vector3(horizontal, 0, vertical)」というベクトルにかけることによってこのベクトルを回転させているのだ。
これが本来のQuaternionの使い方である。
順番は必ず「Quaternion × Vector3」の順でなければいけない。
ここではキャラクターの移動という目的に対して変な手段を用いて解決したが、この場合は先ほど紹介した"transform.forward"などを用いて簡単に、
transform.Translate(transform.forward * vertical + transform.right * horizontal);
という風に書ける、ということも追記しておく。
オブジェクトを連続的に回転させたい
Quaternion.Slerp
想像するならば、物音に気付いてこちらを振り返る敵や、戦闘機がスピンするとき。
一瞬でこちらを向くのではなく、ゆっくりと連続的に向いてほしいときは、
Quaternion from; Quaternion to; float t = 0; public void Update() { if(t < 1) t += Time.deltaTime; transform.rotation = Quaternion.Slerp(from, to, t); }
などというふうにしてあげればよい。
そうすると"from"という角度から"to"という角度へ、"t"という割合だけ寄った角度を計算してくれる。ここで"t"は0から1までの少数である。
このサンプルでは、初め"from"という角度を向いていたオブジェクトが、1秒かけてゆっくりと"to"という角度を向く、という処理を行っている。