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では、ゲームプレイの動作、エディタツール、UIコールバック、アニメーションコールバック、タイムラインイベント、および組み込まれたコンポーネントとして存在するには特定すぎる小さなカスタムロジックを書くためにPythonが使用されます。Caveを使用する前に高度なPythonプログラマーになる必要はありませんが、Caveスクリプトの基本的な形を理解することは非常に役立ちます。

このレッスンでは、次のことを学びます:

  • CaveでのPythonの使用用途。
  • PythonスクリプトアセットとPythonコンポーネントがどのように連携するか。
  • start()update()の役割。
  • スクリプトから現在のエンティティにアクセスする方法。
  • エンティティからコンポーネントを取得する方法。
  • PythonコンポーネントまたはPythonコードコンポーネントを使用するタイミング。

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

Pythonを学ぶには?

Pythonのコーディング方法がわからない場合、Uniday Studioでは無料のPythonに関する学習クエストも提供しています。すべてのオプションを見るにはuniday.studio/learnに移動するか、ここから始めてください:

このセクションでは、これら2つの学習クエストで扱われるトピックの理解があると仮定します。


CaveでのPythonの使用用途

Caveでは、ゲームオブジェクトに動作が必要なときに使用するスクリプティングレイヤーがPythonです。

例えば、Pythonは次のようなことに利用できます:

  • プレイヤーや敵を移動させる。
  • ドアを開く。
  • アニメーションを再生する。
  • サウンドをトリガーする。
  • タイムラインを開始する。
  • シーンを変更する。
  • UI要素を更新する。
  • カスタムエディタツールを作成する。

基本的に、あなたはPythonスクリプトを使ってゲームの全てのロジックを作成することができます。

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

したがって、CaveでPythonを書くときは、通常は孤立したコードを書くのではなく、エンティティを制御し、そのエンティティに取り付けられたコンポーネントと対話するロジックを書いています。

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

Pythonコードは通常、Pythonスクリプトアセット内に存在します。

image.png

次に、そのスクリプトをシーンで実行するために、エンティティにPythonコンポーネントを追加し、どのクラスを実行するか選択します。

このように考えてください:

部品 役割
Pythonスクリプトアセット コードを格納します。
Pythonコンポーネント エンティティ上のそのスクリプトからのクラスを実行します。
エンティティ スクリプトによって制御されるオブジェクト。
cave.Componentクラス あなたが書いた実際の動作。

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

この分離は重要です。なぜなら、同じスクリプトを再利用できるからです。シーンに多くのドアを配置し、それぞれが異なるプロパティまたは異なる子オブジェクトを持つ同じドアコントローラスクリプトを使用できます。

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

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

import cave

class MyComponent(cave.Component):
    def start(self, scene):
        print("コンポーネントが始まりました!")

    def update(self):
        pass

ここにはいくつかの重要な詳細があります:

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

MyComponentという名前は任意のものでかまいませんが、実際のプロジェクトではDoorControllerEnemyAICheckpoint、またはPlayerHealthのような明確な名前を使用すべきです。

ライフサイクルメソッド

Caveは、コンポーネントが実行中のときに自動的にいくつかのメソッドを呼び出します。

最も一般的なものは:

メソッド 実行タイミング 一般的な使用法
start(self, scene) コンポーネントが開始するとき。 参照を取得し、プロパティを読み取り、変数を準備します。
firstUpdate(self) すべてのエンティティコンポーネントの開始メソッドの後、初回の更新時に常に実行されます。 他のコンポーネントが初期化されるのに依存する変数を作成します。
update(self) 毎フレーム、シーンが停止していない場合。 動き、入力、タイマー、状態チェック。
pausedUpdate(self) 毎フレーム、シーンが停止している場合。 停止中のロジック。
end(self, scene) コンポーネントが終了するとき。 必要に応じてクリーンアップします。

ほとんどの初心者スクリプトはstart()update()を使用します。たとえば、移動プラットフォームを作成している場合、start()は元の位置を保存するのに適した場所であり、update()は毎フレームプラットフォームを移動させる場所です。

editorUpdatelateUpdateメソッドもありますが、ここでは少し高度な内容なので探索しません。

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

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

これは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コンポーネントを取得します。
  • self.isOpenはスクリプトがドアが開いているかどうかを記憶するために使われる変数です。

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

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

エンティティを制御するには、通常、そのエンティティから1つ以上のコンポーネントを取得する必要があります。

たとえば、プレイヤースクリプトは次のようなコンポーネントを取得することがあります:

  • エンティティを移動または回転させるための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)

これは次を意味します:

  • エンティティにsppedプロパティがあれば、それを使用します。
  • そうでなければ、デフォルト値として2.0を使用します。

これは再利用可能なスクリプトに非常に便利です。1つのSimpleMoverスクリプトを作成し、異なる速度を持つ複数のエンティティで使用できます。

例えば:

エンティティ speedプロパティ
スロープラットフォーム 1.0
ファーストプラットフォーム 4.0
移動ハザード 7.0

スクリプトは変わらないが、エンティティごとに動作が変わります。

あるいは、エンティティのプロパティに依存するのではなく、コンポーネント自体のためにローカルに変更可能な変数を作成することもできます。このように変数を作成します:

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エンティティには視覚的なキャラクターメッシュとアニメーションコンポーネントがあります。

したがって、ルートプレイヤーエンティティ上のスクリプトがアニメーションを再生したい場合、最初に子のメッシュエンティティを取得します:

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つあります:

