myb design

blog

CCMenuでラベル付きボタン、長押しボタン #cocos2d_2011_adcal

@yoichinejiさん主催のcocos2d Advent Calendar 2011に乗っかってみたので、久しぶりにブログを書きます。
クリスマスまでの毎日、cocos2dに関するTipsを1日1つブログにするというイベントです。
前日の記事: CCMotionStreakを使えばライントレースアプリも簡単 (@hkato193さん)

cocos2dを使っていてよくやることの、本当にちょっとしたTipsなので、すでにご存知の方はご容赦を...。
サンプルコードはこちらからダウンロードできます。


ラベル付きボタン

cocos2dでボタンを実装するときにはCCMenuを使います。
押されていない状態(normal)、押された状態(selected)、押せない状態(disabled)のそれぞれの画像(スプライト)を設定するだけで簡単にボタンを実装できます。

CCSprite *normalSprite = [CCSprite spriteWithFile:@"button_normal.png"];
CCSprite *selectedSprite = [CCSprite spriteWithFile:@"button_selected.png"];
CCSprite *disabledSprite = [CCSprite spriteWithFile:@"button_disabled.png"];

CCMenuItemSprite *menuItem = [CCMenuItemSprite itemFromNormalSprite:normalSprite 
                                                     selectedSprite:selectedSprite 
                                                     disabledSprite:disabledSprite 
                                                             target:self 
                                                           selector:@selector(didPressButton:)];
CCMenu *menu = [CCMenu menuWithItems:menuItem, nil];

selected、disabledの画像を別に作らなくても、スプライトを半透明にすることで見た目を変えることはよくやります。

CCSprite *normalSprite = [CCSprite spriteWithFile:@"button.png"];
CCSprite *selectedSprite = [CCSprite spriteWithFile:@"button.png"];
selectedSprite.opacity = 0x7f; // 半透明にする
CCSprite *disabledSprite = [CCSprite spriteWithFile:@"button.png"];
disabledSprite.opacity = 0x7f; // 半透明にする

CCMenuItemSprite *menuItem = [CCMenuItemSprite itemFromNormalSprite:normalSprite 
                                                     selectedSprite:selectedSprite 
                                                     disabledSprite:disabledSprite 
                                                             target:self 
                                                           selector:@selector(didPressButton:)];
CCMenu *menu = [CCMenu menuWithItems:menuItem, nil];

また、メニューなど形は同じで書かれている文字が違うだけのボタンをいくつも用意する、ということもよくあります。
そんなときはCCMenuItemの子にCCLabelを追加します。

CCLabelTTF *label = [CCLabelTTF labelWithString:@"PUSH!" 
                                       fontName:@"Arial" fontSize:20.0f];
[menuItem addChild:label];

さてここで問題なのですが、半透明にしたスプライトをselected、disabledの画像に設定しても文字の方は半透明にならないので、ちょっと浮いてしまいます。

ccadcal01.png (左:通常の状態、右:ボタンが押された状態)

そこで、CCMenuItemSpriteを拡張して、ボタンのスプライトが切り替わるタイミングで子要素にもスプライトの透明度が反映されるようにしてやります。

@implementation ExMenuItemSprite

- (void)adaptChildrenAppearanceTo:(CCNode  *)target
{
    for(CCNode *node in children_)
    {
        if([node isEqual:normalImage_] || [node isEqual:selectedImage_] || [node isEqual:disabledImage_])
            continue;
        
        if(![node conformsToProtocol:@protocol(CCRGBAProtocol)])
            continue;
        
        [(CCNode  *)node setOpacity:[target opacity]];
    }
}

- (void)selected
{
    [super selected];
    [self adaptChildrenAppearanceTo:selectedImage_];
}

- (void)unselected
{
    [super unselected];
    [self adaptChildrenAppearanceTo:normalImage_];
}

- (void)setIsEnabled:(BOOL)enabled
{
    [super setIsEnabled:enabled];
    
    if(enabled)
    {
        if(self.isSelected)
            [self adaptChildrenAppearanceTo:selectedImage_];
        else
            [self adaptChildrenAppearanceTo:normalImage_];
    }
    else
    {
        if(self.disabledImage)
            [self adaptChildrenAppearanceTo:disabledImage_];
        else
            [self adaptChildrenAppearanceTo:normalImage_];
    }
}

