MyNameIsKo
MyNameIsKo

Reputation: 2301

How do I animate a sprite sheet in Xcode without Cocos2d?

I'm developing a simple application that animates an image as the user moves a slider. This could easily be done with individual images, but for obvious reasons that method is inefficient.

Currently, I have the animation broken up into 14 sprite sheets with 16 images per sheet. I created a method that uses CGImageCreateWithImageInRect to find the current image dictated by the slider and update the image view with that image. This works, but not fluidly. I think I understand why, but I have no clue what to do otherwise. While I could use Cocos2d or OpenGL ES, I am stubborn and convinced that this is possible without them. I just want to know how.

Here's some example code:

- (void)setUp{

NSString *string;
NSString *bundleString = [[NSBundle mainBundle] bundlePath];
dsRedPathArray = [[NSMutableArray alloc] initWithCapacity:15];
for (int i = 0; i < 14; i++)
{
  string = [bundleString stringByAppendingFormat:@"/dsRedAni_%d.png", i];
  [dsRedPathArray addObject:string];
}

//initial image starts at (0, 1) of image dsRedAni_9
currentImage = [UIImage imageWithContentsOfFile:[dsRedPathArray objectAtIndex:9]];  
currentRef = CGImageCreateWithImageInRect(currentImage.CGImage, CGRectMake(495, 0,     kModelWidth, kModelHeight));   

modelView.image = [UIImage imageWithCGImage:currentRef];
 }

- (IBAction)sliderMoved:(UISlider*)sender
{
  [self animateModel:sender.value];
}

- (void)animateModel:(int)index
{
  index += 1;
  imageIndex = (index / 16) + 9;
  if (imageIndex > 13)
  {
    imageIndex = -14 + imageIndex;
  }
  currentX = kModelWidth * (index % 4);
  currentY = kModelHeight * ((index / 4) % 4);

  currentRect = CGRectMake(currentX, currentY, kModelWidth, kModelHeight);
  currentImage = [UIImage imageWithContentsOfFile:[dsRedPathArray objectAtIndex: (imageIndex)]];    
  currentRef = CGImageCreateWithImageInRect(currentImage.CGImage, currentRect);

  modelView.image = [UIImage imageWithCGImage:currentRef];
}

Thanks in advance for any help.

Upvotes: 1

Views: 7363

Answers (2)

MyNameIsKo
MyNameIsKo

Reputation: 2301

I finally found a rather quick and efficient, if unconventional, way of cycling through each section of my sprite sheets without any outside help from APIs. Rather than spending time and power cutting up the image into contexts or individual files, I found that it was more efficient for me to create a UIView in the size of the image I needed and then adding the entire sprite sheet to the view as a UIImageView.

With view.clipsToBounds set to YES, the view acts as a mask for my sprite sheet, limiting the visible portion to the size of each image I want to cycle through on the sheet. In order to give the effect of animation, I simply use imageview.center to move the sprite sheet around the view to the desired image coordinates. So, in a sprite-sheet with 16 images I only have to make one call to show the image, then move it around per each frame in the animation.

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
  [self setUp];
  self.backgroundColor = [UIColor clearColor];
  self.clipsToBounds = YES;
}
return self;
}

- (void)setUp
{
  //assign |lastImageIndex| a number not equal to |imageIndex|
  lastImageIndex = -1;

  modelImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1924, 1708)];

  NSString *string;
  NSString *bundleString = [[NSBundle mainBundle] bundlePath];
  UIImage *image;
  if (!imageArray)
  {
    imageArray = [[NSMutableArray alloc] init];
    NSLog(@"Init egfp |imageArray|");
  }

  for (int i = 0; i < 10; i++)
  {
    string = [bundleString stringByAppendingFormat:@"/moleculeAni_%d.png", i];
    image = [UIImage imageWithContentsOfFile:string];
    [imageArray addObject:image];
    if (i == 9)
    {
      NSLog(@"Filled egfp |imageCache|");
    }
  }

  [self addSubview:modelImageView];  
}

- (void)animateModel:(int)index
{
  if (index != 1)
  {
   index -= 1;
  }
  imageIndex = (index / 16);

  if (imageIndex < 9)
  {
    currentX = 962 - (481 * (index % 4));
    currentY = 854 - (427 * ((index / 4) % 4));
  }
  else
  {
    currentX = 962 - (481 * (index % 4));
    currentY = 427 - (427 * ((index / 4) % 4));
  }

  if (imageIndex != lastImageIndex)
  {
    if (imageIndex < 9 && onLastFrame)
    {
      modelImageView.frame = CGRectMake(0, 0, 1924, 1708);
      onLastFrame = NO;
    }
    else if (imageIndex == 9 && !onLastFrame)
    {
      modelImageView.frame = CGRectMake(0, 0, 1924, 854);
      onLastFrame = YES;
    }


    NSLog(@"Image: %d", imageIndex);
    tempImage = [imageArray objectAtIndex:imageIndex];
    modelImageView.image = tempImage;
  }

  modelImageView.center = CGPointMake(currentX, currentY);

  lastImageIndex = imageIndex;
}

Most of the code here is spent determining where the imageview needs to move in order to display the correct image. This is taking the method I explained above and is spreading it across 10 sprite sheets each with 16 images spread evenly (except the last which has 8). The only slowdown came when the program was swapping between images every 16th frame. To get around this, my controller has the view quickly cycle through all the images beneath another view (like a curtain) so the user is only privy to a loading screen. Once the images have been cycled, the curtain view is removed and the animation (controlled by the user with a slider) moves like butter.

Upvotes: 4

Bryan Cimo
Bryan Cimo

Reputation: 1308

One way to do this would be to cut up the images with CGImageCreateWithImageInRect, like you started to above. Then, add them to an array:

myArray addObject:[UIImage imageWithCGImage:currentRef];

Next use a UIImageView with setAnimationImages, like this:

UIImageView *myAnimationImageView;
[myAnimationImageView setAnimationImages:myArray];

Then You can start the animation.

There is a good project doing this here: https://github.com/r3econ/UIImage-Sprite-Additions

Upvotes: 2

Related Questions