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 GameTop Down Gameを作成すると、Caveはすでに移動し、見えるアニメーション付きキャラクターがあり、基本的なロコモーションアニメーションを再生するプレイヤーを生成します。

image.png

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

学べること:

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

Playerテンプレートの構造

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

構造は大まかに以下のようになっています:

image.png

正確な子エンティティは選択したプロジェクトオプションによって異なる場合がありますが、重要な考え方は、ルートのPlayerエンティティがゲームプレイセットアップを持ち、子のMeshエンティティが見えるアニメーション付きキャラクターを持つということです。

スターターの敵も非常に似た考え方を使っています。敵のルートエンティティにはキャラクター物理と動作があり、その子のMeshエンティティには見えるメッシュとAnimation Componentが含まれます。

ルートPlayerエンティティ

ルートのPlayerエンティティはゲームプレイキャラクターを表します。通常は以下を含みます:

  • Transform Component
  • Character Component
  • プレイヤー移動、UI、アニメーションヘルパー、およびその他のゲームプレイロジックのためのPythonコンポーネント。
  • ヘルスやオプションの動作設定などのプロパティ。
  • カメラ、UI、視覚メッシュ、ヘルパーオブジェクト用の子エンティティ。

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

Mesh子エンティティ

プレイヤーテンプレート内にはMeshという子エンティティがあります。この子エンティティは通常以下を含みます:

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

Mesh Componentはキャラクターに見える3Dモデルとマテリアルを提供します。キャラクターが複数のマテリアルを使用する場合、Caveは複数のMesh Componentで表現でき、これはインポートのレッスンで説明したマルチマテリアルのルールに従います。

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

簡単にまとめると:

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

この分離は意図的です。

なぜ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はこれらをつなぎます:

パーツ 意味
メッシュ 見えるキャラクターモデル。
アーマチュア キャラクター内の骨格。
アニメーション 待機、歩行、走行、落下などの動き。
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 # ここは入力やゲームプレイ条件に置き換えてください。

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 エンティティがある主な理由の一つです。

アニメーションブレンディング

アニメーションのブレンディングはトランジションを滑らかにします。

ブレンディングなしでは、アニメーションの変更が瞬時にパッと切り替わりますが、ブレンディングがあると同じレイヤー内のアニメーション間で滑らかに遷移できます。

例えば:

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

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

ここでの blend=0.2 は、新しいアニメーションへ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は背骨、腕、手、武器のボーンのみ制御。

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 は上半身用に使えます。

ボーン名はインポートしたアーマチュアによって変わるので、必ず自身のアーマチュアで正しいボーン名を確認してください。

実用的なレイヤー例

三人称キャラクターが同時に歩きつつ攻撃できる状況を考えます。

設定例は:

レイヤー 担当内容
レイヤー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 : Animation Component。
  • handle : 実行中のアニメーションレイヤーやハンドラ。

ごく簡単な足音コールバックの例:

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

これを拡張して地面の素材やキャラクターの速度、状態によって異なる音を選ぶこともできます。

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

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

複数のエンティティが同じアニメーションを再生しても、それぞれのエンティティでコールバックが実行されます。

これによりコールバックは再利用可能です。例えば同じ歩行アニメーションが複数のキャラクターで足音を鳴らすことができますが、その際もコールバック内で正しく所有エンティティを使う必要があります。

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

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

CaveはPythonを通じたポーズ後評価のコールバックもサポートしています。これはAnimation Componentがアーマチュアのポーズ評価後に呼ばれる高度な機能です。

用途例:

  • 逆運動学(Inverse Kinematics)
  • 頭の狙い
  • 武器の狙い
  • 足の設置
  • 最終ポーズ調整

スターターの Player Toolkit にはFoot IKの例があり、以下で登録します:

self.animator.addPostEvaluationCallback(self.postEvaluation)

すぐにIKを書く必要はありませんが、Caveでは通常のアニメーション再生後にPythonで最終ポーズを調節できることを知っておくと良いでしょう。

Animation Socket Component

Animation Socket Component は親のアニメーション付きエンティティのボーンを追従する子エンティティを実現します。

子エンティティはAnimation Componentを持つ親の下に置き、ソケットでボーンを選んで位置、回転、オプションでスケールをコピーし、オフセットも設定可能です。

ソケットはアニメーションキャラクターに装備やエフェクトを付けるのに便利です。

  • 手に剣を持つ。
  • 手に銃を持つ。
  • 腕に盾を装備する。
  • 頭にヘルメットをかぶる。
  • 背骨のボーンにバックパックを装着する。
  • 手に小道具を取り付ける。

例えば、キャラクターが剣を持っている場合、その剣は手のボーンに追従するAnimation Socket Componentを持つ子エンティティにできます。キャラクターが攻撃、走行、待機している間、剣は正しいアニメーション位置に保持されます。

覚えておくべきこと

  • スタータープレイヤーは、ルートのPlayerエンティティと子のMeshエンティティからなるテンプレートです。
  • ルートのPlayerは通常、物理、移動、プロパティ、ゲームプレイロジックを担当します。
  • 子のMeshは通常、可視モデル、マテリアル、アーマチュア、Animation Componentを担当します。
  • この分離により、物理ボディと視覚キャラクターの座標変換を別々に管理できます。
  • Animation Componentは、アーマチュアとアニメーションアセットを使用してスケルタルアニメーションを再生します。
  • Pythonでは、Animation Componentをanimatorと呼ぶことが一般的です。
  • playByNameは名前でアニメーションアセットを再生し、ブレンド、ループ、レイヤーに対応しています。
  • レイヤーは複数のアニメーションを重ねることを可能にします。
  • ボーンフィルターはレイヤーがスケルトンの一部だけに影響を与えることを可能にします。
  • アニメーションコールバックはアニメーションのタイミングをゲームプレイイベントに繋げます。
  • アニメーションソケットは小道具をアニメーションボーンに取り付けます。

スターターのPlayerEnemyテンプレートを調査するときは、この構造を目安にしてください:ゲームプレイ用のルートエンティティ、視覚とアニメーション用の子のMeshエンティティ。