Cocos2d-x で Node ツリーをダンプするコードを紹介

Cocos2d-x

自分のアプリは2013年頃から作り始めた関係で、いまやほぼ化石となった Cocos2d-x を使っています。

Cocos2d-x は開発環境が Unity などと比べて貧弱(個人の感想)で、Node のツリー状態を簡単に確認することができず、UI周りで困ることが結構ありました。

Nodeツリーをダンプする処理を自作して使っているので、今回はそれを紹介したいと思います。

Cocos2d-x でのUI周りで困ること

Cocos2d-x は Scene をコード上で生成したりする関係で、Nodeのツリー構造がどうなってるのか把握しづらくなる事が多い気がします。

ダイアログなどの各画面のレイアウトは CocosStudio で作っていますが、結局コード上でレイアウトファイルをロードしてNodeツリーに追加するわけで、意図通りに表示されなくて調査が必要なことが多いです。

もう色々面倒なので、Nodeツリーをダンプするコードを開発初期に用意してそれを使ってデバッグしていました。Unity でいう Hierarchy のツリー相当のものになりますね。

Nodeツリーをダンプするコード

以下がダンプ用のユーティリティ関数になります。dumpNodeTree() が本体ですね。

#include "cocos2d.h"
#include <sstream>
#include <string>

USING_NS_CC;

std::string replaceLineFeedToSpace(const std::string& str)
{
    std::string result = str;

    size_t pos;
    while ((pos = result.find("\r\n")) != std::string::npos) {
        result.replace(pos, 1, " ");
    }
    while ((pos = result.find("\n")) != std::string::npos) {
        result.replace(pos, 1, " ");
    }
    while ((pos = result.find("\r")) != std::string::npos) {
        result.replace(pos, 1, " ");
    }

    return result;
}

void dumpNodeTreeRecursive(const Node* pNode, int& count, const std::string& prevPrefix, bool isLastChild)
{
    std::stringstream prefixSs;
    std::stringstream nextPrefixSs;
    prefixSs     << prevPrefix;
    nextPrefixSs << prevPrefix;
    if (count > 1) {
        if (isLastChild) {
            prefixSs     << " └";
            nextPrefixSs << "   ";
        } else {
            prefixSs     << " ├";
            nextPrefixSs << " │";
        }
    }

    const Vector<Node*>& children = pNode->getChildren();
    ssize_t childrenNum = children.size();

    const Vec2& pos = pNode->getPosition();
    const Size& size = pNode->getContentSize();
    std::string prefix = prefixSs.str();
    std::string name = replaceLineFeedToSpace(pNode->getName());
    std::string description = replaceLineFeedToSpace(pNode->getDescription());
    const Vec2& anchor = pNode->getAnchorPoint();
    const Vec2& anchorPoint = pNode->getAnchorPointInPoints();
    float sx = pNode->getScaleX();
    float sy = pNode->getScaleY();
    int t = pNode->getTag();
    int lz = pNode->getLocalZOrder();
    float gz = pNode->getGlobalZOrder();
    const char* p = prefix.c_str();
    const char* n = name.c_str();
    const char* d = description.c_str();
    float x = pos.x;
    float y = pos.y;
    float w = size.width;
    float h = size.height;
    float ax = anchor.x;
    float ay = anchor.y;
    float apx = anchorPoint.x;
    float apy = anchorPoint.y;
    const char* sx1 = ((isLastChild && count > 1) ? "}" : "");
    const char* sx2 = (childrenNum > 0 ? "{" : "");
    CCLOG("%s #%d <%s> (%.0f, %.0f) %.0fx%.0f, scale:(%.2f, %.2f), anchor:(%.2f, %.2f)<%.0f, %.0f>, tag:%d, LZ:%d, GZ:%.0f, [%s] %s%s", p, count, n, x, y, w, h, sx, sy, ax, ay, apx, apy, t, lz, gz, d, sx1, sx2);

    ++count;
    std::string nextPrefix = nextPrefixSs.str();
    for (ssize_t i = 0; i < childrenNum; ++i) {
        const Node* pChildNode = children.at(i);
        dumpNodeTreeRecursive(pChildNode, count, nextPrefix, (i == childrenNum - 1));
    }
}