@end

adaptChildrenAppearanceTo:というメソッドの引数に画像を渡すと、その画像と同じ透明度を子要素に設定していきます。
あとはボタンが押されたとき(selected)、離されたとき(unselected)、使用可・不可状態が変わったとき(setIsEnabled:)に、状況に合った画像を引数にしてadaptChildrenAppearanceTo:を呼べばいい、という寸法です。

ccadcal02.png これでバッチリ!


長押しボタン

指をなるべく動かさず素早くボタンの機能を切り替える、というようなことが必要なシチュエーションがあったので、長押しボタンを実装してみました。
CCMenuではタッチイベントは個々のボタンであるCCMenuItemではなく、CCMenuで扱っています。
そこでCCMenuを拡張して、タッチの開始時にタイマーを仕込み、タッチが終了する前にタイマーが作動したら長押しされたことにするようにしました。

@implementation ExMenu

- (id)init
{
    if((self = [super init]))
    {
        longPressTimer = nil;
        longPressState = kExMenuLongPressStateNone;
    }
    return self;
}

- (void)dealloc
{
    if(longPressTimer != nil)
        [longPressTimer invalidate];
    
    [super dealloc];
}

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
    BOOL result = [super ccTouchBegan:touch withEvent:event];
    if(result)
    {
        if(longPressTimer != nil)
            [longPressTimer invalidate];
        
        longPressTimer = [NSTimer scheduledTimerWithTimeInterval:kExMenuLongPressInterval 
                                                          target:self selector:@selector(didFireLongPressTimer:) 
                                                        userInfo:nil repeats:NO];
        longPressState = kExMenuLongPressStateBegan;
    }
    else
    {
        longPressState = kExMenuLongPressStateNone;
    }
    
    return result;
}

-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
    if(longPressState == kExMenuLongPressStateFired)
    {
        // didFireLongPressTimerでccTouchEndedを呼ぶのでここでは呼ばない
    }
    else
    {
        [super ccTouchMoved:touch withEvent:event];
    }
}

- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
    if(longPressTimer != nil)
    {
        [longPressTimer invalidate];
        longPressTimer = nil;
    }
    
    if(longPressState == kExMenuLongPressStateFired)
    {
        // didFireLongPressTimerでccTouchEndedを呼ぶのでここでは呼ばない
    }
    else
    {
        longPressState = kExMenuLongPressStateNone;
        [super ccTouchEnded:touch withEvent:event];
    }
}

- (void)ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
    if(longPressTimer != nil)
    {
        [longPressTimer invalidate];
        longPressTimer = nil;
    }
    
    longPressState = kExMenuLongPressStateNone;
    [super ccTouchCancelled:touch withEvent:event];
}

- (void)didFireLongPressTimer:(NSTimer *)timer
{
    longPressTimer = nil;
    
    if(longPressState == kExMenuLongPressStateBegan)
    {
        longPressState = kExMenuLongPressStateFired;
        [super ccTouchEnded:nil withEvent:nil];
    }
    else
    {
        longPressState = kExMenuLongPressStateNone;
    }
}

- (BOOL)isLongPress
{
    return (longPressState == kExMenuLongPressStateFired);
}

@end

ccTouchBegan:withEvent:でNSTimerのインスタンスを作って、一定時間後にdidFireLongPressTimer:が呼ばれるようにタイマーを始動します。
didFireLongPressTimer:ではlongPressStateに長押しが成立したという状態を記憶していて、superのccTouchEnded:withEvent:を呼びます。
すると、押されたボタンに設定されているセレクタが通常通り呼ばれるので、ボタンの親であるExMenuのisLongPressメソッドを参照して処理を分岐させることができる、という仕組みです。

- (void)didPressButton:(CCMenuItem *)sender
{
     ExMenu *menu = (ExMenu *)sender.parent;
     if([menu isLongPress])
     {
          // 長押しされた!
     }
}

サンプルでは、CCMenuItemToggleを使ってボタンを長押しするともうひとつのボタンとトグルで切り替わるようにしてみました。

ccadcal03.png


さて、cocos2d Advent Calendar 2011もここから2周目に入りそうです。
まだまだ参加枠はあるようなので小粋なTipsをお持ちの方、ぜひ参加してみませんか?

