Keep your place in this quest

Log in or sign up for free to subscribe, follow lesson progress, and access more learning content.

さて、Caveでキャラクターやアニメーションがどのように動作するかを見たので、一歩引いて、通常すべてをつなぐスクリプトシステムであるPythonについて理解しましょう。

Caveでは、Pythonを使ってゲームプレイの挙動、エディターツール、UIのコールバック、アニメーションのコールバック、タイムラインイベント、そして組み込みコンポーネントとして存在するには特異すぎる小さなカスタムロジックの記述を行います。Caveを使い始めるにあたって高度なPythonプログラマーになる必要はありませんが、Caveスクリプトの基本的な構造を理解することは非常に役立ちます。

このレッスンで学ぶこと:

  • CaveでPythonが何に使われるか。
  • Python ScriptアセットとPython Componentがどのように連携するか。
  • start()update()の役割。
  • スクリプトから現在のエンティティにアクセスする方法。
  • エンティティからコンポーネントを取得する方法。
  • Python ComponentとPython Code Componentを使い分けるタイミング。

目標はPython言語全体を教えることではなく、Caveのスクリプトが開いたときに理解しやすく感じるようにすることです。

Pythonを学ぶには?

もしPythonのコーディングがわからない場合、Uniday StudioはPythonの無料学習クエストも提供しています。すべてのオプションはuniday.studio/learnで確認できますし、ここから始めても良いです:

このセクションでは、これら2つの学習クエストで扱われるトピックについての基本的な理解がある前提で進めます。


CaveでPythonが使われる場面

Caveでは、ゲームオブジェクトに振る舞いが必要なときに使用されるスクリプトレイヤーがPythonです。

たとえば、Pythonは以下のような動作に使えます:

  • プレイヤーや敵の移動。
  • ドアの開閉。
  • アニメーションの再生。
  • 音の起動。
  • タイムラインの開始。
  • シーンの変更。
  • UI要素の更新。
  • カスタムエディターツールの作成。

基本的に、Pythonスクリプトを書くことでゲームのすべてのロジックを作成できます。

だからこそ、起動プロジェクトにはすでにPythonスクリプトが含まれています。例えばデフォルトのプレイヤーは単なるアニメーション付きメッシュではなく、入力を読み取りキャラクターを移動させ、メッシュを回転し、正しいアニメーションを再生するゲームプレイロジックも持っています。

つまり、CaveでPythonを書く時は通常、孤立したコードを書いているのではなく、エンティティを制御し、そのエンティティに付属するコンポーネントと連携するロジックを書いています。

スクリプトアセットとPythonコンポーネント

Pythonコードは通常、Python Scriptアセットの中にあります。

image.png

そして、そのスクリプトをシーンで実行するには、エンティティにPython Componentを追加し、そのスクリプトから実行したいクラスを選びます。

考え方は次の通りです:

パーツ 役割
Python Scriptアセット コードを格納。
Python Component そのスクリプトのクラスをエンティティ上で実行。
エンティティ スクリプトにより制御されるオブジェクト。
cave.Componentクラス あなたが書いた振る舞いの実体。

例えば、Door Controllerというスクリプトアセットがあり、その中にDoorControllerというcave.Componentを継承したクラスがあるとします。ドアのエンティティにPython Componentを追加し、そのクラスを選択します。

この分離は重要で、同じスクリプトを再利用できるためです。シーンにたくさんのドアを置いても、それぞれが同じドアコントローラスクリプトを使い、異なるプロパティや子オブジェクトを持たせることができます。

最小限のCaveコンポーネント

基本的なCaveのPythonコンポーネントは次のようになります:

import cave

class MyComponent(cave.Component):
    def start(self, scene):
        print("The component started!")

    def update(self):
        pass

いくつか重要なポイント:

  • import cave はCaveのPython APIにアクセスします。
  • class MyComponent(cave.Component) でCaveが実行できるコンポーネントクラスを作成。
  • start(self, scene) はコンポーネントが開始したときに呼び出される。
  • update(self) はコンポーネントがアクティブな間、毎フレーム呼び出される。

