概要

このドキュメントでは、カスタムC/C++エンジンでCreatureランタイムを使用する方法について説明します。ランタイムの言語はC++です。書き出したJSONファイルを読み込んでシーンで再生する方法の例を説明します。

ランタイムを取得する

ランタイムをCreatureのGame Runtimesウィンドウから直接ダウンロードするか、リポジトリ(こちら)から取得することができます。あなたのカスタムエンジンでは、コアCreatureランタイムライブラリファイルを含むCocos2d-xランタイムを取得する必要があります。Cocos2d-x固有のファイルを無視します。

コンパイルに必要なライブラリ

コアランタイムファイルに加えて、以下のライブラリも含まれています:

  • gason (ヘッダーを含み、単一なgason.cppのソースもコンパイルします)

  • glm (ヘッダーのみなので、インクルードパスが設定されていることを確認してください)

これらの2つのライブラリをプロジェクトのソース形式に含めてください。

気にする必要があるファイル

  • MeshBone.cpp & MeshBone.h - これはCreatureランタイムのポーズエンジンです。スケルトン全体、それに対応するボーン、およびメッシュ領域には、ここでアクセスできます。

  • CreatureModule.cpp & CreatureModule.h - Creatureのキャラクターを管理し、アニメーションファイルに読み込まれたキャラクターをポーズするレイヤーです。

ヘッダインクルード

次のヘッダーを含める必要があります:

#include "MeshBone.h"
#include "CreatureModule.h"

読み込みと初期化

dragonTest.jsonと呼ばれる書き出されたドラゴンのアニメーションファイルがあるとしましょう。character-dragon.pngと呼ばれる対応するテクスチャアトラスもあると仮定します。 最初にファイルアセットを読み込むことから始めます:

auto filename = CCFileUtils::getInstance()->fullPathForFilename("dragonTest.json");
auto texture_filename = CCFileUtils::getInstance()->fullPathForFilename("character-dragon.png");

CreatureModule::CreatureLoadDataPacket json_data;
CreatureModule::LoadCreatureJSONData(filename, json_data);

上のコードはJSONデータをディスクからメモリにロードします。次に、これらの読み込まれたアセットを利用できる実際のオブジェクトを作成しましょう:

auto cur_creature = std::shared_ptr<CreatureModule::Creature>(new CreatureModule::Creature(json_data));

creature_manager = new CreatureModule::CreatureManager(cur_creature);
creature_manager->CreateAnimation(json_data, "default");
creature_manager->CreateAnimation(json_data, "second");   

上記の例では、JSONファイルに2つのアニメーションクリップがあります: defaultsecondです。このため、creature_managerオブジェクトから2つのアニメーションを作成して再生できるようにする必要があります。

読み込みが完了したので、再生用のアクティブなアニメーションを設定できます:

creature_manager->SetActiveAnimationName("default");

これにより、現在アクティブなアニメーションとしてdefaultが設定されます。

あなた自身のエンジンにランタイムを適応させる

あなたのエンジンにCreatureキャラクターを再生して表示するには、再生/ポージングのメカニズムだけでなく、最終的なCreatureのキャラクターメッシュのトポロジー、ポイント、テクスチャー座標にアクセスする必要があります。

-CreatureManager - アニメーションと読み込んだキャラクターを管理するクラスです。このクラスを使用して、一定のデルタタイムだけアニメーションを進めます。ゲームループまたはレンダラーは、この"game tick"ごとにこのクラスの更新機能と対話する必要があります。

-Creature - 読み込んだCreatureを含むクラスです。CreatureManagerからアクセスできます。このクラスは、画面に表示するために必要なすべてのジオメトリ/レンダリングデータをあなたのゲームエンジンのために含みます。

ステップ1: コンストラクタ

実際にどのように機能するかを見てみましょう。読み込んだキャラクターを表示するレンダラークラスがエンジンにあるとします。

実際に、Cocos2d-xレンダラを例として、どのように行われているかを見てみましょう。このレンダラーのコンストラクタは次の処理を行います:

Renderer::Renderer(CreatureModule::CreatureManager * manager_in,
                   cocos2d::Texture2D * texture_in)
