0

我有一个实用函数,用于在圆形路径中移动 CCNode,无论是完整的圆还是部分圆。

该函数工作得非常好,但是如果我希望 CCNode 不断地跟随路径,我通过传入的 Block 来完成,最终调用相同的函数(有点递归,但不是真的)。

我发现的问题是,由于该函数在内部使用块,因此即使在调用 stopAllActions 或 removeFromParentAndCleanup:YES 之后,正在运行操作的 CCNode 也会保留,即使 CCNode 已被清理并从屏幕,它保留在内存中并且没有被释放。这似乎会影响性能,即使节点没有显示,因为 CCNode 和其他依赖项仍然(不知何故)在 cocos2d 系统中。

这是移动 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 文档中阅读,我们有:

“如果在方法的实现中使用块,对象实例变量的内存管理规则会更加微妙:

如果通过引用访问实例变量,则保留 self;如果您按值访问实例变量,则会保留该变量。”

所以这告诉我,通过以我的方式在我的函数中使用块导致隐藏保留。

稍后调用 stopAllActions 不会触发释放。

这对我有用的唯一方法是,如果在我节点的 cleanup() 消息中添加[self release].

我不喜欢这样,因为它与执行保留的代码分离。

我的一个新想法是以某种方式将其重写为新的 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 块的不必要副本。

我还看到了一些真正令人讨厌的问题的可能性,其中之一可能是保留的原因。如果不显着重构此代码,我看不到如何修复它。

一个问题是操作数。它在(复制的)块内使用,该块将保留操作数。但更糟糕的是,操作数本身使用 moveToNextPoint 块运行一个序列,这与您当时正在(重新)分配的相同。我不知道那是合法的。此外,调用块操作也会复制块,使事情变得更加复杂,因为这也可能保留操作数。

由于此方法的上下文是不相关类的类方法,而不是操作数本身,我怀疑它也起作用。

您还传递了一个处理程序块,该处理程序块用于将被复制的 CCCallBlock 操作块,这意味着处理程序块也至少在序列期间保留。

长话短说,这段代码违反了一些内存管理准则,并且使用了几个交错块使得调试变得困难。仅此方法的参数列表就是一种代码异味:它同时做了太多不同的事情。

拆开,重新开始。考虑每一步你需要做什么。谁拥有哪个块,哪个引用。测试每个步骤的保留周期/泄漏。问问自己:真的需要使用这么多交错的块吗?你能让它更有顺序吗?谁应该运行哪些代码?也许某些部分的 CCNode 类别更有意义。

于 2013-03-16T23:11:29.727 回答