MyComponentの名前は何でも構いませんが、実際のプロジェクトではDoorControllerEnemyAICheckpointPlayerHealthのようにわかりやすい名前を使うべきです。

ライフサイクルメソッド

Caveはコンポーネントが動作中に自動でいくつかのメソッドを呼びます。

よく使われるのは以下の通りです:

メソッド 呼ばれるタイミング 主な用途
start(self, scene) コンポーネント開始時 リファレンス取得、プロパティ読み込み、変数準備
firstUpdate(self) エンティティコンポーネントのstart後、最初の更新時 他のコンポーネントの初期化完了後に依存変数を作成
update(self) 毎フレーム(シーンがポーズされていない場合) 移動、入力、タイマー、状態チェック
pausedUpdate(self) 毎フレーム(シーンがポーズ中の場合) ポーズ時のロジック
end(self, scene) コンポーネント終了時 必要ならクリーンアップ

ほとんどの初心者用スクリプトはstart()update()を使います。例えば動くプラットフォームなら、start()で元の位置を保存し、update()で毎フレームプラットフォームを動かします。

editorUpdatelateUpdateメソッドもありますが、ここではより高度なので取り扱いません。

現在のエンティティにアクセス

Caveコンポーネント内で、self.entityはそのPython Componentが所有するエンティティを指します。

これはCaveスクリプトの最も重要な概念の一つです。スクリプトがシーン内を独立して漂うことはなく、エンティティに属しています。

簡単な例:

import cave

class DoorController(cave.Component):
    def start(self, scene):
        self.transform = self.entity.getTransform()
        self.isOpen = False

    def update(self):
        pass

このスクリプトでは:

  • self.entityはドアのエンティティ。
  • self.entity.getTransform()はドアのTransform Componentを取得。
  • self.isOpenはドアが開いているかを覚えておく変数。

このパターンは頻繁に使います。まずエンティティを取得し、次に必要なコンポーネントや子エンティティを取得し、それらをロジックで使います。

他のコンポーネントの取得

エンティティを制御するには、普通は一つ以上のコンポーネントを取得します。

例えばプレイヤースクリプトなら、

  • 移動や回転のためのTransformコンポーネント。
  • キャラクター移動のためのCharacterコンポーネント。
  • 子メッシュエンティティのアニメーション再生用Animationコンポーネント。
  • ループサウンドのためのAudioコンポーネント。

小さな例:

import cave

class SimpleMover(cave.Component):
    def start(self, scene):
        self.transform = self.entity.getTransform()
        self.speed = 2.0

    def update(self):
        self.transform.move(0, 0, self.speed * cave.getDeltaTime(), local=True)

これは毎フレームエンティティを前方に動かします。

重要なのはcave.getDeltaTime()です。update()は毎フレーム呼ばれるので、時間差で乗算することでフレームレートが変わっても移動速度が一定になります。

カスタムプロパティの読み取り

テストのために値をハードコーディングするのは問題ありませんが、本格的なゲームオブジェクトには編集可能なプロパティの方が通常良いです。

例えば、以下のように書く代わりに:

self.speed = 2.0

エンティティのプロパティから値を読む方法:

self.speed = self.entity.properties.get("speed", 2.0)

意味は:

  • もしエンティティにspeedプロパティがあればその値を使う。
  • なければデフォルト値の2.0を使う。

これは再利用可能なスクリプトに非常に便利です。一つのSimpleMoverスクリプトを複数のエンティティに使い、それぞれ異なるスピードで動かせます。

例:

エンティティ speed プロパティ
遅いプラットフォーム 1.0
速いプラットフォーム 4.0
動く障害物 7.0

スクリプトは同じでも、エンティティごとに挙動が変わります。

あるいは、エンティティのプロパティに頼らず、Component自体にローカルで変更可能な変数を作成することもできます。例えば:

import cave

class PlatformMover(cave.Component):
    # これはローカル変数になります:
    speed = 2.0

    def start(self, scene: cave.Scene):
        pass

    def update(self):
        events = cave.getEvents()

このspeed変数は各コンポーネントインスタンスごとにローカルで編集可能になります:

