0

完全な円または部分的な円のいずれかの円形パスで CCNode を移動するために使用するユーティリティ関数があります。

この関数は非常にうまく機能しますが、CCNode が継続的にパスをたどるようにしたい場合は、渡された Block を介して同じ関数を呼び出します (一種の再帰的ですが、実際にはそうではありません)。

私が見つけた問題は、関数が内部でブロックを使用するため、アクションが実行されている CCNode が保持され、stopAllActions または removeFromParentAndCleanup:YES を呼び出した後でも、CCNode がクリーンアップされ、メモリに残り、割り当て解除されません。これは、ノードが CCNode として表示されていなくても、パフォーマンスに影響を与えているように見えます。

CCNode を移動する関数は次のとおりです。

@interface CocosUtil : NSObject {

}

typedef void (^NodeCompletionBlock)(CCNode *sprite);

+ (void) moveOperand:(CCNode*)operand throughCircleWithCentre:(CGPoint)centreOfElipse
     startingDegrees:(float)startingDegrees
     endingAtDegrees:(float)endingDegrees
      startingRadius:(float)startingRadius
        endingRadius:(float)endingRadius
 withInitialDuration:(ccTime)initialDuration
    withMainDuration:(ccTime)duration
           clockwise:(BOOL)clockwise
     completionBlock:(NodeCompletionBlock)handler;

@end


@implementation CocosUtil

+ (float) angleFromDegrees:(float)deg {
    return fmodf((450.0 - deg), 360.0);
}

// Calculates the angle from one point to another, in radians.
//
+ (float) angleFromPoint:(CGPoint)from toPoint:(CGPoint)to {
    CGPoint pnormal = ccpSub(to, from);
    float radians = atan2f(pnormal.x, pnormal.y);

    return radians;
}

+ (CGPoint) pointOnCircleWithCentre:(CGPoint)centerPt andRadius:(float)radius atDegrees:(float)degrees {
    float x = radius + cos (CC_DEGREES_TO_RADIANS([self angleFromDegrees:degrees])) * radius;
    float y = radius + sin (CC_DEGREES_TO_RADIANS([self angleFromDegrees:degrees])) * radius;
    return ccpAdd(centerPt, ccpSub(CGPointMake(x, y), CGPointMake(radius, radius)));
}

+ (void) moveOperand:(CCNode*)operand throughCircleWithCentre:(CGPoint)centreOfElipse
     startingDegrees:(float)startingDegrees
     endingAtDegrees:(float)endingDegrees
      startingRadius:(float)startingRadius
        endingRadius:(float)endingRadius
 withInitialDuration:(ccTime)initialDuration
    withMainDuration:(ccTime)duration
           clockwise:(BOOL)clockwise
     completionBlock:(NodeCompletionBlock)handler {
    float range;

    if (clockwise == YES) {
        if (endingDegrees <= startingDegrees) {
            range = (360.0 + endingDegrees) - startingDegrees;
        } else {
            range = endingDegrees - startingDegrees;
        }
    } else {
        if (endingDegrees >= startingDegrees) {
            range = (360.0 + startingDegrees) - endingDegrees;
        } else {
            range = startingDegrees - endingDegrees;
        }
    }

    __block float degrees = startingDegrees;
    __block float radius = startingRadius;

    const float incrementAngle = 10.0;

    float intervals = (range / incrementAngle) - 1;

    ccTime interval = duration / intervals;
    float radiusStep = (endingRadius - startingRadius) / intervals;

    if (clockwise == YES) {
        degrees += incrementAngle;
    } else {
        degrees -= incrementAngle;
    }

    radius += radiusStep;

    __block void (^moveToNextPoint)();

    moveToNextPoint = [^(){
        if (fabsf(degrees - endingDegrees) < 1.0) {
            [operand runAction:[CCSequence actions:
                                [CCEaseBounceOut actionWithAction:
                                 [CCMoveTo actionWithDuration:interval position:[self pointOnCircleWithCentre:centreOfElipse andRadius:radius atDegrees:degrees]]],
                                [CCCallBlock actionWithBlock:
                                 ^{
                                     if (handler != nil) {
                                         handler(operand);
                                     }
                                 }],
                                nil]];
        } else {
            [operand runAction:[CCSequence actions:
                                [CCMoveTo actionWithDuration:interval position:[self pointOnCircleWithCentre:centreOfElipse andRadius:radius atDegrees:degrees]],
                                [CCCallBlock actionWithBlock:moveToNextPoint],
                                nil]];

            if (clockwise == YES) {
                degrees += incrementAngle;

                if (degrees > 360.0) {
                    degrees = degrees - 360.0;
                }
            } else {
                degrees -= incrementAngle;

                if (degrees < 0.0) {
                    degrees = degrees + 360.0;
                }
            }

            radius += radiusStep;
        }
    } copy];

    [operand runAction:[CCSequence actions:
                        [CCMoveTo actionWithDuration:initialDuration position:[self pointOnCircleWithCentre:centreOfElipse andRadius:startingRadius atDegrees:startingDegrees]],
                        [CCCallBlock actionWithBlock:moveToNextPoint],
                        nil]];

}

