Keep your place in this quest

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

今、Caveでキャラクターとアニメーションがどのように機能するかを理解しましょう。キャラクターは、レベルを歩き回る3Dモデルだけではありません。実際のゲーム設定では、通常、物理、移動ロジック、視覚的メッシュ、アーマチュア、アニメーション、およびどのアニメーションを再生するかを決定するコードまたはLogic Bricksを組み合わせます。

実用的にするために、CaveのスタータープロジェクトからのデフォルトのPlayerテンプレートをメインリファレンスとして使用します。Third Person GameまたはTop Down Gameを作成すると、Caveはすでに動き、表示されるアニメーションキャラクターを持ち、基本的な移動アニメーションを再生するプレイヤーを生成します。

image.png

このレッスンでは、そのセットアップがどのように整理され、Pythonからアニメーションを制御する方法を示します。

あなたが学ぶ内容:

  • スターターPlayerテンプレートがどのように構築されているか。
  • なぜ視覚的メッシュがルートプレイヤーエンティティではなく、子エンティティなのか。
  • Animation Componentは何をするのか。
  • Pythonからアニメーターを取得する方法(Logic Bricksにも同様のロジックが適用されます)。
  • 名前でアニメーションを再生する方法。
  • ブレンド、レイヤー、およびボーンフィルターがどのように機能するか。
  • アニメーションコールバックとソケットがシステムにどのように適合するか。

プレイヤーテンプレート構造

スタータープロジェクトでは、PlayerはEntity Templateです。これは、プレイヤーのセットアップが再利用可能であり、複数のシーンに配置できることを意味します。

構造はおおよそ次のようになります:

image.png

正確な子は、選択したプロジェクトオプションによって異なることがありますが、重要なアイデアは、ルートPlayerエンティティがゲームプレイ設定を所有し、子Meshエンティティが表示されるアニメーションキャラクターを所有しているということです。

スターターエネミーは非常に似たアイデアを使用しています。敵のルートエンティティはキャラクターの物理演算と挙動を持ち、その子Meshエンティティには視覚的メッシュとAnimation Componentが含まれています。

ルートプレイヤーエンティティ

ルートPlayerエンティティはゲームプレイキャラクターを表します。通常、次のものが含まれています:

  • Transform Component
  • Character Component
  • プレイヤーの移動、UI、アニメーションヘルパー、およびその他のゲームプレイロジックのためのPythonコンポーネント。
  • 健康またはオプションの挙動設定などのプロパティ。
  • カメラ、UI、視覚的メッシュ、およびヘルパーオブジェクトなどのために使用される子を持つ。

Character Componentは特に重要で、キャラクタースタイルの物理(歩行、ジャンプ、衝突、傾斜、世界に対する移動)を処理します。これにより、ルートPlayerは主にゲームプレイと物理を担当します。

メッシュ子エンティティ

プレイヤーテンプレート内には、Meshという子エンティティがあります。この子には通常次のものが含まれています:

  • Transform Component
  • 1つ以上のMesh Components
  • Animation Component

Mesh Componentはキャラクターに視覚的な3Dモデルとマテリアルを提供します。キャラクターが複数のマテリアルを使用する場合、Caveは同様のマルチマテリアルルールに従って複数のMesh Componentsでそれを表すことができます。

Animation Componentはアーマチュアを評価し、アニメーションを再生します。

したがって、簡単に言うと:

エンティティ 主な責任
Player 物理、移動、ゲームプレイロジック、プロパティ。
Player -> Mesh 表示モデル、マテリアル、アーマチュアアニメーション。

この分離は意図的です。

なぜメッシュが子エンティティなのか

メッシュが直接ルートPlayerエンティティに配置されていない理由が気になるかもしれません。

その理由は、物理キャラクターと視覚的アニメーションキャラクターが異なるトランスフォームを必要とすることが多いからです。

ルートPlayerトランスフォームはゲームプレイボディを表します。それは世界を移動し、壁に衝突し、Character Componentを持つものです。子Meshトランスフォームはキャラクターの見た目を表します。

この分離は多くの状況で便利です:

  • メッシュは物理カプセルとは異なるスケールが必要になることがあります。
  • メッシュはキャラクターの衝突に合わせるために小さなオフセットが必要になることがあります。
  • メッシュは移動ボディから独立して回転する必要があるかもしれません。
  • メッシュは移動方向を向く必要があるかもしれませんが、ルートはゲームプレイのオリエンテーションを保持します。
  • メッシュはキャラクターが横に移動するときでも前を向き続ける必要があるかもしれません。

