问题
I would like to know when certain frames of an animation are running in order to set various conditions. In the code below, how can I use a counter or set conditions to determine when specific animation frames, say 3rd and 8th are currently running?
NSMutableArray *frameArray = [NSMutableArray array];
for(int i = 1; i < 12; i++)
{
CCLOG(@"item %d added", i);
[frameArray addObject:
[birdSpriteFrameCache spriteFrameByName:
[NSString stringWithFormat:@"Sprite%d.png", i]]]; }
//Starting the Animation
CCAnimation *animation = [CCAnimation animationWithSpriteFrames:frameArray delay: 0.3];
id action =[CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation :animation]];
[firstSprite runAction:action];
回答1:
If you use cocos2d 2.0, there is now a notification being served on a per frame basis. Straight from the docs:
/*******************/
/** Notifications **/
/*******************/
/** @def CCAnimationFrameDisplayedNotification
Notification name when a CCSpriteFrame is displayed
*/
#define CCAnimationFrameDisplayedNotification @"CCAnimationFrameDisplayedNotification"
When creating an animation, you can add to each frame a userInfo dictionary that will be received with the notification. Here is the line from CCActionInterval :
NSDictionary *dict = [frame userInfo];
if( dict )
[[NSNotificationCenter defaultCenter]
postNotificationName:CCAnimationFrameDisplayedNotification
object:target_
userInfo:dict
];
so i guess you can add a dict object for frames 3 and 8 and 'do your thing' in the notification call back.
ob cit: not tried, but should work for you.
EDIT : now tried. It took me all but one hour to convert a very clunky time-based algorithm to a solid event-driven implementation in one of my game's combatController class. I get served a notification for 2 frames only : the 9th frame of the attack animation (where i can now in perfect sync play a weapon sound) and the 11th frame of the hurt animation (where, if the victim dies, i can just stop the animation and fade out slowly the critter). Way to go cocos2d team. Not all that hard, the API is clean and crisp.
Here is part of the code (my first crack at it, not proud :) ) , the code uses some of my other stuff but you should be able to get started with the general idea.
-(void) registerForFrames{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(gotFrame:)
name:CCAnimationFrameDisplayedNotification
object:nil];
}
-(void) deregisterForFrames {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:CCAnimationFrameDisplayedNotification
object:nil];
}
-(NSDictionary *) frameEventForFrameNumber:(NSUInteger) frameNumber
animation:(NSString *) animationType {
return [[FrameEvent frameEventForListener:frameListenerCombatController
animationName:animationType
frameNumber:frameNumber] asDictionary];
}
-(FrameEvent*) frameEventForFrame:(NSDictionary *) theDic{
return [FrameEvent frameEventListenerWithContentsOfDictionary:theDic];
}
-(void) gotFrame:(id) notification{
NSDictionary *userInfoDictionary = [notification userInfo];
FrameEvent *ev = [self frameEventForFrame:userInfoDictionary];
if (!ev) {
MPLOGERROR(@"*** userInfo returned nil frameEvent object, bailing out!");
return;
}
if (ev.frameListenerType==frameListenerUnknown){
MPLOGERROR(@"*** Got served an unknown dictionary, bailing out!");
return;
}
if (ev.frameListenerType==frameListenerCombatController) {
MPLOG(@"Got Frame %@",ev.description);
if([ev.animationName isEqualToString:@"attack"]) {
[self scheduleOnce:@selector(attackTurnAround) delay:0.];
}
if ([ev.animationName isEqualToString:@"hurt"]) {
// more here !
MPLOG(@"TODO : schedule dead critter method");
}
} else {
MPLOGERROR(@"*** Not processing frame listener of type [%@], bailing out!",
[GEEngineSpecs frameListenerAsString:ev.frameListenerType]);
}
}
and finally the key part, put a user info on a frame :
- (CCAction *)attackActionFor:(GEClassAnimationSpec *)animSpec playerAsString:(NSString *)playerKey {
NSMutableArray *animFrames = [NSMutableArray array];
for (int i = 1; i <= animSpec.frames; i++) {
NSString *sfn = [NSString stringWithFormat:@"%@%@%i.png", animSpec.fileName, playerKey, i];
CCSpriteFrame *sf = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:sfn];
[animFrames addObject:sf];
}
float animFrameDelay = 1.0f / K_ATTACK_FRAME_RATE;
CCAnimation *anim = [CCAnimation animationWithSpriteFrames:animFrames delay:animFrameDelay];
anim.restoreOriginalFrame = NO;
CCAnimationFrame * ninth = [anim.frames objectAtIndex:8];
NSDictionary *ui = [self frameEventForFrameNumber:9 animation:@"attack"];
ninth.userInfo=ui;
CCAction *action = [CCSequence actions:
[CCAnimate actionWithAnimation:anim],
[CCCallFunc actionWithTarget:self selector:@selector(resumeAttackerIdle)],
nil
];
return action;
}
回答2:
This is in notifications as someone else mentioned, but as a heads up: I had some trouble with this a while ago when I had 2 different sprites running the same animation starting at slightly different times.
I think it was triggering the notification every time either sprite hit that frame, and I eventually scrapped that chunk of code because I couldn't figure out how to coordinate which sprite was triggering it, and wound up with a different approach because I couldn't get it to behave.
Just something to watch for if you're getting wacky results.
来源:https://stackoverflow.com/questions/17543659/how-to-determine-when-specific-animation-frames-are-running