「語の定義」は今後大きくは書き換わらないと思います。 それ以降は執筆中です。まだ通読しないほうがよいかも知れません。
最近の更新内容[]
- 「イベントハンドラ」を、添付のマニュアルにあわせて「アクション」と呼びなおした。
この記事は、EQ2にUIBuilderがついているのはわかった。開いてみた。数字と文字がいっぱい。なんだろうこれは?? というレベルまで進んでいる人を想定して書いている。
この記事のゴールは、EQ2のUIがどういう体系に沿って作られているかを解き明かすことで、このページを見た人が、UIでどのようなことができるかという想像力を働かせる助けになることにある。
「すぐ動く実例」的なものはどうしても後回しになってしまっているが、そうした優れた実例はこのページ以外のところで数多く公開されているから、問題はないと思われる。むしろこのページは「すぐ動く実例を貼り付けたが、うまく動かない。なぜか」という立ち位置にいる人に読み込む手間を取ってもらえば、自己解決できるだけの基礎を得られるものになることもまた目指している。
語の定義[]
カスタムUIはXMLドキュメントである。XMLはそれを理解するパーサーにかけることで結果を出力するが、するとXML自体がプログラム言語であると捕らえることもできる。
パーサーがあるということは、UIの文法は一定の体系に沿って作られているため、そこで使われている言葉に一定のルールがある。このルールを理解することで、おぼろげにでもXMLに何が書いてあるのか、だんだん理解できるようになっていく。
この理解を進めるために、まずUIを構成している「部品」にどのようなものがあるかを列挙していき、相互にどのような働きあいをしているのかをまとめておく。 唯一の作成ソフトであるUIBuilderが英語のソフトウエアなので、用語には英語の表記を並べておく。
オブジェクト(Object)[]
UIはオブジェクトからできている。翻訳すると「物体」というぐらいの意味で、UIの最小単位である。UIBuilderで左上のツリー図に出ているものは、すべてオブジェクトであり、デフォルトUIは「root」という名前のオブジェクトを頂点として、組織図的にオブジェクトがぶら下がっている。これがUIの概観である。
ひとつひとつのオブジェクトの中身は、ウィンドウの枠、HPを表示するためのバー、プレイヤーウィンドウに表示するためのアイコンといった画像であったり、ゲーム画面に書かれる文字そのものであったり、フォントの定義という実体のないものであったり、ゲームデータにアクセスするための接点であったり、つまりなんでも入っている。
UIBuilderでは31種類のオブジェクトが作成できる。たとえばデータを表示するウィンドウのオブジェクト構造は、「Page」という種類のオブジェクトを基盤として、その下に「Image」「Text」「Sliderbar」「Button」などのオブジェクトがぶら下がることで成り立っている。オブジェクト間にこのような上下関係があるとき、Pageを「親オブジェクト」、それ以外のものを「子オブジェクト」と呼ぶ。
既存のオブジェクトがどのタイプに当たるかは、オブジェクトを選択するとプロパティリストの上あたりに[]に囲まれて表示される。オブジェクトについているアイコンでもだいたい判別できる。 オブジェクトが表現するものはあまりにも多いので、他の語の説明や具体例でおいおいイメージを掴んでいって欲しい。
ちなみに、UIBuilderは一切のアップデートが行われておらず、メニューから作成できるオブジェクトはEQ2リリース当時の古いもののままだ。現在のオブジェクトはいろいろと機能や外見が改善されているので、もし真新しいオブジェクトを追加するのなら、現在のものをベースに新しく作る方法を取ったほうがよい。その方法は後ほど紹介する。
EQ2のUIでは、便宜を図るためオブジェクトにいくつかの亜種を用意して呼び分けているようだ。
スタイル(Style)[]
スタイルはオブジェクトの一種であるが、オブジェクトの「親」として、他のオブジェクトの内容の基礎になるための特殊なものである。
具体例から入ったほうが分かりよいだろう。ボタン、チェックボックス、枠線などはほとんどのウィンドウについているオブジェクトであるが、このすべてにいちいち画像を関連付け、押したときの反応(へこむとか、音が鳴るとか)を指定し、サイズを決めてやるのは大変な苦労である。これを解消するためにスタイルは存在する。そのような情報はスタイルにすべて書いておき、オブジェクトとしてのボタンなどには、利用するスタイルの種類と、扱うゲームデータのことだけ書いておくことができるのだ。押すボタンごとに違う音が鳴って欲しいなら、スタイルを増やせば実現できる。
スクロールバー、ドロップダウンメニューのような複雑な基礎部品の外見や基礎動作もすべてスタイルで作られている。さすがにデータ的に複雑だがスタイルを自作するなら参考になるだろう。たとえば、スクロールバーのスタイルには「上下にスクロールする幅がないときは上と下の矢印は暗くなり、表示部分を表すバーも出ない。スクロールバーが上端に触れている場合、上端の矢印は暗くなる。下端の場合は下端の矢印が暗くなる」などといった情報が書き込まれている。
スタイル自体はオブジェクトのようにウィンドウオブジェクトの配下へ直接配置することは普通なく、どこかへひとかたまりにあらかじめ用意しておき、対応するオブジェクトのStyleというプロパティの値に設定して呼び出す使い方をする。
プログラムの知識がある人には「継承するためのオブジェクト」とでも言うと分かりやすいだろうか。
フォルダ(Folder)[]
中身(プロパティ)のないオブジェクト。用途はWindowsやMacOSのフォルダと同じで、オブジェクト間に親子関係を作りたくないが、同種のオブジェクトとしてまとめておきたいときに用意される。デフォルトUIでは上記のスタイルをまとめておくための部品置き場に利用されている。
エフェクタ(Effector)[]
オブジェクトに点滅やリサイズなどのアニメーションをさせるための特殊スタイルのようだ。 まだ調べがついていない。
プロパティ(Property/Properties)と値(value)[]
正式にはオブジェクトプロパティ(Object Properties)という。プロパティ自体の意味は「所有物」で、ここではオブジェクトの中身のことである。
オブジェクトはただの入れ物に過ぎないので、プロパティを設定することでその中身が何であるか決まっていく。たとえば、Textオブジェクトは文字を受け入れるための受け皿であり、そのプロパティに実際に表示したい文字が書き込まれる、という間柄になる。
UIBuilderの左下に列挙されているリストが、現在選択中のオブジェクトのプロパティである。
プロパティの中身のことを、さらに「値(あたい、value)」という。先ほどの例を使えば、Textオブジェクトに表示させたい文字を書き込むためのプロパティはやはりTextという名前なので、Textプロパティの値に文字を書き込むことになる。
値には数値、文字のほかに真偽値(またはBool(ブール)値、Boolean(ブーリアン))というものがある。真偽値を取るプロパティには「true(真)」「false(偽)」の2つの値のみが許されており、どちらであるかだけを判断する。真偽値を取るプロパティは大抵、その値によってオブジェクトの表示状態などに大きな影響を与える。プロパティの取れる値は、オブジェクトを作ったときに中身を見れば、最初から真偽値か、それ以外の値が書いてあるのですぐに分かる。
オブジェクトの種類ごとにプロパティの内容は決まっている。
プロパティにはいくつか重要なものがあるので特に説明しておく。
Nameプロパティ[]
Nameプロパティは、そのオブジェクトの名前であり、UIBuilderのオブジェクト相関図には、このNameプロパティにセットした値が表示される。また実用上も、そのオブジェクトを他のオブジェクトと区別する唯一の目印となる、最も重要なプロパティである。
ある親オブジェクトにButtonオブジェクトが3つぶら下がっていて、画面に3つのボタンが表示されている場合、プレイヤーがどのボタンをクリックしたかプログラム側が判別できるのは、Nameプロパティがセットされているからである。よって、同じ親オブジェクトの下にいる子オブジェクト間では、必ず別々のNameを設定しなければならない。それ以外のものとNameプロパティが同じなのは構わない(混乱しないなら)。
また、ふつうのオブジェクトに、特定のNameプロパティの値をセットすることで、それがゲームプログラム側からすると特別な意味を持つオブジェクトに変わることがある。装備の表示されるアイコンはその好例であり、ただの画像アイコンオブジェクトに、装備部位に対応するNameを与えるだけで装備部位のグラフィックが出るようにできている。
Nameプロパティは、後述するイベントハンドラを利用する場合にも重要な役割を果たす。
ここで書いておくが、Nameに「root」「parent」「self」「child」という値を与えてはならない。パーサーが特殊な意味として使っているからだ。また、プロパティの名前と同じName(Nameとか、Colorとか、Enabledとか)もできれば与えないほうがよい。呼び出しの説明を見てもらえばわかるが、あとで面倒だ。
visibleプロパティ[]
visibleとは「見える」という意味で、この値がtrueだとそのプロパティの内容はゲーム中も見え、falseだと見えない。ゲーム中でウィンドウが出たり消えたりするのは、visibleプロパティを変化させて実現している。
UIBuilderでプロパティが並んでいるリストの右上にある目のアイコンを押すと、visibleプロパティのオンとオフを入れ替えることができる。実際には右側のプレビューウィンドウで表示を確認するとき切り替えるのが主な用途だ。
ひとつ注意して欲しいのは、rootの直下にあるオブジェクトをvisibleにしたままゲームをはじめると、それ以下のオブジェクトがすべて消せなくなること。
DynamicDataプロパティ[]
DynamicData(ダイナミックデータ)はUIの核心のひとつである。ダイナミックデータというのは、ゲーム世界で「動いている」情報、ほとんどすべてのことである。UIの作成者は、ダイナミックデータアイテム(Dynamic Data Item)というオブジェクトを通じてそのうちいくつかのデータにアクセスし、UIに直接その情報を表示することができる。
一番分かりやすい例は「PCのヒットポイント」だ。ダイナミックデータアイテムを通じてPCのヒットポイントにアクセスすれば、好きなウィンドウの好きな場所にPCのヒットポイントを表示できる。デフォルトUIはPCウィンドウというものにHPを表示しているが、UIを自由に作成する技術があれば、別にどこへ表示しても構わないのだ。
ダイナミックデータアイテムは、Rootオブジェクト直下の「GameData」オブジェクト(正しくはデータソースコンテナ)の中にすべて詰まっている。UI作成者がアクセスできるダイナミックデータはゲームアップデートが行われるたびに変化しているので、何か変更があったらこの中をのぞいて様子をチェックする必要がある。
また、そのダイナミックデータアイテムがどんな情報を提供してくれるかは、デフォルトUIや他のUI作成者の扱いや、アップデートノートなどを調べるか、あるいは手元で全部表示させて実験するかしないと分からない。 緑色のキューブのアイコンがついているのが、目指すダイナミックデータアイテムだ。中には何も書かれていないがそれが普通なので心配しなくていい。
ダイナミックデータアイテムは、通常このDynamicDataプロパティから呼び出しを行って利用する。呼び出しを行うとオブジェクトのタイプに応じたプロパティに、ダイナミックデータの内容が格納される。
たとえばTextオブジェクトから自分のPCの名前を呼び出すと、Textプロパティに名前が格納される…つまり自分のPCの名前が表示される。BarオブジェクトにPCのヒットポイントを呼び出すと、ヒットポイントの現在量に応じたサイズのヒットポイントバーを書くためのデータに置き換わる。 呼び出しについては後ほど詳しく説明する。
アクションプロパティ[]
アクションプロパティはプロパティの一種であるが、プレイヤーやゲーム世界からの何らかのアクションを受け止めて、オブジェクトに反応をさせるためのプロパティである。アクションプロパティはだいたいのオブジェクトに実装されている。
アクションプロパティはすべてOn〜ではじまっており、その名前通りの状況に反応する。そしてその状況が実現したときのみ、プロパティの内容が実行される。
以下はアクションプロパティと、アクションプロパティが反応する状況である。
- OnActivate
- 有効にされたとき
- OnDeactivate
- 無効にされたとき
- OnEnable
- 使用可能になったとき
- OnDisable
- 使用不能になったとき
- OnEffectFinished
- 効果が終了したとき
- OnShow
- 見えるようになったとき
- OnHide
- 隠れたとき
- OnHoverIn
- マウスカーソルが乗ったとき
- OnHoverOut
- マウスカーソルが離れたとき
- OnAutoAttack
- 近接オートアタック中のとき
- OnRangedAutoAttack
- 遠隔オートアタック中のとき
有効・無効、使用可能・不能というのは、これに対応する「Activated」「Enabled」という真偽値プロパティがあるのでこの値を見ている。
アクションプロパティの名前に〜effectorと末尾についている、同様のプロパティが入っているオブジェクトもある。これは他のアクションプロパティと違い、エフェクタをここに指定してオブジェクトに該当する状況でアニメーションをさせるためのものだ。たとえばデフォルトUIは、オートアタック中にあわせてOnAutoAttackEffectorに点滅する動作のエフェクタを組み合わせて利用している。
アクションプロパティの値には通常、数値ではなく「式」を置く。つまり他のプロパティやオブジェクトを書き換えたり参照したりするための命令だ。アクションプロパティに反応したとき、他のプロパティの値を書き換えるのが主な用途だ。式には条件式をつけて状況判断をさせることもできる。この式を利用すると、UIに非常にいろいろなことをさせることができる。
非常に単純な例を挙げる。
<Text ... OnHoverIn="Text=(乗りました)">
これはテキストオブジェクトにマウスカーソルが乗ったとき、そのテキストプロパティを「乗りました」と書き換える。OnHoverOutを指定すれば元のテキストに戻すこともできる。
これを応用すると、「キャラクター情報ウィンドウが開くとき、OnShowをトリガーとして、キャラクターの職業を判断し、背景画像を書き換える」というようなことができる。これはカスタムUIのひとつ、ProfitUIが実際に使っている用法だ。
式の文法、条件式やその詳しい使い方、複雑な用例については後で深く掘り下げるとしよう。
とりあえず当面知っておいて欲しい言葉はこのぐらいである。この段階ですべて理解する必要はない。各要素の相互関係のイメージができ上がれば十分だ。 では次へ進もう。
他のオブジェクトを呼び出す[]
前述のスタイル呼び出し、アクションプロパティの利用、ダイナミックデータの呼び出し、スタイルでの画像呼び出しなどなど、非常にたくさんの局面で使われるのが、他のオブジェクトの呼び出しである。
あるオブジェクトから他のオブジェクトに干渉し、連動させるのだ。早い話が、オブジェクトに画像を表示させたり、ダイナミックデータを表示させたりすることができるのである。 オブジェクトの呼び出しこそが、UIに無限の可能性を与えている。
呼び出しの書式は以下のようになる。
オブジェクトの名前 . オブジェクトの名前 . … . オブジェクトの名前
または
オブジェクトの名前 . オブジェクトの名前 . … . プロパティの名前
オブジェクトの呼び出しは "." が区切り目となる。左から順番に上位のオブジェクトが参照され、"." で区切ると、そのオブジェクトの子オブジェクトに該当する名前のオブジェクトがあるか探しに行く。
オブジェクトだけではなく、そのプロパティを直接指定して呼び出すこともできる。呼び出しの最後のひとつは、まず直上のオブジェクトのプロパティの中に、該当する名前のものがあるかを参照しに行く。なければオブジェクトを探す。
呼び出しを行った結果、戻ってくる値を「戻り値」という。
オブジェクトを呼び出すと、そのオブジェクトのタイプに応じたプロパティの値を返してくる。たとえば、Textオブジェクトを呼び出すと、そのTextプロパティの値を戻り値として返してくる。プロパティを呼び出すと、もちろんその値を戻り値として返してくる。
たとえば、自分のPCのヒットポイントと、その最大値を表示させるためにダイナミックデータへアクセスしたいとする。そのための記述は以下のようになる。
/GameData.Stats.HealthRange
- Root(/)直下の、GameDataオブジェクトの下位の、
Statsオブジェクトの下位の、HealthRangeダイナミックデータアイテムに
アクセスする
「/」を頭につけると、Rootを先頭としてオブジェクトを探す。 頭につけないと、その呼び出しをしたオブジェクトの親オブジェクトと同じ高さからオブジェクトを探しはじめる。「何だって?」と思ったかも知れない。それが普通である。そういうわけで/や後述のparent、selfを使わない呼び出しは、どこから呼び出しをするか分かりづらいのであまり薦めない。
一通り理解したらUIBuilderを開いて、適当なウィンドウの呼び出しが書き込まれている記述の通りにオブジェクトを追いかけてみるとイメージが掴みやすいだろう。
parent、self、child[]
オブジェクト呼び出しのときに使える特殊な名前として、Parent、Self、Childというものがある。 呼び出しをしたオブジェクトから見てそれぞれ、「自分の親オブジェクト」「自分自身」「自分の下位の、プロパティ名と同じNameを持つオブジェクト」という意味になる。
- Parent
- 自分の親オブジェクト
- Self
- 自分自身
- Child
- 自分の下位の、プロパティ名と同じNameを持つオブジェクト
使い道としてはたとえば、myobjというオブジェクトからそれ自身のTextプロパティを参照しようと思った場合、「myobj.Text
」と書いてしまうと、「myobjの下位にあるmyobjオブジェクトのTextプロパティを参照する」という意味になってしまい、本来の目的が達成できない。ここで「self.Text」とするのだ。
childだけは使い方が変わっているが、便利なので知っておいて欲しい。
たとえばレイドウィンドウで、1番グループの1番目のメンバーのキャラクター名を取得するため、名前の格納されたNameオブジェクトにアクセスしようと思ったとする。目的のNameオブジェクトは「Group1.Member1.Name」という位置にあるのだが、この通りに書くと、上のルールによってこの呼び出しはMember1オブジェクトの Nameプロパティ を参照し、「Member1」という値を返してしまう。
そこでchildの出番だ。「Group1.Member1.child.Name」と書くと、パーサーは.Nameの部分をプロパティだとみなさず、先にオブジェクトのNameを探してくれる。オブジェクト構造設計のミスから生まれたのだと思われる機能だが、Nameを適当につけてしまい、まずい結果になったときは救われるかも知れない。
画像の表示[]
+と−のボタンで、オブジェクトの重ね順を変更できる。
上位にあるオブジェクトほど、重ね順は上になる。 画像を呼び出すときだけは、フォルダのような呼び出し方をする。 画像の形式はDDSという。 画像は大きなものを取り込み、その一部をプログラムから「切り取って」使う。
アクションプロパティあれこれ[]
式については数学で習っている話がほとんどなので今更感がある人が多いだろうが、大した分量でもないので一通り触れておく。
「=」は、「記号の右側の内容を、左側に書いてあるプロパティへ放り込む」という意味になる。これを「代入」と呼ぶ。
記号 | 呼び名 | 英名 | 意味 |
---|---|---|---|
= | イコール | equal | 右辺を左辺に代入する |
== | ダブルイコール | 両辺は等しい | |
< | 小なり | lesser than | 右辺のほうが大きい |
> | 大なり | larger than | 左辺のほうが大きい |
. | 結合子 | concatanate | 要素を結合する |