2011/12/02

coco2d Advent Calendar 2011 2日目: cocos2dでオリジナルフォントクラスを作ろうぜ!

@yoichinejiさんから連絡があって、なんでもcocos2dのAdvent Calendarをやるとのこと!なんだか楽しそうなので、1も2も無く参加してみましたー。というわけで、以前さいたまiPhone勉強会で発表した内容と同内容になりますが、詳しい解説を入れて再録させてもらいました。ネタはフォントクラスについて。一応中級編とさせてもらいます。内容的にはcocos2dを踏まえていますが、基本的なビットマップフォントの考え方みたいな感じになりますでしょうか。僕が勝手にこうだと思い込んでいるものなので、いろいろ間違ってたり、効率悪い点などご指摘いただけると幸いです。
 cocos2d advent calendarのatnd:http://atnd.org/events/22814
 以下本文ですー。


<中級編>cocos2dでオリジナルフォントクラスを作ろうぜ!

●フォントについてあれこれ

 ゲームではよく変わったフォントで文字が表示されることが多いですよね。特に昔のゲームなんかはゲームの世界観を表す上でかなり重要なポジションを担っていたと思います。iOSデバイスが出てからはそのスペックに適したゲームとしてレトロ風味のものも多数出ていますが、やはり時代性を表す上でドットで作られたフォントは美味しい要素です。
 以下の図は拙作のアプリからですが、上図が等幅フォント、下図はプロポーショナルフォントで日本語を取り扱っています。



 cocos2dにはTrueTypeフォントを直接展開してテクスチャ化し使用する文字スプライトクラス(CCLabelTTF)や、ツール(Hiero Bitmap Font ToolやBMFontなど)で事前にテクスチャ化して準備しておき、それを切り出して使用するためのフォントクラス(CCLabelAtlas, CCLabelBMFont)が元々用意されていますが、今のゲームのテイストの場合は良いですが、レトロ風味ということでいうと向いていません。
 なぜなら、公開されているツールを使用する場合だと、テクスチャにフォントがアンチエイリアス有りで展開されてしまったりするからですね。まあ、テクスチャの作り方にも依るので一概に向いていないとは言えませんが…。なお、上記の上画像の下の方に表示されている日本語の文章はCCLabelTTFを使用したもので、それ以外の文字はテクスチャからの切り出しです。
 あと、日本語のことを考えるともっと向いてない要素があります。基本的にキャラクターコードに従ってテクスチャへ展開するので、無駄な領域が多く必要になってしまうわけです。その点、自前でクラスから作る場合は、トリッキーなことも含めて自分の管理下にあるわけで、仕様から好きに決められて良いというところがあります。
 正攻法でいけば、cocos2dのフォントクラスに合うフォントファイルとテクスチャを用意すれば良いですが、それは別の機会にとっておきましょう。正直言うと調べて対応するより自分で作ってしまったほうが早いってのもあります。あと、どうせ作るならcocos2dのフォントプロトコル(CCLabelProtocol)に沿った作りにすればよかったのですが、それに気づいたのは作った後だったので、すみません。以下の例はかなり変則的です。

 と、そんな前提がありまして、自前でフォントクラスを用意してみてはどうですか?という例のご紹介です。
 サンプルコードはこちらからどうぞ:http://xionchannel.no-ip.org/advent_calendar_cocos2d_day2.zip


●等幅フォントクラスを作ってみよう!

 まずは、ドットを打って文字をデザインしていきます。要領はドット絵と同じですね。作りを簡単にするには等幅フォントがいいので、その場合は、格子状に文字を配置しながらデザインしていきます。

 フォント用のテクスチャができたら、それを切り出して使用するということになります。フォントクラスを簡単に設計するためには、文字コード順に文字が並んでいると良いです。途中必要ない部分があれば省いてしまっても良いです。省かれた部分のコードの取り扱いはフォントクラスの方で対処すれば良いです。
//指定のキャラクターのCGRectを返す
- (CGRect)getRectWithASCII:(char)ascii {
  char c;
  if (ascii>='0' && ascii<='9') {
    c = ascii-'0';
  }
  else if (ascii>='A' && ascii<='Z') {
    c = ascii-'A'+10;
  }
  else if (ascii=='!') {  //!
    c = ('Z'-'A'+10)+1;
  }
  <中略>
  else {
    c = ('Z'-'A'+10)+8;  //スペースに置き換える
  }
  return CGRectMake(8*(c%8), 8*(c/8), 8, 8);
}

 こんな風に文字コードを元に切り出しCGRectを生成して、CCSpriteを作成するようにします。あとはスプライトを並べていけば文字列の表示が達成できるというものです。等幅フォントの肝はこんな感じになります。簡単ですね。切り出しはもっとスマートにやる方法もありそうですね。汚いソースですみません。


