how can I use animation in cocos2d? - iphone

I am trying to develop a Roulette game for iPhone. How can I animation (spin) the Roulette board?

It's quite simple in Cocos2D:
[sprite runAction:[RotateBy actionWithDuration:dur angle:360]];
to make one turn or
id action = [RepeatForever actionWithAction: [RotateBy actionWithDuration:dur angle:360]];
[sprite runAction:action];
and
[sprite stopAction:action];
if you need to rotate continuously.
Don't forget to make sure that sprite transformAnchor is set to center of the image. And I guess next question should arise - how to make it stop smoothly ;)
More on actions: http://lethain.com/entry/2008/oct/03/notes-on-cocos2d-iphone-development (it's for older version, so it uses deprecated 'do' instead of 'runAction')

I have no idea how to do this in cocos2d (or even what that is), but you can do this using Core Animation either with CALayers or UIViews. Probably the simplest way would be to create a UIImageView containing an image of your roulette wheel and animate that.
To accomplish this, first set up your UIImageView by initializing it with your roulette wheel image. When you want the wheel to spin, use the following code:
CATransform3D rotationTransform = CATransform3DMakeRotation(1.0f * M_PI, 0, 0, 1.0);
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
rotationAnimation.toValue = [NSValue valueWithCATransform3D:rotationTransform];
rotationAnimation.duration = 0.25f;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 10;
[rotatingImage.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
assuming that rotatingImage is your UIImageView.
In this example, the wheel would rotate 5 times, with each rotation taking 0.5 seconds. The rotations are split in half because Core Animation will go to the next closest state, so the most you can rotate something is a half rotation before the animation wants to rotate in the other direction. That is, the pi radian (180 degree) rotation here goes a half-circle, but if you used (1.5f * pi) for your rotation angle, it would only go a quarter-circle. Likewise, if you used (0.999f * pi), the circle would rotate in a clockwise manner.
You'll want to implement acceleration and deceleration of your wheel, and for those a CAKeyframeAnimation would take the place of the CABasicAnimation in this example.

You can rotate a view, by some number of radians, regardless of whether it is less than a full rotation or many multiples of a full rotation, without having to split the rotation into pieces. As an example, the following code will spin a view, once per second, for a specified number of seconds. You can easily modify it to spin a view by a certain number of rotations, or by some number of radians.
- (void) runSpinAnimationWithDuration:(CGFloat) duration;
{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1.0;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[myView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}

There are a couple of ways you can do it.
If you have the frame-by-frame animation for the wheel, check out AtlasDemo (part of cocos2d distribution).
Otherwise, take a look at Sprite's RotateBy: method.

Related

Stop CABasicAnimation at specific point

I'm using a rotation animation created with CABasicAnimation. It rotates a UIView over 2 seconds. But I need to be able to stop it when the UIView is touched. If I remove the animation the view is in the same position as before the animation started.
Here's my animation code:
float duration = 2.0;
float rotationAngle = rotationDirection * ang * speed * duration;
//rotationAngle = 3*(2*M_PI);//(double)rotationAngle % (double)(2*M_PI) ;
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: rotationAngle ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.delegate = self;
[self.view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
How can I stop the UIView's rotation right where it is, when it's touched? I know how to manage the touch part, but I can't figure out how to stop the view at the animation's current angle.
Solution:
I solved the problem by getting the angle of the presentation layer, removing the animation and setting the view's transform. Here's the code:
[self.view.layer removeAllAnimations];
CALayer* presentLayer = self.view.layer.presentationLayer;
float currentAngle = [(NSNumber *)[presentLayer valueForKeyPath:#"transform.rotation.z"] floatValue];
self.view.transform = CGAffineTransformMakeRotation(currentAngle);
Good question! For this, it's helpful to know the Core Animation architecture.
If you check out the diagram in the Core Animation Programming Guide that describes the Core Animation Rendering Architecture, you can see that there's three trees.
You have the model tree. That's where you set the values of what you want to happen. Then there's the presentation tree. That's what is pretty much happening as far as the runtime is concerned. Then, finally is the render tree. That's what the user sees.
In your case, you want to query the values of the presentation tree.
It's easy to do. For the view that you have attached the animation, get the layer and for that layer, query the presentationLayer's values. For example:
CATransform3D myTransform = [(CALayer*)[self.view.layer presentationLayer] transform];
There's no way to "pause" an animation mid flow. All you can do is query the values, remove it, and then re-create it again from where you left off.
It's a bit of a pain!
Have a look at some of my other posts where I go into this in a bit more detail, e.g.
Restoring animation where it left off when app resumes from background
Don't forget also that when you add an animation to a view's layer, you aren't actually changing the underlying view's properties. So what happens? We'll you get weird effects where the animation stops and you see the view in it's original position.
That's where you need to use the CAAnimation delegates. Have a look at my answer to this post where I cover this:
CABasicAnimation rotate returns to original position
You need to set the rotation to the rotation of the presentationLayer and then remove the animation from the layer. You can read about the presentation layer in my blog post about Hit testing animating layers.
The code to set the final rotation would be something like:
self.view.layer.transform = [(CALayer*)[self.view.layer presentationLayer] transform];
[self.view.layer removeAnimationForKey:#"rotationAnimation"];

How to animate the pendulum of a clock where the pendulum starts in the 6 o'clock position?

I try to animate the motion of a pendulum which is starting at the 6 o'clock position, then swings a few times and then stops again in the 6 o'clock position.
I already know how to change the anchor point of a layer and how to rotate it.
I have a method to rotate a layer.
- (void)spinLayer:(CALayer *)inLayer duration:(CFTimeInterval)inDuration direction:(int)direction forPartsOfPi:(float) degrees withReverse:(bool) reverse andRepeatCount:(float) repeatCount;
I thought it might be best to split the movement of the pendulum in 3 "swings" The first swing is from the starting position to lets say 9 o'clock position. So this is the first time i call the Method.
[self spinLayer:layer duration:0.4 direction:SPIN_CLOCK_WISE forPartsOfPi:M_PI/2 withReverse:NO andRepeatCount:0];
Then, in the animationDidFinish Delegate i call the method once again to let it do the several swings.
For the rotation of the Pendulum is now double the length of the first swing i call:
[self spinLayer:layer duration:0.8 direction:SPIN_COUNTERCLOCK_WISE forPartsOfPi:M_PI withReverse:YES andRepeatCount:4];
after that i want the pendulum to return to 6 o'clock position so i call:
[self spinLayer:layer duration:0.4 direction:SPIN_COUNTERCLOCK_WISE forPartsOfPi:M_PI/2 withReverse:NO andRepeatCount:0];
Here is the implementation of my Method:
- (void)spinLayer:(CALayer *)inLayer duration:(CFTimeInterval)inDuration direction:(int)direction forPartsOfPi:(float) degrees withReverse:(bool) reverse andRepeatCount:(float) repeatCount
{
CABasicAnimation* rotationAnimation;
// Rotate about the z axis
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
// Rotate 180 degress, in direction specified
rotationAnimation.toValue = [NSNumber numberWithFloat: degrees * direction];
// Perform the rotation over this many seconds
rotationAnimation.duration = inDuration;
rotationAnimation.autoreverses = reverse;
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.removedOnCompletion = FALSE;
rotationAnimation.repeatCount = repeatCount;
rotationAnimation.delegate = self;
// Set the pacing of the animation
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// Add animation to the layer and make it so
[inLayer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
So the problem is, that the second swing doesn't start at the end of the first swing but on the 6 o'clock position.
Can anybody set me straight how to do this?
Thx in advance
Mav
The animation starts from the value of the model layer's property. The animation only affects the presentation layer. So after the first animation is done, the model layer still points in the 6 o'clock orientation.
What you should do is in your spinLayer method, set the layer's transform to the final orientation transform:
layer.transform = CATransform3DMakeRotation(radians, 0, 0, 1);

executing code (selector) while animating CABasicAnimation

CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
rotationAnimation.toValue = [NSNumber numberWithFloat: 2*M_PI];
rotationAnimation.duration = 1.0;
[self.layer addAnimation:rotationAnimation forKey:#"rotationAnimation1"];
I would like to execute code when the degree is M_PI, in other words I have two buttons on the UIView and I want to change the labels of them while rotating the UIView and I don't want the user to feel the the buttons are changing in front of him so , best way is to execute the label changing while the UIView is rotating to half of the way.
Help me
Split your rotationAnimation in two steps, each of M_PI radians rotation.
When the first animation is done (i.e., from the animation's delegate animationDidStop:finished: function) start the second while also changing the label.
Or you could simply start the second animation after 0.5 sec.

Non-Smooth animation with Core Animation

This is a weird request, but I'm using Core Animation (CALayers), and I want my animation to be choppy and non-smooth. I want an image I set up to rotate like a second hand on a clock. Here's my code:
UIImage *arrowImage = [UIImage imageNamed:#"arrow.jpg"];
CALayer *arrow = [CALayer layer];
arrow.contents = (id)arrowImage.CGImage;
arrow.bounds = CGRectMake(0, 0, 169.25, 45.25);
arrow.position = CGPointMake(self.view.bounds.size.width / 2, arrowImage.size.height / 2);
arrow.anchorPoint = CGPointMake(0.0, 0.5);
[self.view.layer addSublayer:arrow];
CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim1.fromValue = [NSNumber numberWithFloat:0];
anim1.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
anim1.duration = 4.0;
[arrow addAnimation:anim1 forKey:#"transform"];
It produces a gliding motion, which I don't want. How do I get around this?
Any help is appreciated.
If you want it to be really choppy, don't use Core Animation at all. On the other hand, if you want something somewhere in between those two extremes, don't use linear media timing. Instead, you might want to try kCAMediaTimingFunctionEaseIn so that the animation accelerates slightly as the hand moves.
The simple way to do this would be to simply apply a transform to your view. The second hand would snap from one position to the next. Just change the rotation by 360/60 = 6 degrees for each second.
If you want the second-hand to do an animation for each tick, you could use a very fast UIView block-based animation. (say with a 1/15 second duration or so.)
Take a look at the UIView class methods who's names start with animateWithDuration.
Something like this:
- (void) moveSecondHand;
{
seconds++;
angle = M_PI*2*seconds/60 - M_PI/2;
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
[UIView animateWithDuration: 1.0/15
animations: *{
secondHand.transform = transform
}];
}
That's about all it would take. You're trigger that code with a timer once a second. By default animations use ease-in, ease-out timing, which models physical movement pretty well. Try different durations, but 1/15 is probably a good starting point (you want it fast, but not too fast to see.)
If you want a wobble to your animation you will need to get much fancier, and create an animation group that first moves it by the full amount, and then does a repeating animation that overshoots the stopping point by a small amount and then goes back.

iPhone Development - Basic drawing and animation question

I have a case where i'm drawing shapes, e.g. a Triangle (Points A, B, C). Now i want to animate it so that Points A, B, C move towards Point X, Y, Z. (Lets just say on button click)
I'm using a drawRect: method in my custom view for drawing. I don't want my view to move, i rather want my drawing to move (because i have multiple drawings).
Any pointers? Any relative articles?
Regards,
Mustafa
Core Animation is a good solution for this type of thing. You have CAShapeLayer that allows you to draw shapes according to a path and you can animate using a basic animation or a keyframe animation. You can do something like this in your button click:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
[animation setFromValue:[NSValue valueWithCGPoint:CGPointMake(0.0, 0.0)]];
[animation setToValue:[NSValue valueWithCGPoint:CGPointMake(320.0, 480.0)]];
[animation setDuration:2.0f];
[shapeLayer addAnimation:animation forKey:#"positionAnimation"];
This will animate the layer from point 0.0, 0.0 (upper left hand corner) to 320.0, 480.0 (lower right hand corner) over the course of two seconds. When you add the animation to your layer, it will start playing immediately. If you are looking to rotate the animation (wasn't sure from the post), you can do this:
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation
animationWithKeyPath:#"transform.rotation.z"];
[rotationAnimation setFromValue:DegreesToNumber(0)];
[rotationAnimation setToValue:DegreesToNumber(360)];
[rotationAnimation setDuration:2.0f];
[rotationAnimation setRepeatCount:10000]; // keep spinning
[shapeLayer addAnimation:rotationAnimation forKey:#"rotate"];
The DegreesToNumber is a helper function that converts degrees to radians and returns an NSNumber object:
CGFloat DegreesToRadians(CGFloat degrees)
{
return degrees * M_PI / 180;
}
NSNumber* DegreesToNumber(CGFloat degrees)
{
return [NSNumber numberWithFloat:
DegreesToRadians(degrees)];
}
There are lots of articles on the web about Core Animation, but this should get you started. Let me know if you need clarification.