# アクティブなシーンを返します:
scene = cave.getScene()

# エンティティが所属するシーンを返します:
scene = self.entity.getScene()

便利なことに、cave.Componentstartおよびendメソッドは、シーンをパラメータとして受け取ります。これは、あなたがそれらのメソッドでシーンを使用する可能性が非常に高いからです。Python Code Componentのようなローカルコードでは、デフォルトであなたのために定義されたscene変数もあり、「そのまま使う」ことが期待できます。

シーンを取得したら、以下のコードを使用して名前を使って特定のエンティティを取得できます:

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

Caveは、シーンクラスに他のシーンクエリを実行するための多くのメソッドを提供しています。レイキャストやスフィアキャストを行ったり、接触ボックスやスフィアをチェックしたり(基本的な衝突クエリのため)、または特定のタグを持つすべてのエンティティ、特定のプロパティを持つすべてのエンティティ、特定の名前を持つすべてのエンティティを取得することもできます。したがって、詳細についてはPython APIを確認する価値があります。

常に、あなたのクエリが有効な結果を返したかどうか確認することを忘れないでください。例えば:

# 存在しないエンティティをクエリ:
ent = scene.get("This Entity Doesnt Exist")

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

エンティティコンポーネントの取得

特定のエンティティを取得し、それが知られていないことを確認したら、特定のコンポーネントを取得する方法を理解するのが良いアイデアです。

エンティティにはgetという特定のメソッドがあり、コンポーネント名を文字列として渡すと、そのエンティティを自動的に調べて、検索に一致するコンポーネントがあるかどうかを確認します:

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

あなたの生活を楽にするために、コンポーネント名が「Component」で終わる場合(非常に一般的なことです)、このメソッドでコンポーネント名を入力する際にこの終了を完全に省略できます。また、複数の単語からなるコンポーネント名にホワイトスペースを含めるか、すべてを連結するかを選択することもできます。

例えば、エンティティから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のような外部エディタでIntelliSenseが動作するか、Cave Engineがその埋め込みスクリプトエディタで提供する自動補完が機能します。

Transform Componentについては、最も一般的なコンポーネントタイプの1つで、頻繁にクエリされるため、エンティティにはその主要なTransformを取得するためのネイティブメソッドがあります:

transf = self.entity.getTransform()

self.entity.getTransform()を呼び出して取得するのは、self.entity.get("Transform")を呼び出して取得するのと同じ結果になりますが、最初のオプションはより速く最適化されています。

エンティティに同じタイプの複数のコンポーネントがある場合、最初の一致を返しますが、時にはすべての一致が必要な場合があります。例えば、Caveでは複数のメッシュコンポーネントを持つエンティティで表されるマルチマテリアルメッシュでは、すべてのメッシュコンポーネントを取得したいと思うかもしれません。そのためにはgetAllメソッドを使用できます:

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

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

エンティティPythonコンポーネントの取得

今、私たちはネイティブCaveコンポーネントの取得方法を知りましたが、もしエンティティからPythonを使用してあなた自身がカスタムで作成したコンポーネントを取得したい場合はどうでしょうか?

そのためには、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コンポーネントの他に、CaveにはPython Code Componentもあります。Python Code Componentは、エンティティに直接書かれたクイックスクリプトに便利で、最初に別のPythonスクリプトアセットを作成する必要がありません。

それには他のすべてのメソッドが含まれており、違いは、Python Code ComponentではPythonスクリプトをコンポーネント自体の中に直接書くことであり、モジュール化されず再利用可能ではありません。したがって、エンティティのコピーを作成すると、そのスクリプトもコピーされます。しかし、時には、コインを回すような簡単なロジックを作成するための迅速な方法になることがあります。

これは以下の場合に適しています:

  • クイックテスト。
  • 小さなコールバック。
  • 一時的な動作。
  • プロトタイプ。

これは大規模なゲームプレイシステムには最適な選択肢ではありません。なぜなら、コードは再利用しにくく、整理しにくいからです。しかし、小規模なコードには最適です。

Python ComponentとPython Code Componentの違い

これは単純なルールとして使ってください:

使用するもの いつ
Python Component 動作を再利用したり、アセットとして編集したり、時間とともに成長させる必要がある時。
Python Code Component 動作が小さい場合、ローカルな場合。

例えば、再利用可能なEnemyAIは、Pythonコンポーネントによって使用されるPythonスクリプトアセットであるべきです。何かを印刷するだけの小さなコードやボタンが押された時に関数を呼び出すようなものは、Python Code Componentで良いでしょう。

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

最初のCaveスクリプトとして良いものは、小さくて可視的なものです。

以下のいずれかを作成してみてください:

  • 前に進むプラットフォーム。
  • シーン開始時に開くドア。
  • 数秒ごとにオンオフするライト。
  • 音を再生して自分自身を無効にするピックアップ。
  • 別のシーンに変更するボタン。

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

覚えておくべきこと

CaveにおけるPythonスクリプトは通常、Pythonコンポーネントを通じてエンティティに添付されています。

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

  1. start()を使用して、参照を取得し値を準備します。
  2. update()を使用して、毎フレーム動作を実行します。
  3. self.entityを使用して、スクリプトを所有するエンティティにアクセスします。
  4. コンポーネントを使用して、実際にオブジェクトを移動、アニメーション、サウンド再生、または制御します。

このパターンが自然に感じられるようになると、Caveでのスクリプトはずっと神秘的でなくなります。あなたは単にコードを書いているのではありません。エンティティに動作を教えているのです。