次の記事: cocos2dキャラクタークラス設計の考察 (@ajinotatakiさん)

Visual Studio 2010 ProfessionalでWindows Phone 7の開発。

もろもろ事情があってWindows Phone 7の開発環境を整えていますが、その際にちょっとはまったことをメモ。

元々MacにParallelsで入れたWindows 7上にVisual Studio 2010 Professionalを入れていたので、これで開発すればいいかー、と思っていたら、WP7のSDKはVS 2010 Express、Expression Blendなどがオール・イン・ワン・パッケージになっていて、普通にインストールしただけではテンプレートがProfessionalの方には適用されませんでした。

メニューは英語だけどまあいいかと、HelloWorldを作るところまではExpressを使っていましたが、本格的に開発を始めようとしたところでExpressではインデントにタブコードを挿入できないらしいことに気がつき、以下の記事を参考にProfessionalの方にWP7のテンプレートを入れました。

Phone 7 に届くまで #52:VS2010 日本語環境にWP7のテンプレートを入れる - 高橋 忍のブログ - Site Home - MSDN Blogs

2.と3.の間にシステムを再起動しないと4.がうまく実行されませんでしたが、これでタブでインデントもできて、メニューも日本語に。

ちなみに、デスクトップのSilverlightと同じように開発できるらしいので、こちらの記事が参考になるかもしれませんよ!

有料iPhone/iPadアプリの3月分売り上げを義援金として寄付しました。

Appleからの有料iPhone/iPadアプリの3月分売り上げ入金額の明細が届いたので、振り込みに行ってきました。

IMG_1609.JPG

売り上げは31,330円、手数料等が4,000円で差し引き27,330円の入金だったので、27,330円を日本赤十字社に寄付しました。微々たる金額ですが、復興の一助になればと思います。
今後もアプリを通じて復興の支援をしていきたいと思います。

banner.jpg

株式会社フォーユーさんの制作されたバナーを使わせていただいています。ありがとうございます!

東北地方太平洋沖地震に際して、有料iPhone/iPadアプリの3月分売り上げ全額を寄付することにしました。

東北地方太平洋沖地震で被災された方々に、心からお見舞いを申し上げます。

微力ながら復興の一助になればと思い、現在リリースしている有料のiPhone/iPadアプリの3月分売り上げを、手数料を引いてAppleから振り込まれた金額全額を義援金として寄付することにしました。(寄付先は今のところ、日本赤十字社を予定しています)
3月分の売り上げが確定して振り込まれるのは、4月末ごろの見込みです。そのころにまた結果を報告したいと思います。

対象のアプリはこちらです:

se_icon_75.jpg
Spam Eater

メールサーバー上でスパムメールを除去するツール。(*POP3のみ対応)

tt_icon_75.jpg
The Twins

iPad専用!上下のバーで打ち返すブロック崩し。

こちらの記事で触れられていますが、寄付"だけ"を目的にアプリを購入することは効率がよろしくないです。アプリが気になった方はぜひ購入してください。

その他にも、現在位置をメールなどで知らせることができるアプリ、"Signal Fire"も無料でリリースしています。こちらもお役に立てれば。

sf_icon_75.jpg
Signal Fire

banner.jpg

株式会社フォーユーさんの制作されたバナーを使わせていただいています。ありがとうございます!

2011-03-16 バナーのリンク先を変更。バナーの配布元を追加。

6th gen iPod nanoでNike+ その2。

昨日代用品で手首装着して調子良かったので、スポーツショップにリストバンドを買いに行ってきました。

とはいえ、やはり売っているのはよくあるタオル地の厚みのあるものばかりで、nanoをクリップするにはちょっと不安な感じ。...ふと見るとワックル・リストというシリコン製のリストバンドが売っていたので、買ってみました。

IMG_0993.JPG

こんなの。2,100円とお高いですが、ゲルマニウム付きなので血行がよくなりそう。

IMG_0995.JPG

早速装着!...どう見ても髪留めのゴムです。

しかし、シリコン製なので汗をかいても滑らず、上の写真のような平たい部分をクリップしているので安定感はバッチリで、走行中も外れそうになることはありませんでした。
これでより快適にNike+を活用できそうです。