Verse のクラスは、同じような動作とプロパティのオブジェクトを作成するためのテンプレートです。 クラスは複合型、つまり他の型のデータとそのデータに対して実行できる関数を組み合わせたものです。
クラスは階層的であるため、親 (スーパークラス) から情報を継承して子 (サブクラス) と共有できます。 クラスは、ユーザーが定義するカスタム型の場合があります。 インスタンスとの違いに注意してください。
たとえば、作成するゲームに複数のネコが登場するとします。 ネコには名前と年齢があり、ニャーと鳴くことができます。 cat クラスは次のようになります。
cat := class:
Name : string
var Age : int = 0
Sound : string
Meow() : void = DisplayMessage(Sound)クラス内でネスティングされている変数の定義は、そのクラスのフィールドを定義します。 また、クラス内で定義された関数は、メソッドと呼ばれることもあります。 フィールドとメソッドはクラスのメンバーと呼ばれます。 上記の例では、Sound がフィールド、Meow が cat のメソッドに該当します。
クラスのフィールドには、デフォルト値が入っていることもあれば、入っていないこともあります。また、フィールドに指定できる値を制限する型だけが定義されている場合もあります。 上記の例では、Name にはデフォルト値が入っていますが、Age には入っていません。 この値は、<converges> エフェクトを含む式を使用して指定することができます。 デフォルト値の式では、識別子 Self を使用できない場合があります。これについて、以下に説明します。
たとえば、猫が首を傾げるようにしたいとします。 IdentityRotation() メソッドを使用する次のコードで、最初の回転 HeadTilt を初期化することができます。これには、<converges> 指定子が含まれているため、副次的エフェクトなしに完了することが保証されているからです。
cat := class:
...
# A valid expression
HeadTilt:rotation = IdentityRotation()クラスを作成する
猫がどのようなものであり、どのように動作できるのかをクラスで定義したら、アーキタイプからクラスのインスタンスを作成できます。 アーキタイプは、クラス フィールドの値を定義します。 たとえば、cat クラスから Percy という名前の年老いた猫を作成してみます。
OldCat := cat{Name := ”Percy”, Age := 20, Sound:= ”Rrrr”}この例のアーキタイプは { and } で囲まれた部分です。 クラスのすべてのフィールドに対して値を定義する必要はありませんが、少なくともデフォルト値がないすべてのフィールドに値を定義する必要があります。 いずれかのフィールドが省略されている場合、作成されたインスタンスには、そのフィールドのデフォルト値が入ります。
この場合、cat クラスの Age フィールドにはそのフィールドに割り当てられたデフォルト値 (0) が入ります。 このフィールドにはデフォルト値が入っているため、このクラスのインスタンスを作成するときに値を指定する必要はありません。 このフィールドは変数であるため、作成時に値を指定しても、その変数の値は作成後に変更できます。
一方、cat の Name フィールドは 変更できる変数ではなく、デフォルトで変更できないようになっています。 これは、作成時にデフォルト値を指定できるものの作成後は変更できない、つまり、不変であることを意味します。
Verse のクラスはテンプレートであるため、cat クラスからいくつでもインスタンスを作成できます。 ここで Flash という名前の子猫を作成してみましょう。
Kitten := cat{Name := ”Flash”, Age := 1, Sound := ”Mew”}フィールドにアクセスする
cat のインスタンスを作成したら、OldCat.Name か Kitten.Name でそれぞれの cat の Name フィールドにアクセスしたり、OldCat.Meow() か Kitten.Meow() でそれぞれの cat の Meow メソッドを呼び出したりできます。
どちらの cat にも同じ名前のフィールドがありますが、それらの値は異なります。 たとえば、Sound フィールドの値が異なるため、OldCat.Meow() と Kitten.Meow() の動作は異なります。
Self
Self は、メソッドが呼び出されたクラスのインスタンスを参照するためにクラス メソッド内で使用できる、Verse での特殊な識別子です。 Self を使用しなくてもメソッドが呼び出されたインスタンスの他のフィールドを参照することはできますが、インスタンス全体を参照する場合は Self を使用する必要があります。
たとえば次のように、DisplayMessage でメッセージを関連付ける pet の引数が必要な場合がこれに該当します。
DisplayMessage(Pet:pet, Message:string) : void = …
cat := class:
…
Meow() : void = DisplayMessage(Self, Sound)猫の鳴き声をより大きくした状態で初期化する場合は、設定済みの Sound 変数をベースに検討する可能性があります。 ただし、これは次のコードでは機能しません。デフォルト値の式は識別子 Self を使用できないため、LoudSound がインスタンス メンバー Sound を参照できないからです。
cat := class:
...
Sound : string
Meow() : void = DisplayMessage(Self, Sound)
# The following will fail since default class values
# can't reference Self
LoudSound : string = "Loud " + Self.Sound
LoudMeow() : void = DisplayMessage(Self, LoudSound)サブクラスと継承
クラスは、スーパークラスから継承できます。スーパークラスにあるすべてのフィールドが継承するクラスに含まれます。 このようなクラスは、スーパークラスのサブクラスと呼ばれます。 次に例を示します。
pet := class:
Name : string
var Age : int = 0
cat := class(pet):
Sound : string
Meow() : void = DisplayMessage(Self, Sound)
dog := class(pet):
Trick : string
ここでは、cat と dog を定義するときに class(pet) を使用することにより、cat と dog が pet クラスを継承することを宣言します。 つまり、cat と dog は pet のサブクラスになります。
これには次のようないくつかのメリットがあります。
cat と dog の両方に名前と年齢があるため、それらのフィールドは
petクラスで 1 回定義するだけで済み、catとdog両方のフィールドがそれらのフィールドを継承します。petクラスは、petのサブクラスのインスタンスを参照するための型として使用できます。 たとえば、pet の名前だけが必要な関数を記述する場合、cat と dog の両方に対してそのような関数を 1 回記述するだけで済むうえ、他のpetサブクラスはどれも後から導入できます。VerseIncreaseAge(Pet : pet) : void= set Pet.Age += 1
詳細については、「サブクラス」ページをご覧ください。
オーバーライド
サブクラスを定義するときには、スーパークラスで定義したフィールドをオーバーライドすることにより、それらの型をより具体的にしたり、デフォルト値を変更したりできます。 そのためには、サブクラスでもう一度フィールドの定義を記述する必要がありますが、そのときには名前に <override> 指定子を付けます。 たとえば、Lives フィールドをデフォルト値 1 で pet に追加し、cat のデフォルト値をオーバーライドして 9 にできます。
pet := class:
…
Lives : int = 1
cat := class(pet):
…
Lives<override> : int = 9メソッド呼び出し
クラス インスタンスのフィールドにアクセスするときには、そのフィールドのインスタンスの値にアクセスします。 メソッドの場合、このフィールドは関数であり、オーバーライドするとフィールドの値が新しい関数に置き換えられます。 メソッドを呼び出すとフィールドの値が呼び出されます。 つまり、呼び出されるメソッドはインスタンスによって決まります。 ここでは、次の例について検討します。
pet := class:
…
OnHearName() : void = {}
cat := class(pet):
…
OnHearName<override>() : void = Meow()
dog := class(pet):
…
CallFor(Percy) のコードを書くと、cat で定義されたとおりに OnHearName メソッドが呼び出されます。 CallFor(Fido) (Fido が dog クラスのインスタンス) のコードを書くと、dog で定義されたとおりに OnHearName メソッドが呼び出されます。
可視性指定子
可視性指定子をクラスのフィールドとメソッドに追加して、それらにアクセスできる対象を制御できます。 たとえば、所有しているクラスだけがプライベート フィールドにアクセスできるように、Sound フィールドに private 指定子を追加することが可能です。
cat := class:
…
Sound<private> : string
MrSnuffles := cat{Sound := "Purr"}
MrSnuffles.Sound # Error: cannot access a private fieldクラスで使用できるすべての可視性指定子を次に示します。
public:アクセスに制限がありません。
internal:現在のモジュールにアクセスが制限されます。 これがデフォルトの可視性です。
protected:現在のクラスとサブクラスにアクセスが制限されます。
private:現在のクラスにアクセスが制限されます。
アクセス指定子
アクセス指定子をクラスに追加することで、そのクラスを構築できるユーザーを制御することができます。 これは、あるクラスのインスタンスを特定のスコープでのみ構築できるようにしたい場合などに便利です。
pets := module:
cat<public> := class<internal>:
Sound<public> : string = "Meow"
GetCatSound(InCat:pets.cat):string =
return InCat.Sound # Valid: References the cat class but does not call its constructor
MakeCat():void =
MyNewCat := pets.cat{} # Error: Invalid access of internal class constructorcat クラスのコンストラクタをそのモジュール pets 外で呼び出すと、class キーワードが internal とマークされているためその呼び出しは失敗します。 これは、クラス識別子自体が public (pets モジュール外のコードから cat を参照可能) とマークされている場合も該当します。
クラス キーワードで使用できるすべてのアクセス指定子を次に示します。
public:アクセスに制限がありません。 これがデフォルトのアクセスです。
internal:現在のモジュールにアクセスが制限されます。
concrete 指定子
クラスに concrete 指定子がある場合、cat{} などの空のアーキタイプでクラスを作成できます。 つまり、このクラスのフィールドには必ずデフォルト値が必要です。 さらに、具象クラスのサブクラスはすべて、それ自体が具象でなければなりません。
次に例を示します。
class1 := class<concrete>:
Property : int = 0
# Error: Property isn't initialized
class2 := class<concrete>:
Property : int
# Error: class3 must also have the <concrete> specifier since it inherits from class1
class3 := class(class1):
Property : int = 0抽象クラスから concrete クラスを直接継承できるのは、両方のクラスが同じモジュールで定義されている場合のみです。 ただし、過渡的な場合にはあてはまりません。concrete クラスは別のモジュールの 2 つ目の concrete クラスを直接継承できます。そのモジュールで、その 2 つ目の concrete クラスがそのモジュールの abstract クラスを直接継承している場合です。
unique 指定子
unique 指定子をクラスに適用して、一意のクラスにすることができます。 unique クラスのインスタンスを作成するために、Verse は結果のインスタンスに一意の ID を割り当てます。 これにより、unique クラスのインスタンスは、その ID を比較するだけで等しいかどうかを判断できます。 unique 指定子がないクラスは、そのような識別子がなく、等しいかどうかを判断するにはそのフィールドの値を比較する必要があります。
つまり、unique クラスは = および <> 演算子で比較でき、comparable 型のサブタイプです。
次に例を示します。
unique_class := class<unique>:
Field : int
Main()<decides> : void =
X := unique_class{Field := 1}
X = X # X is equal to itself
Y := unique_class{Field := 1}
X <> Y # X and Y are unique and therefore not equalfinal 指定子
final 指定子は、クラスとクラスのフィールドでのみ使用できます。
クラスに final 指定子がある場合、そのクラスのサブクラスを作成することはできません。 次の例では、pet クラスに final 指定子があるため、このクラスをスーパークラスとして使用することはできません。
pet := class<final>():
…
cat := class(pet): # Error: cannot subclass a “final” class
…フィールドに final 指定子がある場合、サブクラスのフィールドをオーバーライドすることはできません。 次の例では、Owner フィールドに final 指定子があるため、cat クラスはこのフィールドをオーバーライドすることはできません。
pet := class():
Owner<final> : string = “Andy”
cat := class(pet):
Owner<override> : string = “Sid” # Error: cannot override “final” fieldメソッドに final 指定子がある場合、サブクラスのメソッドをオーバーライドすることはできません。 次の例では、GetName() メソッドに final 指定子があるため、cat クラスがこのメソッドをオーバーライドすることはできません。
pet := class():
Name : string
GetName<final>() : string = Name
cat := class(pet):
…
GetName<override>() : string = # Error: cannot override “final” method
…クラス本体のブロック式
block 式はクラス本体で使用できます。 クラスのインスタンスを作成するときには、block 式は定義された順番で実行されます。 クラス本体の block 式で呼び出される関数では、NoRollback エフェクトを利用できません。
一例として、cat クラス本体に 2 つの block 式を、また Meow() メソッドに transacts エフェクト指定子を追加します (メソッドのデフォルト エフェクトに NoRollback エフェクトがあるため)。
cat := class():
Name : string
Age : int
Sound : string
Meow()<transacts> : void =
DisplayOnScreen(Sound)
block:
Self.Meow()
cat クラスのインスタンス OldCat が作成されると、2 つの block 式が実行されます。cat は最初に「Rrrr」と鳴き、出力ログに「Garfield」が出力されます。
インターフェース
インターフェースは、制限のあるクラスの形式で、値を持たないメソッドのみを含めることができます。 クラスは他のクラスを 1 つしか継承できませんが、継承可能なインターフェースの数に制限はありません。
持続可能型
次の条件を満たすクラスは持続可能です。
持続可能指定子で定義されている。
unique が指定されていない。
superclass がない。
parametric が指定されていない。
同じように持続可能なメンバーのみが含まれている。
変数メンバーがない。
持続可能なクラスをモジュール スコープの weak_map 変数で使用すると、ゲーム セッションの間中、その値を維持できます。 Verse での持続性の詳細については、「Verse で持続可能データを使用する」を参照してください。
次の Verse の例では、クラスでカスタム プレイヤー プロファイルを定義する方法を示します。後で格納、更新、アクセスすることができます。 クラス player_profile_data には、獲得した XP、ランク、完了したクエストなどのプレイヤーの情報が格納されます。
player_profile_data := class<final><persistable>:
Version:int = 1
Class:player_class = player_class.Villager
XP:int = 0
Rank:int = 0
CompletedQuestCount:int = 0
QuestHistory:[]string = array{}