UDN
Search public documentation:
UnrealScriptFoundationsJP
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
中国翻译
한국어
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
UE3 ホーム > [UnrealScriptHomeJP][UnrealScript ホーム]] > UnrealScript の基本コンセプト
UnrealScript の基本コンセプト
概要
UnrealScript とは何か?
.uc
というファイル拡張子がつき、単一の UnrealScript クラスが定義されています。クラスとは何か、クラスはエンジンによってどのように使用されるのか、新たなクラスはどのように定義するか、といった問題に関する詳細については、 UnrealScript のクラス のページを参照してください。
UnrealScript は、ほとんどどのテキストエディタを使用しても作成することができます。ただし、UnrealScript に特化したハイライト表示機能やその他特別な機能を提供するテキストエディタもあり、これらを使用すると作業が格段と容易になります。また、nFringe や WOTGreal といった IDE によって、UnrealScript を使用する「Unreal」プロジェクトの開発に向けた、完全に統合されたソリューションが提供されています。
スクリプトの命名と位置
Development\Src
ディレクトリ内部のさまざまなフォルダに置かれます。このディレクトリには、デフォルトで多数のフォルダが含まれています。たとえば、Core 、Engine 、UDKBase 、UnrealEd などです。これらはそれぞれ異なる UnrealScript のプロジェクト (あるいは パッケージ) を表しています。これらのパッケージフォルダのそれぞれには、 Classes
フォルダがあり、このフォルダにはそのパッケージに属するすべての UnrealScript が含まれています。
ゲームのために、カスタムの UnrealScript を追加してエンジンに使用させるには、1 個以上の新たなパッケージフォルダとそれに続く Classes
フォルダを Development\Src
ディレクトリ内に作成します。この中に、UnrealScript を配置することになります。
エンジンに使用されるカスタムの UnrealScript パッケージをセットアップする際に役立つ情報については、 カスタムの UnrealScript プロジェクト のページを参照してください。
UnrealScript の構成
/********************* * Class Declaration * *********************/ class MyActor extends Actor; /************************************ * Instance Variables/Structs/Enums * ************************************/ enum MyEnum { ME_None. ME_Some, ME_All } struct MyStruct { var int IntVal; var float FloatVal; } var int IntVar; var float FloatVar; var bool BoolVar; var Actor ActorVar; var MyEnum EnumVar; var MyStruct StructVar; /********************** * Functions & States * **********************/ function MyFunction() { local int TempInt; if(ActorVar != none && BoolVar) { TempInt = IntVar; } } state NewState { function MyFunction() { local float TempFloat; if(ActorVar != none && BoolVar) { TempFloat = FloatVar; } } } /********************** * Default Properties * **********************/ defaultproperties { IntVar=5 FloatVar=10.0 BoolVar=true }
- クラスの宣言
- あらゆる UnrealScript は、クラスの宣言から始まります。クラスの宣言では、当該スクリプトに関連づけられているクラスの名前、および、当該クラスが継承している他のクラスが示されるとともに、クラス指定子によって他の要素が制御されます (クラス指定子は、宣言の最後にオプションとして追加されます)。このクラス宣言は、スクリプトの先頭に置かれなければなりません。(ただし、クラスを説明するコメントや著作権を示すコメントなどは冒頭に付け加えることができます)。
- インスタンス変数 / 構造体 / 列挙型変数
- インスタンス変数の宣言がクラスの宣言に続きます。 これによって、クラスに含まれるプロパティが示されます。
- Default Properties
-
defaultproperties
(デフォルトのプロパティ) ブロックは、必ず、スクリプトの最後に置かれます。このブロックによって、クラス内で宣言されているインスタンス変数であればどれであっても、そのためのデフォルトの値を指定することができます。
スクリプトとクラス vs オブジェクトとアクタ
スクリプト間のコミュニケーション
Pawn
です。(そして、たまたまその変数名も Pawn
となっています)。この変数の宣言は、次のようになります。
var Pawn Pawn;
none
のままです。つまり、その変数に値 (すなわち、参照したいインスタンス) を実際に入れなければなりません。これに関しては、残念なことに、従うべき定まったルールというものがありません。ワールド内にあるオブジェクトのインスタンスへの参照を得る方法は、多数あります。どのように参照を得るかという問題は、もっぱら個々の状況や、2 つのオブジェクト間の関係に依存します。次に、その例をいくつかあげてみます。
- ヘルパー関数 - 特定のクラスではヘルパー関数が用意されていることがあります。これは、よく使用されるオブジェクトへの参照を返す関数です。たとえば、
GFxMoviePlayer
には、GetPC()
関数があります。これは、ムービーを所有するPlayerController
への参照を返します。 - イベント - あるアクタは、他のアクタ内でイベントをアクティベートします (例 : Touch イベント)。多くの場合、イベントをトリガーするアクタは、エンジンによって自動的に参照がイベントに渡されます。このことによって、その参照をイベント内で使用することができるようになります。あるいは、変数に参照を保存することによって、後で使用することもできます。
- スポーン - Spawn() 関数は、生成したアクタへの参照を返します。あるアクタが他のアクタをスポーンし、そのスポーンしたアクタとすぐに (または後ほど) コミュニケーションしなければならない場合は、Spawn() 関数によって返された参照を使うことができます (ないしは、参照を変数に保存することができます)。
- 第三者 - 参照が必要となるオブジェクトは、参照をもっている他のクラスの中ですでに参照されている場合が頻繁にあります。つまり、その参照を利用することによって当初の目的を果たすことができることになります。よく必要となり利用される例としては、現在のゲームタイプ (すなわち GameInfo クラスのインスタンス) があげられます。ゲームタイプへの直接的な参照をもっている唯一のクラスは、WorldInfo クラスです (その場合、
Game
変数が利用されます)。しかし、他の場所からゲームタイプにアクセスしなければならない場合がしばしばあります。幸いにして、あらゆる Actor は、現在の WorldInfo インスタンスへの参照をもっています (そのためにはWorldInfo
変数を使います)。すなわち、WorldInfo への参照を利用することによって、GameInfo インスタンスへの参照にアクセスすることができるということです。この方法は、特に UnrealScript を始めたばかりの方には、とかく見過ごされがちなものです。すでに参照をもっているオブジェクトについてチェックを怠らなければ、探している参照が見つかるかもしれません。 - イタレーション - イテレータ関数を利用するということは、検索を実行するようなものです。イテレータ関数は、ある基準を指定することができ、オブジェクトへの参照という形態で結果のリストを返します。これらの参照は、イテレータ内で直接利用することができます。あるいは、変数に保存して後で利用することもできます。
- エディタ - 場合によっては、レベルデザイナーに任せて、エディタ内でアクタへの参照をセットしてもらうことができます。これに必要となるのは、参照を保持する編集可能な変数を作成することだけです。
Health
変数にアクセスする場合は、次のようになります。
Pawn.Health
StartFire()
関数を利用して次のようにします。
Pawn.StartFire(0);
GFxMoviePlayer
クラスがもつ GetPC()
関数を利用してみます。この関数は、Scaleform ムービーを所有する PlayerController を返します。たとえば、現在のプレイヤーがもつ Health にアクセスすることを考えてみます。Health は、PlayerController によってコントロールされる Pawn の中に保持されています。先に見たように、Controller にある Pawn
変数を使用することができますが、今回は、その前に Controller を参照する必要があります。 なぜなら GFxMoviePlayer
クラスにいるからです。ただし、この参照を変数に保存する必要はありません。 GetPC()
関数の呼び出しとともにドット表記を利用するだけで済みます。
GetPC().Pawn.Health
GetPC().Pawn.StartFire(0);
GetPC()
が本質的に PlayerController
インスタンスへの参照として考えることができるからです。このことは、 GFxMoviePlayer
クラス内にある GetPC()
の宣言から分かります。 GetPC()
の返り値は、 PlayerController
に当たります。
/**
* Helper function to get the owning player controller for this movie
*
* @return The PlayerController corresponding to the LocalPlayerOwnerIndex that owns this movie
*/
event PlayerController GetPC()
{
local LocalPlayer LocalPlayerOwner;
LocalPlayerOwner = GetLP();
if (LocalPlayerOwner == none)
{
return none;
}
return LocalPlayerOwner.Actor;
}
既存のスクリプトをどのように利用するか?
Development\Src
ディレクトリに置かれているさまざまなパッケージにおいて、すでに実装されています。これらのクラスがどのようなもので、作成するゲームの中でどのように使用できるのか、ぜひ理解しておくべきです。既存のクラスは、主に、基本的で汎用的な機能を提供しています。これら既存のクラスは、本質的には、エンジン自体の一部となっており、「ゲーム」の一部にはなっていません。(もっとも、この区別は曖昧な場合があります)。メインシステムを構成する多くのものと、メインシステムの一部となっているクラスについては、UDN 上でさまざまな「テクニカルガイド」として解説されています。利用可能なクラスすべてを完全に理解するには、実際のスクリプト自体を検討し、コードを分析し、コメントを読むなどしながら、研究しなければならない場合もあります。その場合は、 UnCodeX が非常に役立つツールとなります。このツールは、スクリプトから、操作が簡単な Javadoc スタイルのドキュメンテーションを生成することができます。
最初に理解しておかなければならないことは、通常の状況において、既存のスクリプトを改変してはならないということです。エンジンを再コンパイルすることなしに、ネイティブクラスやネイティブパッケージ内のクラスを改変してしまうと、深刻な事態に陥る可能性があります。(通常は、ゲーム実行時にエンジンがクラッシュします)。
UnrealScript は、オブジェクト指向の言語です。したがって、各クラスは、他のクラスを継承 (UnrealScript の用語では extends (拡張)) することによって、親‐子関係を作ります。子クラスは、親クラスに存在するすべての変数、関数、ステートなどを継承します。したがって、カスタムのクラスを作成する場合は、既存のクラスを改変するのではなく、既存のクラスを拡張することによって、土台として利用するべきです。このやり方の素晴らしい点は、親クラスから子クラスに受け継がれるものから制約を受けないということです。なぜなら、必要な変数と関数を新たに追加することが可能だからです。また、親クラスから受け継がれる関数についても、親クラスの実装の仕方によって制約を受けることはありません。UnrealScript では、継承された関数をオーバーライドすることによって、望みどおりのアクションを実行させることが可能です。
場合によっては、クラスを変更せずに拡張するだけでは、限界があるように思えることもあります。子クラスすべてが継承することができるようにするために、既存の基本クラスに変数を追加するという発想は普通のことだからです。しかし、通常は、既存のクラスを改変することなく、システムを設計する方法が他にあるはずです。
結論 : 既存のクラスを拡張し、関数をオーバーライドすること。決して、既存のクラスを改変しないこと。
どのクラスを拡張すべきか
オブジェクトの階層
UnrealScript のプログラミング戦略
- UnrealScript は、C/C++ に比較すると遅い言語です。 C++ による通常のプログラムは、UnrealScript によるものよりも 20 倍高速です。この独自のスクリプトを書くためのプログラミング原則は、次のことにつきます。ほとんど休止状態に置かれるスクリプトを書く。言い換えれば、カスタマイズすべき「面白い」イベントを扱う場合に限って UnrealScript を使用するということになります。基本的な動作などの機械的なタスクについては、「Unreal」の物理コードが代わりに処理してくれるため、UnrealScript は使用しません。たとえば、発射物のスクリプトを書く場合は、通常、HitWall() や、Bounce() 、Touch() といった関数を書くことによって、キーとなるイベントが発生した場合の処理方法を指定します。したがって、95% の時間は、この発射物のスクリプトコードによって何も実行されないことになります。物理コードがイベントの発生を知らせてくるのをただ待っているだけです。これは、基本的に非常に効率が良いやり方です。通常のレベルにおいて、UnrealScript は C++ よりも格段に速度が遅いのですが、UnrealScript の実行時間は、平均するとCPU 時間の 5~10% に過ぎません。
- 潜行関数 (例 : FinishAnim および Sleep) を可能な限り利用します。 スクリプトの実行フローの基礎を潜行関数に置くことによって、アニメーション主導または時間駆動のコードを作成することができるようになるため、UnrealScript の効率がかなり向上します。
- スクリプトをテストする際に「Unreal」ログに注意します。 UnrealScript ランタイムは、致命的ではない問題が発生すると、有益な警告をしばしば生成してくれます。
- 無限再帰を引き起こすコードに注意します。 たとえば、Move コマンドがアクタを動かし、アクタが何かにぶつかった時に Bump() を呼び出すとします。この場合、もし Move コマンドを Bump() 関数の内部で使用するならば、無限再帰呼び出しというリスクを背負うことになります。このことは注意すべきです。UnrealScriptは、無限再帰と無限ループという 2 つのエラー状態にはうまく対処することができません。
- アクタをスポーンおよび破棄すると、サーバーサイドに高負荷がかかります。また、ネットワークゲームでは負荷が一層高くなります。このことは、スポーンおよび破棄によってネットワーク帯域幅が占有されるために起きます。 スポーンおよび破棄は、節度を持って扱うべきです。また、アクタは「重い」オブジェクトであると見なすべきです。たとえば、100 個のユニークなアクタをスポーンし、物理コードによってさまざまな軌道を放つようなパーティクルシステムは、作成すべきではありません。非常に遅くなります。
- UnrealScript によるオブジェクト指向の機能を最大限に活用します。 新たな機能を作成するには、既存の関数およびステートをオーバーライドします。それによって、コードが見やすくなり修正が簡単にできるようになります。また、他の人が作ったコードと容易に統合することができるようになります。C でよく使用されるテクニック (例 : アクタのクラスやステートに基づく switch() 文など) は避けるべきです。このようなコードは、新たなクラスを追加もしくはクラスを改変した場合に、機能しなくなる可能性が高いためです。
- UnrealScript の .u パッケージは、.ini ファイルの EditPackages リストによって指定されている順番のとおりにコンパイルされます。 したがって、各パッケージが参照できるオブジェクトは、当該パッケージの中にあるか、それよりも前にコンパイルされたパッケージの中にあるものに限られます。この後にコンパイルされるパッケージのオブジェクトはけっして参照することができません。循環的な参照を行う必要が生じた場合は、次の 2 つの解決法があります。
- 最初の .u パッケージでコンパイルすべき基本クラスの集合と、2 番目の .u パッケージでコンパイルすべき子クラスの集合に分けることによって、基本クラスが子クラスをけっして参照しないようにします。このようなやり方は、この問題に限らず、プログラミングを行う上で役立つやり方であり、普通はうまく機能します。
なお、クラス C が、その後にコンパイルされるパッケージに含まれるクラス (またはオブジェクト) O を参照する場合は、次の 2 つのクラスに分けることができます。1 つは、最初のパッケージにおいて変数 MyO を定義する抽象的な基本クラス C です (ただし、default properties 内にある MyO のデフォルト値は含みません)。もう 1 つは、2 番目のパッケージにおいて MyO のデフォルト値を指定するサブクラス D です (この指定は 2 番目のパッケージ内部においてのみ可能です)。 - これら 2 つの .u パッケージが、参照によって不可分に結びついている場合は、これらを 1 つのパッケージに統合します。 本来パッケージは、コードモジュールの 1 単位としてあるため、統合したほうが合理的です。これらの不可分なクラスの集合を複数のパッケージに分割しても、利点 (例 : メモリの節約) は実際のところありません。
- 最初の .u パッケージでコンパイルすべき基本クラスの集合と、2 番目の .u パッケージでコンパイルすべき子クラスの集合に分けることによって、基本クラスが子クラスをけっして参照しないようにします。このようなやり方は、この問題に限らず、プログラミングを行う上で役立つやり方であり、普通はうまく機能します。