: manager(manager_in), texture(texture_in), debug_draw(false)
{
    setGLProgram(cocos2d::ShaderCache::getInstance()->getGLProgram(cocos2d::GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR));
    scheduleUpdate();
}

CreatureManagerオブジェクトを使用して、メンバー変数managerを初期化します。これにより、クラスの後半にある内部キャラクターだけでなく、マネージャにもアクセスできます。

ステップ2: アニメーションアップデートとアドバンス

すべてのゲームエンジンにはアップデート/ゲームティックコールがあります。この場合、次のように実行します:

void
Renderer::update(float delta)
{
    manager->Update(delta);
}

Creatureのキャラクターを特定のデルタタイムで更新するようにマネージャーに指示する。これは、アニメーションをdelta値だけ進めます。

ステップ3: メッシュを表示する

Creatureオブジェクトは、インデックス、uvs、ポイントを返します。これらの属性は、キャラクターメッシュをレンダリングするのに十分です。以下のように行われます:

void
Renderer::doDraw(const cocos2d::Mat4& transform, uint32_t transformFlags)
{
    getGLProgramState()->apply(transform);
    cocos2d::GL::bindTexture2D(texture->getName());
    
    cocos2d::Color3B nodeColor = getColor();
    
    manager->GetCreature()->FillRenderColours(nodeColor.r,
                                              nodeColor.g,
                                              nodeColor.b,
                                              getDisplayedOpacity());
    
    glEnableVertexAttribArray(cocos2d::GLProgram::VERTEX_ATTRIB_POSITION);
    glEnableVertexAttribArray(cocos2d::GLProgram::VERTEX_ATTRIB_COLOR);
    glEnableVertexAttribArray(cocos2d::GLProgram::VERTEX_ATTRIB_TEX_COORDS);
    
    glVertexAttribPointer(cocos2d::GLProgram::VERTEX_ATTRIB_POSITION,
                          3,
                          GL_FLOAT,
                          GL_FALSE,
                          0,
                          manager->GetCreature()->GetRenderPts());
    
    glVertexAttribPointer(cocos2d::GLProgram::VERTEX_ATTRIB_COLOR,
                          4,
                          GL_UNSIGNED_BYTE,
                          GL_TRUE,
                          0,
                          manager->GetCreature()->GetRenderColours());
    
    glVertexAttribPointer(cocos2d::GLProgram::VERTEX_ATTRIB_TEX_COORDS,
                          2,
                          GL_FLOAT,
                          GL_FALSE,
                          0,
                          manager->GetCreature()->GetGlobalUvs());
    
    glDrawElements(GL_TRIANGLES,
                   manager->GetCreature()->GetTotalNumIndices(),
                   GL_UNSIGNED_INT,
                   manager->GetCreature()->GetGlobalIndices());
    
    if(debug_draw)
    {
        cocos2d::Director* director = cocos2d::Director::getInstance();
        director->pushMatrix(cocos2d::MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
        director->loadMatrix(cocos2d::MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, transform);
        
        cocos2d::DrawPrimitives::setPointSize(5.0);
        glLineWidth(5.0f);
        cocos2d::DrawPrimitives::setDrawColor4F(1, 1, 1, 1);
        
        drawDebugBones(manager->GetCreature()->GetRenderComposition()->getRootBone());
        
        director->popMatrix(cocos2d::MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    }
    
}

ご覧のとおり、レンダリングパイプラインにポイント、UV、およびインデックスを送信し、データを一連の三角形として描画する単純な動作です。

カスタム時間/フレーム範囲

現在アクティブなアニメーションのカスタム時間/フレーム範囲を設定できます。再生範囲を10〜20のフレーム範囲に制限したい場合は、次の操作を行います:

creature_manager->SetUseCustomTimeRange(true);
creature_manager->SetCustomTimeRange(10, 20);

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

2つのアニメーションクリップをブレンドするには、次の手順を実行します:

creature_manager->SetBlending(true);
creature_manager->SetBlendingAnimations("default", "second");
creature_manager->SetBlendingFactor(0.5); // 0 to 1 blends between the 2 clips

ボーンの位置を変更する

場合によっては、キャラクターのボーンの位置を直接変更する必要があります。例えば、ボーンの位置をラグドール物理のスプリング/ジョイントに接続されたいくつかの剛体に従わせることができます。独自のカスタム要件に合わせてボーンの位置を設定する必要がある場合、次の操作を行う必要があります。まず、カスタムボーンオーバーライドメソッドを記述します。yのボーンをある量だけ置き換える例を次に示します:

// This is an example of how you can use a callback function to modify the position of the bones
// on your character. In this example, we will displace all the bones by a fixed amount in y.
void
HelloWorld::bonesModifyCallback(std::unordered_map<std::string, meshBone *>& bones_map)
{
    for(auto& bone_data : bones_map)
    {
        auto cur_bone = bone_data.second;
        auto cur_start_pos = cur_bone->getWorldStartPt();
        auto cur_end_pos = cur_bone->getWorldEndPt();
    
        cur_start_pos.y -= 5;
        cur_end_pos.y -= 5;
    
        cur_bone->setWorldStartPt(cur_start_pos);
        cur_bone->setWorldEndPt(cur_end_pos);
    }
}

また、CreatureManagerにカスタムボーン修正コールバックを使用するように伝える必要があります:

// Example of how to register a callback function to modify the bones
std::function<void (std::unordered_map<std::string, meshBone *>&) > cur_callback =
    std::bind(&HelloWorld::bonesModifyCallback, this, std::placeholders::_1);
creature_manager_2->SetBonesOverrideCallback(cur_callback);

キャラクターのインスタンス化とメモリ

キャラクターの複数のコピー(例: 2体のドラゴン)をインスタンス化する必要がある場合、以下のようにアニメーションを作成する必要があります:

// Create and load the animations
auto new_animation_1 = std::shared_ptr<CreatureModule::CreatureAnimation>(
                                                                        new CreatureModule::CreatureAnimation(json_data,
                                                                                              "default"));

auto new_animation_2 = std::shared_ptr<CreatureModule::CreatureAnimation>(
                                                                          new CreatureModule::CreatureAnimation(json_data,
                                                                                                                "pose2"));

その後、新しいCreatureオブジェクト、新しいCreatureManagerオブジェクト、および新しいCreatureRendererオブジェクトを作成する必要があります。作成したアニメーションを、新しく作成したCreatureManagerオブジェクトに次のように追加します:

// Second creature instancing example. This shows you how to load a second creature.
// Because both the first and second creature share animation data, you end up
// saving memory.
auto creature_2 = std::shared_ptr<CreatureModule::Creature>(new CreatureModule::Creature(json_data));

CreatureModule::CreatureManager * creature_manager_2 = new CreatureModule::CreatureManager(creature_2);
creature_manager_2->AddAnimation(new_animation_1);
creature_manager_2->AddAnimation(new_animation_2);
creature_manager_2->SetActiveAnimationName("pose2");
creature_manager_2->SetIsPlaying(true);
creature_manager_2->SetUseCustomTimeRange(true);
creature_manager_2->SetCustomTimeRange(10, 60);

この例と前の例との違いは、アニメーションが最初に作成され、それぞれのCreatureManagersに追加されることです。これは、アニメーションに割り当てられたメモリ(最もエクスペンシブなもの)が標準の場所に格納されていることを意味します。複数のCreatureManagerオブジェクトは、CreatureAnimationオブジェクトの共有プールからアニメーションを追加します。

ポイントキャッシュによる再生パフォーマンスの高速化

ポーズエンジンのペナルティを支払わずに再生アニメーションのスピードを上げたい場合、特定のアニメーションでポイントキャッシュを有効にすることができます。

ポイントキャッシングは本質的にアニメーションの頂点を保存し、再生がポーズエンジンをバイパスできるようにします。非常に複雑なスケルトンと変形を伴うキャラクターリグは、ポイントキャッシングによって大きな利益を得ます。

特定のアニメーションでポイントキャッシングを有効にするには、次のようにします:

my_creature_manager->MakePointCache("myAnimationName");

これがキャッシュの作成に必要なすべてです。キャッシュはキャラクタではなく、アニメーションに固有のものであることに注意してください。つまり、複数のキャラクターをインスタンス化した場合、そのアニメーションの単一キャッシュのコストのみを支払うことになります。これにより、メモリ節約と再生パフォーマンスの高速化が実現します