ok, I guess it's done. I have made quite a few changes along the way though, will give explanations to them on the way:
header:
1) I think it feels more natural to give world's origin and end position than origin and size
2) CCFollow can either be restricted to specified world boundaries or not - if a game design allows for unlimited world, it will work as well
@class CCNode;
@interface CCFollow : CCAction <NSCopying>
{
/* node to follow */
CCNode * followedNode;
/* whether camera should be limited to certain area */
BOOL boundarySet;
/* if screensize is bigger than the boundary - update not needed */
BOOL boundaryFullyCovered;
/* fast access to the screen dimensions */
CGPoint halfScreenSize;
CGPoint fullScreenSize;
/* world boundaries */
float leftBoundary;
float rightBoundary;
float topBoundary;
float bottomBoundary;
}
/** alter behavior - turn on/off boundary */
@property (nonatomic,readwrite) BOOL boundarySet;
/** creates the action with no boundary set */
+(id) actionWithTarget:(CCNode *) fNode;
/** creates the action with a set boundary */
+(id) actionWithTarget:(CCNode *) fNode worldOrigin:(CGPoint) org worldEnd:(CGPoint) end;
/** initializes the action */
-(id) initWithTarget:(CCNode *) fNode;
/** initializes the action with a set boundary */
-(id) initWithTarget:(CCNode *) fNode worldOrigin:(CGPoint) org worldEnd:(CGPoint) end;
@end
.m
1) If the screenSize is bigger than the world dimensions - there is no need to make any updates. in fact the action can be released in that case, but wasn't sure how to do it safely
2) If just one of the world dimensions is smaller than the screenSize dimension (say screen is wider than the map) - it will update correctly and the camera will be placed in the middle (in this direction, will still move in the other of course)
1) and 2) -> 3) This code should be iPad-proof and work well with its larger screensize, but Im unable to test it
4) It does not support switching between landscape and portrait during runtime - part of the init function that sets screensizes and boundaries should be probably moved to additional function and updated after the device is rotated. or the action can be stopped and initiated again, should work that way
5) Action stops automatically if the follow target is removed elsewhere in the code
6) Due to the fact that moving screen is 'inverted' (eg to move screen left you have to increase its x position), CCFollow only works correctly for scenes/layers
// additional imports required:
#import "Support/CGPointExtension.h"
#import "CCDirector.h"
//
// Follow
//
#pragma mark -
#pragma mark Follow
@implementation CCFollow
@synthesize boundarySet;
+(id) actionWithTarget:(CCNode *) fNode
{
return [[[self alloc] initWithTarget:fNode] autorelease];
}
+(id) actionWithTarget:(CCNode *) fNode worldOrigin:(CGPoint) org worldEnd:(CGPoint) end
{
return [[[self alloc] initWithTarget:fNode worldOrigin:org worldEnd:end] autorelease];
}
-(id) initWithTarget:(CCNode *) fNode
{
if( !(self=[super init]) )
return nil;
followedNode = [fNode retain];
boundarySet = FALSE;
boundaryFullyCovered = FALSE;
fullScreenSize = CGPointMake([CCDirector sharedDirector].winSize.width, [CCDirector sharedDirector].winSize.height);
halfScreenSize = ccpMult(fullScreenSize, .5f);
return self;
}
-(id) initWithTarget:(CCNode *) fNode worldOrigin:(CGPoint) org worldEnd:(CGPoint) end
{
if( !(self=[super init]) )
return nil;
followedNode = [fNode retain];
boundarySet = TRUE;
boundaryFullyCovered = FALSE;
fullScreenSize = CGPointMake([CCDirector sharedDirector].winSize.width, [CCDirector sharedDirector].winSize.height);
halfScreenSize = ccpMult(fullScreenSize, .5f);
leftBoundary = -(end.x - fullScreenSize.x);
rightBoundary = -org.x ;
topBoundary = -org.y;
bottomBoundary = -(end.y - fullScreenSize.y);
if(rightBoundary < leftBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
rightBoundary = leftBoundary = (leftBoundary + rightBoundary) / 2;
}
if(topBoundary < bottomBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
topBoundary = bottomBoundary = (topBoundary + bottomBoundary) / 2;
}
if( (topBoundary == bottomBoundary) && (leftBoundary == rightBoundary) )
boundaryFullyCovered = TRUE;
return self;
}
-(id) copyWithZone: (NSZone*) zone
{
CCAction *copy = [[[self class] allocWithZone: zone] init];
copy.tag = tag;
return copy;
}
-(void) dealloc
{
followedNode = nil;
[super dealloc];
}
-(void) step:(ccTime) dt
{
#define CLAMP(x,y,z) MIN(MAX(x,y),z)
if(boundarySet)
{
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(boundaryFullyCovered)
return;
CGPoint tempPos = ccpSub( halfScreenSize, followedNode.position);
[target setPosition:ccp(CLAMP(tempPos.x,leftBoundary,rightBoundary), CLAMP(tempPos.y,bottomBoundary,topBoundary))];
}
else
[target setPosition:ccpSub( halfScreenSize, followedNode.position )];
#undef CLAMP
}
-(BOOL) isDone
{
return ( ! followedNode.isRunning );
}
-(void) stop
{
target = nil;
followedNode = nil;
}
@end
please let me know what you think