@end

ノードが移動する円弧が 10 度のステップに分割されていることに注意してください。これは、CCActionInterval サブクラスを記述せずに循環モーションを取得するために行われますが、ブロックまたはセレクターを使用してモーションを完了まで実行し続けることを意味します。

ここで、CCNode が完全な円を継続的に移動するようにするために、次のようにこの関数を呼び出します。

- (void) moveLayer:(CCNode*)layer
startingDegrees:(float)startingDegrees
endingAtDegrees:(float)endingDegrees
startingRadius:(float)startingRadius
endingRadius:(float)endingRadius
withInitialDuration:(ccTime)initialDuration
withMainDuration:(ccTime)duration
clockwise:(BOOL)clockwise {
    [CocosUtil moveOperand:layer throughCircleWithCentre:CGPointMake(240.0, 160.0) startingDegrees:startingDegrees endingAtDegrees:endingDegrees startingRadius:startingRadius endingRadius:endingRadius withInitialDuration:initialDuration withMainDuration:duration clockwise:clockwise completionBlock:^(CCNode *sprite) {
        [self moveLayer:layer startingDegrees:startingDegrees endingAtDegrees:endingDegrees startingRadius:startingRadius endingRadius:endingRadius withInitialDuration:initialDuration withMainDuration:duration clockwise:clockwise];
    }];
}

ブロックをまったく渡さないなど、いくつかの異なることを試しましたが、関数をまったく使用しないことを除いて、保持を妨げるものは何もありません。

私が知る限り、XCode doco を読むと、次のようになります。

「メソッドの実装内でブロックを使用する場合、オブジェクト インスタンス変数のメモリ管理のルールはより微妙になります。

参照によってインスタンス変数にアクセスする場合、self は保持されます。インスタンス変数に値でアクセスすると、変数は保持されます。」

したがって、これは、関数内でブロックを使用することで、隠れた保持が発生していることを示しています。

後で stopAllActions を呼び出しても、リリースはトリガーされません。

これが機能する唯一の方法は、ノードの cleanup() メッセージに[self release].

保持を行うコードから分離されているため、これは好きではありません。

私が持っていた新しい考えの 1 つは、何らかの方法で新しい CCActionInterval サブクラスとして書き直すことですが、それで問題が解決するかどうかはまだわかりません。

助言がありますか?

4

2 に答える 2

1

わかった。@LearnCocos2D からのアドバイスを受けて、ここでやろうと思っていたことを実行することが、それを望むかもしれない他の人のための私の解決策です。

基本的に、全体を比較的単純な CCActionInterval サブクラスとして書き直しました。

関連するブロックがまったくないため、非表示の保持はありません。はるかにクリーンで、はるかにエレガントです (私はそう思います)。

インターフェース:

#import "cocos2d.h"

@interface CCMoveThroughArc : CCActionInterval

