taoru's memo

Objective-c,cocos2d,など開発についてのメモ(andoroidも少しだけ)

CCNodeのカテゴリで、他のCCNodeをゆったり追うSoftFollowというのを作ってみた その2 - 追記2/27

CCNodeのカテゴリで、他のCCNodeをゆったり追うSoftFollowというのを作ってみた - taoru's memo という記事を書いたんだけど、
kmVec2でベクトル計算するのがかっこく見えたので使って書きなおしてみた。

CCNode+SoftFollow.h

#import "CCNode.h"

@interface CCNode (SoftFollow)

/**
 *  toNode:   targetNode
 *  strength: 引きよせる強さ。最大値は"1"
 *  distance: targetNodeと一定の距離を保ちたい場合に指定
 */
- (void)followTo:(CCNode *)targetNode strength:(float)strength;
- (void)followTo:(CCNode *)targetNode strength:(float)strength distance:(float)distance;

- (void)stopFollow;

@end

CCNode+SoftFollow.m

#import "CCNode+SoftFollow.h"
#import "CCActionInstant.h"
#import "CCActionInterval.h"
#import "CGPointExtension.h"

#define ACTIONTAG_FOLLOWNODE 314159265351234

@implementation CCNode (SoftFollow)

- (void)followTo:(CCNode *)targetNode strength:(float)strength
{
    [self followTo:targetNode strength:strength distance:0.0f];
}

- (void)followTo:(CCNode *)targetNode strength:(float)strength distance:(float)distance
{
    CCCallBlockN *follow = [CCCallBlockN actionWithBlock:^(CCNode *node) {
        float distX = targetNode.position.x - node.position.x;
        float distY = targetNode.position.y - node.position.y;
        kmVec2 distVec = (kmVec2){distX,distY};
        if (kmVec2Length(&distVec) > distance) {            
            // 距離distanceのベクトル
            kmVec2 scaledVec;
            kmVec2Scale(&scaledVec, &distVec, distance/kmVec2Length(&distVec));
            
            // [targetNodeまでの距離 - distance]のベクトル
            kmVec2 subVec;
            kmVec2Subtract(&subVec, &distVec, &scaledVec);
            
            // 単位ベクトル
            kmVec2 unitVec;
            kmVec2Fill(&unitVec, subVec.x/kmVec2Length(&subVec), subVec.y/kmVec2Length(&subVec));
            
            // 単位ベクトル分だけ必ず進むようにする
            kmVec2 resultVec;
            kmVec2Add(&resultVec, &subVec, &unitVec);
            
            node.position = ccpAdd(self.position, ccp(resultVec.x*min(strength,1), resultVec.y*min(strength,1)));
        }
    }];
    
    CCSequence *seq         = [CCSequence actions:[CCDelayTime actionWithDuration:0.0f],follow, nil];
    CCRepeatForever *repeat = [CCRepeatForever actionWithAction:seq];
    repeat.tag = ACTIONTAG_FOLLOWNODE;
    
    [self runAction:repeat];
}

- (void)stopFollow
{
    [self stopActionByTag:ACTIONTAG_FOLLOWNODE];
}

@end

ここち良い感じに動く!
作法的にどうなのかってのは分からないけど、概ね満足している。
何か問題がありそうな箇所を発見した優しい方、ぜひコメントで教えてください。

追記 2013/02/25

kmVec2の関数を勉強したらもっともっと簡単に書けそうだった。単位ベクトルとか用意されてるんですね。
kmVec2でのベクトル計算 - taoru's memo

気が向いた時に書きなおしてみよう。

追記 2013/02/26

ということでkmVec2の動きも理解できたし書きなおす。
まず上記コードはblocks内でtargetNodeをcopyしちゃってるからまずいっぽい。

で、書きなおしたのが以下になります。

CCNode+SoftFollow.m

#import "CCNode+SoftFollow.h"
#import "CCActionInstant.h"
#import "CCActionInterval.h"
#import "CGPointExtension.h"

#define ACTIONTAG_FOLLOWNODE 314159265351234

@implementation CCNode (SoftFollow)

- (void)followTo:(CCNode *)targetNode strength:(float)strength
{
    [self followTo:targetNode strength:strength distance:0.0f];
}

- (void)followTo:(CCNode *)targetNode strength:(float)strength distance:(float)distance
{
    __block CCNode *target = targetNode;
    
    CCCallBlockN *follow = [CCCallBlockN actionWithBlock:^(CCNode *node) {
        float distX = target.position.x - node.position.x;
        float distY = target.position.y - node.position.y;
        kmVec2 nodeToTargetVec = (kmVec2){distX,distY};
        
        if (kmVec2Length(&nodeToTargetVec) > distance) {
            // ベクトルをdistanceの大きさまで縮める
            kmVec2 goalToTargetVec;
            kmVec2Scale(&goalToTargetVec, &nodeToTargetVec, distance/kmVec2Length(&nodeToTargetVec));
            
            // nodeからgoalまでのベクトル
            kmVec2 nodeToGoalVec;
            kmVec2Subtract(&nodeToGoalVec, &nodeToTargetVec, &goalToTargetVec);
            
            // strengthを乗算
            kmVec2 resultVec;
            kmVec2Scale(&resultVec, &nodeToGoalVec, min(strength,1));
            
            // 単位ベクトル分だけ必ず進むようにする
            if (kmVec2Length(&resultVec) < 1) {
                kmVec2Normalize(&resultVec, &resultVec);
            }
            
            node.position = ccpAdd(node.position, ccp(resultVec.x, resultVec.y));
        }
    }];
    
    CCSequence      *seq    = [CCSequence actions:[CCDelayTime actionWithDuration:0.0f],follow, nil];
    CCRepeatForever *repeat = [CCRepeatForever actionWithAction:seq];
    repeat.tag = ACTIONTAG_FOLLOWNODE;
    
    [self runAction:repeat];
}

- (void)stopFollow
{
    [self stopActionByTag:ACTIONTAG_FOLLOWNODE];
}

オリジナルカラーをドラッグで動かして、それを追わせている。楽しい。

動くには動く。理想通りに綺麗に動いてくれる。
しかしこのコードには問題があって、targetNodeがdeallocされるとクラッシュしてしまう。

if(targetNode == nil) とかも取れないし、色々試したけど解決できなかった。
__block修飾子をつけたオブジェクトがdeallocされているかどうかって、どうやって判定すればいいの…

普通に考えてCCAction継承して、CCFollowとかを真似して作ればいいんじゃないかと思った。
次回、CCActionのsubclass版を作ってみよう。

QLOOKアクセス解析