La solution que j'ai implémentée utilise en effet une file d'attente. Voici une description assez complète:
Tout cela est fait dans une classe de vue appelée PlayerView. Dans l'en-tête I comprennent les suivants:
#import "NSMutableArray+QueueAdditions.h"
@interface PlayerView : UIImageView {
Player* representedPlayer; // The model object represented by the view
NSMutableArray* actionQueue; // An array used as a queue for the actions
bool animatingPlayer; // Notes if the player is in the middle of an animation
bool stoppingAnimation; // Notes if all animations should be stopped (e.g., for re-setting the game)
CGFloat actionDuration; // A convenient way for me to change the duration of all animations
// ... Removed other variables in the class (sound effects, etc) not needed for this example
}
// Notifications
+ (NSString*) AnimationsDidStopNotification;
@property (nonatomic, retain) Player* representedPlayer;
@property (nonatomic, retain, readonly) NSMutableArray* actionQueue;
@property (nonatomic, assign) CGFloat actionDuration;
@property (nonatomic, assign) bool animatingPlayer;
@property (nonatomic, assign) bool stoppingAnimation;
// ... Removed other properties in the class not need for this example
- (void)placePlayer; // puts view where needed (according to the model) without animation
- (void)moveEventHandler:(NSNotification *) notification; // handles events when the player moves
- (void)rotateEventHandler:(NSNotification *) notification; // handles events when the player rotates
// ... Removed other action-related event handles not needed for this example
// These methods actually perform the proper animations
- (void) doMoveAnimation:(CGRect) nextFrame;
- (void) doRotateAnimation:(CGRect)nextFrame inDirection:(enum RotateDirection)rotateDirection;
// ... Removed other action-related methods not needed for this example
// Handles things when each animation stops
- (void) animationDidStop:(NSString*)animationID
finished:(BOOL)finished
context:(void*)context;
// Forces all animations to stop
- (void) stopAnimation;
@end
En aparté, la catégorie QueueAdditions dans NSMutableArray + QueueAdditions.h/m ressemble à ceci:
@interface NSMutableArray (QueueAdditions)
- (id)popObject;
- (void)pushObject:(id)obj;
@end
@implementation NSMutableArray (QueueAdditions)
- (id)popObject
{
// nil if [self count] == 0
id headObject = [self objectAtIndex:0];
if (headObject != nil) {
[[headObject retain] autorelease]; // so it isn't dealloc'ed on remove
[self removeObjectAtIndex:0];
}
return headObject;
}
- (void)pushObject:(id)obj
{
[self addObject: obj];
}
@end
Ensuite, dans la mise en œuvre du PlayerView, Je donne les résultats suivants:
#import "PlayerView.h"
#import <QuartzCore/QuartzCore.h>
@implementation PlayerView
@synthesize actionQueue;
@synthesize actionDuration;
@synthesize animatingPlayer;
@synthesize stoppingAnimation;
// ... Removed code not needed for this example (init to set up the view's image, sound effects, actionDuration, etc)
// Name the notification to send when animations stop
+ (NSString*) AnimationsDidStopNotification
{
return @"PlayerViewAnimationsDidStop";
}
// Getter for the representedPlayer property
- (Player*) representedPlayer
{
return representedPlayer;
}
// Setter for the representedPlayer property
- (void)setRepresentedPlayer:(Player *)repPlayer
{
if (representedPlayer != nil)
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[representedPlayer release];
}
if (repPlayer == nil)
{
representedPlayer = nil;
// ... Removed other code not needed in this example
}
else
{
representedPlayer = [repPlayer retain];
if (self.actionQueue == nil)
{
actionQueue = [[NSMutableArray alloc] init];
}
[actionQueue removeAllObjects];
animatingPlayer = NO;
stoppingAnimation = NO;
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(moveEventHandler:)
name:[Player DidMoveNotification]
object:repPlayer ];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(rotateEventHandler:)
name:[Player DidRotateNotification]
object:repPlayer ];
// ... Removed other addObserver actions and code not needed in this example
}
}
// ... Removed code not needed for this example
- (void) placePlayer
{
// Example not helped by specific code... just places the player where the model says it should go without animation
}
// Handle the event noting that the player moved
- (void) moveEventHandler: (NSNotification *) notification
{
// Did not provide the getRectForPlayer:onMazeView code--not needed for the example. But this
// determines where the player should be in the model when this notification is captured
CGRect nextFrame = [PlayerView getRectForPlayer:self.representedPlayer onMazeView:self.mazeView];
// If we are in the middle of an animation, put information for the next animation in a dictionary
// and add that dictionary to the action queue.
// If we're not in the middle of an animation, just do the animation
if (animatingPlayer)
{
NSDictionary* actionInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSValue valueWithCGRect:nextFrame], @"nextFrame",
@"move", @"actionType",
@"player", @"actionTarget",
nil];
[actionQueue pushObject:actionInfo];
}
else
{
animatingPlayer = YES; // note that we are now doing an animation
[self doMoveAnimation:nextFrame];
}
}
// Handle the event noting that the player rotated
- (void) rotateEventHandler: (NSNotification *) notification
{
// User info in the notification notes the direction of the rotation in a RotateDirection enum
NSDictionary* userInfo = [notification userInfo];
NSNumber* rotateNumber = [userInfo valueForKey:@"rotateDirection"];
// Did not provide the getRectForPlayer:onMazeView code--not needed for the example. But this
// determines where the player should be in the model when this notification is captured
CGRect nextFrame = [PlayerView getRectForPlayer:self.representedPlayer onMazeView:self.mazeView];
if (animatingPlayer)
{
NSDictionary* actionInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSValue valueWithCGRect:nextFrame], @"nextFrame",
@"rotate", @"actionType",
rotateNumber, @"rotateDirectionNumber",
@"player", @"actionTarget",
nil];
[actionQueue pushObject:actionInfo];
}
else
{
enum RotateDirection direction = (enum RotateDirection) [rotateNumber intValue];
animatingPlayer = YES;
[self doRotateAnimation:nextFrame inDirection:direction];
}
}
// ... Removed other action event handlers not needed for this example
// Perform the actual animation for the move action
- (void) doMoveAnimation:(CGRect) nextFrame
{
[UIView beginAnimations:@"Move" context:NULL];
[UIView setAnimationDuration:actionDuration];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
self.frame = nextFrame;
[UIView commitAnimations];
}
// Perform the actual animation for the rotate action
- (void) doRotateAnimation:(CGRect)nextFrame inDirection:(enum RotateDirection)rotateDirection
{
int iRot = +1;
if (rotateDirection == CounterClockwise)
{
iRot = -1;
}
[UIView beginAnimations:@"Rotate" context:NULL];
[UIView setAnimationDuration:(3*actionDuration)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
CGAffineTransform oldTransform = self.transform;
CGAffineTransform transform = CGAffineTransformRotate(oldTransform,(iRot*M_PI/2.0));
self.transform = transform;
self.frame = nextFrame;
[UIView commitAnimations];
}
- (void) animationDidStop:(NSString*)animationID
finished:(BOOL)finished
context:(void *)context
{
// If we're stopping animations, clear the queue, put the player where it needs to go
// and reset stoppingAnimations to NO and note that the player is not animating
if (self.stoppingAnimation)
{
[actionQueue removeAllObjects];
[self placePlayer];
self.stoppingAnimation = NO;
self.animatingPlayer = NO;
}
else if ([actionQueue count] > 0) // there is an action in the queue, execute it
{
NSDictionary* actionInfo = (NSDictionary*)[actionQueue popObject];
NSString* actionTarget = (NSString*)[actionInfo valueForKey:@"actionTarget"];
NSString* actionType = (NSString*)[actionInfo valueForKey:@"actionType"];
// For actions to the player...
if ([actionTarget isEqualToString:@"player"])
{
NSValue* rectValue = (NSValue*)[actionInfo valueForKey:@"nextFrame"];
CGRect nextFrame = [rectValue CGRectValue];
if ([actionType isEqualToString:@"move"])
{
[self doMoveAnimation:nextFrame];
}
else if ([actionType isEqualToString:@"rotate"])
{
NSNumber* rotateNumber = (NSNumber*)[actionInfo valueForKey:@"rotateDirectionNumber"];
enum RotateDirection direction = (enum RotateDirection) [rotateNumber intValue];
[self doRotateAnimation:nextFrame inDirection:direction];
}
// ... Removed code not needed for this example
}
else if ([actionTarget isEqualToString:@"cell"])
{
// ... Removed code not needed for this example
}
}
else // no more actions in the queue, mark the animation as done
{
animatingPlayer = NO;
[[NSNotificationCenter defaultCenter]
postNotificationName:[PlayerView AnimationsDidStopNotification]
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys: nil]];
}
}
// Make animations stop after current animation by setting stopAnimation = YES
- (void) stopAnimation
{
if (self.animatingPlayer)
{
self.stoppingAnimation = YES;
}
}
- (void)dealloc {
if (representedPlayer != nil)
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
[representedPlayer release];
[actionQueue release];
// …Removed other code not needed for example
[super dealloc];
}
@end
Explication:
la vue sous scribes aux notifications appropriées de l'objet modèle (lecteur). Quand il capture une notification, il vérifie s'il fait déjà une animation (en utilisant la propriété animatingPlayer). Si c'est le cas, il prend les informations de la notification (en notant comment le joueur est censé être animé), place ces informations dans un dictionnaire et ajoute ce dictionnaire à la file d'attente d'animation. Si aucune animation n'est en cours, la méthode placera animatingPlayer sur true et appellera une routine d'animation appropriée [Whatever]. Chaque routine d'animation [Whatever] effectue les animations appropriées, en définissant setAnimationDidStopSelector sur animationDidStop: finished: context :. À la fin de chaque animation, la méthode animationDidStop: finished: context: (après avoir vérifié si toutes les animations doivent être arrêtées immédiatement) exécutera l'animation suivante dans la file en extrayant le dictionnaire suivant de la file d'attente et en interprétant ses données approprié faire [Whatever] Méthode d'animation.S'il n'y a pas d'animations dans la file d'attente, cette routine définit animatingPlayer sur NO et envoie une notification afin que les autres objets puissent savoir quand le joueur a correctement arrêté sa série d'animations en cours.
C'est à peu près tout. Il peut y avoir une méthode plus simple (?) Mais cela a plutôt bien fonctionné pour moi. Consultez mon application Mazin dans l'App Store si vous êtes intéressé à voir les résultats réels.
Merci.