夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【cocos2d】タッチを処理するクラスを作っていたらtouchDisptcherもどきになった

CCSpriteのオブジェクトでタッチを処理しようとしたら結局cocos2dのtouchDispatcherみたいなものを作っていたという話。
touchDispatcherの事は今日まで使った事がなく、よく理解していなかったのですが、「もしかしたら同じようなものなのでは」と思い調べてみるとやっぱり同じでした。

まあ、全く同じではないですが、たぶん同じような作りなので、使うには同じ注意が必要になります。
touchDispatcherを使うときの注意点とは、

init, deallocメソッドではなくonEnter, onExitメソッドでselfをタッチディスパッチャーに登録・登録解除すること。

です。

initは必ずしも成功するとは限らないので、initが完了し、オブジェクトが確実に初期化されたタイミングのonExitで登録するのが正しいようです。
私はこれを知らずに今までinit内でやっていました。

deallocでselfを登録解除するのがマズいのは確実でオブジェクトが解放されなくなります。
これは以前ハマりました。
removeChildなどでオブジェクトを親ノードから外したとき、deallocメソッドが実行されてオブジェクトが消え去るためにはそのオブジェクトのリテインカウントが0になっている必要があります。
タッチディスパッチャーの方ではそのオブジェクトをアレイに入れて保持しているので、dealloc内で登録解除しようとしても永遠にdeallocメソッドは呼ばれないため登録解除されません。
一方、親ノードから外れたときに確実に呼ばれるonExitで登録解除してあげれば、そのオブジェクトのリテインカウントが0になり、deallocが呼ばれ、オブジェクトは解放されます。
ARCだとリテインカウントを意識しないのでハマりやすいかもしれません。私はARCしか使った事がないので想像ですが。

onEnter, onExitを使うときにも忘れやすい注意点があります。

onEnter, onExitでは[super onEnter], [super onExit]を忘れずに実行すること。

これを忘れると原因不明のクラッシュが発生することがあります。デバッグが大変なので、絶対に忘れないようにご注意。

せっかく作ったので、これからも自家製タッチディスパッチャーを使ってあげようと思います。
自分なりに拡張できるのがメリットですね。私の自作クラスは、タッチ中かどうかを知る事ができるtouchingプロパティとイベントを受け取っていなくてもタッチ座標を知る事ができるlocationプロパティを備えています。これらのプロパティはタッチイベントを受け取る登録をしていないオブジェクトからも参照可能です。また、タッチ位置はcocos2dの座標に変換された状態で受け取れます。便利!

インターフェース

@interface TouchLayer : CCLayer {
    
}

+ (TouchLayer *)sharedTouch;
- (void)registerRecipient:(id)receipient;
- (void)unregisterRecipient:(id)recipient;

@property (nonatomic, readonly) BOOL touching;
@property (nonatomic) CGPoint location;

@end

@protocol TouchNotifying <NSObject>

@optional
- (void)touchNotificationDown:(CGPoint)location;
- (void)touchNotificationMove:(CGPoint)location;
- (void)touchNotificationUp:(CGPoint)location;

@end

実装

@implementation TouchLayer
{
    BOOL _touching;
    CGPoint _location;
    NSMutableArray* _recipients;
}

+ (TouchLayer *)sharedTouch
{
    static TouchLayer* sharedInstance = nil;
    if (sharedInstance == nil) {
        sharedInstance = [[self alloc] init];
    }
    return sharedInstance;
}

- (id)init
{
    if ((self = [super init])) {
        _touching = NO;
        CGSize winSize = [CCDirector sharedDirector].winSize;
        _location = ccp(winSize.width/2, winSize.height/4);
        _recipients = [NSMutableArray array];
        self.isTouchEnabled = YES;
    }
    return self;
}

- (void)registerRecipient:(id)receipient
{
    [_recipients addObject:receipient];
}

- (void)unregisterRecipient:(id)recipient
{
    [_recipients removeObjectIdenticalTo:recipient];
}

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    _touching = YES;
    _location = [self convertTouchlocation:touches];
    for (id recipient in _recipients) {
        if ([recipient respondsToSelector:@selector(touchNotificationDown:)]) { // 実装チェック
            [recipient touchNotificationDown:_location];
        }
    }
}

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    _touching = YES;
    _location = [self convertTouchlocation:touches];
    for (id recipient in _recipients) {
        if ([recipient respondsToSelector:@selector(touchNotificationMove:)]) {// 実装チェック
            [recipient touchNotificationMove:_location];
        }
    }
}

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    _touching = NO;
    _location = [self convertTouchlocation:touches];
    for (id recipient in _recipients) {
        if ([recipient respondsToSelector:@selector(touchNotificationUp:)]) {// 実装チェック
            [recipient touchNotificationUp:_location];
        }
    }
}

//タッチ座標をcocos2d座標に変換
- (CGPoint)convertTouchlocation:(NSSet *)touches
{
    UITouch* touch = [touches anyObject];
    CGPoint location = [touch locationInView:touch.view];
    location = [[CCDirector sharedDirector] convertToGL:location];
    return location;
}


@end