how to stop a sprite from leaving the screen - objective-c

I am using the accelerometer to move a sprite around the screen but I don't want to leave the screen.
I try with this
self.physicsWorld.contactDelegate = self;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.categoryBitMask = edgeCategory;
sprite.physicsBody.collisionBitMask = edgeCategory;
But the sprite still leaves the screen
I also change the scene anchor point
self.anchorPoint = CGPointMake(0.5, 0.5);

Try creating 4 rectangle SpriteNodes and making them very thin, long lines that make up your border. Give them all the same CategoryBitMask, etc.
This way, You can set the roof and floor using self.size.width as the length and around 1 or two as the thickness. Opposite for the Sides.
This will ignore the problem I think you are having where the scenes physicsBody rectangle is not centered to the middle of the screen.
If you did not previously do this, using your method:
Set sprite.categorybBitMask = spriteCategory;
Set self.collsionBitMask = spriteCategory;
one of the nodes MUST BE DYNAMIC! for collision to work.

Related

How to get location of moving a UIImageView

In touchesBegan I have this, and some other irrelevant code. :
CGPoint center;
center = [[self.squareOne.layer presentationLayer] center];
squareOne uses a block-based UIView animation to move. The animation code:
self.squareOne.center = position;
self.squareOne.transform = CGAffineTransformMakeRotation((a + 1) * M_1_PI);
If it really matter this is what position is:
position.x = self.widthOfScreen/8 + self.widthOfScreen;
position.y = y;
//y is any number between 0 and the height of the screen
But when I click the screen, I get the dreaded: Thread 1: signal SIGABRT error. (I say the other code in touchesBegan is irrelevant because those 2 lines of code are the problem. Pretty much the rest are just if-statements which will ultimately just make squareOne become hidden). I am asking how I can get the location of a UIImageView mid-animation when I click the screen. It doesn't matter if you get the center, I just want to know how to get a relative value of squareOne's position while it's moving.
I think the issue is that there is no center on a presentation layer, but you can get a frame. Try this...
CALayer *layer = self.squareOne.layer;
CGRect frame = [[layer presentationLayer] frame];
CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMaxY(frame));
Hopefully that helps.

SpriteKit - Set Scale and Physics

What is the correct way to "zoom out" on your scene.
I have an object that I apply an impulse to fire it across the screen. It for example will fire about 100 px across., this works as expected - increase the force it flys more, increase the density it flys less etc.
The problem i have is zooming, the only way I know to zoom out on a scene is to setScale, and the shrinks all my nodes as expected.
But then instead of the object flying the same amount (just zoomed out) it flys more than double the distance.
When I log the mass / density etc of the object before and after I scale they are the same, as expected.
So why doesn't it fly the same amount ? Tried changing the impulse to match the scale, but it doesnt work, yes it flys less distance - but its not one for one with the scaling.
Tricky question...
Thanks for ideas.
I believe you're not supposed to scale the SKScene (like it hints you if you try setScale method with SKScene). Try resizing it instead.
myScene.scaleMode = SKSceneScaleModeAspectFill;
And then while zooming:
myScene.size = CGSizeMake(myScene.size.width + dx, myScene.size.height + dy);
*Apple documentation says:
Set the scaleMode property to SKSceneScaleModeResizeFill. Sprite Kit automatically resizes the scene so that it always matches the view’s size.
The easy fix (thanks to Chris LaPollo, an author on RW)
[self runAction:[SKAction scaleTo:0.5 duration:0]];
Nothing else needed.
The odd thing is you cannot do
[self setScale:0.5];
As you get this warning, and it doenst work - but running an action does -- weird!!!
SKScene: Setting the scale of a SKScene has no effect.
For those like me who ended up here after a search, changing the scale of the scene to zoom out no longer works.
Instead, encapsulate all your nodes in an empty SKNode and run actions on this one:
self.rootNode = [SKNode node];
// Add your children nodes here to the rootnode.
[self addChild:self.rootNode];
// Zoom out
[self.rootNode runAction:[SKAction scaleBy:2 duration:5]];
// Zoom in
[self.rootNode runAction:[SKAction scaleBy:.5 duration:5]];
self is the SKScene.
I hope this helps.

cococs2d everything 20px off on y axis

