I'm making an application, for the iPhone, which is a 2D game.
I have done all the spritesheets, but now i need to animate some of them particulary :
My game is a game where your hero should avoid some obstacles. The borders should move to give the impression that the game is more difficult by increasing the speed (so the hero looks like going faster and faster). I have my image of the border, but really don't know how to animate it dynamically, and when the end of the image is coming, put the top of the image to come after it.
Note : I know how to make a translation, to animate an image but here i need to animate it faster and faster, so to change dynamically the speed of the animation.
Thanks for your help !
Code or a UIImageView :
nuages_bas2 = [[UIImageView alloc] initWithFrame:CGRectMake(-100, 0, 160, 1000)];
UIImage * ImageNuages = [UIImage imageNamed:#"Menu_nuage.png"];
nuages_bas2.image = ImageNuages;
nuages_bas2.alpha = 0.0f;
[menu_principale addSubview:nuages_bas2];
[nuages_bas2 release];
Code for one of the animations :
- (void)AnimationNuagesBas2
{
nuages_bas2.alpha = 1.0f;
CABasicAnimation * nuagesbas2 = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
nuagesbas2.fromValue = [NSNumber numberWithFloat:480.0f];
nuagesbas2.toValue = [NSNumber numberWithFloat:-960.0f];
nuagesbas2.duration = 35.0f;
nuagesbas2.repeatCount = 10;
[nuages_bas2.layer addAnimation:nuagesbas2 forKey:#"nuagesbas2"];
}
First things first, if you're making a game, you probably should not be using UIKit unless it's something very simple. You should have a look at libraries like cocos2d
As for this question, you may want to look at CAKeyframeAnimation. I'll attempt to sketch out some code that will do something like this, but you'll probably want to modify it(Also, I don't have the means to test it).
Note, what follows below is a hack that consists of creating a keyframe animation where the object goes back and forth each time with a smaller duration.
- (void)AnimationNuagesBas2
{
nuages_bas2.alpha = 1.0f;
CAKeyframeAnimation * nuagesbas2 = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.y"];
float from = [NSNumber numberWithFloat:480];
float to = [NSNumber numberWithFloat:-960];
int repeatCount = 10;
float duration = 6;
float durationDecrease = 0.5;
float t = 0;
NSMutableArray * values = [NSMutableArray array];
NSMutableArray * times = [NSMutableArray array];
[values addObject:from];
[times addObject:[NSNumber numberWithFloat:0]];
for (int i = 0; i < repeatCount; i++){
t += duration/2;
[values addObject:to];
[times addObject:[NSNumber numberWithFloat:t]];
t += duration/2;
[values addObject:from];
[times addObject:[NSNumber numberWithFloat:t]];
duration -= durationDecrease;
}
[nuages_bas2.layer addAnimation:nuagesbas2 forKey:#"nuagesbas2"];
}
Another approach would be to set yourself as the delegate to the CABasicAnimation and set it with a repeatCount of 0. Then, every time it's done, you re-initiate it with a smaller duration.
Related
Progress so far:
So what I have at the moment is this:
(the green point represents the parent "BlankNode, adding children then rotating them around that node,
Im a bit stick how to get it work properly, for some reason they dont sit next to eachother but opposite (as showen in http://i.stack.imgur.com/w7QvS.png)
inGameLevel
myArc = [[Arcs alloc]initWithArcCount:myAmmountOfSprites];
[self addChild:myArc];
My wish is for the sprite.rotation to be slightly offset from the next loaded...here they are split...
(The diagram belows showing the arc shape I would like to load the sprites in)
**With one stick loaded, maybe its easier to spot the mistake
(if I load a second sprite it loads directly opposite to the previous and not at the expected angle incremented
In this version I have just loaded the stick and blanknode, positioned it using anchor points, Im confused how the rotation works... **
SKSpriteNode *blank = [[SKSpriteNode alloc]
///like the otherone
blank.zRotation=0;
blank.anchorPoint = CGPointMake(0.5, 0.5);
[self addChild:blank];
//set to 0 value so I can see what its natural state is (it is vertical and above the parent node)
//but this value will be incremented each time a new sprite is added
int rotationAmount = 0;
Rotation = Rotation-rotationAmount; //will increment
objectPic = [SKSpriteNode spriteNode....as normal
//use blank nodes anchorpoint
objectPic.anchorPoint = blank.anchorPoint;
//Rotation
objectPic.zRotation = Rotation;
float moveUp_donut = 0.3;
//"moveUp_donut" moving this value up moves the stick up
//and outward from the center
objectPic.anchorPoint =
CGPointMake(0.0,-moveUp_donut); //(0.0,-moveOutward);
[blank addChild:objectPic];
}
}
I have made an xcode project available for anyone interested to have a look at the problem, hopefully you can explain how to get the rotation working correctly.
at the moment it is just loading one sprite, so you might need to play with the setting,
myArc = [[Arcs alloc]initWithArcCount:addLotsOfSticks];
//and play with the rotation ammount
int rotationAmount = 3;
http://www.filedropper.com/rotationtest
Solution Found! see below:
🌸
A huge thanks to WangYudong for giving such a great answer!
I made a sample project and hope it can help. The algorithm is not base on your project, so make some change to fit your need.
Firstly, add a blank node to the middle of the scene:
self.blank = [[SKSpriteNode alloc] initWithColor:[SKColor greenColor]size:CGSizeMake(20, 20)];
self.blank.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:self.blank];
Then, create the stick:
- (SKSpriteNode *)newStick
{
SKSpriteNode *stick = [[SKSpriteNode alloc] initWithColor:[SKColor redColor]size:CGSizeMake(5, 100)];
return stick;
}
And given the amount of sticks, the radius (of the inner circle), the starting radian and ending radian, add a method:
- (void)loadStickArcWithStickAmount:(NSUInteger)amount radius:(CGFloat)radius startRadians:(CGFloat)startRad endRadians:(CGFloat)endRad
{
for (NSUInteger index = 0; index < amount; index++) {
SKSpriteNode *stick = [self newStick];
CGFloat halfStickLength = stick.size.height / 2;
CGFloat rotateRad = startRad + (endRad - startRad) / (amount - 1) * index;
stick.zRotation = M_PI_2 + rotateRad;
stick.position = CGPointMake((radius + halfStickLength) * cos(rotateRad),
(radius + halfStickLength) * sin(rotateRad));
[self.blank addChild:stick];
}
}
Some hints:
rotateRad divides radians of endRad - startRad.
M_PI_2 is an offset of zRotation.
Trigonometric maths calculates the position of sticks.
Both anchor points of blank node and stick remain default (0.5, 0.5).
Use the method:
[self loadStickArcWithStickAmount:27 radius:50.0 startRadians:M_PI endRadians:2*M_PI];
to achieve the following result:
I'm using CALayer, CAReplicatorLayer and CABasicAnimation to animate a bar chart into place. The bar chart is made of up "segments" which I render as small rectangles with a CALayer. Then I add them as a sublayers to CAReplicatorLayer in a loop. Finally, I add an animation for each small rectangle that drops the rectangle into its place in the chart.
Here is the final state of the animation:
And here is what it looks like in the middle of the animation:
Here's the code that makes it happen:
#define BLOCK_HEIGHT 6.0f
#define BLOCK_WIDTH 8.0f
#interface TCViewController ()
#property (nonatomic, strong) NSMutableArray * blockLayers; // blocks that fall from the sky
#property (nonatomic, strong) NSMutableArray * replicatorLayers; // replicator layers that will replicate the blocks into position
#end
#implementation TCViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
self.blockLayers = [NSMutableArray array];
self.replicatorLayers = [NSMutableArray array];
NSArray * dataBlocksData = #[#1,#2,#3,#1,#5,#2,#11,#14,#5,#12,#10,#3,#7,#6,#3,#4,#1];
__block CGFloat currentXPosition = 0.0f;
[dataBlocksData enumerateObjectsUsingBlock:^(NSNumber * obj, NSUInteger idx, BOOL *stop) {
currentXPosition += BLOCK_WIDTH + 2.0f;
if (obj.integerValue == 0) return;
// replicator layer for data blocks in a column
CAReplicatorLayer * replicatorLayer = [[CAReplicatorLayer alloc] init];
replicatorLayer.frame = CGRectMake(currentXPosition, 0.0f, BLOCK_WIDTH, 200.0f);
// single data block layer (must be new for each replicator layer. can't reuse existing layer)
CALayer * blockLayer = [[CALayer alloc] init];
blockLayer.frame = CGRectMake(0, 200-BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
blockLayer.backgroundColor = [UIColor whiteColor].CGColor;
[replicatorLayer addSublayer:blockLayer];
replicatorLayer.instanceCount = obj.integerValue;
replicatorLayer.instanceDelay = 0.1f;
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(0, -BLOCK_HEIGHT, 0); // negative height moves back up the column
[self.blockLayers addObject:blockLayer];
[self.replicatorLayers addObject:replicatorLayer];
}];
}
- (void)viewDidLayoutSubviews {
// add replicator layers as sublayers
[self.replicatorLayers enumerateObjectsUsingBlock:^(CAReplicatorLayer * obj, NSUInteger idx, BOOL *stop) {
[self.view.layer addSublayer:obj];
}];
}
- (void)viewDidAppear:(BOOL)animated {
// add animations to each block layer
[self.blockLayers enumerateObjectsUsingBlock:^(CALayer * obj, NSUInteger idx, BOOL *stop) {
// position animation
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
animation.fromValue = #(-300);
animation.toValue = #(0);
animation.duration = 1.0f;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
[obj addAnimation:animation forKey:#"falling-block-animation"];
}];
}
Finally, here's the catch. If you specify no timingFunction, a Linear function or an EaseIn function the animations don't fully complete. Like the blocks don't fall completely into place. It looks like they're close to finishing but not quite. Just a frame or two off. Swap out the timing function assignment in -viewDidAppear: and you'll get this weirdness:
Go ahead, try it out. I even tried specifying a function with control points (link) that are exactly like an EaseIn function:
// use control points for an EaseIn timing:
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.00 :1.0 :1.0];
// or simply specify the ease in timing function:
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
For some reason EaseOut and Default timing functions work fine but an ease in style function appears to take longer to complete, visually, perhaps because of the length of the curve of interpolation?
I have tried many things to narrow down the issue:
Not looping over these structures and using only 1 layer and 1 replicator layer with the same animation. By that I mean, animate just one column. No luck. Looping isn't the issue.
Put the creation, sublayer-adding and animation-adding code all in -viewDidAppear. Didn't seem to make a difference if the animations were added earlier or later in the view's life cycle.
Using properties for my structures. No difference.
Not using properties for my structures. Again, no difference.
Added callbacks for animation completion to remove the animation so as to leave the columns in the correct "final" state. This was not good but worth a try.
The issue is present in both iOS 6 and 7.
I really want to use an EaseIn timing function because its the best looking animation for something falling into place. I also want to use the CAReplicatorLayer because it does exactly what I want to do.
So what's wrong with the EaseIn timing functions as I'm using them? Is this a bug?
I agree that this appears to be a bug in the combination of CAReplicatorLayer and the kCAMediaTimingFunctionEaseIn timing function.
As a workaround, I suggest using a keyframe animation with three keyframes. The first two keyframes define your desired animation with the ease-in timing. The last keyframe is defined to be identical to the second keyframe, but gives the animation additional time to sit on the final keyframe, which seems to let the replicator layer animate that last replicated layer into the final state.
My suggestion therefore is:
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.y"];
// extra keyframe duplicating final state
animation.values = #[#-300.0f, #0.0f, #0.0f];
// double length of original animation, with last half containing no actual animation
animation.keyTimes = #[#0.0, #0.5, #1.0];
animation.duration = 2.0f;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
// ease-in for first half of animation; default after
animation.timingFunctions = #[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
[obj addAnimation:animation forKey:#"falling-block-animation"];
I got 5 objects with their images. I add them to array and shuffle them.
// Adding Things to Things Strip
[thingStrip removeAllObjects];
for (Thing *i in myThings) {
[self addThing:[i icon]];
}
// Randomizing Things' order in the Things Strip
for (int x = 0; x<thingStrip.count; x++) {
int randInt = (arc4random() % (thingStrip.count - x)) + x;
[thingStrip exchangeObjectAtIndex:x withObjectAtIndex:randInt];
}
// Adding Things' Images
for (int i=0; i<thingStrip.count; i++) {
UIImage *thingImage = thingStrip[i];
Then I add them to UIImage and create CALayers with them.
All I need is:
Make them move from right to left continuously without any gaps.
Scale them smoothly from 80x53 at right position to 120x80 at left position
Make opacity from 0.5 at right position to 1.0 at left position.
Each iteration lasts 0.5 seconds.
Whole animation lasts 5 seconds or stop on screen touch.
After animation is stopped I need left object proceed to the left end of the screen.
After animation is stopped I need to know which object image stopped at left end of the screen to use its' object data later.
For now I can do moving animation, but my solution is not so good.
// Adding Things' Images
for (int i=0; i<thingStrip.count; i++) {
UIImage *thingImage = thingStrip[i];
//for (int j = 0; j < 10; j++)
//{
CALayer *thingFrame = [CALayer layer];
thingFrame.bounds = CGRectMake(0.0, 0.0, 80.0, 53.0);
thingFrame.position = CGPointMake(i*thingFrame.frame.size.width, 515.0);
//thingFrame.position = CGPointMake(i*thingFrame.frame.size.width + j * thingStrip.count * thingFrame.frame.size.width, 515.0);
thingFrame.contents = (id)thingImage.CGImage;
[thingFrame setAnchorPoint:CGPointMake(0.0, 0.0)];
[self.view.layer addSublayer:thingFrame];
CGFloat currentPosition = [[thingFrame valueForKeyPath:#"position.x"] floatValue];
CGFloat newPosition = currentPosition - thingStrip.count * thingFrame.frame.size.width;
CABasicAnimation *thingMoveAnimation = [CABasicAnimation animationWithKeyPath:#"position.x"];
thingMoveAnimation.duration = 0.5f;
thingMoveAnimation.repeatDuration = 5.0f;
thingMoveAnimation.delegate = self;
thingMoveAnimation.fromValue = #(currentPosition);
thingMoveAnimation.toValue = #(newPosition);
thingMoveAnimation.fillMode = kCAFillModeForwards;
[thingFrame addAnimation:thingMoveAnimation forKey:#"position.x"];
//}
}
This code works only if I use for loop with 'j' (commented one) which adds 5 of each image. Only then I get smooth moving animation without any gaps. I guess it's not right solution. When I use only one for loop, I get animation of 5 images moving left again and again, but not continuously one after another.
I don't have working solution for scaling and opacity animation and I don't know how to end it by touching the screen and get needed image data.
I was trying to find similar solution for a while but found nothing.
I'm looking for help with working code.
Thanks in advance.
I am experimenting with the Google Maps for iOS SDK latest version 1.2.1.2944 to animate a GMSGroundOverlay. The user has control over the image sequence, so using an animated UIImage isn't a possibility sadly, so i'm loading in the UIImage on the fly. The GMSGroundOverlay.icon is set to the UIImage that is being updated.
Aside from high memory usage, I seem to have struck a limitation in that whenever I try to overlay a UIImage using GMSGroundOverlay.icon that is more than 1000px x 1000px, it crashes. Referencing a UIImage of 1000px x 1000px gets around the crash.
It strikes me though that maybe I should utilise CATiledLayer for handling the image to only load into memory and subsequently into the icon property of GMSGroundOverlay, but has anyone had any experience of using CATiledLayer with Google Maps for iOS SDK and sequencing images as an animated GMSGroundOverlay?
I got this answer from pressinganswer.com, i think it may helps you.
As currently I cannot use the "position" keypath for animating, I ended up animating it using the "latitude" and "longitude" keypaths separately.
First calculate the points and add them to 2 separate arrays, one for latitude value (y) and one for longitude (x) and then use the values property in CAKeyFrameAnimation to animate. Create 2 CAKeyFrameAnimation objects (1 for each axis) and group them together using CAAnimationGroup and animate them together to form a circle.
In my equation I vary the length of the radius on each axis so that I can also generate an oval path.
NSMutableArray *latitudes = [NSMutableArray arrayWithCapacity:21];
NSMutableArray *longitudes = [NSMutableArray arrayWithCapacity:21];
for (int i = 0; i <= 20; i++) {
CGFloat radians = (float)i * ((2.0f * M_PI) / 20.0f);
// Calculate the x,y coordinate using the angle
CGFloat x = hDist * cosf(radians);
CGFloat y = vDist * sinf(radians);
// Calculate the real lat and lon using the
// current lat and lon as center points.
y = marker.position.latitude + y;
x = marker.position.longitude + x;
[longitudes addObject:[NSNumber numberWithFloat:x]];
[latitudes addObject:[NSNumber numberWithFloat:y]];
}
CAKeyframeAnimation *horizontalAnimation = [CAKeyframeAnimation animationWithKeyPath:#"longitude"];
horizontalAnimation.values = longitudes;
horizontalAnimation.duration = duration;
CAKeyframeAnimation *verticleAnimation = [CAKeyframeAnimation animationWithKeyPath:#"latitude"];
verticleAnimation.values = latitudes;
verticleAnimation.duration = duration;
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = #[horizontalAnimation, verticleAnimation];
group.duration = duration;
group.repeatCount = HUGE_VALF;
[marker.layer addAnimation:group forKey:[NSString stringWithFormat:#"circular-%#",marker.description]];
If I want to animate UITableViewCell so it would bounce from left to right a few times, How can I do that? I'm trying that:
var bounds = activeCell.Bounds;
var originalLocation = bounds.Location;
var loc = originalLocation;
UIView.Animate(0.2,()=>{
loc.X = originalLocation.X + 20;
activeCell.Bounds = new RectangleF (loc, bounds.Size);
loc.X = originalLocation.X - 20;
activeCell.Bounds = new RectangleF (loc, bounds.Size);
});
It animates only the last state (i.e. moves element to the left). I tried to put them in separated Animate blocks - it didn't help. Tried to use different UIAnimationOptions - the same.
Here is a nice article explaining how to make it bounce.
http://khanlou.com/2012/01/cakeyframeanimation-make-it-bounce/
Moreover, there is an explanation the formula used to compute the bounce path.
For my personal use, I've taken the absolute value of the computation to simulate a rebound on ground.
- (void) displayNoCommentWithAnimation{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position.y"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.duration = 2;
int steps = 120;
NSMutableArray *values = [NSMutableArray arrayWithCapacity:steps];
double value = 0;
float e = 2.71;
for (int t = 0; t < steps; t++) {
value = 210 - abs(105 * pow(e, -0.025*t) * cos(0.12*t));
[values addObject:[NSNumber numberWithFloat:value]];
}
animation.values = values;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.delegate = self;
[viewThatNeedToBounce.layer addAnimation:animation forKey:nil];
}
- (void) animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
CAKeyframeAnimation *keyframeAnimation = (CAKeyframeAnimation*)animation;
[viewThatNeedToBounce.layer setValue:[NSNumber numberWithInt:210] forKeyPath:keyframeAnimation.keyPath];
[viewThatNeedToBounce.layer removeAllAnimations];
}
The problem with your approach is that UIView.Animate will record the changes that you make to your view, but only the final state for them.
If you change the Bounds property one hundred times in your animate block, only the last one is the one that will matter from the perspective of the animation framework.
CoreAnimation has a couple of quirks that are explained in the WWDC 2010 and WWDC2011 videos. They have great material and they explain a few of the tricks that are not very obvious.
That being said, animating cells in a UITableView is a complicated matter because you are really poking at a UITableView internals, so expect various strange side effects. You could lift the code from TweetStation that does that animation and deals with various corner cases. But even TweetStation and the Twitter for iOS app do not manage to be perfect, because you are animating things behind the back of a UIView that is constantly updating and making changes to very same properties you are animating.
From the top of my head, the easiest approach would be to put the animation code into a method and call that recursive as often as you want. Code untested, but it should work or at least give you an idea.
// Repeat 10 times, move 20 right and the left and right etc.
FancyAnim(activeCell, activeCell.Bounds.Location, 10, 20);
private void FancyAnim(UITableViewCell activeCell, PointF originalLocation, int repeat, float offset)
{
var bounds = activeCell.Bounds;
var loc = originalLocation;
UIView.Animate(0.2,
delegate
{
// Called when animation starts.
loc.X = originalLocation.X + offset;
activeCell.Bounds = new RectangleF (loc, bounds.Size);
},
delegate
{
// Called when animation ends.
repeat--;
// Call the animation method again but invert the movement.
// If you don't do this too often, you should not run out of memory because of a stack overflow.
if(repeat >= 0)
{
FancyAnim(activeCell, originalLocation, repeat, -offset);
}
});
You can however also use a path animation. You would define a path "20 units right, back to center, 20 units left, back to center" and repeat that animation as often as you like.
This requires you to deal with CAKeyFrameAnimation and will be slightly more code.
This site can get you jump started: http://www.bdunagan.com/2009/04/26/core-animation-on-the-iphone/
Lack of documentation and good samples sometimes really makes even simple tasks so annoyingly challenging.
Here is the solution
Sure code isn't elegant, but it works. Hope it will someday help somebody else, so he or she wouldn't need to spend half a day on something stupidly simple like that
var activeCell = ((Element)sender).GetActiveCell();
var animation =
(CAKeyFrameAnimation)CAKeyFrameAnimation.FromKeyPath ("transform.translation.x");
animation.Duration = 0.3;
animation.TimingFunction = // small details matter :)
CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseOut.ToString());
animation.Values = new NSObject[]{
NSObject.FromObject (20),
NSObject.FromObject (-20),
NSObject.FromObject (10),
NSObject.FromObject (-10),
NSObject.FromObject (15),
NSObject.FromObject (-15),
};
activeCell.Layer.AddAnimation (animation,"bounce");