この記事の3行まとめ
- Epic Gamesが開発するプログラミング言語「Verse」の仕様をまとめたドキュメント「Book of Verse」が公開
- エフェクトシステム、Live Variablesなどの言語仕様が全19章にわたって記述されている
- 記事執筆時点ではドラフト段階であり、頻繁な更新が見込まれる
Epic Gamesが開発するプログラミング言語「Verse」の言語仕様を体系的にまとめたドキュメント「Book of Verse」が、GitHub上で公開されています。
Verseは、Epic Gamesが開発するマルチパラダイムのプログラミング言語です。現在は「Unreal Editor for Fortnite(以下、UEFN)」でゲームロジックの記述に使用できるほか、今後登場するUnreal Engine 6でも採用が予定されています。
このたび公開されたBook of Verseは、従来提供されていたVerseの言語リファレンスとは異なり、UEFNに依らない言語自体の設計や仕様を説明しています。なお、記事執筆時点でBook of Verseはドラフト段階であり、頻繁な更新が見込まれるとのこと。
以下、同ドキュメントで扱われている言語仕様のうち、いくつかをピックアップして紹介します。
関数の特性を明示する「Effects」
Verseでは、メモリの確保や読み書き、停止性の保証など、関数が何をするかをエフェクト指定子(Effect Specifier)として明示的に宣言します。
たとえば、<reads>は可変状態の読み取り、<writes>は書き込みを意味します。また、参照透過な純粋関数には<computes>を指定します。
これらはコンパイラによって検証されるため、関数の振る舞いがコメントや規約ではなく言語レベルで保証されます。たとえば、副作用のない純粋な計算を意味する<computes>が宣言された関数の中では、状態の書き込みを行う<writes>の関数を呼び出すことができません。
# 状態を書き換える関数
ApplyDamage(Target:character, Amount:int)<writes>:void =
set Target.Health -= Amount
# <computes>は副作用のない純粋な計算を宣言する
# この中で<writes>を持つ関数を呼ぶとコンパイルエラーになる
ProcessAndApply(Target:character, Base:int)<computes>:int =
Result := Base * 2
ApplyDamage(Target, Result) # コンパイルエラー!
Result
# 状態を書き換える関数
ApplyDamage(Target:character, Amount:int)<writes>:void =
set Target.Health -= Amount
# <computes>は副作用のない純粋な計算を宣言する
# この中で<writes>を持つ関数を呼ぶとコンパイルエラーになる
ProcessAndApply(Target:character, Base:int)<computes>:int =
Result := Base * 2
ApplyDamage(Target, Result) # コンパイルエラー!
Result
上記のような制約がコンパイラによって強制されるため、チーム開発や大規模なコードベースでも「この関数は状態を変更しない」といった前提を作業者間で共有する必要がなくなり、安全性を保ったまま開発を進められます。
リアクティブな値を定義する「Live Variables」
Live Variablesは、ある変数の値が別の変数に連動して自動的に更新される、リアクティブプログラミングのための機能です。
通常、変数Aが変数Bに依存している場合、Bが変わるたびにAを手動で更新する必要があります。Live Variablesを使うと、この更新を自動化できます。
var MaxHealth:int = 100
var Damage:int = 0
var live Health:int = MaxHealth - Damage
# Healthは常にMaxHealth - Damageの値に自動更新される
set Damage = 30 # Healthが自動的に70になる
set Damage = 80 # Healthが自動的に20になる
var MaxHealth:int = 100
var Damage:int = 0
var live Health:int = MaxHealth - Damage
# Healthは常にMaxHealth - Damageの値に自動更新される
set Damage = 30 # Healthが自動的に70になる
set Damage = 80 # Healthが自動的に20になる
var liveで宣言された変数は、右辺の式で参照している変数が変わるたびに値が自動で再計算されます。参照先は実行時に決定されるため、条件分岐によって参照先を変えることも可能です。
また、Live Variablesはawaitやwhenといったリアクティブな構文と組み合わせることで、コールバック関数やイベントリスナーを手動で管理しなくても、ゲーム内の状態変化に応じた処理を宣言的に記述できます。
# awaitは条件が真になるまで処理を一時停止する構文
# HPが0以下になるまで待機し、条件を満たしたら処理を再開する
await{Health <= 0}
Print("Game Over")
# whenは条件の値が変わるたびに繰り返し処理を実行する構文
# Scoreが変わるたびに繰り返し実行される
when(Score):
set DisplayedScore = Score
Print("Score updated to: {Score}")
set Score = 100 # 実行される
set Score = 100 # 値が変わっていないので実行されない
set Score = 200 # 実行される
# awaitは条件が真になるまで処理を一時停止する構文
# HPが0以下になるまで待機し、条件を満たしたら処理を再開する
await{Health <= 0}
Print("Game Over")
# whenは条件の値が変わるたびに繰り返し処理を実行する構文
# Scoreが変わるたびに繰り返し実行される
when(Score):
set DisplayedScore = Score
Print("Score updated to: {Score}")
set Score = 100 # 実行される
set Score = 100 # 値が変わっていないので実行されない
set Score = 200 # 実行される
なお、記事執筆時点でLive Variablesは将来実装予定の仕様とされています。
後方互換性を言語レベルで保証する
運営型のゲームでは、運用中にコードを更新する際、既存のプレイヤーデータや他の開発者のコードとの互換性が問題になります。Verseでは後方互換性のルールが言語レベルで定義されており、ルールに違反する更新はコードの公開時にエラーとして検出されます。
たとえば、一度リリースしたpublicな定義は削除できません。関数のエフェクトは副作用を減らす方向への変更(例:<reads> → <computes>)は許可されますが、副作用を増やす方向は禁止されます。型の変更もより具体的な方向(例:rational → int)のみが許可されます。
# 公開済みの関数: <reads>を宣言
GetStatus()<reads>:string = ...
# 更新で<computes>に緩和 → 許可(副作用が減る方向)
GetStatus()<computes>:string = ...
# 更新で<writes>を追加 → エラー(副作用が増える方向)
GetStatus()<writes>:string = ... # 公開時にエラー
# 公開済みの関数: <reads>を宣言
GetStatus()<reads>:string = ...
# 更新で<computes>に緩和 → 許可(副作用が減る方向)
GetStatus()<computes>:string = ...
# 更新で<writes>を追加 → エラー(副作用が増える方向)
GetStatus()<writes>:string = ... # 公開時にエラー
Book of Verseでは上記のほかにも、Verseの仕様が全19章にわたって解説されています。
詳細は、Book of Verseをご確認ください。
Book of Verse「Book of Verse」GitHubリポジトリコーヒーがゲームデザインと同じくらい好きです