Rotate CAEmitterCell content based on Animation Direction - ios

Using CAEmitterLayer and Cell to show particles that have direction sensitive content image (an arrow).
Want the content (arrow image) to point in the direction the cell is moving.
Here is code for having all the arrows move from outside edge toward center. How to rotate image so the image points in direction of movement:
emitterLayer = [CAEmitterLayer layer];
emitterLayer.emitterPosition = self.view.center;
emitterLayer.emitterSize = self.view.bounds.size;
emitterLayer.emitterMode = kCAEmitterLayerOutline;
emitterLayer.emitterShape = kCAEmitterLayerRectangle;
CAEmitterCell* arrow = [CAEmitterCell emitterCell];
arrow.birthRate = 10;
arrow.velocity = 100;
arrow.emissionLatitude = M_PI;
arrow.scale = 0.5;
arrow.lifetime = 2;
arrow.contents = (id) [[UIImage imageNamed:#"arrowOutline.png"] CGImage];
emitterLayer.emitterCells = #[arrow];
[self.view.layer addSublayer:emitterLayer];
How to get the content image to adjust based on direction of cell movement?

Ended up using 4 different CAEmitterLayers one for each direction where emitterPosition is each edge of the screen and emitterSize is length of given edge.
If there is better solution please share.

Related

Spritekit PhysicsBody applyTorque

I'm working on a universal app.
I want to applyTorque to a Paddle
But the problem is the paddle sizes are bigger on a ipad than the one one iphone.
How can I calculate the torque to apply the same affect to the Paddle's physicalBody?
SKSpriteNode *bar = [SKSpriteNode spriteNodeWithImageNamed:nameImage];
bar.name = #"bar";
bar.size = CGSizeMake(PaddleWidth, PaddleHeight);
bar.physicsBody =[SKPhysicsBody bodyWithTexture:bar.texture size:bar.size];
bar.physicsBody.restitution = 0.2;
bar.physicsBody.angularDamping = 0;
bar.physicsBody.friction = 0.02;
bar.physicsBody.mass=.2;
[paddle addChild:bar];
SKSpriteNode *anchor = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(PaddleWidth, PaddleHeight)];
anchor.name = #"anchor";
anchor.size = CGSizeMake(PaddleHeight, PaddleHeight);
anchor.position = CGPointMake(bar.position.x + bar.size.width/2, 0);
[paddle addChild:anchor];
CGFloat anchorRadius = anchor.size.width/20;
anchor.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:anchorRadius];
anchor.physicsBody.dynamic = NO;
CGPoint positionInScene = [self convertPoint:anchor.position toNode:self.scene];
pin = [SKPhysicsJointPin jointWithBodyA:bar.physicsBody
bodyB:anchor.physicsBody
anchor:positionInScene];
pin.shouldEnableLimits = YES;
pin.lowerAngleLimit = -0.5;
pin.upperAngleLimit = 0.5;
[self.scene.physicsWorld addJoint:pin];
-(void)flip{
SKNode *bar=[self childNodeWithName:#"bar"];
CGFloat torque;
torque=10;
[bar.physicsBody applyTorque:torque];
}
You can use the size of the paddle as a factor in the torque value:
CGSize paddleSize = <Initialize with paddle size>
CGFloat torque=paddleSize.width * factor; // factor will be the value you need to multiply in order to reach your desired value
So if for example paddle width is 100 use a constant factor of 0.05
On the iPad the different paddle width will make the torque value to be calculated accordingly using the same factor value as above.
You can set a constant mass of the physicsBody so that the torque applied affects the paddle the same way regardless of the node's size.
//While instantiating the physicsBody for the "bar" node
bar.physicsBody.mass = 0.2; //This will need to be adjusted according to your needs.
From the documentation:
The mass of the body affects its momentum as well as how forces are
applied to the object.

Creating image which sweeps across the screen

I'm trying to create a banner that sweeps across the current scene. I want to create a banner which sweeps down the screen to show the current round. My attempt at it is creating a UIImageView and add it to the current view. However, i assume its calling the didMoveToView Function and resetting everything in that scene, which is something i dont want it to do. Here is my attempt:
-(void)createBanner{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Banner"]];
[imageView setFrame:CGRectMake(0,0, imageView.frame.size.width, imageView.frame.size.height)];
[imageView setClipsToBounds:YES];
[self.view addSubview:imageView];
CABasicAnimation *sweep = [CABasicAnimation animationWithKeyPath:#"position"];
sweep.fromValue = [NSValue valueWithCGPoint:CGPointZero];
sweep.toValue = [NSValue valueWithCGPoint:CGPointMake(0.0, self.frame.size.height)];
sweep.duration = 10;
sweep.additive = YES;
[imageView.layer addAnimation:sweep forKey:#"sweep"];
}
EDIT: i am using sprite kit in order to create the game.
As hamobi said, it's best to use an 'SKSpriteNode' in Sprite Kit and not UIKit. Assuming your'e adding to an 'SKScene', your code above translated to Sprite Kit is:
-(void)createBanner{
SKSpriteNode* spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"Banner"]
//It's good practice not to resize the sprite in code as it should already be the right size but...
spriteNode.size = CGSizeMake(self.size.width, self.size.height)
//Set its center off to the left of the screen for horizontal sweep, or you can do vertical and set it off the top of the screen...
spriteNode.postion = CGPointMake(-spriteNode.size.width/2, self.size.height/2)
self.addChild(spriteNode)
//Then to sweep from left to right...
SKAction* sweep = [SKAction moveTo:CGPointMake(spriteNode.size.width/2, self.size.height/2) duration:10]
spriteNode.runAction(sweep)
}
I think that covers most of it.

figuring out the conversions from UIKit coordinates to SKScene coordinates

I am new to SpriteKit but not to iOS. I'm having a great deal of difficulty figuring out the conversions from UIKit coordinates to SKScene coordinates. In addition, their relations to the units used for the SpriteKit physics engine evades me.
Here's what I'm trying to do: I am launching a sprite with an initial velocity in both x and y directions. The sprite should start near the screen's bottom left corner (portrait mode) and leave near the bottom right corner. Another sprite should appear somewhere on the previous sprite's trajectory. This sprite is static.
The blue frame is the screen boundary, the black curve is the first sprite's trajectory and the red circle is the static sprite.
Now using the screen as 640 x 960. In my SKScene's didMoveToView: method I set the gravity as such:
self.physicsWorld.gravity = CGVectorMake(0.0f, -2.0f);
Then I make the necessary calculations for velocity and position:
"t" is time from left corner to right;
"y" is the maximum height of the trajectory;
"a" is the acceleration;
"vx" is velocity in the x direction;
"vy" is velocity in the y direction;
and
"Tstatic" is the time at which the dynamic sprite will be at the static sprite's position;
"Xstatic" is the static sprite's x-position;
"Ystatic" is the static sprite's y-position;
-(void)addSpritePair {
float vx, vy, t, Tstatic, a, Xstatic, Ystatic, y;
a = -2.0;
t = 5.0;
y = 800.0;
vx = 640.0/t;
vy = (y/t) - (1/8)*a*t;
Tstatic = 1.0;
Xstatic = vx*Tstatic;
Ystatic = vy*Tmark + 0.5*a*Tstatic*Tstatic;
[self spriteWithVel:CGVectorMake(vx, vy) andPoint:CGPointMake(xmark, ymark)];
}
-(void)spriteWithVel:(CGVector)velocity andPoint:(CGPoint)point{
SKSpriteNode *staticSprite = [SKSpriteNode spriteNodeWithImageNamed:#"ball1"];
staticSprite.anchorPoint = CGPointMake(.5, .5);
staticSprite.position = point;
staticSprite.name = #"markerNode";
staticSprite.zPosition = 1.0;
SKSpriteNode *dynamicSprite = [SKSpriteNode spriteNodeWithImageNamed:#"ball2"];
dynamicSprite.anchorPoint = CGPointMake(.5, .5);
dynamicSprite.position = CGPointMake(1 ,1);
dynamicSprite.zPosition = 2.0;
dynamicSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:hoop.size.width/2];
dynamicSprite.physicsBody.allowsRotation = NO;
dynamicSprite.physicsBody.velocity = velocity;
dynamicSprite.physicsBody.affectedByGravity = YES;
dynamicSprite.physicsBody.dynamic = YES;
dynamicSprite.physicsBody.mass = 1.0f;
dynamicSprite.markerPoint = point;
dynamicSprite.physicsBody.restitution = 1.0f;
dynamicSprite.physicsBody.friction = 0.0f;
dynamicSprite.physicsBody.angularDamping = 0.0f;
dynamicSprite.physicsBody.linearDamping = 0.0f;
[self addChild:hoop];
[self addChild:marker];
}
When I execute the code, the following happens:
Can someone please help me out?

Zoom a rotated image inside scroll view to fit (fill) frame of overlay rect

Through this question and answer I've now got a working means of detecting when an arbitrarily rotated image isn't completely outside a cropping rect.
The next step is to figure out how to correctly adjust it's containing scroll view zoom to ensure that there are no empty spaces inside the cropping rect. To clarify, I want to enlarge (zoom in) the image; the crop rect should remain un-transformed.
The layout hierarchy looks like this:
containing UIScrollView
UIImageView (this gets arbitrarily rotated)
crop rect overlay view
... where the UIImageView can also be zoomed and panned inside the scrollView.
There are 4 gesture events that occur that need to be accounted for:
Pan gesture (done): accomplished by detecting if it's been panned incorrectly and resets the contentOffset.
Rotation CGAffineTransform
Scroll view zoom
Adjustment of the cropping rect overlay frame
As far as I can tell, I should be able to use the same logic for 2, 3, and 4 to adjust the zoomScale of the scroll view to make the image fit properly.
How do I properly calculate the zoom ratio necessary to make the rotated image fit perfectly inside the crop rect?
To better illustrate what I'm trying to accomplish, here's an example of the incorrect size:
I need to calculate the zoom ratio necessary to make it look like this:
Here's the code I've got so far using Oluseyi's solution below. It works when the rotation angle is minor (e.g. less than 1 radian), but anything over that and it goes really wonky.
CGRect visibleRect = [_scrollView convertRect:_scrollView.bounds toView:_imageView];
CGRect cropRect = _cropRectView.frame;
CGFloat rotationAngle = fabs(self.rotationAngle);
CGFloat a = visibleRect.size.height * sinf(rotationAngle);
CGFloat b = visibleRect.size.width * cosf(rotationAngle);
CGFloat c = visibleRect.size.height * cosf(rotationAngle);
CGFloat d = visibleRect.size.width * sinf(rotationAngle);
CGFloat zoomDiff = MAX(cropRect.size.width / (a + b), cropRect.size.height / (c + d));
CGFloat newZoomScale = (zoomDiff > 1) ? zoomDiff : 1.0 / zoomDiff;
[UIView animateWithDuration:0.2
delay:0.05
options:NO
animations:^{
[self centerToCropRect:[self convertRect:cropRect toView:self.zoomingView]];
_scrollView.zoomScale = _scrollView.zoomScale * newZoomScale;
} completion:^(BOOL finished) {
if (![self rotatedView:_imageView containsViewCompletely:_cropRectView])
{
// Damn, it's still broken - this happens a lot
}
else
{
// Woo! Fixed
}
_didDetectBadRotation = NO;
}];
Note I'm using AutoLayout which makes frames and bounds goofy.
Assume your image rectangle (blue in the diagram) and crop rectangle (red) have the same aspect ratio and center. When rotated, the image rectangle now has a bounding rectangle (green) which is what you want your crop scaled to (effectively, by scaling down the image).
To scale effectively, you need to know the dimensions of the new bounding rectangle and use a scale factor that fits the crop rect into it. The dimensions of the bounding rectangle are rather obviously
(a + b) x (c + d)
Notice that each segment a, b, c, d is either the adjacent or opposite side of a right triangle formed by the bounding rect and the rotated image rect.
a = image_rect_height * sin(rotation_angle)
b = image_rect_width * cos(rotation_angle)
c = image_rect_width * sin(rotation_angle)
d = image_rect_height * cos(rotation_angle)
Your scale factor is simply
MAX(crop_rect_width / (a + b), crop_rect_height / (c + d))
Here's a reference diagram:
Fill frame of overlay rect:
For a square crop you need to know new bounds of the rotated image which will fill the crop view.
Let's take a look at the reference diagram:
You need to find the altitude of a right triangle (the image number 2). Both altitudes are equal.
CGFloat sinAlpha = sin(alpha);
CGFloat cosAlpha = cos(alpha);
CGFloat hypotenuse = /* calculate */;
CGFloat altitude = hypotenuse * sinAlpha * cosAlpha;
Then you need to calculate the new width for the rotated image and the desired scale factor as follows:
CGFloat newWidth = previousWidth + altitude * 2;
CGFloat scale = newWidth / previousWidth;
I have implemented this method here.
I will answer using sample code, but basically this problem becomes really easy, if you will think in rotated view coordinate system.
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(80, 200, 100, 100)];
container.backgroundColor = [UIColor blueColor];
UIView* content2 = [[UIView alloc] initWithFrame:CGRectMake(-50, -50, 150, 150)];
content2.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.5];
[container addSubview:content2];
[self.view setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:container];
[container.layer setSublayerTransform:CATransform3DMakeRotation(M_PI / 8.0, 0, 0, 1)];
//And now the calculations
CGRect containerFrameInContentCoordinates = [content2 convertRect:container.bounds fromView:container];
CGRect unionBounds = CGRectUnion(content2.bounds, containerFrameInContentCoordinates);
CGFloat midX = CGRectGetMidX(content2.bounds);
CGFloat midY = CGRectGetMidY(content2.bounds);
CGFloat scaleX1 = (-1 * CGRectGetMinX(unionBounds) + midX) / midX;
CGFloat scaleX2 = (CGRectGetMaxX(unionBounds) - midX) / midX;
CGFloat scaleY1 = (-1 * CGRectGetMinY(unionBounds) + midY) / midY;
CGFloat scaleY2 = (CGRectGetMaxY(unionBounds) - midY) / midY;
CGFloat scaleX = MAX(scaleX1, scaleX2);
CGFloat scaleY = MAX(scaleY1, scaleY2);
CGFloat scale = MAX(scaleX, scaleY);
content2.transform = CGAffineTransformScale(content2.transform, scale, scale);

iOS scrolling background placement issue

I got a little issue i'm trying to figure out. I want to scroll a background image from top to bottom, but it just won't go the right way. Here's what I got so far:
for (int i = 0; i < 2; i++) {
SKSpriteNode * bg = [SKSpriteNode spriteNodeWithImageNamed:#"Background"];
bg.anchorPoint = CGPointZero;
bg.position = CGPointMake(0, bg.size.height * i);
bg.name = #"bg";
[_bgLayer addChild:bg];
}
The image shows up about 2/3 to the left on the simulator and the right side of the screen is just black. Now doesn't bg.anchorPoint = CGPointZero; move the reference point from center to bottom left? If I get rid of that line and do self.frame.size.width / 2, it centers it. Also the height and width of my *bg are only showing 768 and the Background image is 864x1536 which is another thing that doesn't make sense. Anyone see the problem?

Resources