I'm developing a tile based iOS game. Objects (UIViews) are drawn above the tile map and should walk around, tile by tile.
I'm using a simple UIView animation to do that:
[UIView animateWithDuration:0.20 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(void) {
// set frame
// set rotation
} completion:^(BOOL finished){
// call method to start animation again
}];
The problem is that even when only animating about 5 objects it isn't smooth. After each animation there is a very small pause. Because it should be perceived as one animation (not tile by tile) it is noticable.
I need to do some checks for each move (1 move = 1 tile), so I can't just use one big animation. The checks are done independently and should update the data before the animation is finished. So I don't think the lag is because of that.
Are there any other ways on solving this? Maybe with Sprite Kit?
Even when I rewrote the animation logic so that the animation distance would be as long as possible it was still very jerky with a few objects.
It seems that animating multiple UIImageViews with transparency isn't a very good idea.
I'm now using Sprite Kit and everything is smooth and running at 60fps. It wasn't a big change, as Sprite Kit is quite simular.
Related
I'm building a title sequence for our game, where each title is a roughly half-screen-sized retina image which I'm displaying using a UIImageView.
The title sequence has simple 3 stages as it gradually grows and fades in/out:
// 1. Fade in and grow
[UIView animateWithDuration:1.0f animations:^{
titleImageView.alpha = 1.0f;
titleImageView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} completion:^(BOOL finished) {
// 2. Stay opaque, grow a little more
[UIView animateWithDuration:2.0f animations:^{
titleImageView.transform = CGAffineTransformMakeScale(1.1f, 1.1f);
} completion:^(BOOL finished) {
// 3. Fade out, grow even further
[UIView animateWithDuration:2.0f animations:^{
titleImageView.alpha = 0.0f;
titleImageView.transform = CGAffineTransformMakeScale(1.3f, 1.3f);
} completion:nil];
}];
}];
At the start of each animation stage, there's a stutter as a frame or two is dropped. It's especially noticeable on older hardware such as iPhone 4 and iPad 3, but it's even noticeable on an iPad Air, which is surprising.
Some deductions:
It's got nothing to do with the loading of the UIImage itself, because I've tried pre-loading the data and ensuring that the PNG has been decompressed. Also the stutter happens at every stage of the animation, even after it has been on screen for a while.
I used the Time Profiler instrument and noticed that CA seemed to be copying around PNG data in the background each time it stuttered, although I'm not sure why / what for. Surely the CALayers shouldn't need to re-create any image data on CPU each time the transform is changed?
EDIT: Also note that I tried it without animations (just setting those transform properties, also without alpha changes), and got the same results.
Also note that I have some OpenGL ES graphics going on in the background (it's a game with UIKit controls in the foreground), but that hasn't caused problems in the past...
So, it's slightly ugly, but I solved it with two approaches simultaneously:
I converted the cascading UIView animations into a single CALayer keyframed animation so that they're continuous rather than stopping at each stage, calling the completion handler, and starting a new animation. Perhaps this stops them from "settling" and re-rendering in their new scale, as Brian Nickel pointed out.
But this didn't prevent the very first stutter as it showed each new UIImage. To solve this, I created all UIImageViews (6 of them in total) up-front, added them to the superview, and set their alphas to zero. The images aren't huge, and there aren't too many of them, so this seemed okay.
I have a view which users can key in some values into six textview boxes, which later get saved into a plist. My next view has a imageview which has a circle I designed. I am want to animate this circle with a 360 rotation based on how many textfields have values.
If the user types values for one textfield it goes at 1x speed.
If the user types values for two textfield it goes at 2x speed.
etc.
Can anyone please tell me how I can create this clockwise rotated animation of my imageView with a speed which is set based on the textfields which have values. All values are stored within the p.list. The animation needs to be on infinite too.
Thanks Guys,
You use the animateWithDuration: animations: completion: to animate UIView objects.
It is used like this:
[UIView animateWithDuration:duration animations:^(void)animations
completion:^(BOOL finished)completion];
Inside the animations block you enter the animation you want like for example in your case
imageView.transform = CGAffineTransformMakeRotation(M_PI*2); // This will rotate it 360 degrees
And to make it infinite you can add this method inside a function and in the completion block you call that function to make it infinite.
This is the result of what I want to achieve:
(source: mouseaddict.com)
I have static images for my app, but i want to add motion to them.
Imagine a meter. It has a needle that is locked at the bottom (at 6:00 on a round-clock-style-face) and whose needle-top swivels from left to right in an arc. the arc (zero) starts with the needle pointed toward (roughly) the 9:30 position on a round-clock-style-face and the top-most part of that arc should end at roughly 2:30 on a round-clock-style-face.
I have several graphical elements 1) the round meter face (png) and 2) the verticaly oriented png file of a meter needle. I need to cause the needle to move within the round face in an arc'ing pattern.
So, assuming the two requirements above, what is the best way to swivel the needle within the meter using animation?
I have seen this: Speedometer -- but is this the best way? My main issue is that I want the needle locked at the bottom and move left-to-right only a small amount..as WELL as the fact that there is little (actually no) documentation on it. Things like "calculateDeviationAngle" are unexplained.
Also the Speedometer seems to move a large amount and the pivot point on that example would not work...(i dont think)
I would like to call this with a command like:
[self moveNeedleToPosition:degreeOrClockFaceNumber] and have it move from it's current position to a bit past the degreeOrClockFaceNumber and bounce back to rest at degreeOrClockFaceNumber.
TIA
I did the following: I broke the image up into several pieces. The outline (black) portion, the backgrounds and the needle. Each piece having the same exact "footprint" (shapes).
Assuming theImageView is the imageView for the needle (layered on top of the other two imageViews), I created the following function:
-(void) rotateNeedle:(UIImageView *)theImageView toAngle:(float) theAngle
{
[UIView animateWithDuration:0.5
delay: 0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
theImageView.transform =CGAffineTransformMakeRotation((M_PI / 180) * (theAngle + 10));
}
completion:^(BOOL finished){
//
[UIView animateWithDuration:0.5
animations:^{
theImageView.transform = CGAffineTransformMakeRotation((M_PI / 180) * theAngle);
}];
}];
}
The first animation in the block performs the "shoot-past" portion of the animation...ie: moving 10 degrees past the actual value. The second animation in the completion block then "bounces" the needle back to the correct value. The speed of these animations create the physics effect. It works quite well for me.
For my application, I am trying to implement a character who displays a walking animation when walking and displays a jumping animation when in the air.
Using Cocos2D, I've created a simple animation. The problem is that the I am using CCRepeatForever and I cannot seem to stop the animation or switch it once I set it off.
Steffen Itterheim's book discussed some animation in the chapter regarding sprites. The way he created an animation was to add multiple sprite frames to a CCAnimation object and then run it with CCRepeatForever.
I'm currently looking at two possiblities:
1) Create multiple sprites and add them to a CCArray and then loop through the multiple sprites.
2) Find an alternate solution to CCRepeatForever and find a way to stop the animation or switch to a different animation.
The problem with number 1 is that I cannot figure out a way to render a selective sprite to the stage. It seems to be that the only way to add a sprite to the screen is to use [self addChild:mySprite]; This is limiting and problematic if I want to switch between multiple sprites. Is there a way to selectively render a sprite?
The problem for number 2 is that there seems to be no alternate to CCRepeatForever and the other animation classes are not sufficient for running the animation.
Thank you!
Assuming you assign a tag to the action like the following.
CCSprite *sprite = [CCSprite spriteWithFile:#"image.png"];
CCRotateBy *spinAction = [CCRotateBy actionWithDuration:1 angle:90];
CCRepeatForever *spinForever = [CCRepeatForever actionWithAction:spinAction];
[spinForever setTag:ANIMATION_TAG];
[sprite runAction:spinForever];
you can stop the animation by calling
[sprite stopActionByTag:ANIMATION_TAG];
I am trying out different looks of a little game I am writing to play with animation on iOS.
My goal is this to have a grid of tiles which based on gameplay changes the display of each tile to one of a set of images. I'd like each tile (up to 24x24) to flip around when its face changes. As the game progresses, more and more tiles need to be flipped at the same time. In the first implementation where I tried to flip them simultaneously the animation got very jerky.
I changed my approach to not flip them all at once, but just a few at a time, by scheduling the animation for each tile with a slightly increasing delay per tile, so that when say the 10th tile starts animating, the first one is already done. It takes little while longer for the whole process to finish, but also leads to a nice visual ripple-effect.
However, one problem remains: At the beginning of a game move, when the player picks a new color, it takes a few fractions of a second on the device, before the animation starts. This gets worse as the game progresses and more flips need to be scheduled per move, up to the point where the animation seems to hang and then completes almost instantly without any of the frames in between being actually discernible.
This is the code (in my UIView game grid subclass) that triggers the flipping of relevant tiles. (I removed an optimization that skips tiles, because it only matters in the early stages of the game).
float delay = 0.0f;
for (NSUInteger row=0; row<numRows; row++) {
for (NSUInteger col=0; col<numCols; col++) {
delay += 0.03f;
[self updateFlippingImageAtRow:row col:col delay:delay animated:YES];
}
}
The game grid view has an NSArray of tile subviews which are addressed using the row and col variables in the loop above.
updateFlippingImageAtRow:col:delay:animated is a method in my FlippingImageView (also a subclass of UIView) boils down to this (game logic omitted):
-(void)animateToShow:(UIImage*)image
duration:(NSTimeInterval)time
delay:(float)delay
completion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:time
delay:delay
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
forView:self
cache:YES];
self.frontImage = image;
}
completion:completion
];
}
Works fine, however, I conclude from the Instruments measuring which tells me that my time is spent in the animation block, that as the game goes on and the number of tiles to flip goes up, that the number of animations that get scheduled at the very beginning of the operation is a problem, and that the system then tries to catch up by dropping frames.
Any suggestions how to improve the performance of this? The exact timing of the animation is not really important.
You can think about doing this with CoreAnimation and CALayers instead of UIViews. It is incredebly powerful and optimized framework.
It's not an easy thing, you'll have to recode at least some of your classes (view hierarchy and hit tests are the first things that come to my mind), but it's worth a try and it's rather painless process, because CALayer is very similar to UIView.
Next step is OpenGL. It definitely can operate several hundreds of objects in realtime, but it requires much more work to do.
You might want to try using CoreAnimation instead. One way to do the flip animation would be:
Create a CABasicAnimation that animates the first half of the flip (rotation along the y axis).
In the delegate method animationDidStop:finished: you set the new image and then create a new animation that animates the second half.
You simply apply the animation to the layer property of your view.
Note that you can use setValue:forKey: to "annotate" an animation (remember what object the animation is about). When you add an animation to a layer it gets copied, not retained, so you can't identify it by simply comparing pointer values.