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]];
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 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.
Is there a way to rotate a Node in SpriteKit around an arbitrary point?
I now I can manipulate the anchorPoint of my Node, but that is not sufficient if the rotation point I want to use lies outside of the Node.
What is the best way to achieve this kind of rotation in SpriteKit?
Since you're asking for the best way, here's one that works well (best is subjective):
Create an SKNode and set its position to the center of rotation. Add the node that should rotate around that center as child to the center node. Set the child node's position to the desired offset (ie radius, say x + 100). Change the rotation property of the center node to make the child node(s) rotate around the center point. The same works for cocos2d btw.
I was also trying to solve this problem a few weeks back, and did not implement the anchor points solution because I did not want to have to worry about removing the anchor point when lets say the object collides with another node and should leave its orbit and bounce away.
Instead, I came up with two solutions, both of which work if tweaked. The first took a long time to perfect, and is still not perfect. It involves calculating a certain number of points around a center position offset by a set radius, and then if a certain object comes in a certain distance of the center point, it will continually use physics to send the object on a trajectory path along the "circumference" of the circle, points that it calculated (see above).
There are two ways of calculating points with a radius
The first uses the pythagorean theorem, and the second ultimately uses trigonometry proper.
In the first, you increment a for loop by a certain amount, while it is less that 361 (degree), and for each iteration of the loop, calculate using sine and cosine a point with that angle at a certain radius from the center point.
The second uses the pythagorean theorem, and its code is below:
After you calculate points, you should create a scheduled selector [<object> scheduled selector...]; or a timer in your didMoveToView, or use a fixed update method, in addition to an instance variable called int which will hold the index of the next location to which your object will move. Every time the timer method is called, it will move the object to the next point in your calculate points array using your own or the below code labeled physicsMovement; You can play around with the physics values, and even the frequency of the ttimer for different movement effects. Just make sure that you are getting the index right.
Also, for more realism, I used a method which calculates the closest point in the array of calculated point to the object, which is called only once the collision begins. It is also below labeled nearestPointGoTo.
If you need any more help, just say so in the comments.
Keep Hacking!
I used the second, and here is the source code for it:
The code itself didn't go through
Second point calculation option
+(NSArray *)calculatePoints:(CGPoint)point withRadius:(CGFloat)radius numberOfPoints: (int)numberOfPoints{ //or sprite kit equivalent thereof
// [drawNode clear];
NSMutableArray *points = [[NSMutableArray alloc]init];
for (int j = 1; j < 5; j++) {
float currentDistance;
float myRadius = radius;
float xAdd;
float yAdd;
int xMultiplier;
int yMultiplier;
CCColor *color = [[CCColor alloc]init]; //Will be used later to draw the position of the node, for debugging only
for (int i = 0; i < numberOfPoints; i += 1){
//You also have to change the if (indextogoto == <value>) in the moveGumliMethod;
float opposite = sqrtf( powf(myRadius, 2) - powf(currentDistance, 2) );
currentDistance = i;
switch (j) {
case 1:
xMultiplier = 1;
yMultiplier = 1;
xAdd = currentDistance;
yAdd = opposite;
color = [CCColor blueColor];
break;
case 2:
xMultiplier = 1;
yMultiplier = -1;
xAdd = opposite;
yAdd = currentDistance;
color = [CCColor orangeColor];
break;
case 3:
xMultiplier = -1;
yMultiplier = -1;
xAdd = currentDistance;
yAdd = opposite;
color = [CCColor redColor];
break;
case 4:
xMultiplier = -1;
yMultiplier = 1;
xAdd = opposite;
yAdd = currentDistance;
color = [CCColor purpleColor];
break;
default:
break;
}
int x = (CGFloat)(point.x + xAdd * xMultiplier); //or sprite kit equivalent thereof
int y = (CGFloat)(point.y + yAdd * yMultiplier); //or sprite kit equivalent thereof
CGPoint newPoint = CGPointMake((CGFloat)x,(CGFloat)y); //or sprite kit equivalent thereof
NSValue *pointWrapper = [NSValue valueWithCGPoint:newPoint]; //or sprite kit equivalent thereof
NSLog(#"Point is %#",pointWrapper);
[points addObject:pointWrapper];
}
}
return points;
}
Calculating Nearest Point To Object
-(CGPoint)calculateNearestGumliPoint:(CGPoint)search point { // MY Character is named Gumli
float closestDist = 2000;
CGPoint closestPt = ccp(0,0);
for (NSValue *point in points) {
CGPoint cgPoint = [point CGPointValue];
float dist = sqrt(pow( (cgPoint.x - searchpoint.x), 2) + pow( (cgPoint.y - searchpoint.y), 2));
if (dist < closestDist) {
closestDist = dist;
closestPt = cgPoint;
}
}
return closestPt;
}
I think the best way to make this work is through two SKNode and joint them with SKPhysicsJointPin (Look at the pin example below)
I tried to hang a door sign (SKSpriteNode) on my door(`SkScene), and would like to rotate around on the hanging spot when someone touch it
What I did is making a 1x1 SKNode with a HUGH mass and disabled it's gravity effects.
var doorSignAnchor = SKSpriteNode(color: myUIColor, size: CGSize(width: 1, height: 1))
doorSignAnchor.physicsBody = SKPhysicsBody(rectangleOf: doorSignAnchor.frame.size)
doorSignAnchor.physicsBody!.affectedByGravity = false // MAGIC PART
doorSignAnchor.physicsBody!.mass = 9999999999 // MAGIC PART
var doorSignNode = SKSpriteNode(imageNamed:"doorSign")
doorSignNode.physicsBody = SKPhysicsBody(rectangleOf: doorSignNode.frame.size)
and created a SKPhysicsJointPin to connect them all
let joint = SKPhysicsJointPin.joint(
withBodyA: doorSignAnchor.physicsBody!,
bodyB: doorSignNode.physicsBody!,
anchor: doorSignAnchor.position)
mySkScene.physicsWorld.add(joint)
So it will move like actual door sign, rotate around an arbitrary point (doorSignAnchor)
Reference:
Official document about Sumulating Physics
How to Make Hanging Chains With SpriteKit Physis Joints
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.
I have been working at this for a while and searched SO thoroughly for a solution but to no avail. Here is what I am trying to do.
I have a UIScrollView on which the user can zoom and pan for 5 seconds. I have a separate CALayer which is not layered on top of the UIScrollView. I want to scale and translate this CALayer's contents to reflect the zoom and pans occurring on the UIScrollView. I want to achieve this via key frame animation CAKeyFrameAnimation. When I put this into code, the zoom occurs as expected but the position of the content is offset incorrectly.
Here is how I do it in code. Assume that UIScrollView delegate passes zoom scale and content offset to the following method:
// Remember all zoom params for late recreation via a CAKeyFrameAnimation
- (void) didZoomOrScroll:(float)zoomScale
contentOffset:(CGPoint)scrollViewContentOffset {
CATransform3D scale = CATransform3DMakeScale(zoomScale, zoomScale, 1);
CATransform3D translate = CATransform3DMakeTranslation(-scrollViewContentOffset.x,
-scrollViewContentOffset.y, 0);
CATransform3D concat = CATransform3DConcat(scale, translate);
// _zoomScrollTransforms and _zoomScrollTimes below are of type NSMutableArray
[_zoomScrollTransforms addObject:[NSValue valueWithCATransform3D:concat]];
// secondsElapsed below keeps track of time
[_zoomScrollTimes addObject:[NSNumber numberWithFloat:secondsElapsed]];
}
// Construct layer animation
- (void) constructLayerWithAnimation:(CALayer *)layer {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
animation.duration = 5.0;
animation.values = _zoomScrollTransforms;
// Adjust key frame times to contain fractional durations
NSMutableArray *keyTimes = [[NSMutableArray alloc] init];
for( int i = 0; i < [_zoomScrollTimes count]; i++ ) {
NSNumber *number = (NSNumber *)[_zoomScrollTimes objectAtIndex:i];
NSNumber *fractionalDuration = [NSNumber numberWithFloat:[number floatValue]/animation.duration];
[keyTimes addObject:fractionalDuration];
}
animation.keyTimes = keyTimes;
animation.removedOnCompletion = YES;
animation.beginTime = 0;
animation.calculationMode = kCAAnimationDiscrete;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:nil];
}
When the above layer is animated, the content is zoomed properly but it is positioned incorrectly. The content appears to be x and y shifted more than I expected and as a result doesn't exactly retrace the zoom/pans done by the user on the UIScrollView.
Any ideas what I may be doing wrong?
Answer
Ok, i figured out what I was doing wrong. It had to do with the anchor point for the CALayer. Transforms on CALayers are always applied to the anchor point. For CALayers, the anchor point is (0.5, 0.5) by default. So scaling and translations were being conducted along the center point. UIScrollViews, on the other hand, gives offsets from the top left corner of the view. Basically you can think of the anchor point for UIScrollView, for the purposes of thinking about the CGPoint offset value, as being (0.0, 0.0).
So the solution is to set the anchor point of CALayer to (0.0, 0.0). And then everything works as expected.
There are other resources that present this info in a nicer way. See this other question on Stackoverflow that is similar. Also see this article in Apple's documentation that discusses position, anchorpoint and general layer in geometry in great detail.
You are concatenating translation matrix with scaled matrix. The final value of displacement offset will be Offset(X, Y) = (scaleX * Tx, scaleY * Ty).
If you want to move the UIView with (Tx, Ty) offset, than concatenate the translate and scale matrices as below:
CATransform3D scale = CATransform3DMakeScale(zoomScale, zoomScale, 1);
CATransform3D translate = CATransform3DMakeTranslation(-scrollViewContentOffset.x,
-scrollViewContentOffset.y, 0);
CATransform3D concat = CATransform3DConcat(translate, scale);
Try this and let me know if it works.