たとえば、プレイヤーが左に移動していると仮定しましょう。適切な「左に歩く」アニメーションがある場合、キャラクターメッシュは前を向き続け、アニメーションが横の移動を処理することを望むかもしれません。しかし、横のアニメーションがない場合、メッシュは左に回転してキャラクターがその方向で視覚的に歩くようにしたいかもしれません。

どちらのケースも有効です。

それには別々のトランスフォームが必要です:

トランスフォーム 制御
ルートPlayerトランスフォーム 物理位置、ゲームプレイボディ、世界を通っての移動。
Meshトランスフォーム 視覚的オリエンテーション、スケール、オフセット、アニメーションプレゼンテーション。

これが、スタータープレイヤーとエネミーがアニメーションメッシュを子エンティティとして保持する理由です。

Animation Componentが行うこと

Animation Componentはエンティティでスケルタルアニメーションを再生するために使用される主要なコンポーネントです。

これは次を必要とします:

  • Armature
  • デフォルトのAnimation
  • 同じエンティティ上の有効なMesh Component(または複数のもの)。

アーマチュアはスケルトンを定義します。アニメーションはそのスケルトンが時間の経過に伴ってどのように動くかを定義します。Animation Componentは最終的なアニメーションポーズを評価し、それを視覚的なキャラクターに適用します。

デフォルトのスタータープレイヤーでは、Mesh子が使用します:

  • Proto Mesh
  • Proto Mat
  • Proto Armature
  • p-idleをデフォルトアニメーション(アイドルアニメーション)に設定

これらのスターターアセットは、すぐに動作するアニメーションキャラクターを検査できるように存在します。

アーマチュア、アニメーション、およびリターゲット

  • Armatureはキャラクターのスケルトンです。メッシュが従う骨を含みます。
  • Animationはアーマチュア内の骨に対する時間経過の動きを保存します。

Animation Componentはそれらの要素をつなげます:

ピース 意味
Mesh 視覚的なキャラクターモデル。
Armature キャラクター内のスケルトン。
Animation 動き、たとえばアイドル、歩行、走行、または落下。
Animation Component エンティティ上のアニメーションを再生するコンポーネント。

Caveはまた、可能であればアニメーションをリターゲットできます。再生中のアニメーションが異なる互換性のあるアーマチュアに属する場合、Animation Componentはリターゲットを使用してその動きを現在のアーマチュアに適用できます。

リターゲットは、互換性のあるキャラクター間でアニメーションを再利用したい場合に便利ですが、インポートされたリグの品質と互換性にも依存します。アニメーションが歪んで見えたり、オフセットがあったり、奇妙に見える場合は、ソースリグ、インポートされたアーマチュア、およびリターゲティング設定を確認してください。


Pythonからアニメーターを取得

さて、論理を通じてキャラクターをアニメートする方法を探求し始めましょう。

Caveプロジェクトでは、プレイヤーのゲームプレイロジックをルートPlayerエンティティ上に保持し、そこからMesh子にアクセスすることが一般的な慣習です。デフォルトのスタータースクリプトはこのアイデアに従っています。

基本的なパターンは次のようになります:

import cave

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

    def update(self):
        # 例:アイドルアニメーションを再生する。
        self.animator.playByName("p-idle", blend=0.2, loop=True)

この例では:

  • self.entityはルートPlayerエンティティです。
  • getChild("Mesh")は視覚的子エンティティを見つけます。
  • self.mesh.get("Animation")はAnimation Componentを取得します。
  • 変数はanimatorと呼ばれ、Caveスクリプトでの一般的な命名慣習です。
  • playByName(...)はアニメーションアセットを名前で再生します。

これが、コードを通じてアニメーションを制御するために知るべき基本的な接続です。

名前でアニメーションを再生

最も一般的に使用する方法はplayByNameです。

その基本的な使用法は次のようになります:

self.animator.playByName("p-walk", blend=0.2, loop=True)

重要なパラメータは:

パラメータ 意味
anim 再生するアニメーションアセットの名前。
blend Caveが新しいアニメーションにどれくらいの時間をブレンドするべきか(秒単位)。
loop アニメーションを繰り返すかどうか。
layer どのアニメーションレイヤーがアニメーションを再生するか。

たとえば:

self.animator.playByName("p-idle", blend=0.2, loop=True)
self.animator.playByName("p-walk", blend=0.2, loop=True)
self.animator.playByName("p-run", blend=0.2, loop=True)
self.animator.playByName("p-fall", blend=0.2, loop=True)

