Sorry for my poor english.
In my work, We made a app which show a high resolution image into a UIScrollVIew. Besides, the user can play a MP3 that animate the position and zoom of UIScrollView from one second with a specific duration showing the detail of image that MP3 is talking about. Currently, the audio play from second 0 to the end sequently. User can´t interactuate with UIScrollView when the MP3 is playing
Now we want that the user can change the current time when is playing, I´m not sure how calculate the position when the user select a second placed in the midst of one animation.
Example:
Second 1: Duration 4; Second 8: Duration 2; Second 20 Duration 10; Second 31 Duration 3;
If the user select the second 24, we would want animate during 6 seconds, but keeping in mind the original position that UIScrollView should have since second 20 to 24 from original animation.
We are using uiview animation (animateWithDuration) with zoomToRect (animated:NO) method from UIScrollView
I´m not sure how calculate those intermediate values.
Thanks.
I do not know, whether I really get you right or wrong. But you can set the animations progress to a progress value that is in relation to the start point and the total duration of the animation. I. e. in your case
float animationStartPoint = 20.0; // Start of the animation
float duration = 10.0; // Its duration
float partialStartPoint = 24.0; // user selected start point
NSAnimation *animation = …;
…
animation.currentProgress = (partialStartPoint-animationStartPoint)/duration;
[animation startAnimation];
Related
Is there a way to achieve what the timeOffset property of an animation does, starting the animation at some point in time, but without the "wrapping" behaviour?
I have two animations (in two different layers):
// This is the animation of the stroke of a circle
let progressAnim = CABasicAnimation(keyPath: "strokeEnd")
progressAnim.duration = duration
progressAnim.fromValue = 0
progressAnim.toValue = 1
progressAnim.fillMode = kCAFillModeBackwards
progressAnim.timeOffset = elapsed
// This is the animation of a pointer that follow the same circle as above
let arrowAnim = CAKeyframeAnimation(keyPath: "position")
arrowAnim.duration = duration
arrowAnim.rotationMode = kCAAnimationRotateAuto
arrowAnim.path = arrowPath.CGPath
arrowAnim.fillMode = kCAFillModeBackwards
arrowAnim.timeOffset = elapsed
This starts the animations at the wanted progress, but when it reaches what is supposed to the the end, it starts over and lasts for the remaining duration. I realize this is how timeOffset is specified to work, but I was hoping that it is possible to somehow achieve the same without the wrap.
Instead of the timeOffset hack, you can calculate the proper fromValue and corresponding duration.
e.g.
progressAnim.duration = duration - elapsed
progressAnim.fromValue = elapsed / duration
Yes, it's possible. You'll need to use the same settings that you use to pause and resume an "in-flight" animation. The documentation is pretty weak, and the properties of the animation are confusing as hell. Every time I have to work with pausing and resuming animation I have to spend like half a day figuring it out again, and then a week later, I've forgotten how to do it again. It's been at least 6 months since I've dealt with it, so I really forget how to do it.
I have a demo project on github that shows how to pause and resume an animation, and even to use a slider to move an animation forward and backward. I suggest you take a look at that. It has all the pieces you need:
Keyframe view animation demo on Github
I think I figured it out. By trial and error, it seems to work when I set beginTime of the animation to a time in the past, like this:
let beginTime = CACurrentMediaTime() - offset
// Set the beginTime of the progress animation
progressAnim.beginTime = beginTime
// Set the begintTime of the arrow animation
arrowAnim.beginTime = beginTime
This seems to have the desired effect.
I am trying to create a sprite animation of a character firing a gun in Cocos2d. I am struggling to find a way to instantiating and start moving the bullet sprite at the appropriate frame of the load/aim/fire animation of my character.
For example, my character goes through an 12 frame animation to fire his gun and the bullet should be released at frame 7.
Can anyone help?
Thanks in advance
Al
Here is one way of doing this, a similar scenario in a game i am developping. I want , on frame 10 of 16, to schedule an 'attackTurnAround' method (to animate damage animations, hurt on the victim, etc...). Dont waste time on 'frame notifications', sometimes a frame will be skipped, and you will end up with no notifications.
caveat : timing is key. Before getting at this point, i preload EVERYTHING that will happen in 'attackTurnAround' , namely the hurt (or dead) animation, the damage animation, the soundFx that will be heard when the sword hits on frame 10, etc .....
BattleAnimation *ba = [self attackerAttackAnination];
_attackerAttackSprite = ba.firstSprite;
[_attackerNode addChild:_attackerAttackSprite];
CCActionAnimate *anim = [CCActionAnimate actionWithAnimation:ba.animation];
id endAttack = [CCActionCallBlock actionWithBlock:^{
[self endAttack];
}];
id seq = [CCActionSequence actions:anim, endAttack, nil];
_attackerAttackSprite.visible = YES;
_attackerIdleSprite.visible = NO;
[_attackerAttackSprite runAction:seq];
[self scheduleOnce:#selector(attackTurnAround) delay:9. * ba.duration / 16.];
Here's my code:
-(void)play {
animation = [CAKeyframeAnimation animationWithKeyPath: #"contents"];
for (UIImage *image in folder.images) {
[images addObject:(id)image.CGImage];
}
animationArray = [images subarrayWithRange:NSMakeRange(selected, selectedEnd - selected)];
timeBetweenAnimation = 1.0/framesPerSecond;
totalAnimationTime = folder.images.count * timeBetweenAnimation;
animation.duration = totalAnimationTime;
animation.values = animationArray;
animation.delegate = self;
beforeAdd = CACurrentMediaTime();
[displayImage.layer addAnimation:animation forKey:#"contents"];
afterAdd = CACurrentMediaTime();
}
-(void)animationDidStart {
afterStart = CACurrentMediaTime();
}
I am using Core Animation to animate an array of images and I want to get the current image in the animation when I pause the image. I realize that a method of doing this would be to get the image from "animationArray" using timing ratios (played time / total duration time), but I can't capture the begin time. I've tried capturing the begin time, using CACurrentMediaTime(), before and after the call to addAnimation:forKey: and also after animation did start, but it's innaccurate (especially when I have a large array of images). With a larger array of images, the "beforeAdd" and "afterAdd" times are at least a second off. Also, "afterStart" is anywhere between .04 and .08 seconds off independent of the size of my images array.
In the worst case scenario, I could use the "afterStart" begin time. However, if I'm trying to animate 100 images in 1 second, getting the image using this method would be inaccurate by 4 frames. Therefore, I either need a method that doesn't use timing in order to get the current frame of the animation, or I need a way to get a more accurate begin time. Is this possible?
NOTE: I don't want to use UIImageView animation to do this because that animation technique doesn't have smooth transitioning between frames. If there were a way to have smooth transitioning between frames using UIImageView animation I would reconsider, although I'm pretty sure there will still be an issue when calculating the begin time.
Perhaps this question have been repeated many time, but I couldn't found helpful material. Also this is my first project in cocos2D, i want to implement the ProgressBar, CCProgressTimer in cocos2D. I have two sprites, first is moving and the second one is the player (to which you can move), If user successfully eat the first moving object then the progress should be incremented else if it misses then the progress will be decremented. I need your help. Thanks in advance.
Here is my code I used for rounded CCProgressTimers (it looks like clock). You possibly need to have a background sprite and a "movable" sprite above background sprite.
CCSprite *movableSprite = [CCSprite spriteWithFile:#"health100.png"];
CCProgressTimer *healthBar = [CCProgressTimer progressWithSprite:movableSprite];
healthBar.type = kCCProgressTimerTypeRadial; // This is for round progress timer. Possible value for horizontal bar will be kCCProgressTimerTypeHorizontalBarLR
healthBar.midpoint = ccp(0,0.5); // Here is where all magic is
healthBar.barChangeRate = ccp(1, 0); // If you need horizontal bar progress play with these parameters.
// Here we will start an animation process.
// You can do it without animation just setting up healthBar.progress = 45.0f; (45%)
[healthBar runAction:[CCProgressFromTo actionWithDuration:2.0f from:0.0f to:100.0f]];
healthBar.position = ccp(100, 100); // It's your position
[self addChild:healthBar];
I have a custom button which is my own subclass of UIButton. It looks like a circled arrow, starting at some angle startAngle end finishing at some endAngle=startAngle+1.5*M_PI.
startAngle is a button's property which is then used in its drawRect: method.
I want to make this arrow to continuously rotate by 2Pi around its center when this button is pressed. So I thought that I can easily use [UIView beginAnimations: context:] but apparently it can't be used as it doesn't allow to animate custom properties. CoreAnimation also doesn't suite as it only animates the CALayer properties.
So what is the easiest way to implement an animation of a custom property of UIView subclass in iOS?
Or maybe I missed something and it is possible with already mentioned techniques?
Thank you.
Thanks to Jenox I have updated animation code using CADisplayLink which seems to be really more correct solution than NSTimer. So I show the correct implementation with CADisplayLink now. It is very close to the previous one, but even a bit simpler.
We add the QuartzCore framework to our project.
Then we put the following lines in the header file of our class:
CADisplayLink* timer;
Float32 animationDuration; // Duration of one animation loop
Float32 initAngle; // Represents the initial angle
Float32 angle; // Angle used for drawing
CFTimeInterval startFrame; // Timestamp of the animation start
-(void)startAnimation; // Method to start the animation
-(void)animate; // Method for updating the property or stopping the animation
Now in implementation file we set the values for duration of the animation and the other initial values:
initAngle=0.75*M_PI;
angle=initAngle;
animationDuration=1.5f; // Arrow makes full 360° in 1.5 seconds
startFrame=0; // The animation hasn't been played yet
To start the animation we need to create the CADisplayLink instance which will call method animate and add it to main RunLoop of our application:
-(void)startAnimation
{
timer = [CADisplayLink displayLinkWithTarget:self selector:#selector(animate)];
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
This timer will call animate method every runLoop of the application.
So now comes the implementation of our method for updating the property after each loop:
-(void)animate
{
if(startFrame==0) {
startFrame=timer.timestamp; // Setting timestamp of start of animation to current moment
return; // Exiting till the next run loop
}
CFTimeInterval elapsedTime = timer.timestamp-startFrame; // Time that has elapsed from start of animation
Float32 timeProgress = elapsedTime/animationDuration; // Determine the fraction of full animation which should be shown
Float32 animProgress = timingFunction(timeProgress); // The current progress of animation
angle=initAngle+animProgress*2.f*M_PI; // Setting angle to new value with added rotation corresponding to current animation progress
if (timeProgress>=1.f)
{ // Stopping animation
angle=initAngle; // Setting angle to initial value to exclude rounding effects
[timer invalidate]; // Stopping the timer
startFrame=0; // Resetting time of start of animation
}
[self setNeedsDisplay]; // Redrawing with updated angle value
}
So unlike case with NSTimer we now don't need to calculate the time interval at which to update the angle property and redraw the button. We now only need to count how much time has passed from the start of animation and set the property to value which corresponds to this progress.
And I must admit that animation works a bit more smoothly than in case of NSTimer.
By default, CADisplayLink calls the animate method each run loop. When I calculated the frame rate, it was 120 fps. I think that it is not very efficient so I have decreased the frame rate to just 22 fps by changing the frameInterval property of CADisplayLink before adding it to mainRunLoop:
timer.frameInterval=3;
It means that it will call the animate method at first run loop, then do nothing next 3 loops, and call on the 4-th, and so on. That's why frameInterval can be only integer.
Thanks again to k06a for suggestion to use timer. I've made some study about working with NSTimer and now I want to show my implementation, since I think, it can be useful for others.
So in my case I had a UIButton subclass which was drawing a curved arrow which started from some angle Float32 angle; which is the main property from which the drawing of whole arrow starts. That means that just changing the value of angle will rotate whole arrow. So to make animation of this rotation I put the following lines in the header file of my class:
NSTimer* timer;
Float32 animationDuration; // Duration of one animation loop
Float32 animationFrameRate; // Frames per second
Float32 initAngle; // Represents the initial angle
Float32 angle; // Angle used for drawing
UInt8 nFrames; // Number of played frames
-(void)startAnimation; // Method to start the animation
-(void)animate:(NSTimer*) timer; // Method for drawing one animation step and stopping the animation
Now in implementation file I set the values for duration and frame rate of my animation and the initial angles for drawing:
initAngle=0.75*M_PI;
angle=initAngle;
animationDuration=1.5f; // Arrow makes full 360° in 1.5 seconds
animationFrameRate=15.f; // Frame rate will be 15 frames per second
nFrames=0; // The animation hasn't been played yet
To start the animation we need to create the NSTimer instance which will call method animate:(NSTimer*) timer every 1/15 seconds:
-(void)startAnimation
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.f/animationFrameRate target:self selector:#selector(animate:) userInfo:nil repeats:YES];
}
This timer will call animate: method immediately and then repeat it every 1/15 second until it will be manually stopped.
So now comes the implementation of our method for animating a single step:
-(void)animate:(NSTimer *)timer
{
nFrames++; // Incrementing number of played frames
Float32 animProgress = nFrames/(animationDuration*animationFrameRate); // The current progress of animation
angle=initAngle+animProgress*2.f*M_PI; // Setting angle to new value with added rotation corresponding to current animation progress
if (animProgress>=1.f)
{ // Stopping animation when progress is >= 1
angle=initAngle; // Setting angle to initial value to exclude rounding effects
[timer invalidate]; // Stopping the timer
nFrames=0; // Resetting counter of played frames for being able to play animation again
}
[self setNeedsDisplay]; // Redrawing with updated angle value
}
The first thing I want to mention is that for me just comparison line angle==initAngle didn't work due to rounding effects. They don't are exactly the same after full rotation. That's why I check if they are just close enough and then set angle value to initial value to block small drift of angle value after many repeated animation loops.
And to be totally correct, this code must also manage conversion of angles to always be between 0 and 2*M_PI with something like this:
angle=normalizedAngle(initAngle+animProgress*2.f*M_PI);
where
Float32 normalizedAngle(Float32 angle)
{
while(angle>2.f*M_PI) angle-=2.f*M_PI;
while(angle<0.f) angle+=2.f*M_PI;
return angle
}
And another important thing is that, unfortunately, I don't know any easy way to apply easeIn, easeOut or other default animationCurves to this kind of manual animation. I think it doesn't exist. But it is, of course, possible to do it by hands. The line standing for that timing function is
Float32 animProgress = nFrames/(animationDuration*animationFrameRate);
It can be treated as Float32 y = x;, that means linear behavior, a constant speed, which is the same as speed of time. But you can modify it to be like y = cos(x) or y = sqrt(x) or y = pow(x,3.f) which will give some nonlinear behavior. You can think it yourself taking into account that x will go from 0 (start of animation) to 1 (end of animation).
For better looking code it is better to make some independent timing function:
Float32 animationCurve(Float32 x)
{
return sin(x*0.5*M_PI);
}
But now, since the dependence between animation progress and time is not linear, it's safer to use the time as indicator for stopping the animation. (You might want for example to make your arrow to make 1.5 full turns and than rotate back to the initial angle, that means your animProgress will go from 0 to 1.5 and than back to 1 while timeProgress will go from 0 to 1.)
So to be safe we separate time progress and animation progress now:
Float32 timeProgress = nFrames/(animationDuration*animationFrameRate);
Float32 animProgress = animationCurve(timeProgress);
and then check time progress to decide if should the animation stop:
if(timeProgress>=1.f)
{
// Stop the animation
}
By the way, if somebody knows some sources with list of useful timing functions for animation, I would appreciate if you share them.
Built in MacOS X utility Grapher helps a lot in visualizing the functions, so that you can see how your animation progress will depend on time progress.
Hope it helps somebody...
- (void)onImageAction:(id)sender
{
UIButton *iconButton = (UIButton *)sender;
if(isExpand)
{
isExpand = FALSE;
// With Concurrent Block Programming:
[UIView animateWithDuration:0.4 animations:^{
[iconButton setFrame:[[btnFrameList objectAtIndex:iconButton.tag] CGRectValue]];
} completion: ^(BOOL finished) {
[self animationDidStop:#"Expand" finished:YES context:nil];
}];
}
else
{
isExpand = TRUE;
[UIView animateWithDuration:0.4 animations:^{
[iconButton setFrame:CGRectMake(30,02, 225, 205)];
}];
for(UIButton *button in [viewThumb subviews])
{
[button setUserInteractionEnabled:FALSE];
//[button setHidden:TRUE];
}
[viewThumb bringSubviewToFront:iconButton];
[iconButton setUserInteractionEnabled:TRUE];
// [iconButton setHidden:FALSE];
}
}