image.png


子エンティティの取得

多くのCaveオブジェクトは小さな階層構造で作られています。

プレイヤーテンプレートが良い例です。ルートのPlayerエンティティにはキャラクターフィジックスとロジックがあり、子のMeshエンティティにはビジュアルキャラクターメッシュとAnimation Componentがあります。

だから、ルートのプレイヤーエンティティのスクリプトでアニメーションを再生したいときは、まず子のメッシュエンティティを取得します:

import cave

class PlayerAnimationExample(cave.Component):
    def start(self, scene):
        self.mesh = self.entity.getChild("Mesh")
        self.animator = self.mesh.get("Animation")

    def update(self):
        self.animator.playByName("p-idle", blend=0.2, loop=True)

これはデフォルトのプレイヤーコントローラと同じパターンです:

  1. 子エンティティを取得
  2. その子からコンポーネントを取得
  3. 必要に応じてコンポーネントを使う

このパターンを理解すれば、多くのCaveスクリプトがずっと読みやすくなります。

getChildメソッドにはオプションの「recursive」パラメータがあり、デフォルトはTrueです。Trueの場合、名前で指定したエンティティが見つかるまで子の子も含めすべての子エンティティを検索します。

シーン内のエンティティ取得(シーンクエリ)

ゲームを作る際には、シーン内の他のエンティティを取得する必要がよくあります。そこでその方法を見ていきます。最初のステップはシーン自体を取得することで、Caveでは2つの方法があります:

# Returns the active scene:
scene = cave.getScene()

# Returns the scene the entity belongs to:
scene = self.entity.getScene()

利便性のために、cave.Componentstart および end メソッドにはシーンがパラメータとして渡されます。これらのメソッド内でシーンを使う可能性が非常に高いためです。ローカルコード、例えば Python Code Component の場合は、デフォルトで scene 変数が定義されていて、魔法のように「ただ使うだけ」で動作します。

シーンを取得したら、次のコードで特定の名前のEntityを取得できます:

watchtower = scene.get("Watch Tower 01")

Caveのシーンクラスは他にも多数のメソッドを提供しており、レイキャストやスフィアキャスト、接触ボックスやスフィアによる当たり判定のチェック、全エンティティの取得、ルートエンティティの全取得、特定タグのエンティティのみの取得、特定プロパティや名前を持つエンティティ取得など、さまざまなシーンクエリが可能です。詳しくはPython APIを参照してください。

クエリが有効な結果を返したかどうかを必ず確認しましょう。例えば:

# 存在しないEntityをクエリする例:
ent = scene.get("This Entity Doesnt Exist")

if ent is None:
    print("無効なエンティティです!")

EntityのComponentを取得する

特定のEntityを取得し無効でないことを確認したら、そこから特定のコンポーネントをどう取得できるか理解するのが良いでしょう。

Entityには get というメソッドがあり、コンポーネント名を文字列で渡すと、自動的にそのEntity内に該当するコンポーネントがあるかどうかを探してくれます:

animator = self.entity.get("Animation Component")

コンポーネント名が "Component" で終わっている場合(かなり一般的です)、この末尾は省略しても動作します。また、複数単語のコンポーネント名を記述するときに空白を入れても、すべてを繋げてもどちらでも構いません。

例えば、Entityの Rigid Body Component を取得する場合、Pythonでの名前は RigidBodyComponent ですが、以下のどれでも機能します:

rb = self.entity.get("Rigid Body")
rb = self.entity.get("RigidBody")
rb = self.entity.get("Rigid Body Component")
rb = self.entity.get("RigidBodyComponent")
rb = self.entity.get("RigidBody Component")

Pythonは型が厳密でないため、Caveのコードではコンポーネントの型ヒントを付けるのが良いプログラミング習慣です。これは次のように書きます。この場合はPythonの文法上、完全なコンポーネント名を含める必要があります:

rb : cave.RigidBodyComponent = self.entity.get("Rigid Body")

この記述により、Visual Studio Codeなどの外部エディターや、Caveの組み込みスクリプトエディターの補完機能(IntelliSense)が動作します。