/** creates the action */
+(id) actionWithDuration:(ccTime)duration
                  centre:(CGPoint)centreOfCircle
       startingAtDegrees:(float)startingDegrees
         endingAtDegrees:(float)endingDegrees
        startingAtRadius:(float)startingRadius
          endingAtRadius:(float)endingRadius;

/** creates the action */
+(id) actionWithDuration:(ccTime)duration
                  centre:(CGPoint)centreOfCircle
       startingAtDegrees:(float)startingDegrees
         endingAtDegrees:(float)endingDegrees
        startingAtRadius:(float)startingRadius
          endingAtRadius:(float)endingRadius
                reversed:(BOOL)reversed;

/** initializes the action */
-(id) initWithDuration:(ccTime)duration
                centre:(CGPoint)centreOfCircle
     startingAtDegrees:(float)startingDegrees
       endingAtDegrees:(float)endingDegrees
      startingAtRadius:(float)startingRadius
        endingAtRadius:(float)endingRadius
              reversed:(BOOL)reversed;

@end

実装:

#import "CCMoveThroughArc.h"


@implementation CCMoveThroughArc {

    CGPoint centre_;

    float startingDegrees_;
    float endingDegrees_;
    float diffDegrees_;

    float startingRadius_;
    float endingRadius_;
    float diffRadius_;

    BOOL reversed_;
}

/** creates the action */
+(id) actionWithDuration:(ccTime)duration
                  centre:(CGPoint)centreOfCircle
       startingAtDegrees:(float)startingDegrees
         endingAtDegrees:(float)endingDegrees
        startingAtRadius:(float)startingRadius
          endingAtRadius:(float)endingRadius {
    return [[self alloc] initWithDuration:duration centre:centreOfCircle startingAtDegrees:startingDegrees endingAtDegrees:endingDegrees startingAtRadius:startingRadius endingAtRadius:endingRadius reversed:NO];
}

/** creates the action */
+(id) actionWithDuration:(ccTime)duration
                  centre:(CGPoint)centreOfCircle
       startingAtDegrees:(float)startingDegrees
         endingAtDegrees:(float)endingDegrees
        startingAtRadius:(float)startingRadius
          endingAtRadius:(float)endingRadius
                reversed:(BOOL)reversed {
    return [[self alloc] initWithDuration:duration centre:centreOfCircle startingAtDegrees:startingDegrees endingAtDegrees:endingDegrees startingAtRadius:startingRadius endingAtRadius:endingRadius reversed:reversed];
}

/** initializes the action */
-(id) initWithDuration:(ccTime)duration
                centre:(CGPoint)centreOfCircle
     startingAtDegrees:(float)startingDegrees
       endingAtDegrees:(float)endingDegrees
      startingAtRadius:(float)startingRadius
        endingAtRadius:(float)endingRadius
              reversed:(BOOL)reversed {
    if( (self=[super initWithDuration:duration]) ) {
        centre_ = centreOfCircle;

        startingDegrees_ = fminf(startingDegrees, endingDegrees);
        endingDegrees_ = fmaxf(startingDegrees, endingDegrees);

        startingRadius_ = startingRadius;
        endingRadius_ = endingRadius;

        reversed_ = reversed;

        diffDegrees_ = endingDegrees_ - startingDegrees_;
        diffRadius_ = endingRadius_ - startingRadius_;
    }

    return self;
}

-(id) copyWithZone: (NSZone*) zone
{
    CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] centre:centre_ startingAtDegrees:startingDegrees_ endingAtDegrees:endingDegrees_ startingAtRadius:startingRadius_ endingAtRadius:endingRadius_ reversed:reversed_];

    return copy;
}

-(CCActionInterval*) reverse
{
    return [[self class] actionWithDuration:duration_ centre:centre_ startingAtDegrees:startingDegrees_ endingAtDegrees:endingDegrees_ startingAtRadius:startingRadius_ endingAtRadius:endingRadius_ reversed:!reversed_];
}

-(void) startWithTarget:(CCNode *)aTarget
{
    NSAssert1(([aTarget isKindOfClass:[CCNode class]] == YES), @"CCMoveThroughArc requires a CCNode as target.  Got a %@", [[aTarget class] description]);

    [super startWithTarget:aTarget];
}