●プロポーショナルフォントクラスを作ってみよう!

 レトロ風味から少し高級な印象になりますが、プロポーショナルフォントは見た目に美しいので、それをつくろうとする場合はどうしましょうか。はい。それぞれの文字の幅が異なる可能性があるので、その情報をどこかに持たなければなりません。

 まずはフォント用のテクスチャを用意してみましょう。横書き前提とすると、文字の高さは変化せず幅のみ変化することになります。たとえば以下のような感じでテクスチャを作ることになります。なお、縦書き用プロポーショナルフォントとする場合は、幅が変化せず、高さが変化するような作りが良いでしょう。もちろん高さも幅も変化するように作ることも可能ですが、それについては自分で効率良い配置を考えてみてはどうでしょうか。

 さて、テクスチャは用意できたとして、切り出しサイズをまとめる必要があります。どのような形式でも良いですが、iOSっぽくplistにいれてみた場合、以下のような感じにできます。データの置き方は自分でわかれば良いので、ほかの並びでも良いと思います。

 このデータを元にフォントクラス側でテクスチャを切り出して文字を並べていくことになります。文字コードとの対応は等幅の場合と同じようにしても良いです。以下の例の場合、カタカナも扱いたかったため文字コードを意識しない構造にしました。切り出し情報の中に対応する文字を情報として入れておき、ソース文字列に合致する文字があった場合に切り出して文字を配置するという流れです。処理は文字コードを使用したものよりも重いでしょう。

 以下は切り出しの例ですが、先ほどのplistをcharactersというNSDictionaryオブジェクトに読み込んでおきます。キーに対応する文字情報を入れてあるので、キャラクターが入ったNSStringを与えてやれば切り出しサイズが得られるという寸法です。
//指定のキャラクターのCGRectを返す
- (CGRect)getRectWithString:(NSString*)s {
  NSArray *rect = [characters objectForKey:s];
  if (rect) {
    float x = [[rect objectAtIndex:0] intValue];
    float y = [[rect objectAtIndex:1] intValue];
    float width = [[rect objectAtIndex:2] intValue];
    float height = [[rect objectAtIndex:3] intValue];
    return CGRectMake(x, y, width, height);
  }
  else {
    return CGRectZero;
  }
}

 以上のようにしてオリジナルフォントクラスの基本的な部分が出来ました。切り出し用plistを用意するのは面倒ですけど、その分、切り出し自体は簡単ですね。あとは、サイズに応じて並べるときにずらすドット数もあわせてやれば、プロポーショナルな文字を並べることができますね。

 なお、一番最初の例では、カタカナのみ日本語対応していますが、濁点や半濁点を別の文字としてまとめることでキャラクター数を減らしています。なので、表示する際に、濁点や半濁点がある場合は、内部で分割して2文字分として処理して表示するように調整を入れています。昔のファミコンゲームでよく使用している手法ですね。そのために、濁点・半濁点の対応表を作っておいて照らしあわせて処理するようにしています。
//文字スプライトの初期化(private)
- (CCSprite*) __makeCharacterSpriteFrame:(CCSpriteFrame*)f
                string:(NSString*)c
                position:(CGPoint*)pos
                isShadow:(BOOL)isShadow
{
  CCSprite *s = [CCSprite spriteWithSpriteFrame:f];
  [s.texture setAliasTexParameters];
  s.anchorPoint = ccp(0,0);
  s.scale = 2.0f;
  if (![c compare:@"゛"] || ![c compare:@"゜"]) { //濁点・半濁点
    s.position = ccp(pos->x -4*2, pos->y +8*2);
  }
  else {
    s.position = *pos;
    if (!isShadow) pos->x += s.contentSize.width*2;
  }
  return s;
}

//カタカナの濁点等を分解する(private)
- (NSString*) __correctString:(NSString*)string {
  NSMutableString *ret = [NSMutableString string];
  for (int i=0; i < string.length; i++) {
    NSString *c = [string substringWithRange:
            NSMakeRange(i, 1)];
    NSString *replace;
    if ((replace = [self.dakuten objectForKey:c])) {
      //濁点の差し替えがあれば差し替える
      [ret appendString:replace];
    }
    else {
      [ret appendString:c];
    }
  }
  return ret;
}


●フォントクラス応用編

 さあ、ここからがおいしいところですよ。おまけですけど。ゲームに於いて文字で表示されたメニューをタップしたり、文字の内容を変更したり、文字の色が変わったり、一文を与えただけで途中改行されたりというようなよく使う機能があると思います。そのあたりをメソッドやマクロとして組み込んでおくとゲームに使う場合にかなり便利になります。下記はそんな例です。

 ここから先は僕も作ってはいませんが、こんなネタもあったらいいんではというものをご紹介。文字がバラバラになって飛んでいくとか、文字が一文字づつカードがめくれるように回転するとか、ピカっと光るとか、シャクトリムシのように伸び縮みするとか、どれもcocos2dの基本機能を使ってアニメーション登録すればいけますが、一文字一文字バラバラに制御する必要があるので、フォントクラスの外側から制御するよりも、フォントクラス内でアニメーションを与えるような作りの方がやりやすいと思います。みなさんもそんな効果をつけてみてはいかがですか?ひと味違った文字表現になって楽しいですよ!

 そんなこんなで、フォントについてはここまで!お付き合いありがとうございましたー。

 3日目は、@ZuQ9Nnさんです。「cocos2d Advent Calendar 2011 -3日目 波紋のアニメーションやてみた。オッパイぷるるんもあるんだよ!

0 件のコメント:

コメントを投稿