これらはデフォルトのプレイヤーやエネミースクリプトで使用されるのと同じ種類の呼び出しです。

簡単な移動の例

一般的なプレイヤーアニメーションのセットアップは:

  • キャラクターが立っているときにアイドルを再生する。
  • キャラクターが動くときに歩行を再生する。
  • キャラクターが走っているときに走行を再生する。
  • キャラクターが地面にいないときに落下を再生する。

以下は簡略化された例です:

import cave

class SimplePlayerAnimator(cave.Component):
    def start(self, scene: cave.Scene):
        self.character : cave.CharacterComponent = self.entity.get("Character")

        self.mesh : cave.Entity = self.entity.getChild("Mesh")
        self.meshTransform : cave.TransformComponent = self.mesh.getTransform() if self.mesh else None
        self.animator      : cave.AnimationComponent = self.mesh.get("Animation") if self.mesh else None

    def update(self):
        if self.animator is None or self.character is None:
            return

        direction = self.character.getWalkDirection()
        isMoving = direction.length() > 0
        isRunning = False # Replace this with your own input or gameplay condition.

if self.character.onGround():
            if isMoving:
                if isRunning:
                    self.animator.playByName("p-run", blend=0.2, loop=True)
                else:
                    self.animator.playByName("p-walk", blend=0.2, loop=True)
            else:
                self.animator.playByName("p-idle", blend=0.2, loop=True)
        else:
            self.animator.playByName("p-fall", blend=0.2, loop=True)

この例は意図的にシンプルです。実際のスターターコントローラーは、入力、移動方向、ジャンプ、オプショナルなポイント&クリック動作、およびメッシュの回転も処理しますが、アニメーションの考え方は同じです。

メッシュを移動方向に回転させる

視覚的な Mesh エンティティは独自のトランスフォームを持っているため、ルート Player から別に回転させることができます。

スタータープレイヤーは、キャラクターが動くときにこれを行います:

if direction.length() > 0 and self.meshTransform:
    self.meshTransform.lookAtSmooth(
        self.entity.getTransform().transformDirection(-direction),
        6.0 * cave.getDeltaTime()
    )

重要な考え方は、正確な数学ではなく、ゲームプレイのボディと視覚的なメッシュを別々に制御できるということです。

これにより、キャラクターがどのように向くべきかを選択できます:

  • 移動方向を向く。
  • ストレイフ中は前方を向き続ける。
  • 希望の方向に滑らかに回転する。
  • 異なるゲームプレイモードに対して異なる視覚的な方向付けのルールを使用する。

これは、スターターキャラクターが子供の Mesh エンティティを持つ主な理由の一つです。

アニメーションブレンド

アニメーションブレンドは、遷移を滑らかにします。

ブレンドなしで、1つのアニメーションから別のアニメーションへ切り替えると、瞬時にスナップすることがあります。ブレンドを使用することで、Caveは同じレイヤーのアニメーションの間を滑らかに遷移できます。

例えば:

# これから:
self.animator.playByName("p-idle", blend=0.2, loop=True)

# これへ:
self.animator.playByName("p-walk", blend=0.2, loop=True)

ここで、blend=0.2 は、Caveが新しいアニメーションに0.2秒かけてブレンドすることを意味します。

これは特にキャラクターにとって重要です。プレイヤーは、プロトタイプでもアニメーションのポップが厳しいとすぐに気が付きます。

初心者向けのブレンド値は通常小さいです:

  • 0.1 は非常に迅速な遷移に。
  • 0.2 は通常の移動遷移に。
  • 0.4 以上は遅い、重い遷移に。

これは感覚で調整するべきです。

アニメーションレイヤー

Caveは複数のアニメーションレイヤーをサポートしています。各レイヤーは独自のアニメーションを再生でき、高いレイヤーは低いレイヤーの上にスタックできます。任意のレイヤーでアニメーションを実行できます。

デフォルトの移動アニメーションは通常レイヤー 0 で実行されます。

例えば:

self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)

上半身のアクションアニメーション(攻撃、狙い、リロードなど)がある場合、別のレイヤーで再生できます:

self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)

この考え方は:

レイヤー 一般的な目的
レイヤー 0 完全なボディの移動(アイドル、歩行、走行、ジャンプ、落下など)。
レイヤー 1 上半身のアクション(攻撃、狙い、リロード、武器を保持するなど)。

これにより、キャラクターが歩き続けながら、別のレイヤーが上にアクションを追加できます。

