Cocos2d-xでLabelやTextの色を部分的に変更する

Cocos2d-xでラベルの色を途中で変更する場合以下のサイトが出てきます。

masahirosaito.hatenablog.com

Cocos2d-xのバージョンが3.17.1の今でも大抵このサイトの通りで問題ないですが、文字列中の任意の単語の色を一括で変更したいといった場合、元の文字列の文字数管理をしていないと変更がなかなか大変なので少しコードを書いてみました。

色を変えたい単語を指定するとコントロール中の一致する箇所の色が変更できるようにしたいと思います。

確認環境

確認環境以下の通りです。

  • Windows10
  • VisualStudio2017 15.9.14
  • Cocos2d-x 3.17.1

動かしたところ

以下の画像の赤枠内はcocos2d::ui::Text型ですが今回紹介するコードを使用すると以下のように色が変更できます。

f:id:Takachan:20190816203516p:plain

実装コード

cocos2d::ui::Text と cocos2d::Label の両方に対応するためテンプレートを使用します。

// 指定した単語の色を変更する
// T は cocos2d::ui::Text*, cocos2d::Label* のどちらかを指定
template<class T>
static void changeColor(T target, const std::string& word, cocos2d::Color4B color)
{
    // コントロールに設定されてる文字列から一致する位置を取得する
    int index = StringUtil::find_index(target->getString(), word);
    if (index == -1)
    {
        return;
    }

    // UTF-8形式で文字数を取得する
    int length = StringUtil::lenUtf8(word);

    for (int i = 0; i < length; i++)
    {
        cocos2d::Sprite* c = target->getLetter(i + index);
        if (c == nullptr)
        {
            continue;
        }

        c->setColor({ color.r, color.g, color.b });
        if (color.a != 0xFF)
        {
            c->setOpacity(color.a);
        }
    }
}

また上記のメソッド内で使用しているStringUtilの実装は以下の通りです。

std::stringの中に、UTF-8でエンコードされた(可変長の)バイナリ列が入ってると想定しているため、それらを(ある程度)正しく扱うためのコードです。単独で文字識別が難しいため外部ライブラリの"UTF-8CPP"を使用しています。

使用する際はコードをプロジェクトに追加してから呼び出します。

// 自作の汎用文字列操作クラス(一部抜粋)
class StringUtil
{
public:

    // strをUTF-8として文字の長さを取得します。
    static int lenUtf8(const std::string &str)
    {
        auto _pstr = str.c_str();

        int i = 0;
        int len = 0;

        while (_pstr[i] != '\0')
        {
            len++;
            i += lenByte(_pstr[i]);
        }

        return len;
    }

    // 指定した単語のインデックスを取得する
    static int find_index(const std::string& src, const std::string& word)
    {
        // 中身でUTF-8CPPとう外部ライブラリを使用してる
        // → see : https://github.com/nemtrif/utfcpp
        std::vector<unsigned short> src16;
        utf8::utf8to16(src.begin(), src.end(), back_inserter(src16));

        std::vector<unsigned short> word16;
        utf8::utf8to16(word.begin(), word.end(), back_inserter(word16));

        for (size_t i = 0; i < src16.size(); i++)
        {
            if (src16[i] != word16[0])
            {
                continue;
            }
            if (contains_str(src16, word16, i))
            {
                return (int)i;
            }
        }
        return -1;
    }

private:

    // UTF-8の文字列長をとるためのヘルパーメソッド
    //   → 本当はUTF-8 CPPに置き換えたい
    static int lenByte(unsigned char c)
    {
        if (/*(c >= 0x00) && */(c <= 0x7f))
        {
            return 1;
        }
        else if ((c >= 0xc2) && (c <= 0xdf))
        {
            return 2;
        }
        else if ((c >= 0xe0) && (c <= 0xef))
        {
            return 3;
        }
        else if ((c >= 0xf0) && (c <= 0xf7))
        {
            return 4;
        }
        else if ((c >= 0xf8) && (c <= 0xfb))
        {
            return 5;
        }
        else if ((c >= 0xfc) && (c <= 0xfd))
        {
            return 6;
        }

        return 0;
    }
}

使い方

上記コードの使い方は以下の通りです。

まず対象を用意します。

// まず対象のコントロールに文字を設定した状態を作成する。
cocos2d::Label* label = cocos2d::Label::create("攻撃力をアップする。", "Sample.ttf", 24);

次に色を変えたい文字を指定してメソッドを呼び出します。

// 色を変えたい単語
std::string word = "アップする。";
// 変えたい色の指定
cocos2d::Color4B color = cocos2d::Color4B::RED;

// labelと一緒にメソッドに放り込むと最初に一致しした単語の色が変更される
LabelUtil::changeColor(label, word, color);

こうすることで冒頭の画像のような色の色の変更がUTF-8文字列でも行えます。

繰り返し同じ文字が現れる場合、最初に一致したものだけを対象に色を変更します。気に入らない場合コードを変更してください。

Windows以外の環境でどうなるのか確認してないので動かなかかった場合すいませんw

簡単ですが以上です。