UDN
Search public documentation:

MasteringUnrealScriptInterfacesJP
English Translation
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

第 13 章 – インタフェース

これまでに Unreal Scriptでのほとんどすべての作業の実施を十分に行っています。文法、変数および関数のインスタンス化の方法を理解しました。イテレータ、状態およびデリゲートの利用を許すためのわずかな先進的なトピックも説明しました。本章では、あと 1 歩進んで行き、 Interface(インタフェース) の導入により、プロジェクトの成長につれ、依存可能なプログラミングへのドアを開いています。それらのインタフェースの目的、インスタンス化およびプロジェクトを通じてコーディング標準の維持を支援するために使用できる 2 つの例を見ていきます。

インタフェースとは、何でしょうか ?

プログラミングは、素人の観察者には全く分からない、多くの挑戦をもたらします。特に、ゲームの開発は、そのような挑戦が多く起きるため、ソフトウェア工学のほとんどすべての側面を利用する分野です。大きなオブジェクト指向システムで作業する場合は、時々、同じ関数のシグネチャを提供する関数のグループを定義して、すべて単一の方法で使用される多くのクラスを作成します。

class MyPistol extends MyWeapon;

function bool PrimaryFire(float rate)
{
   /* ピストルの処理はここ */
}

/* その他の処理 */

class MyRifle extends MyWeapon;

function bool PrimaryFire(float rate)
{
   /* ライフルの処理はここ */
}

/* その他の処理 */

class MyShotgun extends MyWeapon;

function bool PrimaryFire(float rate)
{
   /* ショットガンの処理はここ */
}

/* その他の処理 */

これは、イギリス海峡を泳いで渡ったり、全て楊枝で家を建てたりすることが可能であると同様にですが - 思い切って、ここでコードを記述することは、完全に実現可能ですが、必要な関数を適切に実装したことを確認していません。また、関数が今後変更されないという保証もありませんので、後日コードのリファクタリングが問題となる可能性があります ; もちろん、仲間や共同開発者が本当に指示に従っているかを確認する手段を提供していないことには言及していません。

多くのオブジェクト指向言語は、この問題を軽減するためのツールを提供していて、 Unreal Script は、 Java および CPP のルーツに対してツールを持っています。定義された標準に従ってクラスを計画する明示的な意味を提供することでコードの品質向上を支援する間に、部分的には、コンパイラに対するモニタリングコード開発の合併症を操作して、インタフェースは、これらのタイプの状況内で利用されます。

この説明で便利な用語は、 Implement(実装) です。以後この章では、ユーザにある機能のグループを提供するクラスまたはデバイスを参照するためにこの用語を使用します。スプーンは道具のインタフェースを実装します、これは、スプーンは、道具としてのすべての機能を提供するということと同じです。スプーンは道具を実装するかも知れませんが、更に機能を提供して、自分自身のやり方に特化しないというのではありません。

インタフェースの例

近所の Ikea や食器店に行ったとしたら、フォーク、スプーンまたはナイフはどうあるべきかという装飾の様子や、チャンスがあれば、それらのものと異なった外見のものが見られるでしょう。このような可能性を考慮しなくても、全体で見たときには、すべての他の食器と同じ標準を実装しています。いくつかのものは、野菜を取り扱うのに便利な穴あきスプーンや 「両者の長所を生かしたもの」としても知られる、2 つの要素の便利さを組み合わせた丸っこい赤毛の継子であるスポークのように、より特殊化されることさえも可能です。

フォーク スプーン ナイフ
  • 食物を扱うには、簡単です
  • 入手したものを容易に取り外すために提供
  • 液体およびゼリーをある場所から他の場所に移動するため
  • 口中に入れられるように、十分に小さいかたまりに食物を分けるため。
表 13.1 道具を使用するインタフェースの簡単な例

道具としては、以下のことを確実にする、フォーク、スプーンおよびナイフにおける他の標準や要件の提示が可能です :

  • 充分に使用できるほど大きいが、人の口や手に充分に適合するように小さくなっています
  • 壊れやすいまたは標準外の物質では作成されていません (発泡スチロールの道具は聞いたことがありません)
  • 自分の指を噛んだり、食事中にガールフレンドの眼を突いたりしないことを確実にする適切な長さとなっています。
  • 食事を可能にするため強いて体をねじるような、許容できないような複雑なやり方で曲がることはありません

USB

インタフェースのもう 1 つの例は、日常的に利用しているもので、おそらく、それぞれのコンピュータ上のものです。 USB デバイスをプラグインした時に、 Wacom のタブレットから、マウス、ハードドライブまでのすべてをプラグインするため 1 つのポートの利用を許す、良く定義されたインタフェースを利用します。USB ドングルを確実にコンピュータに押し込むことが可能であり、数秒の間に windows はデバイスを認識し、 (時にはそうで無い場合もありますが) 直ぐに使用を許可します。 USB は、永久に利用できるものではありません。私の知人の多くは Commodore 64 上で動作するジョイスティックを入手した時に、どれほど大変だったかを記憶していますが、それは、同様に標準化された、シリアルポートと呼ばれるインテルのインタフェースを持っていました。


図 13.1 – 一般的な USB ポート

コンピュータマウス

マウスを取り上げ、左および右のボタンの動作に依存することを許可する、また、予期したとおりに動かす方法など、インタフェースは、周り中にあります。マウスはインタフェースの 2 つめの性質 - 実装は変更する可能性があること - を示しますから、インタフェースのとても良い例です。マウスは、ボール、トラックボール、光、レーザーでも良いですが、その実装に係わらず何年もかかって習得した方法で、このマウスを安心して使用できます。マウスパッド上を動かしても、トラックボールを回しても、画面上でカーソルを動かすため、コンピュータには適切なコマンドが送られます ; ボタンをクリックした場合も、コンピュータから期待した応答が得られます。

電源コンセント

二極および三極の電源コンセントが、冷蔵庫または天井換気扇および市から提供された電力網のような、電子デバイス間のインタフェースとして提供されています。これを取り扱うために複数のインタフェースがあります。三極のインタフェースではアースを利用できますが、二極では利用できません。

電力の要求事項とサージの防止のために、ラップトップまたはデスクトップ・コンピュータのようなデバイスは三極コンセントにのみ差し込むことができます。卓上扇風機または携帯電話アダプタは、二極のみを使用しますが、三極コンセントでは、二極のコンセントと全て同じ機能を提供していて、そこから先に進んでアースも提供したため、二極のアイテムは、どちらのコンセントにも差し込み可能です。

三極のコンセントは、二極のインタフェースを Implement(実装) しているといえます。もし、いくつかの一般的なコンセントのタイプに対して継承木を作成したら、このようなものになりますが、ここでは、冷蔵庫や大型冷凍庫を差し込む可能性のある高電圧の線は除いて確かに簡略化されています。


図 13.2 - 電源コンセントのツリー図

メモを正しい位置に置いて、クラスとその相互関係を記述した 5 万フィートの図を維持することを支援するために、このようなタイプの図を使用することができます。どのレベルで何を使用できるかをたどることも興味深いものです。この図をみて、卓上扇風機は、家の中のどこにでも差し込めますが、強力なヘアドライヤーを使用するならば、バスルームの中をみて、三極のコンセントを実装しているものを探すことができることを知ります。

プログラミングの仕様

プログラミングに関して言えば、他のクラスに依存することが可能な特定の関数を定義するためにインタフェースを使用します。プログラミングを行う時は、同じ入力、出力および名前を持つ多くの関数を定義することがしばしばあります。この組み合わせを関数のシグニチャと呼びます。これらの制限の中で作業して、ある種の要求事項を作成し、どのような機能を提供するかを告げて、プログラマに渡すことができますが、 Interface では、提供したものについて、コンパイラに確認を強制できます。

これを見る一つの方法は、以下のようなオブジェクトのグループの例を考えることです :


図 13.3 インタフェースの概要

この階層構造では、武器に対する計画を見ることができますが、 IWeapon および IZoomedWeapon の 2 つのインタフェースを作成しますが、それらの内容の詳細についてはこの時点では考慮しません。 Pistol、MachineGun および RocketLauncher は、 IWeapon インタフェースを実装していて、 SniperRifle は、 IZoomedWeapon インタフェースを実装していることがわかります。インタフェースで作業する場合には、区別するため、インタフェースには "I" の接頭辞を使用することは標準的な規範です。

IWeapon および IZoomedWeapon のようなインタフェースを、執行者または明示的な法則として見ることは便利かもしれません。コンパイラはインタフェースをコードに対する要求仕様文書として使用します。それぞれのインタフェースは必要な関数を定義して、それを定義する時には、コンパイラはそれらを確実に実装したことの確認を行います。 IWeapon を見ると以下の擬似コードのようなものとなるはずです :

全ての武器は、発砲する率を受け取る Primary Fire と、発砲すべき一斉射撃の数を受け取る Secondary Fire のメソッドを実装します。実行が成功したら、この双方とも false をリターンします。

データの完全性を保証するために、実装を通して自由に使用できる 2 つの定数を定義します。それは、 Maximum Firing Rate および Minimum Firing Counter です。

以上のクラスをコンパイルしようとした時に、コンパイラは、ピストル、マシンガンおよび他のクラスが、必要な関数を実装していることを確認するために、 IWeapon インタフェースを実装していることを確認します。言語に依存して、インタフェースで定義した関数を実装しなければ、恐らく、その結果がエラーとなるか、コンパイル時に、ある種、恐ろしい出力内容となるでしょう。

インタフェースの定義

Unreal は、以前説明したように、関数シグニチャにより、クラスとは異なるインタフェースを示すために Interface キーワードを使用します。 Unreal Script では、任意の数の適当と思われる関数やデータタイプを自由に定義できます。これには、関数、構造体、列挙型またはメモリを実際にインスタンス化しないような、何か他のことを含みます。これらの宣言を集中化して、踏み込む可能性のあるコードの重複の問題を最小化するためにこの事実を使用できます。

Unreal Script では、 Interfaces は、クラスによって設定された標準に従って、単純なやり方で定義されます。以下に、擬似コードの代わりに、 Unreal Script 内で定義されている、今まで見てきた IWeapon インタフェースを示します。

Interface IWeapon;

/* 定数の定義 */
const MaximumFiringRate    = 0.05; // 60/1200
const MinimumFiringCounter = 3;

/* 以下に全ての関数宣言 */
function bool PrimaryFire(float Rate);
function bool SecondaryFire(int Counter);

宣言と定義

Unreal 3 でコンパイルされるには、リターンタイプから入力値までのすべてが、これらの関数シグニチャと一致しなければなりません。これは、この環境内のプログラミングの他の重大な局面を強調します。それは、 Declaration(宣言) と Definition(定義) の間の区別です。

  • リターンタイプや入力引数のような必須要素を提供して、インタフェースは関数を宣言します。
  • クラス内にインタフェースを実装する時には、どのように動作するか、何を行うかのような実際の関数の詳細を定義できます。

レストランでの外食の観点からこれを考えてみると助けになるかもしれません。インタフェースとして最初にメニューが与えられますが、メニューを選ぶと肉とジャガイモの食事が出てきます。インタフェースはメニューで、食べ物自体は実装です。

インタフェースの継承

電源のアダプタの議論と似ていますが、他からインタフェースを作成することができます。これは、クラスの拡張を行う時と同様に、 Extends キーワードを使用して実現されます。

Interface IZoomedWeapon extends IWeapon;

function bool ZoomedFire(float Rate, int FOV);

これは、確かに、インタフェースについて複雑な階層構造の継承木を構築可能なケースですが、それを避けることが可能であればそうすべきです。クラスについて言えばインタフェースは必要なときのみ作成すべきですし、明示的に定義すべきです。インタフェースを不明確に、または、根拠の無い関数を含めて定義すれば、空の関数を持つ複雑なクラスになりがちです。インタフェースの使用は、前もって計画をした時に例外的に有益になります。この種の議論に興味を持った場合は、オブジェクト指向分析と設計を行ったクラスまたは、UML に関する本を強く推奨します。

インタフェースの実装

クラス内への Interface(インタフェース) の実装は、クラスの派生で使用されたやり方と同様に、 Unreal Script では、実は、とても単純です。といっても、インタフェースは、恐らくスタンドアロンとなるでしょう。このホームを派生させるための 2 つの例を見てみましょう。ピストルクラスは IWeapon インタフェースを実装しますが、そのクラスは UScript では以下のようなものになります :

Class Pistol extends UTWeapon implements(IWeapon);

function bool PrimaryFire(float Rate)
{
   if (Rate > class'IWeapon'.const.MaximumFiringRate)
           return true;

       /* ここで、いろいろ実行 */
       return false;
}

function bool SecondaryFire(int Counter)
{
   if (Counter < class'IWeapon'.const.MinimumFiringCounter)
      return true;

   /* ここで、いろいろ実行 */
   return false;
}

お分かりのように、この実装は、インタフェース内で宣言された必要な関数であり、コンパイル時には、エラーになりません。このような独特の行になります…

if (Rate > class'IWeapon'.const.MaximumFiringRate)

この行は、インタフェース内の定数へのアクセスを得るための例です。ルールとして、それらがクラスであるかのようにインタフェースを取り扱ってください、そうすれば同じように定義された要素へのアクセスを得ることができます。(これは、第 3 章で議論されました)

単なる例としても、正しく実装されていないインタフェースをコンパイルしようとしたら、以下のようなエラーが報知されます :

Error, Implementation of function 'SecondaryFire' conflicts with interface 'IWeapon' - parameter 0 'Counter'
(エラー、関数 'Secondary Fire' は、インタフェース 'IWeapon' と矛盾しています - パラメータ 0 'Counter')
Compile aborted due to errors.
(コンパイルはエラーのために中止されました。)

スナイパーライフルを見てみましょう。

Class SniperRifle extends UTWeapon implements(IZoomedWeapon);

function bool PrimaryFire(float Rate)
{
   if (Rate > class'IWeapon'.const.MaximumFiringRate)
      Rate = class'IWeapon'.const.MaximumFiringRate;

   /* ここでいろいろ実行 */
   return false;
}

function bool SecondaryFire(int Counter)
{
   if (Counter < class'IWeapon'.const.MinimumFiringCounter)
      Counter = class'IWeapon'.const.MinimumFiringCounter;

   /* ここでいろいろ実行 */
   return false;
}

function bool ZoomedFire(float Rate, int FOV)
{
   /* バンと一発 ! */
   return false;
}

ここで見ることができるように、このクラスは IZoomedWeapon を実装し、それは順番に IWeapon インタフェースを拡張します。コンパイラは双方のインタフェースが実装されていることを期待して、ルールとして、含まれる関数が同様に定義されていることを確実にするために、現在のインタフェース上のすべてのインタフェースを確認します。これは、インタフェースの木を小さく保つためのもう一つの理由です - やぶや低木を考えてみてください。

どちらのクラスでも定数を定義する必要が無いことも留意してください。

最初にインタフェースを使用して作業を開始した時には、間違いなくつらい経験となり、圧倒されてイライラするかもしれません。インタフェースをたどるために Interface インデックスカードを作成すると便利かもしれません。

IWeapon
関数
  • PrimaryFire(rate)
    • Success をリターンする
  • SecondaryFire(spread)
    • Success をリターンする
定数
  • MaximumFiringRate
  • MinimumFiringSpread
表 13.2 - IWeapon インタフェース

IZoomedWeapon extends IWeapon
関数
  • ZoomedFire(Rate, FOV)
  • Success をリターンする
定数
  • 無し
表 13.3 - IZoomedWeapon インタフェース