Transform Component は非常に頻繁に使うため、Entityには主要なTransformを取得するためのネイティブメソッド getTransform() があります:

transf = self.entity.getTransform()

self.entity.getTransform() を呼び出すのは、self.entity.get("Transform") と同じ結果ですが、前者の方が高速で最適化されています。

もしEntityに同じタイプのコンポーネントが複数ある場合は最初に見つかったものを返しますが、すべて取得したいこともあります。例えば、複数マテリアルのメッシュはCaveでは複数のMeshコンポーネントを持つEntityとして表現されるため、すべてのMeshコンポーネントを取得したいことがあります。その場合は getAll メソッドが使えます:

meshCmps = self.entity.getAll("Mesh")

# すべてのマテリアルを光るものに変更:
for meshCmp in meshCmps:
    meshCmp.material.setAsset("Glowing Material")

EntityのPythonコンポーネントを取得する

ネイティブなCaveコンポーネントを取得する方法はわかりましたが、Pythonで独自に作成したカスタムコンポーネントをEntityから取得するにはどうしたらよいでしょうか?

それには getPy という特別なメソッドを使います。通常の get と全く同じ使い方ですが、こちらはPythonコンポーネントも取得できます:

myCmp = self.entity.getPy("MyCustomComponent")

内部最適化のために特別なメソッドが必要です。Caveが高速に動くようにするための工夫です。

カスタムPythonコンポーネントを取得したら、そのPythonで作られた変数やメソッドに自由にアクセスできます:

myCmp = self.entity.getPy("MyCustomComponent")

# Python変数の変更:
myCmp.customValue = 10

# カスタムメソッドの呼び出し:
myCmp.doSomething()
myCmp.applyDamage(10)

Python Code Component

通常のPython Componentとは別に、Caveには Python Code Component があります。これはPythonスクリプトアセットを別に作らずに、Entity上で直接簡単にスクリプトを書けるコンポーネントです。

他のメソッドも備わっていますが、Python Code Componentはスクリプトをコンポーネント内に直接書き込むのでモジュール化や再利用ができません。Entityをコピーするとスクリプトもコピーされます。ですが、コインが回転するような簡単なロジックを素早く作るには便利です。

用途例:

  • クイックテスト
  • 小さなコールバック
  • 一回限りの挙動
  • プロトタイプ

大規模なゲームプレイシステムには適しません。コードの再利用や整理が難しいためですが、小さなコードなら最適です。

Python Component と Python Code Component の使い分け

簡単なルールは次の通りです:

使うべきもの 状況
Python Component 動作を再利用したい、アセットとして編集したい、成長させたい場合
Python Code Component 動作が小規模でローカルな場合

例えば再利用可能な EnemyAI はPythonスクリプトアセットとしてPython Componentで使います。ボタンが押されたときに何かを表示したり関数を呼ぶだけの小さなコードはPython Code Componentで十分です。

初めての良いスクリプト目標

最初のCaveスクリプトは小さくて見た目にわかりやすいものが良いでしょう。

次のどれかを作ってみてください:

  • 前に動くプラットフォーム
  • シーン開始時に開くドア
  • 数秒おきに点灯・消灯するライト
  • 音を鳴らして自分を無効化するピックアップ
  • クリックで別シーンに移動するボタン

これらは小さな例ですが、最も重要なワークフローを学べます:エンティティを取得し、コンポーネントを取得し、何かを変更し、プレイモードでテストし、調整する。

覚えておくべきこと

CaveのPythonスクリプトは普通Pythonコンポーネントを通じてEntityにアタッチされます。

初心者に最も重要なパターンは:

  1. start() を使って参照を取得し値の準備をする。
  2. update() で毎フレーム動作を実行する。
  3. スクリプトが所属するEntityには self.entity でアクセスする。
  4. コンポーネントを使って実際に動かしたりアニメーションしたり音を鳴らしたり操作する。

このパターンに慣れれば、Caveでのスクリプティングがずっとわかりやすくなります。コードを書くのではなく、エンティティに振る舞いを教えているのです。