Somehow everything is about 20px low on y axis.
I have sprite moving around screen but is limited to screen width & height, this sprite never goes above about 460px & goes below the bottom. There is nothing wrong with my code. I inserted Sprite to check top of screen:
CGSize screenSize = [CCDirector sharedDirector].winSize;
sprite.position = ccp(screenSize.width/2, screenSize.height);
This sprite is properly in the middle of width but about 20 px from the top.
Also when i insert sprite on touchLocation, sprite is also off.
The game is in Portrait view.
Is there something i should be setting?
You should set the status bar to hidden in the following places:
1. Initially hidden in the app plist
2. If you use any xib make sure it has no status bar.
If those 2 don't help - try setting it in code - look at uiapplication documentation specifically setStatusBarHidden:withAnimation:

How to flip a UIView around the x-axis while simultaneously switching subviews

This question has been asked before but in a slightly different way and I was unable to get any of the answers to work the way I wanted, so I am hoping somebody with great Core Animation skills can help me out.
I have a set of cards on a table. As the user swipes up or down the set of cards move up and down the table. There are 4 cards visible on the screen at any given time, but only the second card is showing its face. As the user swipes the second card flips back onto its face and the next card (depending on the swipe direction) lands in it's place showing its face.
I have set up my card view class like this:
#interface WLCard : UIView {
UIView *_frontView;
UIView *_backView;
BOOL flipped;
}
And I have tried flipping the card using this piece of code:
- (void) flipCard {
[self.flipTimer invalidate];
if (flipped){
return;
}
id animationsBlock = ^{
self.backView.alpha = 1.0f;
self.frontView.alpha = 0.0f;
[self bringSubviewToFront:self.frontView];
flipped = YES;
CALayer *layer = self.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / 500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, M_PI, 1.0f, 0.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;
};
[UIView animateWithDuration:0.25
delay:0.0
options: UIViewAnimationCurveEaseInOut
animations:animationsBlock
completion:nil];
}
This code works but it has the following problems with it that I can't seem to figure out:
Only half of the card across the x-axis is animated.
Once flipped, the face of the card is upside down and mirrored.
Once I've flipped the card I cannot get the animation to ever run again. In other words, I can run the animation block as many times as I want, but only the first time will animate. The subsequent times I try to animate lead to just a fade in and out between the subviews.
Also, bear in mind that I need to be able to interact with the face of the card. i.e. it has buttons on it.
If anybody has run into these issues it would be great to see your solutions. Even better would be to add a perspective transform to the animation to give it that extra bit of realism.
This turned out to be way simpler than I thought and I didn't have to use any CoreAnimation libraries to achieve the effect. Thanks to #Aaron Hayman for the clue. I used transitionWithView:duration:options:animations:completion
My implementation inside the container view:
[UIView transitionWithView:self
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromBottom
animations: ^{
[self.backView removeFromSuperview];
[self addSubview:self.frontView];
}
completion:NULL];
The trick was the UIViewAnimationOptionTransitionFlipFromBottom option. Incidentally, Apple has this exact bit of code in their documentation. You can also add other animations to the block like resizing and moving.
Ok, this won't be a complete solution but I'll point out some things that might be helpful. I'm not a Core-Animation guru but I have done a few 3D rotations in my program.
First, there is no 'back' to a view. So if you rotate something by M_PI (180 degrees) you're going to be looking at that view as though from the back (which is why it's upside down/mirrored).
I'm not sure what you mean by:
Only half of the card across the x-axis is animated.
But, it it might help to consider your anchor point (the point at which the rotation occurs). It's usually in the center, but often you need it to be otherwise. Note that anchor points are expressed as a proportion (percentage / 100)...so the values are 0 - 1.0f. You only need to set it once (unless you need it to change). Here's how you access the anchor point:
layer.anchorPoint = CGPointMake(0.5f, 0.5f) //This is center
The reason the animation only ever runs once is because transforms are absolute, not cumulative. Consider that you're always starting with the identity transform and then modifying that, and it'll make sense...but basically, no animation occurs because there's nothing to animate the second time (the view is already in the state you're requesting it to be in).
If you're animating from one view to another (and you can't use [UIView transitionWithView:duration:options:animations:completion:];) you'l have to use a two-stage animation. In the first stage of the animation, for the 'card' that is being flipped to backside, you'll rotate the view-to-disappear 'up/down/whatever' to M_PI_2 (at which point it will be 'gone', or not visible, because of it's rotation). And in the second stage, you're rotate the backside-of-view-to-disappear to 0 (which should be the identity transform...aka, the view's normal state). In addition, you'll have to do the exact opposite for the 'card' that is appearing (to frontside). You can do this by implementing another [UIView animateWithDuration:...] in the completion block of the first one. I'll warn you though, doing this can get a little bit complicated. Especially since you're wanting views to have a 'backside', which will basically require animating 4 views (the view-to-disappear, the view-to-appear, backside-of-view-to-disappear, and the backside-of-view-to-appear). Finally, in the completion block of the second animation you can do some cleanup (reset view that are rotated and make their alpha 0.0f, etc...).
I know this is complicated, so you might want read some tutorial on Core-Animation.
#Aaron has some good info that you should read.
The simplest solution is to use a CATransformLayer that will allow you to place other CALayer's inside and maintain their 3D hierarchy.
For example to create a "Card" that has a front and back you could do something like this:
CATransformLayer *cardContainer = [CATransformLayer layer];
cardContainer.frame = // some frame;
CALayer *cardFront = [CALayer layer];
cardFront.frame = cardContainer.bounds;
cardFront.zPosition = 2; // Higher than the zPosition of the back of the card
cardFront.contents = (id)[UIImage imageNamed:#"cardFront"].CGImage;
[cardContainer addSublayer:cardFront];
CALayer *cardBack = [CALayer layer];
cardBack.frame = cardContainer.bounds;
cardBack.zPosition = 1;
cardBack.contents = (id)[UIImage imageNamed:#"cardBack"].CGImage; // You may need to mirror this image
[cardContainer addSublayer:cardBack];
With this you can now apply your transform to cardContainer and have a flipping card.
#Paul.s
I followed your approach with card container but when i applt the rotation animation on card container only one half of the first card rotates around itself and finally the whole view appears.Each time one side is missing in the animation
Based on Paul.s this is updated for Swift 3 and will flip a card diagonally:
func createLayers(){
transformationLayer = CATransformLayer(layer: CALayer())
transformationLayer.frame = CGRect(x: 15, y: 100, width: view.frame.width - 30, height: view.frame.width - 30)
let black = CALayer()
black.zPosition = 2
black.frame = transformationLayer.bounds
black.backgroundColor = UIColor.black.cgColor
transformationLayer.addSublayer(black)
let blue = CALayer()
blue.frame = transformationLayer.bounds
blue.zPosition = 1
blue.backgroundColor = UIColor.blue.cgColor
transformationLayer.addSublayer(blue)
let tgr = UITapGestureRecognizer(target: self, action: #selector(recTap))
view.addGestureRecognizer(tgr)
view.layer.addSublayer(transformationLayer)
}
Animate a full 360 but since the layers have different zPositions the different 'sides' of the layers will show
func recTap(){
let animation = CABasicAnimation(keyPath: "transform")
animation.delegate = self
animation.duration = 2.0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
animation.toValue = NSValue(caTransform3D: CATransform3DMakeRotation(CGFloat(Float.pi), 1, -1, 0))
transformationLayer.add(animation, forKey: "arbitrarykey")
}

CALayer transforming

I need to rotate a square when the user does a UIRotationGesture.
I have the gesture all set up. The problem is every time the user moves their fingers the square returns to the starting position and then animates to the new position. Rather than move from the previous position to the new position.
i.e if the rotation of the square is 90 degrees and the user continues to rotate to 100. the square will go back to 0 degrees and animate to 100.
Essentially I want the square to mirror the user when they perform a rotate gesture.
- (void)respondToGesture:(UIRotationGestureRecognizer *)rec{
NSLog(#"Rotation: %f", rec.rotation);
[self rotateWithRadian:rec.rotation];
if (rec.state == UIGestureRecognizerStateEnded) {
NSLog(#"gesture ended");
}
}
- (void)rotateWithRadian:(float)radian{
CABasicAnimation *spin = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
spin.removedOnCompletion = NO;
spin.fillMode = kCAFillModeForwards;
[spin setByValue:[NSNumber numberWithFloat:radian]];
[spin setDuration:1.0];
[squarelayer addAnimation:spin forKey:#"spinAnimation"];
Is there a reason you're not directly setting the layer's transform instead of using an animation?
This should do what you want:
- (void)respondToGesture:(UIRotationGestureRecognizer *)rec {
if (rec.state == UIGestureRecognizerStateChanged) {
CGAffineTransform currentTransform = squareLayer.affineTransform;
squareLayer.affineTransform = CGAffineTransformRotate(currentTransform, gesture.rotation);
gesture.rotation = 0;
}
}
You also need to adjust squareLayer.anchorPoint if you want the rotation to happen around the centre of the user's rotation instead of the centre of the layer.
There is a open source ( https://github.com/kirbyt/KTOneFingerRotationGestureRecognizer )for single finger rotation. You can check it out, will be much better. Even if you want only normal rotation gesture, you can look into the code for better rotation code. It uses center as origin.