どのようなインタフェースが有るかをレビューして、 UT3 パラダイム内の 2 つの特異な点を強調してみましょう。それから、更に多くのコードに関わり、本当に面白いものを作り上げるのを見てみましょう。

  • インタフェースは、クラス内に実装しなければならない関数の宣言を許可します
  • それらは、関数の宣言に要求事項を与える手段を提供しています
  • それらは、宣言としても知られている、関数のシグニチャ以外には実装には何の要求事項も提示していません
    • その代わり、どのように関数が使用されるかに焦点を合わせています
    • 実装は、クラス間で変わる可能性が有ります ; 定義が異なるかもしれません
    • 親クラス内で関数が実装されていた場合は、インタフェースの要求事項を満足するはずです
  • UT3 は、 Enumerations(列挙型)、 Structures(構造体) および Constants(定数) を定義する手段を提供していますが、どれも実際のメモリを要求しません
  • 複数のインタフェースの実装が可能ですが、選択した環境でのみ使用されるべきです
    • 同一の階層に 2 つのインタフェースを実装するとコード内で問題となる場合があるため、徹底的に避けるべきです

なぜインタフェースが使用されるのでしょうか ?

既に説明したように、クラスが特定の仕様に適合しているかを確実にするため、インタフェースはコンパイラに対してメカニックを提供します。 EpicGames 社は、オンラインゲームプレイに対する要素の多くおよびいくらかの奇妙な保存データを含みますが UI 要素に限定する訳では無い、ごく少数のインタフェースしか配布しません。もし、詳しく見れば、変数タイプとして、クラス内で経験したことと同様に、例、および 2 つめのメソッドで既に見てきたように個々のインタフェースが実装されていることに気づくでしょう。この主要な例は OnlineSubsystem です :

/** アカウント情報を生成および / または列挙のために使用されるインタフェース */
var OnlineAccountInterface AccountInterface;

/** オンラインプレーヤーメソッドにアクセスするためのインタフェース */
var OnlinePlayerInterface PlayerInterface;

/** オンラインプレーヤー拡張メソッドにアクセスするためのインタフェース */
var OnlinePlayerInterfaceEx PlayerInterfaceEx;

/** システムワイドなネットワーク関数にアクセスするためのインタフェース */
var OnlineSystemInterface SystemInterface;

/** オンラインゲームを作成、検索または破壊するために使用されるインタフェース */
var OnlineGameInterface GameInterface;

/** オンラインコンテンツを使用するためのインタフェース */
var OnlineContentInterface ContentInterface;

/** ボイスコミュニケーションを利用するためのインタフェース */
var OnlineVoiceInterface VoiceInterface;

/** 状態の読み書き操作に対して使用するインタフェース */
var OnlineStatsInterface StatsInterface;

/** ゲームに関したニュースの通知を読むために使用するインタフェース */
var OnlineNewsInterface NewsInterface;

これは、既にインタフェースで学んだことを補足しますが、完全に明白ではないかもしれません。もう一度、以前定義した武器を見直しましょう。インタフェースを定義したということは、それが存在すれば、他のクラスに依存して使用可能となります。ユーザは、以下に挙げるような関数を使用できます :

Function bool foo (IWeapon w)
{
   w.PrimaryFire(60/1200);
   /* 実行したい他の何らかの処理 */
}

以前経験したように、クラス、関数パラメータおよび構造体内で変数タイプを宣言しています。利点は、見方によれば欠点かもしれませんが、使用しようとしている時に、既にあるタイプにキャストする変数を持っていることです。これは、デフォルトコードベース全体を通して利用され、その結果、参照マテリアルに対してとても一般的になっています。

ベースタイプ クラス 定数
  • このタイプのアイテムのみ
  • 例. Bool
  • このクラスまたは何らかのその継承
  • 例. UTPawn
  • このインタフェースを実装する任意のクラス
  • 例. OnlineAccountInterface
表 13.4 - インタフェースは、タイプとして使用される時に受け入れられる

最後に

いつサブクラス化するか、また、いつインタフェース内で生成するかは、多くの人々が尋ねる質問ですので、悪い質問ではありません。自分で実行してみると、インタフェースは、 1 つの異なる点を除いて、サブクラス化と同様の結果となることが分かるでしょう - サブクラス化では、インタフェースを使用する時には、主要な例として説明される、関数のシグネチャの変更の停止はできません。以前説明したように、あるプロジェクトでは、クラスの定義前に、既定義のインタフェースが必要です。

思い切ったことを行うことは、運命を制御するチャンスに任せる 1 つの方法です。単純なタイプミスは多くのデバッグ時間が必要になるかもしれませんが、インタフェースでは、関数が正しく宣言されなかった場合に、その行を教えてくれます。

チュートリアル 13.1 – コンパス、パート I: ICOMPASS インタフェース

単にこの時点までの議論を理解するために、若干時間を使って、ある種のインタフェースを実装してみましょう。初めてなので詳細に焦点を当てて、低めを狙いましょう。後には、より興味深いことができるようになります。

定義する新たな要素、コンパスを探して、マッパーおよび HUD のコーダーがやって来ます。この要素の目的は、自身のレベルの中に置かれ、 Interface および後には実際の Compass クラスを定義するつもりの、 HUD のコーダーが持つある種の必要性から、マッパーに北の方向を示す高精度のコントロールを提供することです。

Compass のニーズ :

Mapper(マッパー) Coder(コーダー)
  • マップ内に配置される予定
  • Yaw として参照される回転可能な方位を持つ
  • マップ内に表示可能なアイコンを持つ
    • マッパーにより提供され、コンパスと名付けられた CompassContent パッケージ内にある
  • Yaw に対する Getter 関数を提供する
  • UI 変更時には、ラジアンおよび角度を計算する必要がある
  • 選択された場合には、開発者に Rotator の獲得を許可する
  • マップの再ロード時には削除されたり変更されたりしない事を確実にする
表 13.5 - コンパスオブジェクトの仕様の概要

この情報の大部分は実装に関連したものですが、コーダーは明確にニーズを理解します。これを 1 つずつ実施していくべきで、そうすれば、コードは明確に分かるようになるでしょう。

1. ICompass Interface を宣言してください。

interface ICompass;

2. オブジェクトの回転していない方位をリターンする GetRadianHeading() 関数を宣言してください。

function float GetRadianHeading();

3. ラジアン値を度数に変換してリターンする GetDegreeHeading() 関数を宣言してください。

function float GetDegreeHeading();

4. オブジェクト Rotation の Yaw をリターンする GetYaw() 関数を宣言してください。

function int GetYaw();

5. 完全なローテータオブジェクトをリターンする GetRotator 関数を宣言してください。

function Rotator GetRotator();

6. ローテータをベクタに変換してリターンする GetVectorizedRotator() 関数を宣言してください。

function vector GetVectorizedRotator();

チュートリアル 13.2 – コンパス、パート II: コンパスクラスの実装

このインタフェースを使用して、コンパスを簡単な方法で作成することができますが、車輪の再発明のような無駄な努力に終わらないことを確実にするためにいくつかのリサーチを行うべきです。すべての要素がそのように見えるかもしれませんが、自身で記述することを避けたほうが良い唯一の要素は、恐らくローテータだけです。それは、また、マッパーが慣れ親しんだインタフェースをローテーションツールと共に提供します。

Object クラスは、最も役に立つ、多くの素晴らしい関数および要素を持っていますが、アクタクラスが探しているローテータを実際に実装していて、位置ベクタも同様に備えていますので、申し分ありません !

var(Movement) const vector   Location; // Actor の位置 ; 設定には Move を使用してください。
var(Movement) const rotator Rotation; // ローテーション。

この時点で、クラスのコーディングが開始でき、インタフェースを実装して、クラスから派生させる方法を知ることができます。

1. ICompass インタフェースを実装し、アクタクラスおよび placeable を拡張して、クラスを定義してください。

class Compass extends actor placeable implements(ICompass);

2. Rotator を取り扱う関数を定義してください。派生を選択する場所に対して、ローテータは既に利用可能です。簡単に 3 つの関数定義を見てみましょう。

// アクタの yaw をリターン
function int GetYaw()
{
   return Rotation.Yaw;
}

function Rotator GetRotator()
{
   return Rotation;
}

function vector GetVectorizedRotator()
{
   return vector(Rotation);
}

Yaw のみを考慮してから、開発者に基本的なアクセスを提供しています。

3. Yaw は、実際には、 HUD を利用しやすくするために必要な形式ではありませんが、マッパーに対しては、十分に動作します。UI 動作に対して、方位が正確であることを確実にするために、ローテータにある種の操作を行う必要があります。2 ~ 3 分使って、これを分割して説明しましょう。まず、 GetRadianHeading() 関数を宣言します。

function float GetRadianHeading()
{
}

a. 3 つのローカル変数が必要です。

local Vector v;
local Rotator r;
local float f;

b. Yaw コンポネントを取得してください。それを新規ローテータオブジェクトの yaw 値にコピーするように留意してください。これが、ローテータを単純化する最初のステップです。

r.Yaw = GetYaw();

c. 角度操作を簡単に取り扱うため、ローテータを Vector に変換してください。

v = vector(r);

d. EpicGames 社が提供する関数を利用して Heading を巻き戻してください。 Unreal 内のプログラミングの数学的な目的に踏み込んだ場合には、同じような多くの関数がとても有用だということがわかるでしょう。

f = GetHeadingAngle(v);

e. 他の組込み関数を使用して、方位を巻き戻してください。これは、実際にはラジアン値をリターンしますが、負の値となるかもしれません。

f = UnwindHeading(f);

f. 2Pi を加算して値を正の値に変換しましょう。

while (f < 0)
   f += PI * 2.0f;

g. そして最後に、値をリターンしてください。

return f;

これは、ビークルと共に使用され、ラジアン変換を保存するアルゴリズムで、 UTVehicle.uc の 1199 行内で見ることができます。

4. Radian 計測値を角度に変換してください。ラジアン計測値を得ることを可能にするため、計算したばかりのラジアン計測値を使用します。定数を格納した変数 RadToDeg はとても便利でその値は、 180/PI で、予め計算されています。

function float GetDegreeHeading()
{
   local float f;

   f = GetRadianHeading();

   f *= RadToDeg;

   return f;
}

5. 力仕事は終わりました。残った唯一のことは、 UI に関係するものです。しかしながら、着手する前や、後にマップで作業している時に、ある種のデバッグ情報の出力は役に立つでしょう。レベルをロードしている時に、デバッグにとても便利な関数は PostBeginPlay です。ゲーム開始時に、ログファイルに方位を出力可能です。