レイヤーの重み

各レイヤーには重みがあり、そのレイヤーが持つ影響の大きさを制御します。

Pythonから変更できます:

self.animator.setLayerWeight(1, 1.0)

また、読めます:

weight = self.animator.getLayerWeight(1)

重みが 1.0 であれば、レイヤーは完全にアクティブです。重みが 0.0 であれば、目に見える影響はありません。

レイヤーの重みは、アクションレイヤーをフェードインまたはフェードアウトさせたいときに便利です。たとえば、武器を徐々に上げたり、狙ったり、特殊ポーズにブレンドするなどです。

ブレンドパラメータは、同じレイヤー間でのみアニメーションをブレンドします。また、異なるレイヤーで再生されているアニメーションをブレンドするつもりはありません。したがって、レイヤー0で再生されるアニメーションがあり、その後突然レイヤー1でアニメーションを再生した場合、ブレンド値にかかわらず、それらの間でブレンドは行われません。レイヤー1ですでに他のアニメーションが実行されている場合のみブレンドされます。レイヤー間でブレンドする場合は、レイヤーの重みを使用してそれを実現するカスタムロジックを作成する必要があります。

ボーンフィルター

ボーンフィルターは、レイヤーによって影響を受けるボーンと各ボーンが受け取る影響の大きさを制御します。

これが上半身のアニメーションを可能にします。

例えば、次のようにしたい場合があります:

  • レイヤー0が全身を制御する。
  • レイヤー1が脊椎、腕、手、および武器のボーンのみを制御する。

Pythonでは、レイヤーのフィルターを作成し、ボーンとその子供に対する影響を割り当てることができます:

def setupUpperBodyLayer(self):
    armature = self.animator.armature.get()
    spine = armature.getBone("mixamorig:Spine")

    upperBody = self.animator.createLayerFilter(1)
    upperBody.defaultBlend = 0.0
    upperBody.setToBone(spine, 1.0, recursive=True)

この例では:

  • defaultBlend = 0.0 は、レイヤーがデフォルトでボーンに影響を与えないことを意味します。
  • setToBone(spine, 1.0, recursive=True) は、脊椎とその子供が完全に影響を受けることを意味します。
  • レイヤー 1 は、上半身アニメーション用に使用できるようになります。

ボーン名は、インポートしたアーマチュアによって異なります。常にアーマチュアを検査し、キャラクターのために正しいボーン名を使用してください。

実用的なレイヤーの例

歩きながら攻撃できる三人称キャラクターを想像してください。

1つの可能な設定は次の通りです:

レイヤー 対応する内容
レイヤー 0 アイデンティティ、歩行、走行、落下。
レイヤー 1 上半身攻撃。

コードは次のようになります:

import cave

class CombatAnimator(cave.Component):
    def start(self, scene: cave.Scene):
        self.mesh = self.entity.getChild("Mesh")
        self.animator : cave.AnimationComponent = self.mesh.get("Animation") if self.mesh else None

        if self.animator:
            armature = self.animator.armature.get()
            spine = armature.getBone("mixamorig:Spine")

            upperBody = self.animator.createLayerFilter(1)
            upperBody.defaultBlend = 0.0
            upperBody.setToBone(spine, 1.0, recursive=True)
            self.animator.setLayerWeight(1, 1.0)

    def playWalk(self):
        self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)

    def playAttack(self):
        self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)

この例では、Attackというアニメーションアセットがあると仮定しています。プロジェクトで異なるアニメーション名を使用している場合は、その名前を代わりに使用してください。

重要な考え方は、移動がレイヤー 0 に留まり、攻撃がレイヤー 1 で再生され、フィルタリングされたボーンにだけ影響を与えることです。

アニメーションコールバック

アニメーションは特定のタイミングでPythonコードを実行できます。

Caveは次のようなコールバックをサポートしています:

  • アニメーション開始時。
  • アニメーション終了時。
  • 特定のアニメーションフレームで。

コールバックは、アニメーションがゲームプレイや効果をトリガーする必要があるときに便利です。

例:

  • 足音の音を再生する。
  • ステップ時に埃を発生させる。
  • 攻撃フレームでダメージを与える。
  • 武器のトレイルを開始する。
  • パーティクルエフェクトをトリガーする。
  • アニメーションが終了したことをロジックに通知する。

アニメーションは、コードよりも正確なタイミングを知っていることが多いです。剣がターゲットエリアに達するまでダメージを与えない場合、アニメーションコールバックはそのゲームプレイの瞬間を正しいフレームに配置できます。