- (float) angleFromDegrees:(float)deg {
    return fmodf((450.0 - deg), 360.0);
}

- (CGPoint) pointOnCircleWithCentre:(CGPoint)centerPt andRadius:(float)radius atDegrees:(float)degrees {
    float x = radius + cos (CC_DEGREES_TO_RADIANS([self angleFromDegrees:degrees])) * radius;
    float y = radius + sin (CC_DEGREES_TO_RADIANS([self angleFromDegrees:degrees])) * radius;
    return ccpAdd(centerPt, ccpSub(CGPointMake(x, y), CGPointMake(radius, radius)));
}

-(void) update:(ccTime) t {

    float angle;
    float radius;

    if (reversed_ == NO) {
        angle = (diffDegrees_ * t) + startingDegrees_;
        radius = (diffRadius_ * t) + startingRadius_;
    } else {
        angle = endingDegrees_ - (diffDegrees_ * t);
        radius = (diffRadius_ * (1.0 - t)) + startingRadius_;
    }

    CGPoint pos = [self pointOnCircleWithCentre:centre_ andRadius:radius atDegrees:angle];
    [(CCNode*)target_ setPosition:pos];
}

@end

そして、それを使用するコード:

- (void) moveStartingAtDegrees:(float)startingDegrees
               endingAtDegrees:(float)endingDegrees
                startingRadius:(float)startingRadius
                  endingRadius:(float)endingRadius
           withInitialDuration:(ccTime)initialDuration
              withMainDuration:(ccTime)duration {

    [self runAction:[CCRepeatForever actionWithAction:
                     [CCMoveThroughArc actionWithDuration:duration centre:CGPointZero startingAtDegrees:startingDegrees endingAtDegrees:endingDegrees startingAtRadius:startingRadius endingAtRadius:endingRadius reversed:YES]]];
}

これが他の人に役立つことを願っています。それは確かに私を助けました。私の実際の質問に本当に答えているわけではありませんが、質問の背後にある問題を非常に効果的に扱っています。

于 2013-03-17T06:17:23.397 に答える
0

このコードは複雑です。本当に「ヘルパー」ではありません。

私が見ているのは、moveToNextPoint ブロックの不要なコピーです。

また、本当に厄介な問題の可能性もいくつか見られます。そのうちの 1 つはおそらく保持の原因です。このコードを大幅にリファクタリングせずに修正する方法がわかりません。

問題の 1 つはオペランドです。オペランドを保持する(コピーされた)ブロック内で使用されます。さらに悪いことに、オペランド自体が moveToNextPoint ブロックを使用してシーケンスを実行します。これは、その時点で (再) 割り当てているものとまったく同じです。それが合法だとは知りませんでした。さらに、callblock アクションもブロックをコピーし、オペランドも保持される可能性があるため、さらに複雑になります。

このメソッドのコンテキストは無関係なクラスのクラス メソッドであり、オペランド自体ではないため、それも役割を果たしているのではないかと思います。

また、コピーされる CCCallBlock アクション ブロックで使用されるハンドラ ブロックも渡します。これは、ハンドラ ブロックも、少なくともシーケンスの間保持されることを意味します。

簡単に言えば、このコードはいくつかのメモリ管理ガイドラインに違反しており、インターリーブされた複数のブロックを使用しているため、デバッグが困難になっています。このメソッドだけのパラメーターのリストはコードのにおいがします。あまりにも多くの異なることを一度に実行しています。

分解して、最初からやり直してください。各ステップで、どこで何をする必要があるかを検討してください。誰がどのブロック、どの参照を所有しているか。保持サイクル/リークのすべてのステップをテストします。自問してみてください: 本当にこれほど多くのインターリーブ ブロックを使用する必要があるのでしょうか? もっとシーケンシャルにできますか?誰がどのコードを実行する必要がありますか? おそらく、一部のパーツの CCNode カテゴリの方が理にかなっています。

于 2013-03-16T23:11:29.727 に答える