event PostBeginPlay()
{
   `log("===================================",,'UTBook');
    `log("Compass Heading"@GetRadianHeading()@GetDegreeHeading(),,'UTBook');
   `log("===================================",,'UTBook');
}

6. すべての関数を設定しましたが、マッパーに 2 ~ 3 のビジュアル項目を取り付ける必要があります。特に、オブジェクト上には、アイコンおよび矢印を描く必要があります。 1 つの要素が適切の値を保持しており、マップがリセットされた時に削除されたり、変更されたりしていないことを確認するために 2 ~ 3 のエントリを変更するために時間を使うこともできます。

DefaultProperties
{
}

a. 新規の ArrowComponent 要素、名前付き矢印を定義してください。これは、オブジェクトが指し示す実際の方向を反映するため、マッパーで表示される実際の矢印となるはずです。

Begin Object Class=ArrowComponent Name=Arrow

b. Arrow Color は、 0-255 の範囲ですが、これは、 Photoshop や Web 開発の経験で、おなじみの値かもしれません。希望するようにこれを調整できます。 UnrealEditor 中に色選択機能があり、とても適切に動作しますが、整数値で遊ぶこともできます。

ArrowColor = (B=80,G=80,R=200,A=255)

c. これは、 Arrow の長さであり、太さではありません。この値を増やすにつれて、矢印の長さまたはサイズが増えることに気付くでしょう。

ArrowSize = 1.000000

d. 混乱を最小化するために適切で親しみやすい名前を付けてください。

Name = "North Heading"

e. ここで締めくくりに、コンポネント配列にそれを追加してください。Unreal Script 内のマップコンポネント全体でこのパターンが見られます。

End Object

Components(0) = Arrow

f. 例えば、 Sprite コンポネントです。これは、実装が若干簡単となりますが、表示したいパッケージ、グループおよび / または、テクスチャの名前を知る必要があります。以前に実行したように Begin は、 Sprite に対し新しいコンポネントオブジェクトを定義しています。

Begin Object Class=SpriteComponent Name=Sprite

g. 要素に取り付けたい新しいテクスチャをプラグインするつもりです。エディタ内のすべての 2d スプライトは、常に開発者と顔を会わせていて、侵入は最小限であるべきです。以前説明した通り、 Sprite は、既に 'UTBookTextures.compass' に定義されています。

Sprite=Texture2D' CompassContent.compass'

h. ゲーム中には隠し、プレイ中には実際のゲーム中にローディングすることを避けるため、ここで、 2 ~ 3 のブール値を設定することにします。

HiddenGame = True
AlwaysLoadOnClient = False
AlwaysLoadOnServer = False

i. そして、最後にそれに締めくくりを付けて、 Components 配列に追加してください。渡されるこの要素は、 Name=Sprite エントリ内にあります。

End Object
Components(1) = Sprite

7. 適切な維持管理のためにトグルされるべき、 2 ~ 3 の要素が、まだ、ありますので、この作業を完成する前にそれらを行いましょう。

a. bStatic は、ゲームのプレイ中に、このアクタについて Mappers が任意の何かを変更する能力を持つかどうかを制御する Boolean です。コンパスは北を指し続けるはずですから、 static(静的) にするべきです。

bStatic   = True

b. bHidden は、このアクタの基本コンポネントのビジビリティを制御します。これは Sprite コンポネントで変更したブール値に対するフェールセーフと考えることができます。

bHidden   = True

c. bNoDelete は、ゲームプレイ中にこのアクタを削除する能力を制御します。コンパスの存在を消すと、非常に混乱しそうなので、このプロパティは、明確に True に設定します。

bNoDelete = True

d. bMovable は、アクタの動作に関連しています。アクタクラス内のもう 1 つのフェールセーフです。

bMovable  = False

Component 下の多くの派生クラスを見ることで、コンポネントに関する情報を更に得られます。これらの 2 つは、その起源に基づき、オブジェクト上のスプライトおよび矢印のコンポネントに取り付けると言うだけで充分です。

チュートリアル 13.3 – コンパス、パート III: コンパスのテスト、パート I

少なくともコンパスの要素については、コーディングはすべて完了しました。コードをビルドして、すべてを適切に行われたかを確認するためにエディタをオープンし要素が動作していることをテストするため、マップ内に配置してから、 UT3 にロードしてください。

1. 先に進み、エディタをロードしてください。新たなマップを作成 (また希望する場合には既存のマップをオープン) して、必要な要素を設定するべきです。

2. Actor Classes ブラウザをオープンしてください。これは、 Actor Classes 下の Generic Browser(汎用ブラウザ) にあり、 Unreal Script 内のクラス階層を含みます。.

3. スクリプトパッケージは UTEditor.ini ファイル内の ModPackages リスト内にあるので、既にロードされているはずですが、何らかの理由でロードされていない場合は、File > Open メニューへ行き、コンパイル後の .u ファイルが格納される Unpublished\CookedPC\Scripts へ行ってください。


図 13.4 - MasteringUnrealScript.u を検索する File ダイアログ

4. それがロードされれば、図 13.5 のように、 Actor の下のクラスツリー内にコンパス要素が表示されるはずです。それを選択して、マップを切り替えてください。ここで右クリックが可能となり、 “Add Compass Here” メニューオプションが提供されるはずです。


図 13.5 - コンパス内の Actor Class Browser

5. この要素を選択して、 図 13.6 内のような要素を作成します。エディタ内で選択して、ローテーションツールに切り替え、矢印の回転を見ることで、適切に動作していることを確認できます。


図 13.6 - マップ内の Compass オブジェクト

6. 自身のレベルをロードしたら、ログファイルが以下のようになっていて確かに動作していることが分かります。


図 13.7 - games(ゲーム) ログファイルの引用。

ここで見ることができるように、作成したコンパスは、ラジアンおよび度の形式の双方で適切な値を報告していますので、開発者は、実行する必要のあることを実行できます。これは、計画とインタフェースを共に行う基本的な例です。要件だけ繰り返すと、以下のような処理でした :

  • クラスで行う必要なことを確認
  • 仕様ごとにインタフェースを定義
  • フェールセーフとしてインタフェースを実装して、作成するクラス用に関数を定義
  • 必要なコンポネントを取り付け
  • テスト

このコンパスオブジェクトの実装検討を行ったら、プレーヤーのマップ内の位置をたどって、マップ内の他のプレーヤーの位置も同時に表示する動的ミニマップシステムの完全動作版を作成するため、チュートリアルの次のシリーズにおいては、この考えを基にした構築を行います。

チュートリアル 13.4 – ミニマップ、パート I: MU_MINIMAP クラス

ミニマップは、最近リリースされたサンドボックスをオープンするスタイルのいずれかのゲームをプレイした方にとっては、かなりおなじみのものでしょう。基本的に、これは、スクリーン上に常時表示されるマップで、プレーヤーの周りのワールドの一部を表示しています。プレーヤーが移動したり、向きを変えたりするにつれて、マップは動いたり回転したりします。作成するミニマップは 2 つの部分で成っています。マップとコンパスオーバレイです。

動作可能なミニマップシステムを作成するためには、 3 つのものが必要となります。 問題となっているマップに特有のある種のデータを保持する、マップ内に置かれる Compass クラスのサブクラス、スクリーンにマップを描くために操作する新規のヘッドアップディスプレイ (HUD) クラス、および、新規 HUD クラスの使用を強制するために新たなゲームタイプです。 HUD クラスは、基底 UTHUD クラスを拡張して、単にミニマップを描くために必要な機能を追加しています。ゲームタイプクラスは、使用された HUD のタイプをオーバーライドする UTDeathMatch ゲームタイプのとても簡単な拡張であり、ミニマップシステムに関連したちょっとした設定を実行します。

始めに、このチュートリアルでは、 Compass クラスのサブクラスである MU_Minimap クラスを宣言します。

1. ConTEXT をオープンして、 UnrealScript ハイライタを使用して MU_Minimap.uc という名前の新規ファイルを作成してください。

2. Compass クラスから拡張して、新規 MU_Minimap クラスを宣言してください。

class MU_Minimap extends Compass;

3. このクラスでは、いくつかの編集可能な変数の宣言が必要です。まず、 マテリアルに対する参照を保持する MaterialInstanceConstant がマップ自身に対して使用されます。すべてのコードを作成し終わった後にもう一度マテリアルの設定を確認したら、マップ内で MU_Minimap アクタを設定する準備ができます。

var() MaterialInstanceConstant Minimap;


図 13.8 - ミニマップテクスチャの例。

4. コンパスオーバレイのためにマテリアルを参照する他の MIC 変数も必要です。

var() MaterialInstanceConstant CompassOverlay;


図 13.9 – コンパスオーバレイテクスチャの例。

5. クラスに追加する sphere(球体) コンポネントは、レベルを設定して正しいマップスクリーンショットを取得するために編集可能になります。このアクタの位置はマップの中心を示し、球体の半径はマップで表されるそれぞれの方向への拡張を示すと言う考えです。

var() Const EditConst DrawSphereComponent MapExtentsComponent;

6. bForwardAlwaysUp と言う名前の Bool 変数は、プレーヤーの前方への動作は常にスクリーン上の上方向への動作、または、 MU_Minimap アクタの回転によって決定される North 方向角 に寄る正しいオフセットとして表示されるかどうかの指定をデザイナーに許します。これは、理に適っているようため、いつでも True に設定しておくほうが良い気配が濃厚ですが、選択肢として残しておきます。

var() Bool bForwardAlwaysUp;


図 13.10 – 矢印は、前方への動作方向を示します。左側では bForwardAlwaysUp は False に設定され、右側では True に設定されています。

7. マップ内でプレーヤーの位置を追跡し、マップテクスチャの位置を変換するために、マップテクスチャによってカバーされる X-軸および Y-軸内の形でワールドスペース座標の範囲を知る必要があります。 2 つの Vector2D 変数は以下の値を保持します。

var Vector2D MapRangeMin;
var Vector2D MapRangeMax;

8. 他の Vector2D 変数は、マップテクスチャの中心に対する X および Y のワールドスペース座標を保持します。

var Vector2D MapCenter;

9. MapCenter 変数の値は、 PostBeginPlay() 関数内で代入されます。親クラスの PostBeginPlay() 関数の呼出しも確認するために、本関数をオーバーライドして、この変数に値を代入してください。

function PostBeginPlay()
{
   Super.PostBeginPlay();

   MapCenter.X = MapRangeMin.X + ((MapRangeMax.X - MapRangeMin.X) / 2);
   MapCenter.Y = MapRangeMin.Y + ((MapRangeMax.Y - MapRangeMin.Y) / 2);
}

10. 次に、依然として PostBeginPlay() 関数内ですが、MapCenter で始め、 MapExtentsComponent の SphereRadius を減算して、マップのそれぞれの軸の延長を計算してください。

MapRangeMin.X = MapCenter.X - MapExtentsComponent.SphereRadius;
MapRangeMax.X = MapCenter.X + MapExtentsComponent.SphereRadius;
MapRangeMin.Y = MapCenter.Y - MapExtentsComponent.SphereRadius;
MapRangeMax.Y = MapCenter.Y + MapExtentsComponent.SphereRadius;

11. 最後に、デフォルトの 1024.0 の半径で、緑色を使用して、 DrawSphereComponent を作成してください。また、これが最も望ましい機能のようですので、デフォルトプロパティ内の bForwardAlwaysUp の値も True に設定してください。

defaultproperties
{
   Begin Object Class=DrawSphereComponent Name=DrawSphere0
        SphereColor=(R=0,G=255,B=0,A=255)
        SphereRadius=1024.000000
   End Object
   MapExtentsComponent=DrawSphere0
   Components.Add(DrawSphere0)

   bForwardAlwaysUp=True
}

12. 作業結果を失わないためにスクリプトを保存してください。

チュートリアル 13.5 – ミニマップ、パート II: MINIMAPGAME クラス

本チュートリアルは、新規 Minimap ゲームタイプクラスの生成に焦点を当てています。この目的は、単に、マップ内に配置された MU_Minimap アクタへの参照を保持して、以下のチュートリアルで作成する新規 HUD クラスの利用をゲームに告げるだけです。

1. ConTEXT をオープンし、 UnrealScript ハイライタを使用する MinimapGame.uc という名前の新規ファイル作成してください。

2. UTDeathMatch クラスから拡張した新規の MInimapGame クラスを宣言してください。

class MinimapGame extends UTDeathMatch;

3. 本クラスは宣言する変数を 1 つ持ち、 GameMInimap と名づけられた MU_Minimap オブジェクト参照を持ちます。

var MU_Minimap GameMinimap;

4. ゲームの初期化時に、マップ内に配置された MU_Minimap アクタに対する参照をこの変数に設定しなければなりません。関数の親クラスのバージョンを確実に呼び出すために InitGame() 関数をオーバーライドしてください。

function InitGame( string Options, out string ErrorMessage )
{
   Super.InitGame(Options,ErrorMessage);
}

5. InitGame() 関数の内部で、ローカル MU_Minimap 変数が必要です。

local MU_Minimap ThisMinimap;

6. AllActors イテレータは、あるレベル内で MU_Minimap アクタを検索し、 GameMinimap 変数に代入するために使用されます。

foreach AllActors(class'MasteringUnrealScript.MU_Minimap',ThisMinimap)
{
   GameMinimap = ThisMinimap;
   break;
}

7. デフォルトプロパティでは、ゲームタイプの HUDType 変数は、使用するために作成した HUD クラスを強制するためにオーバーライドされます。

HUDType=Class'MasteringUnrealScript.MinimapHUD'

8. また、 MapPrefixes(0) 変数は、このゲームタイプと関連するマップがどれかを決めるためにオーバーライドされます。

MapPrefixes(0)="COM"

9. 作業内容を失わないようにスクリプトを保存してください。

チュートリアル 13.6 – ミニマップ、パート III: MINIMAPHUD 初期設定

ミニマップアクタおよびゲームタイプクラスを作成し終わったら、 HUD クラスに注意を振り向けましょう。本チュートリアルでは、クラスの宣言とその変数およびそれらの変数のために幾つかのデフォルトプロパティの設定に焦点を合わせます。

1. ConTEXT をオープンして、 UnrealScript ハイライタを使用する MinimapHUD.uc という名前の新規ファイルを生成してください。

2. UTHUD クラスから拡張した MinimapHUD クラスを宣言してください。

class MinimapHUD extends UTHUD;

3. 本クラスは、レベル内のミニマップアクタに対する自己参照ポインタも保持します。

var MU_Minimap GameMinimap;

4. TileSize という名前の Float 変数は、常時表示される、フルマップの大きさを指定する値を保持します。もしフルマップテクスチャが 2048x2048 で、この値は 0.25 ならば、マップテクスチャの一部は、 512x512 に表示されます。

var Float TileSize;


図 13.11 – TileSize 値の 0.25 および 0.5 を使用して描かれたマップの一部。

5. MapDim と名付けられた Int 変数は、 1024x768 のデフォルト解像度の画面上に描かれたマップの大きさを表します。

var Int MapDim;


図 13.12 – MapDim は、画面上描かれるマップの大きさを指定します。

6. 他の Int 変数は、 1024x768 のデフォルト解像度のマップ上のプレーヤーを表すボックスのサイズを指定します。

var Int BoxSize;


図 13.13 – BoxSize は、スクリーン上に描かれるプレーヤーボックスの大きさを指定します。

7. 最後の変数は、マップ上のプレーヤーを描くために使用される 2 つの色の配列です。 1 色は、 HUD のオーナーのため、もう 1 色はマップ内の他のすべてのプレーヤーのためです。

var Color PlayerColors[2];

8. デフォルトプロパティブロックは、かなり直接的になるはずです。

defaultproperties
{
   MapDim=256
   BoxSize=12
   PlayerColors(0)=(R=255,G=255,B=255,A=255)
   PlayerColors(1)=(R=96,G=255,B=96,A=255)
   TileSize=0.4
   MapPosition=(X=0.000000,Y=0.000000)
}

9. 作業結果を失わないようにスクリプトを保存してください。

チュートリアル 13.7 - ミニマップ、パート IV: MINIMAPHUD 関数

マップを描くための機能の実装に移る前に、 PostBeginPlay() および DrawHUD() 関数は MinimapHUD クラス内でオーバーライドされる必要があり、 GetPlayerHeading() と呼ばれる新規関数が追加されます。

1. ConTEXT および MinimapHUD.uc ファイルをオープンしてください。

2. 最初に、 PostBeginPlay() 関数はオーバーライドされて、マップ内のミニマップアクタに対するゲームタイプの参照をクラス内の GameMIniMap へ代入するために使用されます。

simulated function PostBeginPlay()
{
   Super.PostBeginPlay();

   GameMinimap = MinimapGame(WorldInfo.Game).GameMinimap;
}

3. 次に、 DrawHUD() 関数はオーバーライドされて、マップを描く責任を持つ関数、 DrawMap() 関数への呼び出しが追加されます。これは、基本的には、プレーヤーの生死に関わらず、また、ゲームが未だ続いているか終了したかに関わらず、いつでもマップの描画を強制します。

function DrawHUD()
{
   Super.DrawHUD();

   DrawMap();
}

4. GetPlayerHeading() 関数は、以前作成された Compass クラス内に見られる GetRadianHeading() 関数と良く似ています。ここでは、この関数を Compass クラスからコピーして、 MinimapHUD クラスへと貼り付けてください。以下のコードが MinimapHUD クラス内に在るはずです。

function float GetRadianHeading()
{
   local Vector v;
   local Rotator r;
   local float f;

   r.Yaw = GetYaw();
   v = vector(r);
   f = GetHeadingAngle(v);
   f = UnwindHeading(f);

   while (f < 0)
      f += PI * 2.0f;

   return f;
}

5. GetPlayerHeading() の関数の名前を変更してください。

function float GetPlayerHeading()
{
   local Vector v;
   local Rotator r;
   local float f;

   r.Yaw = GetYaw();
   v = vector(r);
   f = GetHeadingAngle(v);
   f = UnwindHeading(f);

   while (f < 0)
      f += PI * 2.0f;

   return f;
}

6. 次に、以下の行を :

r.Yaw = GetYaw();

次のように変更してください :

r.Yaw = PlayerOwner.Pawn.Rotation.Yaw;

7. 作業結果を失わないようにスクリプトを保存してください。

チュートリアル 13.8 – ミニマップ、パート PART V: DRAWMAP() 初期設定

DrawMap() 関数は、必要な残りの計算のすべてを行い、画面上にマップを描くための責任があります。チュートリアル内では、関数およびすべてのローカル変数が宣言されています。

1. ConTEXT および MinimapHUD.uc スクリプトをオープンしてください。

2. DrawMap 関数を宣言してください。

function DrawMap()
{
}

3. 2 つのローカル Float 変数は、マップ内のミニマップアクタによって指定された North の方向に対する方位、および現在プレーヤーが向いている方向に対する方位を保持します。

local Float TrueNorth;
local Float PlayerHeading;

4. マップの回転およびコンパスオーバレイの回転に関するローカル Float 変数を宣言してください

local Float MapRotation;
local Float CompassRotation;

5. いくつかのローカル Vector 変数が宣言されています。それらの使用法は、後に詳しく説明されます。

local Vector PlayerPos;
local Vector ClampedPlayerPos;
local Vector RotPlayerPos;
local Vector DisplayPlayerPos;
local vector StartPos;

6. ミニマップマテリアルは、マップの円形表示を強制するために透過マスクを使用します。適切な位置にこのマスクを動かすため、 Vector Parameter の R および G コンポネントが、マスクテクスチャの位置のオフセットを取るためにテクスチャ座標に追加されます。 LinearColor ローカル変数では、マテリアル内の Vector Parameter に適切な値を渡すことが必要です。

local LinearColor MapOffset;

7. ローカル Float 変数は、マップによって表されるワールドスペース座標内の距離を保持します。簡略化のために、四角いマップテクスチャを使用することを要求しますので、 1 つの範囲だけが必要となります。

local Float ActualMapRange;

8. 最後に、ローカルの Controller 変数が、マップ内のすべてのプレーヤーの位置を描くためにイテレータと共に使用されます。

local Controller C;

9. 次に進む前に、画面上にマップを書く位置とマップの調整後のサイズとプレーヤーボックスのサイズが設定されます。クラスの MapPosition 変数は、相対位置の値を保持しています。これらと、ビューポートの幅と高さを積算することで、マップを描画する絶対位置を得られます。ビューポートの現在の幅と高さは FullWidth および FullHeight 変数の形式で提供されます。

MapPosition.X = default.MapPosition.X * FullWidth;
MapPosition.Y = default.MapPosition.Y * FullHeight;

10. マップおよびプレーヤーボックスのサイズは、これらの変数のデフォルト値と現在の解像度のビューポートに対するスケールファクタを積算して個々のフレームで計算されます。スケールファクタは、 ResolutionScale 変数内で保持されます。

MapDim = default.MapDim * ResolutionScale;
BoxSize = default.BoxSize * ResolutionScale;

11. 作業結果を失わないようにスクリプトを保存してください。

チュートリアル 13.9 – ミニマップ、パート VI: PLAYERPOS および CLAMPEDPLAYERPOS

PlayerPos および ClampedPlayerPos 変数では、マップの中心からの正規化されたオフセット値としてプレーヤーの現在の位置を保持します。フルマップの長さを各方向 1.0 と考えると、これらの変数のそれぞれのコンポネントは、中心からのオフセット値ですので、 -0.5 から 0.5 までの値を持つことができます。どうしてマップの中心からのオフセット値を使うのかと思うかもしれません。その理由は、マップがマテリアルの内部で、その中心の周りを回転し、これ以降見るように正しくすべてを計算するために相対位置を知る必要があるからです。

もちろん、正規化した値を計算する前には、ワールドスペースの座標値でマップがカバーする長さを知らなければなりません。これが、本チュートリアルの出発点です。

1. ConTEXT および the MinimapHUD.uc スクリプトをオープンしてください。

2. ActualMapRange は、 X-軸 および Y-軸 の間の 2 つの範囲で大きいものを取って、それらは等しいはずですが、計算されます。これは、単にフェールセーフです。それぞれの軸の範囲は GameMinimap の MapRandMin および MapRangeMax 要素に設定された値の差分を取って計算されます。

ActualMapRange = FMax(GameMinimap.MapRangeMax.X - GameMinimap.MapRangeMin.X,
         GameMinimap.MapRangeMax.Y - GameMinimap.MapRangeMin.Y);

3. マップとして使用するためにレベルのスクリーンショットを取得するため、次の部分は、手が込んでいます。透視図の歪みが無くなるので UnrealEd の内部の Top ビューポートを使用しなければなりません。しかしながら、そのビューポートに表示された軸は、垂直方向が X 水平方向が Y となります。 HUD および Canvas が関係する限りは、ビューポートの水平方向は X 垂直方向は Y です。更に、厄介なことには、 Top ビューポートから見た UnrealEd 内の X-軸の値は、それが下から上に動くにつれて増加しますが、それに対して、ゲームのビューポートは、上から下に動くにつれて増加します。

それは、詰まる所、それらの軸を変換しなければならない事を示し、X-軸のワールド座標を取り扱う時に、双方の値は反対の符号とならなければなりません。このようにして HUD を取り扱うやり方で UnrealEd の Top ビューポート内に置くようにして、ワールドスペース座標を調整します。

PlayerPos の X コンポネントから開始しましょう。中央からの正規化されたオフセット値を得るために、マップの中心はプレーヤーの位置から減算されなければなりません。それから、その値は、計算したばかりの範囲で除算されなければなりません。 HUD 内の位置の X コンポネントが、ワールドスペースの位置の Y コンポネントに対応することを覚えておいてください。

PlayerPos.X = (PlayerOwner.Pawn.Location.Y – GameMinimap.MapCenter.Y) / ActualMapRange;

4. PlayerPos の Y コンポネントは、ワールドスペースの位置の X コンポネントに対応しますが、正負逆の値を得るために -1 で乗算しなければなりません。これを行う最も簡単な方法は、減算の順番を入れ変えることです。

PlayerPos.Y = (GameMinimap.MapCenter.X - PlayerOwner.Pawn.Location.X) / ActualMapRange;

5. これで、マップ上のプレーヤーの位置が得られますが、プレーヤーが縁にとても近くなった時は何が起こるでしょう ? ミニマップはプレーヤーの位置を中央に置いてその周りのマップを表示するように設計されているため、もしも、ずっとプレーヤーをミニマップの中心に表示しながら、プレーヤーに縁近くに寄ることを許可したならば、マップテクスチャタイリングのリスクを冒すことになります。これに対応するために、まったくタイリングを許さないくらい縁から遠く離れた場所に常に居るように制限する 2 番目の位置を持つ ClampedPlayerPos を使用します。


図 13.14 – 左がクランプを行わないマップで、右がクランプしたマップ。

これを行うために、 FClamp() 関数が使用されます。中でクランプする 2 つの限界値と共に、クランプされるべき値を渡すことで、位置が安全な範囲内に在ることを保証できます。この 2 つの限界は以下のようなものです :

-0.5 + (TileSize / 2.0)

および

0.5 - (TileSize / 2.0)

すでに、正規化されたオフセット値は -0.5 と 0.5 の間であると述べました。それらから、表示されているマップの半分の部分を加算または減算すれば、マップテクスチャのタイリングのオーバーラップを起こさない部分であることを確認することができます。

プレーヤーの位置の X コンポネントをクランプしてください。

ClampedPlayerPos.X = FClamp(   PlayerPos.X,
            -0.5 + (TileSize / 2.0),
            0.5 - (TileSize / 2.0));

6. 同じことを Y コンポネントにも行います。

ClampedPlayerPos.Y = FClamp(   PlayerPos.Y,
            -0.5 + (TileSize / 2.0),
            0.5 - (TileSize / 2.0));

7. 作業結果を失わないようにスクリプトを保存してください。

チュートリアル 13.10 – ミニマップ、パート VII: マップの回転

ここでは、プレーヤーが向いている方角を示すためにマップが回転するので、楽しいことが始まります。マップの回転自身は実はとても簡単です ; Rotator 式を実行するマテリアル内の Scalar Parameter にラジアン値を渡すだけです。これをより簡単にして、マップは、プレーヤーが向きを変えているのと反対方向に回転すべきですので、マテリアル内の Rotator は、 GetPlayerHeading() または GetRadianHeading() の理想的な関数によって計算された回転の方向と逆に回転します。

マップ内のプレーヤーの回転した位置を計算する部分は、とても楽しいところです。テクスチャの中心からのプレーヤーの相対位置は分かりますが、テクスチャが回転を始めた瞬間に、計算したばかりの位置はプレーヤーが表示されるべきマップ上の点にはもはや関係しません。しかしながら、少しばかり三角法を使用して、回転した位置を計算できます。まず、全部でどれだけ回転するかを知る必要があります。

1. ConTEXT および MinimapHUD.uc スクリプトをオープンしてください。

2. TrueNorth および PlayerHeading 変数には適切なラジアン値を設定する必要があります。

TrueNorth = GameMinimap.GetRadianHeading();
Playerheading = GetPlayerHeading();

3. ここで、これらの値を MapRotation、 CompassRotation および InverseRotation の設定に使用可能ですが、どのように行うかは GameMInimap ミニマップアクタの bForwardAlwaysUp の値に依存します。この変数の値を条件とする If-文を作成してください。

if(GameMinimap.bForwardAlwaysUp)
{
}
else
{
}

4. もし、 bForwardAlwaysUp が True であれば、マップは主に PlayerHeading 上で回転され、 CompassRotation は、 Playerheading と TrueNorth の間の差分となります。

MapRotation = PlayerHeading;
CompassRotation = PlayerHeading - TrueNorth;

5. もし、 bForwardAlwaysUp が False であれば、マップは、 Playerheading と TrueNorth の間の差分に基づき回転され、 CompassRotation は、 MapRotation と同じとなります。

MapRotation = PlayerHeading - TrueNorth;
CompassRotation = MapRotation;

6. 他の地点の周りの点を回転する時の基本的な考えは、円のパラメータ式を使用することです :

この場合、半径は、マップの中心からプレーヤーの位置までの距離、または、 PlayerPos ベクタの長さになります。

VSize(PlayerPos)

回転角の解析は、若干複雑さが要求されます。回転角は、正の X-軸、または 0 ラジアン、およびマップの中心から、回転した後のプレーヤーの位置へのベクタの間の角度です。


図 13.15 – 回転したプレーヤーの位置を計算するために必要な角度。

“結局は、回転した後のプレーヤーの位置を計算すれば良いのだな。位置が分からなかったら、どうやって角度を見つければ良いのだろう ?" と思うかもしれません。プレーヤーの真の位置を知り、正の X-軸とマップの中心からその位置へのベクタの間の角度を見つけることができます。この処理は、プレーヤーの位置の Y および X コンポネントを、三角形の対辺と隣辺の長さを与えて、アークタンジェントを計算する Atan() 関数に渡すことで実行されます。例えば :

Atan(PlayerPos.Y, PlayerPos.X)


図 13.16 – プレーヤーの実際の位置への角度。

ここでは、回転する位置の量を計算します。正の X-軸およびプレーヤーの位置の間の角度から MapRotation を減算して、正の X-軸と回転した位置の間の角度を計算することができます。そこで、上の式の真の値は次のようになります :

Atan(PlayerPos.Y, PlayerPos.X) – MapRotation


図 13.17 – 回転角を減算すると求める角度となります。

すべてを合わせると、回転したプレーヤーの位置は以下のように計算されます :

DisplayPlayerPos.X = VSize(PlayerPos) * Cos( ATan(PlayerPos.Y, PlayerPos.X) - MapRotation);
DisplayPlayerPos.Y = VSize(PlayerPos) * Sin( ATan(PlayerPos.Y, PlayerPos.X) - MapRotation);

7. PlayerPos を回転して DisplayPlayerPos を設定したことに留意してください。同様に ClampedPlayerPos を回転して RotPlayerPos を設定する必要もあります。

RotPlayerPos.X = VSize(ClampedPlayerPos) * Cos( ATan(ClampedPlayerPos.Y, ClampedPlayerPos.X) - MapRotation);
RotPlayerPos.Y = VSize(ClampedPlayerPos) * Sin( ATan(ClampedPlayerPos.Y, ClampedPlayerPos.X) - MapRotation);

8. DisplayPlayerPos は、回転したマップ上のプレーヤーの実際の位置であり、プレーヤーボックスを描くために使用されます。 RotPlayerPos は、マップの表示された部分の中心を示すマップ上の位置です。これは、 StartPos または、表示されたマップの部分の左上の隅、を発見するために使用される位置です。これは、中央からのオフセット値であり、ここで絶対値が必要となるので、 X および Y コンポネントの両方に 0.5 を追加することで計算されます。それから、 TileSize の半分がそれぞれから減算されます。結果は、タイリングが起こらないことを確実にする最後の予防措置と同様に、 0.0 と 1.0 - TileSize の間でクランプされますが、この値は既にこれらの限界値内に落ち込んでいるかもしれません。

StartPos.X = FClamp(RotPlayerPos.X + (0.5 - (TileSize / 2.0)),0.0,1.0 - TileSize);
StartPos.Y = FClamp(RotPlayerPos.Y + (0.5 - (TileSize / 2.0)),0.0,1.0 - TileSize);


図 13.18 – 描画される部分の左上隅は StartPos です。

9. マップの回転の最後の局面は、透過マスクを正しくパンするため、マテリアルに渡される MapOffset 値を設定することです。MapOffset の R および G のコンポネントは逆に RotPlayerPos の X および Y コンポネントに関連します。言い換えれば、 RotPlayerPos の値は -1 で積算され MapOffset の R および G コンポネントに代入されます。しかし第一に、それらは、最後の予防措置のように、再度、以前 ClampedPlayerRot 値がクランプしたのと同じ範囲にクランプされます。

MapOffset.R =  FClamp(-1.0 * RotPlayerPos.X,
          -0.5 + (TileSize / 2.0),
          0.5 - (TileSize / 2.0));
MapOffset.G =  FClamp(-1.0 * RotPlayerPos.Y,
          -0.5 + (TileSize / 2.0),
          0.5 - (TileSize / 2.0));

10. 作業結果を失わないように、スクリプトを保存してください。

チュートリアル 13.11 – ミニマップ、パート VII: マテリアルパラメータとドローマップの設定

マテリアルパラメータの更新とマップの描画を開始するのに必要なすべてのものは計算され、実行の準備ができました。このチュートリアルでは、マップ、コンパスオーバレイおよびプレーヤーボックスの描画同様に、マップおよびコンパスオーバレイマテリアルのパラメータの設定を説明します。

1. ConTEXT および MinimapHUD.uc スクリプトをオープンしてください。

2. マップマテリアルは、 MapRotation、 TileSize および MapOffset のパラメータを持ちます。 MapRotation は、マップテクスチャの回転を制御するスカラーパラメータです。 TileSize もスカラーパラメータで、透過マスクのタイリング、従って、サイズを制御します。 MapOffset は、透過マスクの位置を制御するベクタパラメータです。コンパスオーバレイマテリアルは、オーバレイの回転を制御する 1 つのスカラーパラメータ CompassRotation を持ちます。これらは、 MaterialInstanceConstant クラスの適切な Set*Paramater() 関数を使用し、パラメータの名前を渡して値を代入することで、すべてを設定することが可能です。パラメータ名で何を行おうとしているかを簡単に知ることができるのと同様に、個々のパラメータに対する値を保持する変数には名前が付けられます。

GameMinimap.Minimap.SetScalarParameterValue('MapRotation',MapRotation);
GameMinimap.Minimap.SetScalarParameterValue('TileSize',TileSize);
GameMinimap.Minimap.SetVectorParameterValue('MapOffset',MapOffset);
GameMinimap.CompassOverlay.SetScalarParameterValue('CompassRotation',CompassRotation);

3. 描画に入る前に、どのように HUD がスクリーン上にアイテムを描くかを簡単に説明すべきです。実際には、 HUD は、自分自身では全く描画を行いません。他のクラス Canvas がすべての描画機能を持っています。 HUD クラスは、現在の Canvas への参照を含み、任意のアイテムをスクリーン上に描画する必要がある時に使用されます。どのように動作するかを 1 度理解してしまえば、マップの描画はかなり単純です。他アイテムの後、同じ場所に描かれたアイテムは、最初の物の上に描かれるため、心に留めておく 1 つ大事なことは、アイテムを描画する順番です。

最初に、 Canvas の描画の位置をマップが描かれるべき場所に設定する必要があります。これは MapPosition 変数によって指定されています。

Canvas.SetPos(MapPosition.X,MapPosition.Y);

4. 次に、マップが Canvas の DrawMaterialTile() 関数を使用して描かれます。この関数は、描画すべきマテリアル、描画するタイルの幅と高さ、描画を開始するマテリアル内の位置、描画するマテリアルの 1 部の幅と高さを受け取ります。

Canvas.DrawMaterialTile(GameMinimap.Minimap,
            MapDim,
            MapDim,
            StartPos.X,
            StartPos.Y,
            TileSize,
         TileSize );


図 13.19 – マップは、スクリーンに描画されます。

5. 次に、 Canvas の位置はプレーヤーが描いている場所に設定しておきます。これは、 DisplayPlayerPos では、 0.5 を加算するオフセット値から絶対位置への変換がおこなわれる必要があることを表します。それから、StartPos を減算することによってフルマップの一部のみが描画されるため、その値は StartPos からオフセットに変換されているはずです。この値は、 0.0-1.0 の範囲の値に正規化するために、現在の TileSize で除算されます。 UV 座標の正規化された位置は、スクリーンの座標に変換するために、マップタイルの面積 または MapDim によって乗算されます。それから、プレーヤーボックスサイズを半分に減らすため、プレーヤーボックスは、その場所上で中央揃えされます。最後に、すべての物が MapPosition へ追加されます。

Canvas.SetPos(   MapPosition.X + MapDim * (((DisplayPlayerPos.X + 0.5) - StartPos.X) / TileSize) - (BoxSize / 2),MapPosition.Y + MapDim * (((DisplayPlayerPos.Y + 0.5) - StartPos.Y) / TileSize) - (BoxSize / 2));

6. Canvas の DrawColor は、プレーヤーのために選択した PlayerColors 配列の先頭の要素に設定されます。

Canvas.SetDrawColor(   PlayerColors[0].R,
         PlayerColors[0].G,
         PlayerColors[0].B,
         PlayerColors[0].A);

7. ここで、プレーヤーボックスは、適切なサイズで描画されます。

Canvas.DrawBox(BoxSize,BoxSize);


図 13.20 – プレーヤーのボックスは、マップの前面のスクリーンに描かれます。

8. コンパスオーバレイを描くために、 Canvas の位置は MapPosition に戻されます。

Canvas.SetPos(MapPosition.X,MapPosition.Y);

9. それから、 GameMinimap の CompassOverlay マテリアルは、再度、 DrawMaterialTile() 関数を使用して描かれます。

Canvas.DrawMaterialTile(GameMinimap.CompassOverlay,MapDim,MapDim,0.0,0.0,1.0,1.0);


図 13.21 – コンパスオーバレイは、マップの前面に描画されます。

10. 作業結果を失わないようにスクリプトを保存してください。

チュートリアル 13.12 – ミニマップ、パート VIII: 他のプレーヤーを描画する

本チュートリアルでは、レベル内の他のプレーヤーは、ミニマップ内で表示可能な範囲内の場所に居ると仮定して、個別にマップ上に描画されます。

1. ConTEXT および MinimapHUD.uc スクリプトをオープンしてください。

2. プレーヤーを描くコードの後で、コンパスオーバレイが描画される前に、WorldInfo 参照を使用し、以前宣言した基底 Controller クラスと C ローカル変数を渡して、 AllControllers イテレータをセットアップしてください。プレーヤーボックスを描いた後、コンパスオーバレイが描かれる前にこれを行う理由は、 2 つあります。第 1 に、プレーヤーの位置を計算するために使用した変数のいくつかを、それらの値を上書きする心配なしに再利用できます。第 2 に、すべての最前面にコンパスオーバレイを描くことで、他のプレーヤーがマップの表示可能な領域から抜ける時に、他のプレーヤーのボックスの存在が消えていくのを隠し、きれいな遷移となります。

foreach WorldInfo.AllControllers(class'Controller',C)
{
}

3. ここで、イテレータ内の現在の Controller が PlayerOwner ではないのでその上に描画できない事を確認するために If-文を使用してください。

if(PlayerController(C) != PlayerOwner)
{
}

4. この If-文のなかでは、現在の Controller の Pawn の正規化されたオフセット位置を計算する必要があります。これは、以前、現在の Controller のためだけに、プレーヤー用に計算された DisplayePlayerPos と同じです。これは、既に存在する PlayerPos および DisplayPlayerPos を計算しているコードをコピーして、 If-文内に貼り付けるのが、多分最も簡単でしょう。

PlayerPos.X = (PlayerOwner.Pawn.Location.Y - GameMinimap.MapCenter.Y) / ActualMapRange;
PlayerPos.Y = (GameMinimap.MapCenter.X - PlayerOwner.Pawn.Location.X) / ActualMapRange;

DisplayPlayerPos.X = VSize(PlayerPos) * Cos( ATan(PlayerPos.Y, PlayerPos.X) - MapRotation);
DisplayPlayerPos.Y = VSize(PlayerPos) * Sin( ATan(PlayerPos.Y, PlayerPos.X) - MapRotation);

ここで、 PlayerOwner の語が存在したら、単純に変数 C で置き換えてください。

PlayerPos.X = (C.Pawn.Location.Y - GameMinimap.MapCenter.Y) / ActualMapRange;
PlayerPos.Y = (GameMinimap.MapCenter.X - C.Pawn.Location.X) / ActualMapRange;

DisplayPlayerPos.X = VSize(PlayerPos) * Cos( ATan(PlayerPos.Y, PlayerPos.X) - MapRotation);
DisplayPlayerPos.Y = VSize(PlayerPos) * Sin( ATan(PlayerPos.Y, PlayerPos.X) - MapRotation);

5. これにより、マップの中心からの現在の Controller の Pawn の相対的な回転後の位置が分かります。ここで、この Controller を描くべきかどうかを決定するために、この位置が回転後の位置から特定の距離の中に有るかどうかを確認しなければなりません。

VSize() 関数は、プレーヤーの位置から Controller の位置への距離を取得するために使用されます。

VSize(DisplayPlayerPos - RotPlayerPos)

この距離の上限は、基本的に TileSize の半分で、プレーヤーボックスの対角線の長さの半分以下となります。唯一の問題は、 TileSize は、 0.0-1.0 の範囲に正規化されており、 BoxSize は、スクリーンの座標値なので正規化が必要なことです。

プレーヤーボックスの対角線の長さの半分は、以下のように計算されます :

Sqrt(2 * Square(BoxSize / 2))

この長さを正規化するために、マップの面積で除算してから、 TileSize で乗算します。

(TileSize * Sqrt(2 * Square(BoxSize / 2)) / MapDim)

そのため、最終的な距離は、 TileSize の半分から、計算結果を減算したものとなります。

((TileSize / 2.0) - (TileSize * Sqrt(2 * Square(BoxSize / 2)) / MapDim))

ここで、 2 人のプレーヤーの間の距離とこの距離を比較する If-文を作成してください。

if(VSize(DisplayPlayerPos - RotPlayerPos) <= ((TileSize / 2.0) - (TileSize * Sqrt(2 * Square(BoxSize / 2)) / MapDim)))
{
}

6. スクリーン上にプレーヤーのボックスを描画する 3 行のコードをコピーして、 If-文内に貼り付けてください。

Canvas.SetPos(   MapPosition.X + MapDim * (((DisplayPlayerPos.X + 0.5) - StartPos.X) / TileSize) - (BoxSize / 2),MapPosition.Y + MapDim * (((DisplayPlayerPos.Y + 0.5) - StartPos.Y) / TileSize) - (BoxSize / 2));

Canvas.SetDrawColor(   PlayerColors[0].R,
         PlayerColors[0].G,
         PlayerColors[0].B,
         PlayerColors[0].A);

Canvas.DrawBox(BoxSize,BoxSize);

7. SetDrawColor() 関数呼出し内でアクセスされている PlayerColors 配列のインデックスを 2 番目の要素に変更してください。

Canvas.SetDrawColor(   PlayerColors[1].R,
         PlayerColors[1].G,
         PlayerColors[1].B,
         PlayerColors[1].A);


図 13.22 – レベル内の他のプレーヤーはマップ上に現れるようになります。

8. スクリプトを保存してコンパイルしてください。この章用のファイルと共に DVD 上で提供された CompassContent.upk パッケージが Unpublished\CookedPC ディレクトリ内に有ることを確認してください。文法エラーが有れば修正してください。

チュートリアル 13.13 – ミニマップ、パート IX: マップの設定およびスクリーンショット

ミニマップシステムを試験するためのすべての環境の整備を開始する時です。まず、 MU_Minimap アクタのマップを設定して、マップとしてそれを使用するためにスクリーンショットを設定する必要があります。

1. UnrealEd をオープンして、本章のファイルと共に DVD で提供された COM-CH_13_Minimap.ut3 マップをオープンしてください。


図 13.23 – COM-CH_13_Minimap.ut3 マップ。

2. Actor Classes Browser をオープンして、 Actor->Compass->MU_Minimap の下にリスト表示された MU_Minimap クラスを選択してください。


図 13.24 – MU_Minimap クラスが選択されました。

3. ビューポート内に、新規 MU_Minimap アクタをマップに追加してください。できる限りマップの中心に近くそれを配置してください。正確である必要はありません、近ければ良いです。このマップ内で North として使用されている方向の調整を望むならば、アクタを Y-軸の周りで回転しても良いです。


図 13.25 – MU_Minimap アクタの配置。

4. Top ビューポート内では、適切に縮小してから、 MU_Minimap アクタを選択して Properties Window をオープンしてください。

MapExtentsComponent セクションを拡張して MU_Minimap カテゴリ内の SphereRadius プロパティを検索してください。ビューポート内の球体がマップの完全にプレイ可能な領域を含むまで、このプロパティの値を増やしてください。マップの外側にも、ある程度の何も無い空間を残すようにしてください。このプロパティに対する適切な値は 1600 程度となります。


図 13.26 – 球体の半径は調整されます。

5. このチュートリアルの残りで、別に扱うことができるように、このマップを保存してください。

6. マップマテリアルおよびコンパスオーバレイマテリアルを割り当てる前に、ミニマップで使用されるレベルのスクリーンショットを取得する必要があります。これがインドアレベルであるため、 Top ビューポートからのスクリーンショットを取得する際には、アウトドアマップに対しては、より多くの作業が必要です ; 主に天井を取り去る必要があり、そうすれば、 Top ビューポート内で部屋の内部を見ることが可能です。

a. これは、この場合はそれほど難しいことではありません。天井を作成している静的メッシュの 1 つを選択して、その上で右クリックして、Matching Static Meshes (This Class) を選択してください。これで、天井と床を選択します。


図 13.27 – すべての床及び天井のメッシュが選択されました。

b. Front または Side ビューポート内で、 Hold Ctrl + Alt + Shift + 右マウスボタンを押し続けて、選択される床面のメッシュの周辺にマーキー選択をドラッグしてください。これは、天井の選択のみを残して、選択を解除します。天井を取り除くために Delete キーを押してください。


図 13.28 – マーキー選択は、選択状態のアイテムを削除します。

c. ここで、個々の部屋の中央の 2 つのライトメッシュを選択してください。ライトアクタ自身は選択しないようにしてください。それから、それらを削除するために Delete キーを押してください。


図 13.29 – ライトメッシュは削除されました。

d. 最後に、マップ全体を囲む青い Additive ブラシを選択して、それを削除するために Delete キーを押してください。


図 13.30 – ブラシは取り除かれました。

e. 最後に、 BSP およびライティングを更新するためにメインツールバー内の Build All ボタンを押してください。

7. MU_Minimap アクタを選択してから、 Sheet Brush オプションをオープンするために Toolbox 内の Sheet Brush ビルダボタン上で右クリックしてください。 X および Y の値を 3200 ( SphereRadius の 2 倍) に設定して Build をクリックしてください。赤のビルダブラシがビューポート内で更新されるはずです。


図 13.31 – ビルダブラシは、 MU_Minimap アクタ上で中央揃えされています。

8. 赤のビルダブラシを選択して、レベル内に存在するジオメトリの下に動かしてください。Generic Browser 内の CompassContent パッケージ内に置かれた M_Black マテリアルを検索して選択してから、 CSG をクリックしてください : M_Black マテリアルを適用した赤のビルダブラシを使用する追加のシートを作成するために Toolbox 内にボタンを追加してください。


図 13.32 – シートブラシが追加されました。

9. Top ビューポートを最大化して、マップのリットビューを表示するためにツールバー内の Lit ボタンを押してください。次に、ゲームモードをトグルするために G キーを押してください。ミニマップ使用時にマップテクスチャがどうなるかを基本的に見ることはできるはずです。


図 13.33 – リットビューを表示している Top ビューポート。

10. ここからマップテクスチャを仕上げることは、かなり簡単です。

a. ビューポート内に丁度適合するブラックシートぎりぎりまで縮小して、 Print Screen キーを押してください。


図 13.34 – シートブラシはビューポートを埋めます。

b. ここで、イメージを編集するプログラムをオープンして、新たなイメージを作成してください。この例に対しては、 Photoshop を使用します。


図 13.35 – 新規イメージが作成されました。

c. コピーされたスクリーンショットをイメージに貼り付けるために Ctrl + V を押してください。


図 13.36 – キャプチャされたスクリーンショットはイメージに貼り付けられます。

d. マップテクスチャを示す黒い部分を選択して、イメージをこの領域にトリミングしてください。


図 13.37 – イメージは、黒いエリアにトリミングされます。

e. イメージを拡大縮小するか、そのサイズを調整して、 2048x2048 にしてください。


図 13.38 – イメージはサイズ調整されます。

f. Unreal がインポート可能な形式でファイルを保存してください。 24-bit Targa (.tga) ファイルが通常最も適切に動作します。

11. 他のスクリーンショットを得るために後に必要となるかもしれないので、保存をしたい場合は、 UnrealEd のマップを保存しても良いです。確実に他の名前で保存してください、そうすれば実際のマップを上書きしません。

チュートリアル 13.14 – ミニマップ、パート X: ミニマップマテリアルおよび最後の仕上げ

ミニマップに対するイメージを作成したら、 UnrealEd にそれをインポートして、ミニマップと CompassOverlay に対して MaterialInstanceConstants を作成する時です。これらは、レベル内の MU_Minimap アクタの対応するプロパティにも代入されなければなりません。

1. UnrealEd と、スクリーンショットに対して使用されるマップでは無い MU_Minimap アクタを追加した以前のチュートリアルからのマップをオープンしてください。

2. Generic Browser をオープンして File メニューから Import を選択してください。以前のチュートリアルで保存したマップイメージを選択して、 Open をクリックしてください。

a. 表示されたダイアログ内で、パッケージのドロップダウンリスト内の COM-CH_13_Minimap パッケージを選択して、希望する場合は新しい名前を入力して名前を付け、そうでなければ、デフォルトのファイル名のままにしておいてください。


図 13.39 – レベルのパッケージが選択されました。

b. Options リストでは、 LODGroup を TEXTUREGROUP_UI に設定してください。これは、 Unreal Tournament 3 がテクスチャのサイズ制限用途に、これらのグループを使うので重要です。 UI group は、テクスチャにフルの 2048x2048 のサイズでの表示を許可しますので、通常の圧縮アーチファクトを超えた品質劣化はありません。


図 13.40 – TEXTUREGROUP_UI LODGRoup が選択されました。

c. インポート処理の高速化を行いたいなら、 DeferCompression オプションも確認可能です。パッケージや、この場合はマップを保存するまでに圧縮が行われないようにします。もちろん、これにより、保存プロセスが遅くなりますので、最後には、同じになります。


図 13.41 – DeferCompression オプションが選択されました。

d. テクスチャをインポートするために OK をクリックしてください。

注記 : 選択したパッケージは使用中のレベルの名前になるはずです。もしも、何らか異なった名前を付けたなら、パッケージリストから代わりのものを選択してください。

3. Generic Browser 内で、新規にインポートされた Texture 上で、プロパティをオープンするために、右クリックするか、ダブルクリックしてください。 SRGB プロパティまで下向きスクロールして、そのチェックを外してください。これで、ガンマ補正を使用しないようになります。もし、このオプションをオフにしなかったら、テクスチャはスクリーン上に表示された時に、極端に暗くなります。


図 13.42 – SRGB フラグはトグルでオフにします。

4. ここで、 CompassContent パッケージ内の M_minimap マテリアルを検索してください。その上で右クリックして、 New Material Instance Constant を選択してください。


図 13.43 – 新規の MaterialInstanceConstant は、 M_minimap マテリアルから作成されます。

a. 表示されたダイアログの中で、パッケージのドロップダウンリスト内の COM-CH_13_Minimap パッケージを選んで、希望するなら新しい名前を入力するか、デフォルトの M_minimap_INST のままにしておきましょう。OKをクリックしてください。

注記 : 選択したパッケージは使用中のレベルの名前に成るはずです。もしも、異なった何らかの名前を付けたなら、パッケージリストから代わりのものを選択してください。

5. Material Instance Editor が、新規の MaterialInstanceConstant のために表示された時は、 ScalarParameterValues セクションを展開して、リストされた双方のパラメータの隣のチェックボックスをクリックしてください。それから、 TextureParameterValues セクションを展開して、同様にそれらのパラメータのそれぞれの隣のチェックボックスをクリックしてください。最後に、 VectorParameterValues セクションを展開し、それから、ここで発見したパラメータの隣のチェックボックスをクリックしてください。


図 13.44 – MaterialInstance Editor。

6. インポートしたマップテクスチャを選択してから、マップテクスチャをマテリアルに割り当てるために TextureParameterValues セクション内の MinimapTex パラメータの Use Current Selection In Browser ボタンを押してください。


図 13.45 – マップテクスチャはデフォルトを置き換えます。

7. CompassContent パッケージ内に戻り、 M_compass マテリアル上で右クリックして、 New Material Instance Constant を選択してください。


図 13.46 – M_compass マテリアルから、新規 MaterialInstanceConstant が生成されました。

a. 表示されたダイアログ内で、パッケージのドロップダウンリストから COM-CH_13_Minimap パッケージを選択して、希望があれば新たな名前を入力してください。または、デフォルトの M_compass_INST のデフォルト値のままにしておいてください。 OK をクリックしてください。

注記 : 選択したパッケージは使用中のレベルの名前に成るはずです。もしも、異なった何らかの名前を付けたなら、パッケージリストから代わりのものを選択してください。

8. 新規 MaterialInstanceConstant のために Material Instance Editor が表示された時には、 ScalarParameterValues セクションを展開して、リスト表示された双方のパラメータの隣のチェックボックスをクリックしてください。それから、 TextureParameterValues セクションを展開して、同様に、それらのパラメータそれぞれの隣のチェックボックスをクリックしてください。

9. レベル内で MU_Minimap アクタを選択し、 F4 を押し、そのプロパティをオープンしてください。生成したばかりのミニマップ MaterialInstanceConstant を選択して、 Minimap プロパティについて、 Use Current Selection In Browser ボタンをクリックしてください。それから、コンパスオーバレイ MaterialInstanceConstant を選択して、 CompassOverlay プロパティについて Use Current Selection In Browser ボタンをクリックしてください。


図 13.47 – MaterialInstanceConstants は、 MU_Minimap アクタに対して代入されます。

10. “COM-“ で始まっている限りは、希望する任意の名前でこのマップを保存してから、メインツールバー内の Publish Map ボタンをクリックして、それを発行するか、この場合は単に簡単なテストなので、 Published\CookedPC\CustomMaps フォルダにコピーを保存してください。 Published\CookedPC ディレクトリに CompassContent.upk も同様に、コピーすることを忘れないようにしてください。

チュートリアル 13.15 – ミニマップ、パート XI: ミニマップのテスト

すべてのコードができあがりましたので、新規のミニマップシステムを利用するため、ここではマップを設定します。

1. UT3 をロードして、オフラインでプレイするためにログインまたは選択をしてください。

2. Instant Action ゲームを選択してください。


図 13.48 – Instant Action が選択されました。

3. 次のメニューから MinimapGame ゲームタイプを選択してください。


図 13.49 – MinimapGame が選択されました。

4. リスト内の唯一のマップとして、以前のチュートリアル内で保存したマップを見ているはずです。このマップ上でダブルクリックしてください。


図 13.50 – マップが選択されました。

5. この小さなレベルの PlayerStarts では、プレーヤー以外を spawn(スポーン) するために 2 つのボットがあれば充分ですので、次のメニューでボットの数を 2 以下に設定してください。


図 13.51 – ボットが設定されました。

6. ゲームを開始してください。レベルがロードされたらすぐに、スクリーンの左上隅に表示されたマップを見るべきですが、対戦が始まらない限りは、回ったり移動したりしても何の効果もありません。


図 13.52 – マップがスクリーン上に表示されます。

7. 対戦を開始してください、そうすれば、マップ内でのプレーヤーの実際の位置を反映したマップを見られるはずです。移動したり回ったりするとマップが更新されるはずです。表示しなければならないくらい充分に近い時には、緑のボックスとしてボットも表示されることも分かるでしょう。


図 13.53 – マップは、プレーヤーの回転および位置を反映します。

ミニマップシステムは、丁度期待したように動作しているはずです。明らかに、この効果は、大きな野外の環境ではもっとずっと有用で興味深いものになるでしょう。この小さなインドアのマップは、単にテストを行うための簡単な手段です。

チュートリアル 13.16 – 捕獲ボリューム、パート I: 初期設定

マッパーが戻ってきて、新たな要求を行いました。マップで、いくつかの素晴らしい効果に接続可能な新規ボリュームが必要となりました。この時点まで、作業は完全に Kismet 内で行われており、2~3のとても簡単な作業を行うため、とても複雑なシーケンスを作成していました。この新しいボリュームを作成し、完全な Kismet シーケンスを置き換えるために、必要な Kismet 定義を実装することが、我々の双肩にかかっています。

彼とのミーティング後に、以下の仕様のリストを作成しました :

  • 配置可能なブラシのボリュームでなければならない
  • デフォルトの他のブラシと区別をするため、色は Light Green であるべきである
  • kismet 内に 3 つの出力イベントを持つ
    • Red Captured (レッド捕獲済) – レッドチームが捕獲を実行した時
    • Blue Captured (ブルー捕獲済) – ブルーチームが捕獲を実行した時
    • Unstable (不安定) – 争っている、または、捕獲の状態が変化した時
  • 設定可能な要素がいくつかあります
    • Time to Capture(捕獲までの時間) – 個々のボリュームに対してエディタ内に設定された整数 (デフォルト 3)
    • Capture(捕獲) が可能な最小のプレーヤーの数 (デフォルト 1)
    • Rewarded Points(獲得ポイント) – 捕獲者に対する報酬 (デフォルト 1)
  • 捕獲が開始されたかを確認するため、半秒毎に設定されるタイマを持つべきである
  • トグル可能であるべきである

50000フィートの大きさの表示の中に居たとしても、これは解決するには複雑な問題です。最も直接的に見えますので、この問題には、主要な箇条書きに分けた部分ごとにアプローチします。

ボリュームによって使用されるインタフェースを定義することから始めます。

1. ConTEXT をオープンして、UnrealScript ハイライタを使用して ICaptureVolume.uc と名付けられた新規ファイルを作成してください。

2. 新しい CaptureVolume に対する Interface を定義してください。

interface ICaptureVolume;

a. ボリュームの有効化された状態と結び付けるような OnToggle() 関数を定義してください。

function OnToggle(SeqAct_Toggle action);

b. ボリュームの占有者をテストするために使用され、ボリュームの捕獲が開始されているかどうかをリターンする CheckBeginCapture() 関数を宣言してください。

function bool CheckBeginCapture();

c. GetTouchingUTPawns() 関数はそのユーティリティのために使用されます。これは、すべてのタッチした pawn(ポーン) を集め、それからリターンされた、赤または青のボウルに投げ入れます。単一の値のみをリターン可能であるので、 out 変数が利用されます。関数は、ボリューム内のキャラクタの総計値をリターンします

function int GetTouchingUTPawns(out array<UTPawn> redTouching, out array<UTPawn> blueTouching);

d. tCheckCapture() は、タイマでフックされた関数で、裏で多くの計算を実行します。

function tCheckCapture();

e. UpdateEvents() 関数は、出力キスメットインタフェースを操作し、トリガを掛けられたイベントに対するフラグを受け取ります。

function UpdateEvents(int flag);

3. 作業結果を失わないようにスクリプトを保存してください。

4. インタフェースを明確に定義したら、 CaptureVolume 自身に対して、配管を作成するときです。すべてのボリュームは Vollume クラスから派生しますので、他の派生クラス内から派生する必要のある物は何も無いので、先例に従うべきです。これに対してはそれほど多くないので、単に、このボリュームのコードを記述するステップをたどってみるべきでしょう。

a. UnrealScript ハイライタを使用する CaptureVolume.uc という名前の新規ファイルを作成してください。

b. クラスを定義して、 ICaptureVolume Interface を実装します

class CaptureVolume extends Volume placeable implements(ICaptureVolume) Config(UTBook);

c. ここで、デフォルトプロパティの中にピットインしましょう。ここに、注目すべき 1 つの要素が有りますが、それは、 BrushColor です。これは、コンパスのチュートリアルで経験したのと同じ整数値を受け取ります。

defaultproperties
{
   // UnrealEd 用で、削除されないように
   BrushColor = (B=128, G=255, R=128, A=255)
   bColored = True
   bStatic = false
}

d. もし、前回説明したステップをたどった場合は、この時点で、コンパイルが可能で、実際にエディタ内に新規ボリュームをみました。このボリュームは、他のボリュームと同様に動作し、ボリュームを手短にまとめたリストで配布されますが、これは、うす緑となります。


図 13.54 - UnrealEditor 内の新規 CaptureVolume

5. ここからが、だんだん難しくなってきます。このボリュームの様々な要素の構成を可能にするためにマッパーに対する多くの変数を作成します。 ここは、またコードの可読性を助けるために 2 つの列挙型の作成も可能となるところです。以下に、変数の目的に関するインラインコメントを含むコードブロックが見られます。デフォルトプロパティは更新されて、末尾に追加されます。

a. ECaptureEvent は、 3 つのトリガをかけられた状態の列挙型です。これは、コード中の浮動小数点の魔法のような定数を置き換えて使用し、主に読みやすくするために行われています。

enum ECaptureEvent
{
   CAP_REDCONT,
   CAP_BLUECONT,
   CAP_UNSTABLE
};

b. ETeams は、直接的で直前の列挙型と同様の目的を持ちます。

enum ETeams
{
   RED_TEAM,
   BLUE_TEAM,
   NO_TEAM
};

c. ここで、クラスの本体に入って行きます。 iTimeToCapture は、ボリュームを捕獲するために必要な秒数を制御する整数型の変数です。 (Capture) 文によって制御された Capture サブカテゴリ下で、次の 3 つの変数のそれぞれが、マッパーに対して利用可能です。

var (Capture) int iTimeToCapture;

d. iPointReward は、個人ベースで、捕獲するグループに対して許された報酬です。マッパーがここでは値を望まないことを選択した場合は、それを 0 に変更できます。

var (Capture) int iPointReward;

e. この変数は実は重要です。ボリューム内に、この数のプレーヤーが存在している場合にのみボリュームにトリガがかけられます。

var (Capture) int iMinimumPlayersForCapture;

f. これらの 2 つの変数は、ボリュームの状態を監視し続けるため、すなわち、誰がそれを制御していて、誰が制御を奪おうとしているか、のそれぞれに対して使用されます。

var int CapturingTeamID;
var int CapturedTeamID;

g. TimeCapturing は、このボリュームの捕獲に掛った時間の Interval(間隔) を監視し続けます。

var float TimeCapturing;

h. このボリュームで誰が捕獲を行う役目なのかを監視し続けるために、 CapturingMembers は、使用されます。

var array<UTPawn> CapturingMembers;

i. これは、マッパーが適切と思うように、ボリュームを利用不可にしたり利用可能にしたりすることを確実にするため、トグルルーチンによって使用されます。

var bool bEnabled;

6. 新規変数のデフォルト変数を反映してデフォルトプロパティを更新するべきです。デフォルトプロパティブロックは、自立しますが、 CapturedTeamID に対しては、作成した列挙型からの定数を使用し、デフォルト値を明確に定義した値を設定して、代入していることを指摘しておきます。その他の値は、マッパーによって渡されます。

defaultproperties
{
   // 主に UEd に対する設定。
   BrushColor = (B=128, G=255, R=128, A=255)
   bColored = True
   bStatic = false

   // ボリュームに対するデフォルト値
   iMinimumPlayersForCapture = 1
   CapturedTeamID = NO_TEAM
   iTimeToCapture = 3
   iPointReward = 5
}

7. 作業結果を失わないために、スクリプトを保存してください。

チュートリアル 13.17 – 捕獲ボリューム、パート II: TOUCH および時間

エディタ内にボリュームを持ちます、ここで、ボリュームのいくつかのより興味深い一面に踏み込んでいくべきです。特に、ボリュームにタッチされた時間 ; 直ぐにそれを宣言すれば、より容易になることが証明されます。これは、非常に込み入った問題となる可能性がありますが、幸いに、イベント関数、 Touch の利用が認められています。

この Touch Event は、獲得された状態の変更を確認するタイマにトリガを掛けます。タイマは、毎ティックはチェックする必要が無いが、とはいえ、ある時間間隔でチェックする必要があるものを監視し続ける際にはとても便利です。それらには、単に、それが再帰的かどうかの float 値、および、この時間間隔で実行しようとするコールバック関数を渡します。仕様では、このタイマについては既に 記述されていますので、処理にも取り掛かりましょう。

1. ConTEXT および CaptureVolume.uc スクリプトをオープンしてください。

2. まず、 Touch イベントを定義する必要があります。

event Touch(Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal )
{
}

3. 多くの派生関数において、依存関係や予測された値の代入の問題を防ぐために、そのスーパーバージョンを呼び出すことは有益です。

Super.Touch(Other, OtherComp, HitLocation, Hitnormal);

4. ボリュームが利用可能になった時には、関数と結びついたタイマを実行したいと思っています。ここでは、時間間隔を 0.5 秒に設定して、停止するまで 0.5 秒毎にタイマの実行を許可するための第 2 のパラメータとして true も渡します。

if (bEnabled) // 有効化された場合は、... 夢中になって処理する。
   SetTimer(0.5f, true, 'tCheckCapture');

5. Touch イベントから移動して、タッチ実行の問題を取り扱わなければなりません。役に立つ関数は、それ自身がかけがえのないものであることと証明されるでしょうから、これに取り掛かりましょう。関数およびその引数を定義しています。

function int GetTouchingUTPawns(out array<UTPawn> redTouching, out array<UTPawn> blueTouching)
{
}

6. Count 変数は、包括的となり、それはボリューム内のチーム、 UTPawn の数の両方です。それは、最終的にリターンされます。直ちに、繰り返しに対して P が利用されます。

local int Count;
local UTPawn P;

Count = 0;

7. 必要なポーンについて繰り返し処理を行うことは、ひどいことに聞こえるかもしれませんがそれほどではありません。 UnrealScript は、多くのとても便利なイテレータを持っていますが、目的や考え無しでこれらを使用しないように確認してください。特に tick(ティック) 関数内では非常にコストが掛かる可能性があります。

foreach self.TouchingActors(class'UTPawn', P)
{
}

8. 次のものに移動しない場合は、 Pawn が生きていることを確認したいです。

if (P == None || P.health <= 0 || P.Controller.IsDead() || P.GetTeam() == None)
{
   continue;
}

9. 生きていることが確認されたら、適切なチームにそれらを追加する必要があります。

if (P.GetTeam().TeamIndex == RED_TEAM)
{
   redTouching.AddItem(P);
   Count++;
}
else
{
   blueTouching.AddItem(P);
   Count++;
}

10. 最後に Count をリターンします。

return Count;

11. 作業結果を失わないようにスクリプトを保存してください。

チュートリアル 13.18 – 捕獲ボリューム、パート III: 捕獲された状態

ボリュームの大部分を綿密に計画したので、そのビジョンを 1 つにまとめます。作成したボリュームは、有用な目的やその他のものを提供する興味深い関数をいくつか持ちますが、更に越えなければならないハードルがまだいくつかあります。次に、この状況で Boolean の true をリターンする予定の CheckBeginCapture ルーチンに踏み込みます。

1. ConTEXT および CaptureVolume.uc スクリプトをオープンしてください。

2. 通常通り、インタフェース毎に、関数を定義する必要があります。

simulated function bool CheckBeginCapture()
{
}

3. 赤と青のポーンを保持するために、 2 つの配列が必要で、計算はこれらを使用して行われます。

local array<UTPawn> redTouching;
local array<UTPawn> blueTouching;

4. 最終テストを簡略化するため、捕獲しているチームのサイズの確認を続けるために使われるカウンタを生成してください。

local int Count;

5. 直ちに配列を埋めるため、 GetTouchingUTPawns ユーティリティ関数を使用できます。

GetTouchingUTPawns(redTouching, blueTouching);

6. もし、このボリュームの中にプレーヤーがいなければ、サイズを確認して、タイマをクリアしてからリターンしてください。

if (blueTouching.length == 0 && redTouching.length == 0)
{
   ClearTimer('tCheckCapture', self);
          return false;
}

7. チームが 1 つ以上存在する場合は、 CAP_UNSTABLE トリガを送り、リターンする必要があります。

else if (!(blueTouching.length == 0 ^^ redTouching.length == 0))
{
   UpdateEvents(CAP_UNSTABLE);
   return false;
}

8. それらの 2 つのテストが終了したら、両方で無く、赤または青のチームのみが存在していていることを確認して休止できます。まず赤から集中的に行います …

if (redTouching.length > 0)
{
}

a. 後にポイントの集計を行う時に使用する配列に、ボリュームにタッチしているプレーヤーをコピーしてください。

CapturingMembers = redTouching;

b. ここでプレーヤの数を取得してください。

Count = redTouching.length;

c. 赤チームに CapturingTeamID を設定してください。

CapturingTeamID = RED_TEAM;

9. また、ここで、青チームに対して、同様の処理を繰り返してください。

else
{
   CapturingMembers = blueTouching;
   Count = blueTouching.length;
   CapturingTeamID = BLUE_TEAM;
}

10. このボリュームの捕獲が可能であることを確かめ、捕獲するチームで有り、捕獲されるチームでは無いことを確かめるためにカウントをテストしてください。この 2 つめのテストは、スコアが水増しされる際に、同じチームによって捕獲されていないボリュームを確認するためのものです。

if ((iMinimumPlayersForCapture <= Count) && (CapturingTeamID != CapturedTeamID))
    return true;
else
   return false;

11. 作業結果を失わないようにスクリプトを保存してください。

これで、クラスの大部分はできあがりました。ほんの少し関数を説明が残っていますが、完成し、ゲームの本当に素晴らしいイベントを見るまでにはそれほど長くはかかりません。

チュートリアル 13.19 – 捕獲ボリューム、パート IV: タイマ関数

次のステップはタイマ関数となります。この関数は 0.5 秒毎に実行されているため、リアルタイムのゲーム開発ではこの関数は効率的でなくなり、かなり使い勝手が悪いものとなる可能性があることは、心に留めておくと良いでしょう。

1. tCheckCapture 関数を定義してください。

simulated function tCheckCapture()
{
}

2. 後に実施する予定の繰り返しを助ける 2 つの変数を作成する必要があります。

local UTPawn P;
local UTPlayerReplicationInfo ScorerPRI;

3. TimeCapturing の値が負であれば、値をクリアしてください。

if (TimeCapturing < 0)
   TimeCapturing = 0;

4. ここで、何かにトリガをかける必要があるかを確認するために、作成した CheckBeginCapture 関数を呼び出します。そうでなければ、かなりの数のものをクリアする必要があります。終了した時にはタイマをクリアする必要があることに留意してください。

if (!CheckBeginCapture())
{
   CapturingTeamID = NO_TEAM;
      TimeCapturing = 0;
   ClearTimer('tCheckCapture', self);
   return;
}

5. もし、捕獲を開始するとしたら、ここで、捕獲に費やした時間を更新すべきです。この時間は、完全に直感的ではないですが、EpicGames 社のおかげで、この処理を助けてくれる関数があります。

TimeCapturing += GetTimerRate('tCheckCapture', self);

6. この新規の時間値をこのボリュームに対する設定値と確認することができます。もし、これが大きいか等しければ、捕獲に進む必要があります。

if (TimeCapturing >= iTimeToCapture)
{
}

a. 捕獲中のチームが青である場合は、青を捕獲するイベントを送ります、また、赤の場合も同様です。

UpdateEvents(CapturingTeamID == BLUE_TEAM ? CAP_BLUECONT : CAP_REDCONT);

b. 捕獲中のプレーヤーに対するスコアを増やしてください。ここが、以前定義した 2 つの変数を使用する場所です。

foreach CapturingMembers(P)
{
   ScorerPRI = UTPlayerReplicationInfo(P.Controller.PlayerReplicationInfo);
   ScorerPRI.Score += (iPointReward);
   ScorerPRI.bForceNetUpdate = TRUE;
}

c. Captured Team ID を更新してください。

CapturedTeamID = CapturingTeamID;

d. そして最後に、捕獲時間カウンタ同様に捕獲中のチーム ID をクリアして、タイマのクリア実行が続きます。この関数を終了時に再呼出ししないため、これを実施することは重要です。

CapturingTeamID = NO_TEAM;
TimeCapturing = 0;
ClearTimer('tCheckCapture', self);

7. スクリプトを保存してください。

チュートリアル 13.20 – 捕獲ボリューム、パート V: イベントの更新

シーケンスイベントにトリガを掛けたい時には、作成したボリュームの全てのシーケンスイベント全体を対象に繰り返して、適切なフラグを送り出す必要があります。この関数は、ループを含み、この処理を取り扱います。

1. ConTEXT および CaptureVolume.uc スクリプトをオープンしてください。

2. 関数を定義して、簡単に使用するため、また繰り返しのために 2 つの変数を宣言してください。

function UpdateEvents(int flag)
{
}

3. For ループおよびイテレータと共に使用するための SeqEvent_VolumeCaptured オブジェクト参照で使用されるローカル Int 変数を宣言してください。

local int i;
local SeqEvent_VolumeCaptured CaptureEvent;

4. 全ての GeneratedEvents を対象にしたループを開始してください。これは、 kismet(キスメット) および 他のイベントシーケンス内で作業する時に、プレイに使用される配列です。

for (i = 0; i < GeneratedEvents.Length; i++)
{
}

5. ループの内部で、生成されたイベントを VolumeCaptured シーケンスイベントにキャストしてください、また、キャストが動作したら、適切なフラグを送ってください。 この関数 Notify_VolumeCaptured では、インタフェースを使用します。

CaptureEvent = SeqEvent_VolumeCaptured(GeneratedEvents[i]);
if (CaptureEvent != None)
{
   CaptureEvent.Notify_VolumeCaptured(flag);
}

6. スクリプトを保存してください。

チュートリアル 13.21 – 捕獲ボリューム、パート VI: ボリュームのトグルをオフし、 DEFAULTPROPERTIES ブロックを更新

このクラスについて、 1 つの関数が残っているだけで、それは、トグル関数です。

1. ConTEXT および CaptureVolume.uc スクリプトをオープンしてください。

2. トグルイベントにアタッチした時には OnToggle が呼び出されます。 Lights および他のアクタが実行するように、これは動作します。

simulated function OnToggle(SeqAct_Toggle action)
{
}

3. それは、 Sequence Action を受け入れ、それらに対するインパルスをチェックします。インデックスは 0、 1 および 2 であり、それぞれ On 、 Off および Toggle に関連しています。

if (action.InputLinks[0].bHasImpulse)
   bEnabled = TRUE;
else if (action.InputLinks[1].bHasImpulse)
   bEnabled = FALSE;
else if (action.InputLinks[2].bHasImpulse)
   bEnabled = !bEnabled;

4. それから、ネットワークの更新を強制します。

ForceNetRelevant();

5. ここで、今すぐ実装を行うつもりの Capture シーケンス Event を含むために、作成したデフォルトプロパティブロックは更新する必要があります。

defaultproperties
{
   // 主に UEd を設定。
   BrushColor = (B=128, G=255, R=128, A=255)
   bColored = True
   bStatic = false

   // ボリュームに対するデフォルト値
   iMinimumPlayersForCapture = 1
   CapturedTeamID = NO_TEAM
   iTimeToCapture = 3
   iPointReward = 5

   // 出力イベントにアタッチ
   SupportedEvents(0)=Class'UTBook.SeqEvent_VolumeCaptured'
}

6. スクリプトを保存してください。

チュートリアル 13.22 – 捕獲ボリューム、パート VII: シーケンスイベントのインタフェースおよび実装

Volume を完成させたので、 Captured Volume Sequence Event に関するコードを記述する必要があります。 これには少しいらいらさせられるかもしれませんが、それほどひどくならないように努力しましょう。これは、充分に単純ですので、始めてみましょう。

1. ConTEXT をオープンして UnrealScript ハイライタを使用する ICaptureSequenceEvent.uc と言う名前の新規ファイルを作成してください。

2. シーケンスイベントに対するインタフェースでは 1 つの関数を宣言することのみが必要です。その宣言を行ってください。

interface ICaptureSequenceEvent;

function Notify_VolumeCaptured(int outputIndex);

3. スクリプトを保存してください。

4. 関数の実装は、以前実施したのと同じ作業にしたがって行われます。 UnrealScript ハイライタを使用する SeqEvent_VolumeCaptured.uc と名づけた新規ファイルを作成します。

5. SequenceEvent を拡張し、宣言したインタフェースを実装して、クラスを定義してください。

class SeqEvent_VolumeCaptured extends SequenceEvent DependsOn(CaptureVolume) implements(ICaptureSequenceEvent);

6. Notify_VolumeCaptured() の関数を定義してください。

function Notify_VolumeCaptured(int outputIndex)
{
}

7. Int のローカル動的配列を宣言し、関数に送ったパラメータの値を出力する log 文を作成してください。

local array<int> ActivateIndices;
`log("Notify_VolumeCaptured has been executed" @ outputIndex,,'UTBook');

8. 心配しているのことはただ 1 つなので、 1 回に 1 つのトリガを送るだけです。

ActivateIndices[0] = outputIndex;
if (CheckActivate(Originator, None, false, ActivateIndices))
{
   `log("Notify_VolumeCaptured has been activated",,'UTBook');
}

9. デフォルトプロパティブロックに踏み込むと、考慮に値する、必要に応じて接続するわずかなリンクをもっています。

defaultproperties
{
}

10. これらの Links は、重大で、それらのインデックスは送られる実際の値です。

OutputLinks(0) = (LinkDesc="Red Capture")
OutputLinks(1) = (LinkDesc="Blue Capture")
OutputLinks(2) = (LinkDesc="Unstable")

11. kismet(キスメット) 要素に対して、名前、カテゴリの代入および最大トリガカウントのデフォルトの 2 つの調整を行ってください。

ObjName = "Volume Captured"
ObjCategory = "Objective"
MaxTriggerCount = 0 // デフォルトは無限にトリガを掛け続けます。

12. 最後に、プレーヤーは例外的に、このイベントにはトリガを掛けないことを確認したいです。

bPlayerOnly = False

13. スクリプトを保存してください。

チュートリアル 13.23 – CAPTUREVOLUME の配置と動作の確認

ここで、ボリュームおよびそのシーケンスイベントを完成しましたので、自分自身の mod に対しても作成する方法の明確な例を見ました。このチュートリアルは、他の言語において個々のループで実行するように動作することを示し、イテレータをとても良く示しています。

1. もう一度、 Editor およびコンパイルされたコードパッケージを立ち上げてください。エディタを立ち上げて、新たなマップを作成してください。

2. Actor Classes ブラウザをオープンしてください。

3. File > Open に移動してから、コンパイル済みの .u ファイルが格納された Scripts ディレクトリを指定してください。

4. 一度、パッケージがロードされれば、図 13.55 で見られるように Actor > Brush > Volume の下で、新規ボリュームを見ることができるはずです。それを選択して、マップを修正してください。右クリックを行うことができるはずで、“Add Compass Here” メニューオプションが提供されます。


図 13.55 - CaptureVolume を持つ Actor Class Browser

5. マップ中にボリュームの 1 つを配置してください。直ぐに kismet(キスメット) シーケンスを設定しようと思います。 図 13.56 では、新規ボリュームに対して指定されたオプションを、 Capture サブカテゴリで表示して見ることができます。


図 13.56 - マッパーに対する新たな設定

6. 次のステップは kismet(キスメット) をオープンして、新しいシーケンスイベントを見ることです。ボリュームの 1 つを選択して、 kismet(キスメット) エディタをオープンして右クリックしてください。コンテキストメニューの中には、選択した捕獲ボリュームに対する新規イベントを作成するオプションがあるはずで、その下に作成したシーケンスイベント Volume Captured があるはずです。提示される要素は以下のようなものです :


図 13.57 - ボリュームの Kismet(キスメット)

7. 先に進み、ここでシーケンスの生成が可能ですので、動作中のこの要素を見ることができます。今回作成のものは以下のようになります。


図 13.58 - Custom Kismet Event を使用した Kismet Demo

8. これが完了したら、マップを保存してから、開始することができます。ログファイルは、以下のような内容となるはずです :

Log: Family Asset Package Loaded: CH_Corrupt_Arms_SF
Log: CONSTRUCTIONING: LoadFamilyAsset (LIAM) Took: -0.01 secs
ScriptLog: Finished creating custom characters in 1.8737 seconds
Error: Can't start an online game that hasn't been created
ScriptLog: START MATCH
ScriptLog:   Num Matches Played: 0
UTBook: Notify_VolumeCaptured has been activated
Log: Kismet: Red Capture
UTBook: Notify_VolumeCaptured has been activated
UTBook: Notify_VolumeCaptured has been activated
Log: Kismet: Blue Capture
UTBook: Notify_VolumeCaptured has been activated
Log: Kismet: Red Capture
Error: Can't end an online game that hasn't been created
Log: Closing by request
Log: appRequestExit(0)

9. ゲーム中に捕獲されたボリュームで、スコアボードが更新されたことを見ることができます。


図 13.59 – 40 秒を示すスコアボードは左側、 95 秒を示すものは右側にあります。

これで、本チュートリアルは完了しました。簡単に振り返り、いくつかの重要な点を強調します。

  • 開発しようとする任意のクラスに対してインタフェースを作成できます。
  • ボリュームは、命令を実行するために生成したり、構成したりすることは難しくありません。
  • Kismet(キスメット) は、実際は Sequence Event を通して作成され、とても簡単なインタフェースを持っています
  • イテレータは、多くの時間と労力を節約できますが、どこからそれらを呼び出すかによって非常に費用が掛かる場合があります。
  • もしも、正しくアプローチしたならば、何かを計画すれば、開発のプロセスを簡単にして、スピードアップ可能にすることを支援します。

UT3 内のインタフェース

以下の一覧表は、すべての未テストの Unreal Tournament 3 内の関連するインタフェースを含みますので、便利かもしれません。他のものも有りますが、ゲーム内で、すべて native(ネイティブ) または native(ネイティブ) プロセスに関連しており、本書の範囲を超えています。

IQueryHandler

struct KeyValuePair

struct WebAdminQuery

function init(WebAdmin)

function cleanup()

function bool handleQuery(WebAdminQuery)

function bool unhandledQuery(WebAdminQuery)

function registerMenuItems(WebAdminMenu)

ISession

function string getId()

function reset()

function Object getObject(string)

function putObject(string, Object)

function removeObject(string)

function string getString(string, optional string)

function putString(string, string)

function removeString(string)

ISessionHandler

function ISession create()

function ISession get(string)

function bool destroy(ISession)

function destroyAll()

IWebAdminAuth

function init(WorldInfo)

function cleanup()

function IWebAdminUser authenticate(string, string, out string)

function bool logout(IWebAdminUser)

function bool validate(string, string, out string)

function bool validateUser(IWebAdminUser, out string)

IWebAdminUser

struct MessageEntry

function string getUsername()

function bool canPerform(string)

function PlayerController getPC()

function messageHistory(out array, optional int)

OnlineAccountInterface

function bool CreateOnlineAccount(string,string,string,optional string)

delegate OnCreateOnlineAccountCompleted(EOnlineAccountCreateStatus)

function AddCreateOnlineAccountCompletedDelegate(delegate)

function ClearCreateOnlineAccountCompletedDelegate(delegate)

function bool CreateLocalAccount(string,optional string)

function bool RenameLocalAccount(string,string,optional string)

function bool DeleteLocalAccount(string,optional string)

function bool GetLocalAccountNames(out array)

function bool IsKeyValid()

function bool SaveKey(string)

OnlineContentInterface

delegate OnContentChange()

function AddContentChangeDelegate(delegate, optional byte)

function ClearContentChangeDelegate(delegate, optional byte)

delegate OnReadContentComplete(bool)

function AddReadContentComplete(byte,delegate)

function ClearReadContentComplete(byte,delegate)

function bool ReadContentList(byte)

function EOnlineEnumerationReadState GetContentList(byte, out array)

function bool QueryAvailableDownloads(byte)

delegate OnQueryAvailableDownloadsComplete(bool)

function AddQueryAvailableDownloadsComplete(byte,delegate)

function ClearQueryAvailableDownloadsComplete(byte,delegate)

function GetAvailableDownloadCounts(byte,out int,out int)

OnlineGameInterface

function bool CreateOnlineGame(byte,OnlineGameSettings)

delegate OnCreateOnlineGameComplete(bool)

function AddCreateOnlineGameCompleteDelegate(delegate)

function ClearCreateOnlineGameCompleteDelegate(delegate)

function bool UpdateOnlineGame(OnlineGameSettings)

function OnlineGameSettings GetGameSettings()

function bool DestroyOnlineGame()

delegate OnDestroyOnlineGameComplete(bool)

function AddDestroyOnlineGameCompleteDelegate(delegate)

function ClearDestroyOnlineGameCompleteDelegate(delegate)

function bool FindOnlineGames(byte,OnlineGameSearch)

delegate OnFindOnlineGamesComplete(bool)

function AddFindOnlineGamesCompleteDelegate(delegate)

function ClearFindOnlineGamesCompleteDelegate(delegate)

function bool CancelFindOnlineGames()

delegate OnCancelFindOnlineGamesComplete(bool)

function AddCancelFindOnlineGamesCompleteDelegate(delegate)

function ClearCancelFindOnlineGamesCompleteDelegate(delegate)

function OnlineGameSearch GetGameSearch()

function bool FreeSearchResults(optional OnlineGameSearch)

function bool JoinOnlineGame(byte,const out OnlineGameSearchResult)

delegate OnJoinOnlineGameComplete(bool)

function AddJoinOnlineGameCompleteDelegate(delegate)

function ClearJoinOnlineGameCompleteDelegate(delegate)

function bool GetResolvedConnectString(out string)

function bool RegisterPlayer(UniqueNetId,bool)

delegate OnRegisterPlayerComplete(bool)

function AddRegisterPlayerCompleteDelegate(delegate)

function ClearRegisterPlayerCompleteDelegate(delegate)

function bool UnregisterPlayer(UniqueNetId)

delegate OnUnregisterPlayerComplete(bool)

function AddUnregisterPlayerCompleteDelegate(delegate)

function ClearUnregisterPlayerCompleteDelegate(delegate)

function bool StartOnlineGame()

delegate OnStartOnlineGameComplete(bool)

function AddStartOnlineGameCompleteDelegate(delegate)

function ClearStartOnlineGameCompleteDelegate(delegate)

function bool EndOnlineGame()

delegate OnEndOnlineGameComplete(bool)

function AddEndOnlineGameCompleteDelegate(delegate)

function ClearEndOnlineGameCompleteDelegate(delegate)

function EOnlineGameState GetOnlineGameState()

function bool RegisterForArbitration()

delegate OnArbitrationRegistrationComplete(bool)

function AddArbitrationRegistrationCompleteDelegate(delegate)

function ClearArbitrationRegistrationCompleteDelegate(delegate)

function array GetArbitratedPlayers()

function AddGameInviteAcceptedDelegate(byte,delegate)

function ClearGameInviteAcceptedDelegate(byte,delegate)

delegate OnGameInviteAccepted(OnlineGameSettings)

function bool AcceptGameInvite(byte)

function bool RecalculateSkillRating(const out array)

OnlineNewsInterface

function bool ReadGameNews(byte)

delegate OnReadGameNewsCompleted(bool)

function AddReadGameNewsCompletedDelegate(delegate)

function ClearReadGameNewsCompletedDelegate(delegate)

function string GetGameNews(byte)

function bool ReadContentAnnouncements(byte)

delegate OnReadContentAnnouncementsCompleted(bool)

function AddReadContentAnnouncementsCompletedDelegate(delegate)

function ClearReadContentAnnouncementsCompletedDelegate(delegate)

function string GetContentAnnouncements(byte)

OnlinePlayerInterface

delegate OnLoginChange()

delegate OnLoginCancelled()

delegate OnMutingChange()

delegate OnFriendsChange()

function bool ShowLoginUI(optional bool)

function bool Login(byte,string,string,optional bool)

function bool AutoLogin()

delegate OnLoginFailed(byte,EOnlineServerConnectionStatus)

function AddLoginFailedDelegate(byte,delegate)

function ClearLoginFailedDelegate(byte,delegate)

function bool Logout(byte)

delegate OnLogoutCompleted(bool)

function AddLogoutCompletedDelegate(byte,delegate)

function ClearLogoutCompletedDelegate(byte,delegate)

function ELoginStatus GetLoginStatus(byte)

function bool GetUniquePlayerId(byte,out UniqueNetId)

function string GetPlayerNickname(byte)

function EFeaturePrivilegeLevel CanPlayOnline(byte)

function EFeaturePrivilegeLevel CanCommunicate(byte)

function EFeaturePrivilegeLevel CanDownloadUserContent(byte)

function EFeaturePrivilegeLevel CanPurchaseContent(byte)

function EFeaturePrivilegeLevel CanViewPlayerProfiles(byte)

function EFeaturePrivilegeLevel CanShowPresenceInformation(byte)

function bool IsFriend(byte,UniqueNetId)

function bool AreAnyFriends(byte,out array)

function bool IsMuted(byte,UniqueNetId)

function bool ShowFriendsUI(byte)

function AddLoginChangeDelegate(delegate,optional byte)

function ClearLoginChangeDelegate(delegate,optional byte)

function AddLoginCancelledDelegate(delegate)

function ClearLoginCancelledDelegate(delegate)

function AddMutingChangeDelegate(delegate)

function ClearMutingChangeDelegate(delegate)

function AddFriendsChangeDelegate(byte,delegate)

function ClearFriendsChangeDelegate(byte,delegate)

function bool ReadProfileSettings(byte,OnlineProfileSettings)

delegate OnReadProfileSettingsComplete(bool)

function AddReadProfileSettingsCompleteDelegate(byte,delegate)

function ClearReadProfileSettingsCompleteDelegate(byte,delegate)

function OnlineProfileSettings GetProfileSettings(byte)

function bool WriteProfileSettings(byte,OnlineProfileSettings)

delegate OnWriteProfileSettingsComplete(bool)

function AddWriteProfileSettingsCompleteDelegate(byte,delegate)

function ClearWriteProfileSettingsCompleteDelegate(byte,delegate)

function bool ReadFriendsList(byte,optional int,optional int)

delegate OnReadFriendsComplete(bool)

function AddReadFriendsCompleteDelegate(byte,delegate)

function ClearReadFriendsCompleteDelegate(byte,delegate)

function EOnlineEnumerationReadState GetFriendsList(byte,out array,optional int,optional int)

function SetOnlineStatus(byte,int,const out array,const out array)

function bool ShowKeyboardUI(byte,string,string,optional bool,optional bool,optional string,optional int)

function AddKeyboardInputDoneDelegate(delegate)

function ClearKeyboardInputDoneDelegate(delegate)

function string GetKeyboardInputResults(out byte)

delegate OnKeyboardInputComplete(bool)

function bool AddFriend(byte,UniqueNetId,optional string)

function bool AddFriendByName(byte,string,optional string)

delegate OnAddFriendByNameComplete(bool)

function AddAddFriendByNameCompleteDelegate(byte,delegate)

function ClearAddFriendByNameCompleteDelegate(byte,delegate)

function bool AcceptFriendInvite(byte,UniqueNetId)

function bool DenyFriendInvite(byte,UniqueNetId)

function bool RemoveFriend(byte,UniqueNetId)

delegate OnFriendInviteReceived(byte,UniqueNetId,string,string)

function AddFriendInviteReceivedDelegate(byte,delegate)

function ClearFriendInviteReceivedDelegate(byte,delegate)

function bool SendMessageToFriend(byte,UniqueNetId,string)

function bool SendGameInviteToFriend(byte,UniqueNetId,optional string)

function bool SendGameInviteToFriends(byte,array,optional string)

delegate OnReceivedGameInvite(byte,string)

function AddReceivedGameInviteDelegate(byte,delegate)

function ClearReceivedGameInviteDelegate(byte,delegate)

function bool JoinFriendGame(byte,UniqueNetId)

delegate OnJoinFriendGameComplete(bool)

function AddJoinFriendGameCompleteDelegate(delegate)

function ClearJoinFriendGameCompleteDelegate(delegate)

function GetFriendMessages(byte,out array)

delegate OnFriendMessageReceived(byte,UniqueNetId,string,string)

function AddFriendMessageReceivedDelegate(byte,delegate)

function ClearFriendMessageReceivedDelegate(byte,delegate)

function bool DeleteMessage(byte,int)

OnlinePlayerInterfaceEx

function bool ShowFeedbackUI(byte,UniqueNetId)

function bool ShowGamerCardUI(byte,UniqueNetId)

function bool ShowMessagesUI(byte)

function bool ShowAchievementsUI(byte)

function bool ShowInviteUI(byte,optional string)

function bool ShowContentMarketplaceUI(byte)

function bool ShowMembershipMarketplaceUI(byte)

function bool ShowDeviceSelectionUI(byte,int,bool)

function AddDeviceSelectionDoneDelegate(byte,delegate)

function ClearDeviceSelectionDoneDelegate(byte,delegate)

function int GetDeviceSelectionResults(byte,out string)

delegate OnDeviceSelectionComplete(bool)

function bool IsDeviceValid(int)

function bool UnlockAchievement(byte,int)

function AddUnlockAchievementCompleteDelegate(byte,delegate)

function ClearUnlockAchievementCompleteDelegate(byte,delegate)

delegate OnUnlockAchievementComplete(bool)

function bool UnlockGamerPicture(byte,int)

delegate OnProfileDataChanged()

function AddProfileDataChangedDelegate(byte,delegate)

function ClearProfileDataChangedDelegate(byte,delegate)

function bool ShowFriendsInviteUI(byte,UniqueNetId)

function bool ShowPlayersUI(byte)

OnlineStatsInterface

function bool ReadOnlineStats(const out array,OnlineStatsRead)

function bool ReadOnlineStatsForFriends(byte,OnlineStatsRead)

function bool ReadOnlineStatsByRank(OnlineStatsRead,optional int,optional int)

function bool ReadOnlineStatsByRankAroundPlayer(byte,OnlineStatsRead,optional int)

function AddReadOnlineStatsCompleteDelegate(delegate)

function ClearReadOnlineStatsCompleteDelegate(delegate)

delegate OnReadOnlineStatsComplete(bool)

function FreeStats(OnlineStatsRead)

function bool WriteOnlineStats(UniqueNetId,OnlineStatsWrite)

function bool FlushOnlineStats()

delegate OnFlushOnlineStatsComplete(bool)

function AddFlushOnlineStatsCompleteDelegate(delegate)

function ClearFlushOnlineStatsCompleteDelegate(delegate)

function bool WriteOnlinePlayerScores(const out array)

function string GetHostStatGuid()

function bool RegisterHostStatGuid(const out string)

delegate OnRegisterHostStatGuidComplete(bool)

function AddRegisterHostStatGuidCompleteDelegate(delegate)

function ClearRegisterHostStatGuidCompleteDelegateDelegate(delegate)

function string GetClientStatGuid()

function bool RegisterStatGuid(UniqueNetId,const out string)

OnlineSystemInterface

function bool HasLinkConnection();

delegate OnLinkStatusChange(bool)

function AddLinkStatusChangeDelegate(delegate)

function ClearLinkStatusChangeDelegate(delegate)

delegate OnExternalUIChange(bool)

function AddExternalUIChangeDelegate(delegate)

function ClearExternalUIChangeDelegate(delegate)

function ENetworkNotificationPosition GetNetworkNotificationPosition()

function SetNetworkNotificationPosition(ENetworkNotificationPosition)

delegate OnControllerChange(int,bool)

function AddControllerChangeDelegate(delegate)

function ClearControllerChangeDelegate(delegate)

function bool IsControllerConnected(int)

delegate OnConnectionStatusChange(EOnlineServerConnectionStatus)

function AddConnectionStatusChangeDelegate(delegate)

function ClearConnectionStatusChangeDelegate(delegate)

function ENATType GetNATType()

delegate OnStorageDeviceChange()

function AddStorageDeviceChangeDelegate(delegate)

function ClearStorageDeviceChangeDelegate(delegate)

OnlineVoiceInterface

function bool RegisterLocalTalker(byte)

function bool UnregisterLocalTalker(byte)

function bool RegisterRemoteTalker(UniqueNetId)

function bool UnregisterRemoteTalker(UniqueNetId)

function bool IsLocalPlayerTalking(byte)

function bool IsRemotePlayerTalking(UniqueNetId)

function bool IsHeadsetPresent(byte)

function bool SetRemoteTalkerPriority(byte,UniqueNetId,int)

function bool MuteRemoteTalker(byte,UniqueNetId)

function bool UnmuteRemoteTalker(byte,UniqueNetId)

delegate OnPlayerTalking(UniqueNetId)

function AddPlayerTalkingDelegate(delegate)

function ClearPlayerTalkingDelegate(delegate)

function StartNetworkedVoice(byte)

function StopNetworkedVoice(byte)

function bool StartSpeechRecognition(byte)

function bool StopSpeechRecognition(byte)

function bool GetRecognitionResults(byte,out array)

delegate OnRecognitionComplete()

function AddRecognitionCompleteDelegate(byte,delegate)

function ClearRecognitionCompleteDelegate(byte,delegate)

function bool SelectVocabulary(byte,int)

function bool SetSpeechRecognitionObject(byte,SpeechRecognition)

function bool MuteAll(byte,bool)

function bool UnmuteAll(byte)

UIDataStoreSubscriber

native function SetDataStoreBinding(string, optional int)

native function string GetDataStoreBinding(optional int) const

native function bool RefreshSubscriberValue(optional int)

native function NotifyDataStoreValueUpdated(UIDataStore, bool, name, UIDataProvider, int)

native function GetBoundDataStores(out array)

native function ClearBoundDataStores()

UIDataStorePublisher extends UIDataStoreSubscriber

native function bool SaveSubscriberValue(out array, optional int)

UIEventContainer

native final function GetUIEvents(out array, optional class)

native final function bool AddSequenceObject(SequenceObject, optional bool)

native final function RemoveSequenceObject(SequenceObject)

native final function RemoveSequenceObjects(array)

UIListElementCellProvider

const UnknownCellDataFieldName = 'NAME_None';

UIStringRenderer

native final virtual function SetTextAlignment(EUIAlignment, EUIAlignment)

UIStyleResolver

native function name GetStyleResolverTag()

native function bool SetStyleResolverTag(name)

native function bool NotifyResolveStyle(UISkin, bool, optional UIState, const optional name)

サマリ

ここでは、 UnrealScript のもう 1 つの影の存在だったインタフェースについて学びました。 それらの定義方法、それらの目的とこの環境での動作、および 2 つのチュートリアルを見てきました。 2 つのチュートリアルではそれらを実装するための作業を行いました。それらは、とても簡単な概念にも係わらず、開発プロジェクトでは重要な役割を果たすことができ、またそうなるべきです。

インタフェースは、クラス間の実装の制御をコンパイラに依存することを可能にします。これらは、実装が変更される可能性がある際に、提示されている特定のグループの関数が依存することができるテンプレートです。インタフェースは関数、デリゲート、定数または構造体を定義形式に含んでも良いですが、それ以上は含めません。

リソースファイル