足音コールバックの例

スタータープロジェクトには、歩行や走行アニメーションに足音コールバックを含めることができます。それを調査することは、実用的なアニメーションコールバックを実際に見る良い方法です。

アイデアはシンプルです:

  1. アニメーションは、足が地面に触れる時期を知っています。
  2. コールバックはそのフレームに置かれています。
  3. コールバックは音を再生するか、小さなエフェクトを発生させます。

アニメーションコールバック内では、Caveは次のような便利な変数を提供します:

  • entity:所有エンティティ。
  • animator:アニメーションコンポーネント。
  • handle:再生中のアニメーションレイヤー/ハンドラー。

非常に小さな足音コールバックは次のようになります:

cave.playSound("Footstep Grass", volume=0.5)

後に、地面の素材、キャラクターの速度、または現在の状態に応じて異なる音を選択できるようにこのアイデアを拡張できます。

再利用可能なアニメーションコールバック

コールバックはアニメーションアセットに属します。

つまり、複数のエンティティが同じアニメーションを再生すると、コールバックはそれを使用する各エンティティで実行できます。

これにより、コールバックは再利用可能になります。たとえば、同じ歩行アニメーションがそれを再生するすべてのキャラクターに対して足音の音をトリガーできます。ただし、コールバックコードが所有エンティティを正しく使用する必要があります。

アニメーションはタイミングを保存し、エンティティがコンテキストを提供します。

高度なポーズコールバック

Caveはまた、アニメーションコンポーネントがアーマチュアポーズを評価した後に、Pythonによるポスト評価コールバックをサポートしています。

これは高度な機能ですが、以下のために使用できます:

  • 逆キネマティクス。
  • 頭の狙い。
  • 武器の狙い。
  • 足の配置。
  • 最終ポーズの調整。

スターター Player Toolkit には、このコンセプトを使用する足IKの例が含まれています。これは Mesh の子を取得し、Animation コンポーネントを取得し、次のようにメソッドを登録します:

self.animator.addPostEvaluationCallback(self.postEvaluation)

すぐにIKシステムを書かなければならないわけではありませんが、CaveがPythonによって通常のアニメーション再生の後に最終ポーズを調整できることを知っておくと便利です。

アニメーションソケットコンポーネント

Animation Socket Component は、子エンティティが親のアニメーションエンティティのボーンに従うことを可能にします。

子エンティティは、アニメーションコンポーネントを持つ親の下に配置される必要があります。次に、ソケットはボーンを選択し、そのボーンから位置、回転、オプションでスケールをコピーできます。必要に応じてオフセットを使用します。

ソケットは、アニメーションされたキャラクターにものを取り付けるのに便利です。

  • 手に剣。
  • 手に銃。
  • 腕に盾。
  • 頭にヘルメット。
  • 脊椎の骨にバックパック。
  • 手に付けられた小道具。

例えば、キャラクターが剣を持っている場合、その剣は手の骨に従うアニメーションソケットコンポーネントを持つ子エンティティとして設定できます。キャラクターが攻撃したり、走ったり、静止している間、剣は正しいアニメーション位置に付けられたままです。

覚えておくべきこと

  • スタータープレイヤーは、ルートの Player エンティティと子の Mesh エンティティを持つテンプレートです。
  • ルートの Player は通常、物理、移動、プロパティ、およびゲームプレイロジックを処理します。
  • 子の Mesh は通常、視覚的モデル、マテリアル、アーマチュア、およびアニメーションコンポーネントを処理します。
  • この分離によって物理ボディと視覚キャラクターは異なるトランスフォームを持ちます。
  • アニメーションコンポーネントはアーマチュアとアニメーションアセットを使用してスケルタルアニメーションを再生します。
  • Pythonでは、アニメーションコンポーネントを animator と呼ぶことが一般的です。
  • playByName は名前でアニメーションアセットを再生し、ブレンド、ループ、レイヤーをサポートします。
  • レイヤーは複数のアニメーションを重ね合わせることを可能にします。
  • ボーンフィルターは、レイヤーがスケルトンの一部にのみ影響を与えることを許可します。
  • アニメーションコールバックは、アニメーションのタイミングをゲームプレイイベントに接続します。
  • アニメーションソケットは、小道具をアニメーション化された骨に取り付けます。

スターターの Player または Enemy テンプレートを調査する際は、この構造を地図として使用してください:ゲームプレイ用のルートエンティティ、視覚とアニメーション用の子 Mesh エンティティ。