All Data Structures Files Functions Variables Enumerations Enumerator Properties Defines
/Projects/Cogito/src/GameObjects/Lemming.m
Go to the documentation of this file.
00001 //
00002 //  Lemming.m
00003 //  Author: Thomas Taylor
00004 //
00005 //  Code for the lemming characters
00006 //
00007 //  21/11/2011: Created class
00008 //
00009 
00010 #import "Lemming.h"
00011 
00012 @interface Lemming()
00013 
00014 -(void)initAnimations;
00015 -(void)resetState;
00016 -(void)changeStateDelegate:(id)_newState;
00017 -(void)useHelmet:(BOOL)_useHelmet;
00018 -(void)useUmbrella;
00019 -(void)checkForCollisions:(CCArray*)_listOfGameObjects;
00020 -(void)checkLemmingWithinScreenBounds;
00021 -(void)updateDebugLabel;
00022 
00023 @end
00024 
00025 @implementation Lemming
00026 
00027 @synthesize health;
00028 @synthesize state;
00029 
00030 @synthesize helmetUses;
00031 @synthesize umbrellaUses;
00032 
00033 // animation
00034 @synthesize idleAnim;
00035 @synthesize idleHelmetAnim;
00036 @synthesize walkingAnim;
00037 @synthesize walkingHelmetAnim;
00038 @synthesize openUmbrellaAnim;
00039 @synthesize floatUmbrellaAnim;
00040 @synthesize deathAnim;
00041 
00042 // used in debugging
00043 @synthesize ID;
00044 @synthesize debugLabel;
00045 
00046 #pragma mark -
00047 #pragma mark Memory Allocation
00048 
00052 -(void)dealloc
00053 {    
00054     [idleAnim release];
00055     [idleHelmetAnim release];
00056     [walkingAnim release];
00057     [walkingHelmetAnim release];
00058     [openUmbrellaAnim release];
00059     [floatUmbrellaAnim release];
00060     [deathAnim release];
00061     
00062     debugLabel = nil;
00063     
00064     [super dealloc];
00065 }
00066 
00067 #pragma mark -
00068 #pragma mark Initialisation
00069 
00074 -(id)init
00075 {
00076     self = [super init];
00077     
00078     if (self != nil) 
00079     {
00080         self.gameObjectType = kLemmingType;
00081         respawns = kLemmingRespawns;
00082         movementDirection = kDirectionRight;
00083         [self initAnimations];
00084         [self resetState];
00085         [self changeState:kStateSpawning];
00086     }
00087     return self;
00088 }
00089 
00093 -(void)initAnimations
00094 {        
00095     [self setIdleAnim:[self loadAnimationFromPlistWthName:@"idleAnim" andClassName:@"Lemming"]];
00096     [self setIdleHelmetAnim:[self loadAnimationFromPlistWthName:@"idleHelmetAnim" andClassName:@"Lemming"]];
00097     [self setWalkingAnim:[self loadAnimationFromPlistWthName:@"walkingAnim" andClassName:@"Lemming"]];
00098     [self setWalkingHelmetAnim:[self loadAnimationFromPlistWthName:@"walkingHelmetAnim" andClassName:@"Lemming"]];
00099     [self setOpenUmbrellaAnim:[self loadAnimationFromPlistWthName:@"openUmbrellaAnim" andClassName:@"Lemming"]];
00100     [self setFloatUmbrellaAnim:[self loadAnimationFromPlistWthName:@"floatUmbrellaAnim" andClassName:@"Lemming"]];
00101     [self setDeathAnim:[self loadAnimationFromPlistWthName:@"deathAnim" andClassName:@"Lemming"]];
00102 }
00103 
00107 -(void)resetState
00108 {
00109     health = 100;
00110     spawnTime = [[GameManager sharedGameManager] getGameTimeInSecs];
00111     actionsTaken = 0;
00112     isUsingHelmet = NO;
00113     isUsingUmbrella = NO;
00114     umbrellaEquipped = NO;
00115     umbrellaTimer = 0;
00116     objectLastCollidedWith = kObjectTypeNone;
00117     helmetUses = [[[GameManager sharedGameManager] currentLevel] helmetUses];
00118     umbrellaUses = [[[GameManager sharedGameManager] currentLevel] umbrellaUses];
00119     if(movementDirection != kDirectionRight) [self changeDirection];
00120 }
00121 
00122 #pragma mark -
00123 #pragma mark State Changing
00124 
00129 -(void)changeState: (CharacterStates)_newState
00130 {      
00131     // if need to use umbrella, delay, then call useUmbrella
00132     if(umbrellaEquipped) 
00133     {
00134         if(umbrellaTimer >= 70) [self useUmbrella];
00135         return; 
00136     }
00137     //CCLOG(@"Lemming.changeState: %@", [Utils getStateAsString:_newState]);
00138     
00139     [self stopAllActions];
00140     id action = nil;
00141     self.state = _newState;
00142     // reset the fall counter
00143     fallCounter = 0;
00144     
00145     switch(_newState) 
00146     {
00147         case kStateSpawning:
00148             [self setPosition:ccp([GameManager sharedGameManager].currentLevel.spawnPoint.x, [GameManager sharedGameManager].currentLevel.spawnPoint.y)];
00149             if (isUsingHelmet) [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Lemming_idle_helmet_1.png"]];
00150             else [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Lemming_idle_1.png"]];
00151             [self resetState];
00152             respawns--;
00153             [self changeState:kStateFalling];
00154             break;
00155 
00156         case kStateFalling:
00157             if (isUsingHelmet) [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Lemming_idle_helmet_1.png"]];
00158             else [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Lemming_idle_1.png"]];
00159             action = [CCSpawn actions: [CCMoveBy actionWithDuration:0.1f position:ccp(0.0f, kLemmingFallAmount*-1)], action, nil];         
00160             break;
00161             
00162         case kStateIdle:
00163             if (isUsingHelmet) [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Lemming_idle_helmet_1.png"]];
00164             else [self setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Lemming_idle_1.png"]];
00165             break;
00166             
00167         case kStateWalking:   
00168             if(isUsingHelmet) action = [CCAnimate actionWithAnimation:walkingHelmetAnim restoreOriginalFrame:NO];
00169             else action = [CCAnimate actionWithAnimation:walkingAnim restoreOriginalFrame:YES];
00170             id walkingAction = [CCMoveBy actionWithDuration:1.04f position:ccp((movementDirection == kDirectionLeft) ? kLemmingMovementAmount * -1 : kLemmingMovementAmount, 0.0f)];
00171             action = [CCSpawn actions: walkingAction, action, nil];         
00172             break;  
00173             
00174         case kStateFloating:
00175             action = [CCAnimate actionWithAnimation:openUmbrellaAnim restoreOriginalFrame:NO];
00176             break;
00177            
00178         case kStateDead:
00179             action = [CCAnimate actionWithAnimation:deathAnim restoreOriginalFrame:NO];
00180             break;
00181             
00182         case kStateWin:
00183             break;
00184             
00185         default:
00186             CCLOG(@"Lemming.changeState: unknown state '%@'", [Utils getStateAsString:_newState]);
00187             break;
00188     }
00189     
00190     // run the animations
00191     if(action != nil) 
00192     {
00193         if(_newState != kStateDead && _newState != kStateFloating) action = [CCRepeatForever actionWithAction:action];
00194         [self runAction:action];
00195     }
00196 }
00197 
00203 -(void)changeState: (CharacterStates)_newState afterDelay:(float)_delay
00204 {
00205     [self performSelector:@selector(changeStateDelegate:) withObject:[NSNumber numberWithInt:_newState] afterDelay:_delay];
00206 }
00207 
00208 -(void)changeStateDelegate:(id)_newState
00209 {
00210     [self changeState:(CharacterStates)[_newState intValue]];
00211 }
00212 
00213 #pragma mark -
00214 
00219 -(void)takePath:(Action)_action
00220 {
00221     if(_action == -1) { CCLOG(@"Lemming.takePath: Unknown action chosen"); return; }
00222     
00223     switch (_action) 
00224     {
00225         case kActionLeft:
00226             if(movementDirection != kDirectionLeft) [self changeDirection];
00227             break;
00228             
00229         case kActionRight:
00230             if(movementDirection != kDirectionRight) [self changeDirection];
00231             break;
00232             
00233         case kActionLeftHelmet:
00234             if(movementDirection != kDirectionLeft) [self changeDirection];
00235             if(!isUsingHelmet) [self useHelmet:YES];
00236             break;
00237             
00238         case kActionRightHelmet:
00239             if(movementDirection != kDirectionRight) [self changeDirection];
00240             if(!isUsingHelmet) [self useHelmet:YES];
00241             break;
00242             
00243         case kActionEquipUmbrella:
00244             umbrellaEquipped = YES;
00245             break;
00246             
00247         case kActionDownUmbrella:
00248             isUsingUmbrella = YES;
00249             [self performSelector:@selector(useUmbrella) withObject:nil afterDelay:2.0f];
00250             break;
00251             
00252         case kActionDown:
00253             [self changeState:kStateFalling afterDelay:2.0f];
00254             break;
00255             
00256         default:
00257             break;
00258     }
00259     
00260     if(isUsingHelmet && _action != kActionLeftHelmet && _action != kActionRightHelmet) [self useHelmet:NO];
00261 
00262     // increment the actionsTaken 
00263     actionsTaken++;
00264 }
00265 
00269 -(void)changeDirection
00270 {   
00271     if(movementDirection == kDirectionRight)
00272     {
00273         self.flipX = YES;
00274         movementDirection = kDirectionLeft;
00275         [self setPosition:ccp(self.position.x-1, self.position.y)];
00276     }
00277     else
00278     {
00279         self.flipX = NO;
00280         movementDirection = kDirectionRight;
00281         [self setPosition:ccp(self.position.x+1, self.position.y)];
00282     }
00283     
00284     [self changeState:kStateWalking];
00285 }
00286 
00291 -(void)onEndConditionReached
00292 {    
00293     // lemming has played death anim, respawn or remove
00294     if(self.state == kStateDead)
00295     {
00296         if(respawns > 0) [self changeState:kStateSpawning];
00297         else [[LemmingManager sharedLemmingManager] removeLemming:self];
00298     }
00299     // remove lemming if it's reached the exit
00300     else [[LemmingManager sharedLemmingManager] removeLemming:self];
00301 }
00302 
00303 #pragma mark -
00304 #pragma mark Tools
00305 
00309 -(void)useHelmet:(BOOL)_useHelmet
00310 {
00311     if(isUsingHelmet != _useHelmet) 
00312     {   
00313         isUsingHelmet = _useHelmet;
00314         helmetUses--;
00315         [self changeState:kStateWalking];
00316     }
00317 }
00318 
00322 -(void)useUmbrella
00323 {
00324     if(isUsingUmbrella || umbrellaEquipped)
00325     {
00326         //CCLOG(@"Lemming.useUmbrella: isUsingUmbrella: %i umbrellaEquipped: %i -> %i", isUsingUmbrella, umbrellaEquipped, umbrellaTimer);
00327         isUsingUmbrella = NO;
00328         umbrellaEquipped = NO;
00329         umbrellaUses--;
00330         umbrellaTimer = 0;
00331         [self changeState:kStateFloating];
00332     }
00333 }
00334 
00335 
00336 #pragma mark -
00337 #pragma mark Collisions
00338 
00343 -(void)checkForCollisions:(CCArray*)_listOfGameObjects
00344 {
00345     CCArray* collisions = [CCArray arrayWithCapacity:0];
00346     CGRect selfBBox = [self adjustedBoundingBox];
00347     BOOL colliding = NO;
00348     
00349     for (GameObject *gameObject in _listOfGameObjects) 
00350     {
00351         
00352         // no need to check for self-self collisions
00353         if(gameObject == self || gameObject.gameObjectType == kLemmingType) continue;
00354         
00355         CGRect objectBBox = [gameObject adjustedBoundingBox];
00356         if(CGRectIntersectsRect(selfBBox, objectBBox)) 
00357         {
00358             [collisions addObject:gameObject];
00359             if(gameObject.gameObjectType == kObjectTerrain || gameObject.gameObjectType == kObjectTrapdoor) colliding = YES;
00360         }
00361     }
00362     
00363     // check if the lemming should be falling
00364     if(!colliding && state != kStateSpawning && state != kStateFalling && state != kStateFloating && state != kStateDead) [self changeState:kStateFalling];
00365     
00366     // work out what we're actually colliding with, and call onObjectCollision
00367     if([collisions count] > 0)
00368     {
00369         GameObject* object = nil;
00370         
00371         // work out if we're colliding with two objects
00372         if([collisions count] > 1)
00373         {        
00374             GameObject* object1 = [collisions objectAtIndex:0];
00375             GameObject* object2 = [collisions objectAtIndex:1];
00376             
00377             if(object1.gameObjectType == kObjectTerrain || object2.gameObjectType == kObjectTerrain) 
00378             {
00379                 if(object1.gameObjectType == kObjectTerrain) object = object2;
00380                 else object = object1;
00381             }
00382         }
00383         else object = [collisions objectAtIndex:0];
00384         
00385         // if the object isn't collideable, exit
00386         if(![object isCollideable]) return;
00387         // check that we're not colliding with the same object
00388         if(object.gameObjectType == objectLastCollidedWith) 
00389         {
00390             if(object.gameObjectType == kObjectTerrain && (self.state == kStateFalling || self.state == kStateFloating)); // do nothing
00391             else return;    
00392         }
00393         else objectLastCollidedWith = object.gameObjectType; 
00394         
00395         [self onObjectCollision:object];
00396     }
00397 }
00398 
00402 -(void)checkLemmingWithinScreenBounds
00403 {
00404     CGPoint position = [self position];
00405     
00406     // if the lemming reaches edge of screen, change direction
00407     if (position.x <= 10)
00408     {
00409         [self setPosition:ccp(self.position.x+0.5, self.position.y)];
00410         [self changeDirection];
00411     }
00412     else if(position.x >= 470) 
00413     {
00414         [self setPosition:ccp(self.position.x-0.5, self.position.y)];
00415         [self changeDirection];
00416     }
00417     // if the lemming falls of the bottom of the screen, change to dead
00418     if (position.y <= 20) [self changeState:kStateDead];
00419 }
00420 
00426 -(void)onObjectCollision:(GameObject*)_object
00427 {       
00428     switch([_object gameObjectType]) 
00429     {
00430         case kObstaclePit:
00431         case kObstacleWater:
00432             [self changeState:kStateDead];
00433             break;
00434             
00435         case kObstacleStamper:
00436             if(!isUsingHelmet) [self changeState:kStateDead];
00437             break;
00438             
00439         case kObjectExit:
00440             [self changeState:kStateWin afterDelay:1.0f];
00441             break;
00442             
00443         case kObjectTerrain:
00444             if(self.state != kStateWalking && self.state != kStateDead && self.state != kStateWin && ![(Terrain*)_object isWall]) 
00445             {
00446                 if(fallCounter >= ((float)kLemmingFallTime*(float)kFrameRate) && self.state != kStateFloating) [self changeState:kStateDead];
00447                 else [self changeState:kStateWalking];
00448             }
00449             else if([(Terrain*)_object isWall]) [self changeDirection];
00450             break;
00451         
00452         case kObjectTerrainEnd:
00453         case kObjectTrapdoor:
00454         case kObstacleCage:            
00455             // do nothing...
00456             break;
00457             
00458         default: 
00459             CCLOG(@"Lemming.onObjectCollision: %@", [Utils getObjectAsString:_object.gameObjectType]);
00460             break;
00461     }
00462 }
00463 
00464 #pragma mark -
00465 #pragma mark Update
00466 
00472 -(void)updateStateWithDeltaTime:(ccTime)_deltaTime andListOfGameObjects:(CCArray*)_listOfGameObjects
00473 {      
00474     // if the lemming's dead there's no point continuing
00475     if(self.health <= 0 && self.state != kStateDead) { [self changeState:kStateDead]; return; }
00476     
00477     // make sure the lemming's onscreen
00478     if (state != kStateDead) [self checkLemmingWithinScreenBounds];
00479     
00480     // check for collisions
00481     [self checkForCollisions:_listOfGameObjects];
00482     
00483     // increment any counters
00484     if(self.state == kStateFalling) fallCounter++;
00485     if(umbrellaEquipped) umbrellaTimer++;
00486     
00487     //if(DEBUG_MODE) [self updateDebugLabel];
00488     [self updateDebugLabel];
00489     
00490     [super updateStateWithDeltaTime:_deltaTime andListOfGameObjects:_listOfGameObjects];
00491 
00492     /*
00493      * if actions have finished running...
00494      */
00495     if([self numberOfRunningActions] == 0)
00496     {
00497         if(self.state == kStateFloating) // lemming has opened umbrella, now to make it float
00498         {
00499             // create the movement/animation and play
00500             id floatAction = [CCAnimate actionWithAnimation:floatUmbrellaAnim restoreOriginalFrame:NO];
00501             id floatingAction = [CCMoveBy actionWithDuration:0.75f position:ccp(0.0f, kLemmingMovementAmount*-1)];
00502             floatAction = [CCSpawn actions: floatingAction, floatAction, nil];         
00503             floatAction = [CCRepeatForever actionWithAction:floatAction];
00504             [self runAction:floatAction];
00505         }
00506         
00507         if(self.state == kStateWin || self.state == kStateDead) [self onEndConditionReached];
00508     }
00509 }
00510 
00514 -(void)updateDebugLabel
00515 {    
00516     CGPoint newPosition = [self position];
00517     
00518     [debugLabel setString:@""];
00519     
00520     float yOffset = 20.0f;
00521     newPosition = ccp(newPosition.x, newPosition.y+yOffset);
00522     [debugLabel setPosition:newPosition];    
00523 }
00524 
00525 #pragma mark -
00526 #pragma mark Getters/Setters
00527 
00533 -(CGRect)adjustedBoundingBox
00534 {
00535     CGRect bBox = [self boundingBox];
00536     float xCropAmount = bBox.size.width * 0.25;
00537     float yCropAmount = bBox.size.height * 0.095f;
00538     
00539     bBox = CGRectMake(bBox.origin.x, bBox.origin.y, bBox.size.width-xCropAmount, bBox.size.height-yCropAmount);
00540     
00541     return bBox;
00542 }
00543 
00547 -(int)respawns
00548 {
00549     return respawns;
00550 }
00551 
00552 @end