void dumpNodeTree(const Node* pNode)
{
    CCLOG("========");
    int count = 1;
    std::string prefix("");
    dumpNodeTreeRecursive(pNode, count, prefix, true);
    CCLOG("========");
}

これを、例えば Scene トップからのNodeツリーをダンプしたい場合は

dumpNodeTree(Director::getInstance()->getRunningScene());

みたいな形で呼び出すと、以下のようにログに出力されます。

========
 #1 <> (0, 0) 347x568, scale:(1.00, 1.00), anchor:(0.50, 0.50)<174, 284>, tag:-1, LZ:0, GZ:0, [<Scene | tag = -1>] {
 ├ #2 <> (0, 0) 0x0, scale:(1.00, 1.00), anchor:(0.00, 0.00)<0, 0>, tag:-1, LZ:0, GZ:0, [<Node | Tag = -1] 
 └ #3 <TitleScene> (0, 0) 347x568, scale:(1.00, 1.00), anchor:(0.50, 0.50)<174, 284>, tag:999, LZ:0, GZ:0, [<Layer | Tag = 999>] }{
    └ #4 <Layer> (174, 284) 640x1136, scale:(0.50, 0.50), anchor:(0.50, 0.50)<320, 568>, tag:1, LZ:1, GZ:0, [<Node | Tag = 1] {
       ├ #5 <Base> (320, 568) 640x1136, scale:(1.00, 1.00), anchor:(0.50, 0.50)<320, 568>, tag:0, LZ:0, GZ:0, [ImageView] {
       │ ├ #6 <Background> (320, 568) 640x1136, scale:(1.08, 1.08), anchor:(0.50, 0.50)<320, 568>, tag:10001, LZ:0, GZ:0, [ImageView] 
       │ ├ #7 <TitleLogoJp> (320, 693) 1080x344, scale:(0.50, 0.50), anchor:(0.50, 0.50)<540, 172>, tag:20001, LZ:0, GZ:0, [<Sprite | Tag = 20001, TextureID = 6>] 
       │ ├ #8 <TitleLogEn> (320, 693) 1080x344, scale:(0.50, 0.50), anchor:(0.50, 0.50)<540, 172>, tag:20002, LZ:0, GZ:0, [<Sprite | Tag = 20002, TextureID = 7>] 
       │ └ #9 <Label> (320, 350) 386x68, scale:(1.00, 1.00), anchor:(0.50, 0.50)<193, 34>, tag:1001, LZ:0, GZ:0, [Label] }
       ├ #10 <HeaderLayer> (320, 1022) 640x64, scale:(1.00, 1.00), anchor:(0.50, 1.00)<320, 64>, tag:50001, LZ:0, GZ:0, [ImageView] {
       │ └ #11 <AdLayer> (320, 78) 640x100, scale:(1.00, 1.00), anchor:(0.50, 0.00)<320, 0>, tag:90001, LZ:0, GZ:0, [ImageView] }
       └ #12 <FooterLayer> (320, 0) 640x120, scale:(1.00, 1.00), anchor:(0.50, 0.00)<320, 0>, tag:50002, LZ:0, GZ:0, [ImageView] }{
          ├ #13 <TimeLabel> (320, 160) 309x36, scale:(1.00, 1.00), anchor:(0.50, 0.50)<155, 18>, tag:10003, LZ:0, GZ:0, [Label] 
          └ #14 <VersionLabel> (320, 210) 308x36, scale:(1.00, 1.00), anchor:(0.50, 0.50)<154, 18>, tag:10002, LZ:0, GZ:0, [Label] }
========

座標値とか、ツリー構造とかが意図したものになっているかとかを確認できます。

これでもだいぶ見づらいですが、無いよりはマシかと…。

(もっと見やすい形でHTMLで出力するとか考えたこともありましたが、ひとまずそこまでは不要で今までやってこれています)

まとめ

Cocos2d-x のNodeツリーを確認する手段がないので、自作したコードを紹介しました。

エンジン本体側にこういう機能があれば便利なのですが、無いっぽいんですよね。

無いけど必要なモノは自作して使えるようにしておくと、開発しやすくなりますね。

コメント

タイトルとURLをコピーしました