UIKit Dynamics UICollisionBehavior collision without bounce - uikit-dynamics

I have a view whose boundaries are set up for collisions (setTranslatesReferenceBoundsIntoBoundaryWithInsets) and a subview setup with gravity so that it can collide against the superview bounds.
I'm trying to make the collision 0% bouncy, but I haven't figured out yet how. I tried a UIDynamicItemBehavior for the subview with elasticity to 0, also with ridiculously high friction and nothing. My rationale was that 0 elasticity already means 0 force regeneration on impact but even negative numbers seem to do nothing or very little about it.
Any ideas as to how to make the collision absorb all energy or whatever it takes to make the subview not bounce when it collides against the bounds?

I may be doing it wrong, but the following seemed to work in a brief example:
Allocate UIDynamicItemBehavior for item(s) in question:
self.itemBehaviorInQuestion = [[UIDynamicItemBehavior alloc] initWithItems:#[self.infoView]];
self.itemBehaviorInQuestion.resistance = 0;
self.collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[self.infoView]];
self.collisionBehavior.collisionDelegate = self;
[self.animator addBehavior:self.collisionBehavior];
[self.animator addBehavior:self.itemBehaviorInQuestion];
Implement the following UICollisionBehavior delegate methods:
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
self.itemBehaviorInQuestion.resistance = 100;
}
- (void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier
{
self.itemBehaviorInQuestion.resistance = 0;
}
Setting resistance to a high value at that moment seems to relieve the item of its bounce.

you need to set the value of elasticity:
UIView* square = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
self.view addSubview:square
UIDynamicAnimator* a = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIDynamicItemBehavior* behavior = [[UIDynamicItemBehavior alloc] initWithItems:#[square]];
behavior.elasticity = 0.5;
[a addBehavior:behavior];

I have an idea how to do it, but haven't tested it. Caveat emptor!
Use UIDynamicItemBehavior on your view. Set resistance to a high value after the collision, using the UICollisionBehaviorDelegate method collisionBehavior:endedContactForItem:withItem. 'Resistance' is like friction between the item and the owning view. Here's a sketch...
UIDynamicItemBehavior *lowFriction = [[UIDynamicItemBehavior alloc] init];
lowFriction.resistance = 0.0;
UIDynamicItemBehavior *highFriction = [[UIDynamicItemBehavior alloc] init];
highFriction.resistance = 10.0;
yourCollidingView *ycv = [[yourCollidingView alloc] init]; // yourCollidingView is a subclass of UIView
[noFriction addItem:ycv];
[myAnimator addBehavior:lowFriction];
[myAnimator addBehavior:highFriction];
Then in the delegate
- (void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item1 withItem:(id<UIDynamicItem>)item2 {
[lowFriction removeItem:item1];
[lowFriction removeItem:item2];
[highFriction addItem:item1];
[highFriction addItem:item2];
}

Related

Adding a Hud Layer to Scene Cocos2d-3

To keep it simple, what is the easiest way to make the default [ Menu ] in default HelloWorld Scene (for example) as it's own layer. Issue I'm having now is that the scene is completely black, with nothing showing up!
GameLayer node:
- (id)init
{
// Enable touch handling on scene node
self.userInteractionEnabled = YES;
self.theMap = [CCTiledMap tiledMapWithFile:#"AftermathRpg.tmx"];
self.contentSize = theMap.contentSize;
self.metaLayer = [theMap layerNamed:#"Meta"];
metaLayer.visible = NO;
CCTiledMapObjectGroup *objects = [theMap objectGroupNamed:#"mainChar"];
NSMutableDictionary *startPoint = [objects objectNamed:#"startPosition"];
int x = [[startPoint valueForKey:#"x"] intValue];
int y = [[startPoint valueForKey:#"y"] intValue];
self.mainChar = [CCSprite spriteWithImageNamed:#"mainChar.png"];
mainChar.position = ccp(x,y);
[self addChild:mainChar];
[self addChild:theMap z:-1];
[self setCenterOfScreen: mainChar.position];
return self;
}
HudLayer node
-(id)init
{
CGSize winSize = [[CCDirector sharedDirector] viewSize];
CCButton *backButton = [CCButton buttonWithTitle:#"[ Menu ]" fontName:#"Verdana-Bold" fontSize:18.0f];
backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
[backButton setTarget:self selector:#selector(onBackClicked:)];
[self addChild:backButton];
return self;
}
Scene
+ (GameScene *)scene
{
return [[self alloc] init];
}
- (id)init
{
// Apple recommend assigning self with supers return value
self = [super init];
if (!self) return(nil);
CGSize winSize = [CCDirector sharedDirector].viewSize;
self.gameLayer = [GameLayer node];
[self addChild:gameLayer z:-1];
//self.contentSize = self.gameLayer.contentSize;
hudLayer = [HudLayer node];
hudLayer.position = ccp(winSize.width * 0.9, winSize.height * 0.9);
[self addChild:hudLayer z:1];
return self;
}
From the OP I take that you have two issues with one being that the HUD is not static (i.e. it is moving as your map moves which you don't want) and that it is not positioning at the top of the screen.
Looking at the position issue first, your position is set to normalized. Since the scene's content size has been made to be the size of your map, which I take is larger than your screen, then this is why it is showing up at the top right of the map and not the screen. To fix this don't do normalized positioning. If you want to be able to still express the position in the 0 to 1 range, use (remove the line that sets the position type to normalized also):
CGSize winSize = [[CCDirector sharedDirector] viewSize];
backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
If your map is 10,000 x 10,000 then using the normalized positioning like you are will set the button to (8,500, 9,500) rather than the top of the screen.
Looking at the static issue next, from the looks of it you have the Hello World scene that you are adding everything to right? It also looks like you are moving the Hello World scene with a call to:
[self setCenterOfScreen: player.position];
What you want to do instead is this, you first have a scene:
HelloWorldScene* scene;
And to this scene you are adding two "main" layers with one being your gameplay layers as children of a main gameplay layer and the other being your HUD layer, which for example could look something like:
GameplayLayer* gameLayer;
HudLayer* hudLayer;
[scene addChild:gameLayer];
[scene addChild:hudLayer];
When the player moves (or camera or whatever), what should be moving is the game's layer, not the root Hello World scene. Moving the root scene will move all of its children, which includes the hud. That is not what you want.
When I worked on, for example, the Goldfish Mysteries app (https://itunes.apple.com/us/app/finn-friends-mysteries/id740040227?mt=8) I had essentially layers for:
Story (comprised of multiple sub-layers like bg, characters, etc)
Text (highlighted text that plays along with the narration audio)
HUD (comprised of multiple sub-layers)
Whenever there is movement on the story level it occurs on the story layer. When the HUD appears then the text and story layers are paused recursively (i.e. them and all of their children) along with the narration audio if any is playing but the HUD layer remains untouched. Resuming consists of resuming story, text, and any playing narration. I don't remember off the top if dropping the hud moved down the story and text layers in this specific app since I don't have my iPad in front of me, but i have done apps in the past where dropping the hud shifted all other layers. In that situation and for a simple app it would be fine to move the scene since the scene would only be shifted enough to show the TOC (in this type of app for example). What you look to be doing is moving the entire scene with the player's movement, which is not what you really wanted to do by the looks of it.
Either way you want a clear separation between layers and operations that are meant to only happen on specific layers should be directed only towards those layers.
Hope this helped.
UPDATE (Edited):
Based on your new edited OP, you have a new problem that you've introduced. For the hud you shouldn't have to set the layer's position since inside the hud layer everything is already being laid out relative to the screen anyways. So what you should have instead is:
hudLayer = [HudLayer node];
[self addChild:hudLayer z:1];
The second issue is that you are not properly writing your init methods for the game and hud layer classes. The init should look like:
- (instanceType)init
{
self = [super init];
if (self)
{
// Do your init stuff...
}
return self;
}
You are never calling:
self = [super init];

Better way to initialize UIImageView with non-zero origin

Creating UIImageView with some offset is quite common task when you're building interface in code.
I can see two ways to initialize UIImageView with origin not equal to (0,0):
First way requires only image filename and origin, but contains a lot of code (we can reduce number of lines by one using frame.origin = CGPointMake(x,y); ):
UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image_filename"]];
CGRect frame = imgView.frame;
frame.origin.x = 150;
frame.origin.y = 100;
undoBg.frame = frame;
Second way has much less code, looks cleaner but we need to hardcode image size:
UIImageView *shadowView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 150, 800, 600)];
shadowView.image = [UIImage imageNamed:#"image_filename"];
What is best practice for you and why?
Thanks.
Hardcoding the images sizes is a form of Unnamed numerical constants which is an indication of Code Smell
This sort of thing should be avoided as much as possible as it can generate code that is a lot harder to maintain and is prone to human introduced errors. For example what happens when your graphic artist changes the size of the image? Instead of changing just one thing (the image) you now have to change many things (the image, and every place in the code where the image size has been hard coded)
Remember that you code not for today, but for the people who will come after you and maintain your code.
If anything, if you were really concerned about the extra lines of code, then you would abstract loading the UIImageView into a category, so that it can be used everywhere (note that this code is not tested):
#interface UIImageView (MyExtension)
-(UIImageView*)myLoadImage:(NSString*)named at:(CGPoint)location;
#end
#implementation
-(UIImageView*)myLoadImage:(NSString*)named at:(CGPoint)location
{
UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:named]];
CGRect frame = imgView.frame;
frame.origin.x = location.x;
frame.origin.y = location.y;
return imgView;
}
#end
Then you could simply do:
UIImageView* imageView = [UIImageView myLoadImage:#"image_filename" at:CGPointMake(150,100)];
I use the second one with slight modification,
UIImageView *shadowView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 150, 800, 600)];
shadowView.image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileName ofType:extension] ];
because imageNamed: caches image and cause memory leak.
I usually want my code to be easily readable. On the other hand I want the job done as fast as possible. In this case, there is so little code, I would go with less code. This is because I can get understand it so fast anyways. If it would be a much bigger example, I would use the easily readable code.
Surely it depends on your requirements. If I need to create an imageView in a class where the offset may change then I might do something like:
int myX = 10;
int myY = 100;
int myWidth = 200;
int myHeight = 300;
UIImageView *shadowView = [[UIImageView alloc] initWithFrame:CGRectMake(myX, myY, myWidth, myHeight)];
shadowView.image = [UIImage imageNamed:#"image_filename"];
but if I don't need to vary the offset and I know for a fact that the value won't change and no-one else will be needing to read or re-use my code then there's maybe nothing wrong (imho) with just using numbers in place of the int vars.
btw, you might want to avoid imageNamed as it caches the image which can lead to leaks.

Antialiasing edges of UIView after transformation using CALayer's transform

I have a UIView object that rotates using CALayer's transform:
// Create uiview object.
UIImageView *block = [[UIImageView alloc] initWithFrame....]
// Apply rotation.
CATransform3D basicTrans = CATransform3DIdentity;
basicTrans.m34 = 1.0/-distance;
blockImage.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
After rotating the edges of the object are not antialiasing. I need to antialias them.
Help me, please. How can it be done?
One way to do this is by placing the image inside another view that's 5 pixels bigger. The bigger view should have a transparent rasterized border that will smooth the edges of the UIImageView:
view.layer.borderWidth = 3;
view.layer.borderColor = [UIColor clearColor].CGColor;
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
Then, place your UIImageView inside this parent view and center it (With 2.5 pixels around each edge).
Finally, rotate the parent view instead of the image view.
It works very well - you can also encapsulate the whole thing in class that creates the hierarchy.
Simply add this key-value pair to your Info.plist: UIViewEdgeAntialiasing set to YES.
check allowsEdgeAntialiasing property of CALayer.
block.layer.allowsEdgeAntialiasing = YES; // iOS7 and above.
I had a similar issue when rotating around the z-axis. Setting shouldRasterize = YES prevented the jagged edges however it came at a performance cost. In my case I was re-using the views (and its layers) and keeping the shouldRasterize = YES was slowing things down.
The solution was, to turn off rasterization right after I didn't need it anymore. However since animation runs on another thread, there was no way of knowing when the animation was complete...until I found out about an extremely useful CATransaction method. This is an actual code that I used and it should illustrate its use:
// Create a key frame animation
CAKeyframeAnimation *wiggle = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSInteger frequency = 5; // Higher value for faster vibration
NSInteger amplitude = 25; // Higher value for lower amplitude
// Create the values it will pass through
NSMutableArray *valuesArray = [[NSMutableArray alloc] init];
NSInteger direction = 1;
[valuesArray addObject:#0.0];
for (NSInteger i = frequency; i > 0; i--, direction *= -1) {
[valuesArray addObject:#((direction * M_PI_4 * (CGFloat)i / (CGFloat)amplitude))];
}
[valuesArray addObject:#0.0];
[wiggle setValues:valuesArray];
// Set the duration
[wiggle setAdditive:YES];
[wiggle setValueFunction:[CAValueFunction functionWithName:kCAValueFunctionRotateZ]];
[wiggle setDuration:0.6];
// Turn on rasterization to prevent jagged edges (anti-aliasing issues)
viewToRotate.layer.shouldRasterize = YES;
// ************ Important step **************
// Very usefull method. Block returns after ALL animations have completed.
[CATransaction setCompletionBlock:^{
viewToRotate.layer.shouldRasterize = NO;
}];
// Animate the layer
[viewToRotate.layer addAnimation:wiggle forKey:#"wiggleAnimation"];
worked like a charm for me.
I have not tried using this with implicit animations (i.e. animations that happen due to value change in animatable property for a non-view associated layer), however I would expect it to work as long as the CATransaction method is called before the property change, just as a guarantee the block is given to CATransaction before an animation starts.

IOS: move an UIImageView

In my app I want to move a little UIImageView with inside a .png; this is a little insect and I want to simulate his flight. At example I want that this png do when it move an inverted eight as the infinite simbol ∞
You may use CoreAnimation. You can subclass a view, create a subview for the insect, and then assign an animation to it, following a defined path.
Your UIImageView could be animated. If it's a fly, you can do a few frames for wing moves:
NSArray *images = [NSArray arrayWithObjects:..., nil];
insect.animationImages = images;
insect.animationDuration = ??;
insect.animationRepeatCount = 0;
[insect startAnimating];
Then set an init frame for the insect:
insect.frame = CGRectMake(-120, 310, [[images objectAtIndex:0] size].width, [[images objectAtIndex:0] size].height);
And then define the path:
CGMutablePathRef aPath;
CGFloat arcTop = insect.center.y - 50;
aPath = CGPathCreateMutable();
CGPathMoveToPoint(aPath, NULL, insect.center.x, insect.center.y);
CGPathAddCurveToPoint(aPath, NULL, insect.center.x, arcTop, 240, -100, 490, 360);
CAKeyframeAnimation* arcAnimation = [CAKeyframeAnimation animationWithKeyPath: #"position"];
arcAnimation.repeatCount = HUGE_VALF;
[arcAnimation setDuration: 4.5];
[arcAnimation setAutoreverses: NO];
arcAnimation.removedOnCompletion = NO;
arcAnimation.fillMode = kCAFillModeBoth;
[arcAnimation setPath: aPath];
CFRelease(aPath);
[insect.layer addAnimation: arcAnimation forKey: #"position"];
I leave how to do the infinite loop path up to you :)
Hope it helps!
Normally, if you were to be moving things around, I'd suggest using [UIView animate...]. However, you want something to move on a complex, curvy path. So instead, I'd suggest coming up with an equation that gives the (x,y) for the insect as a function of time, and then start an NSTimer with a fairly small time interval, and every time you get an update, move the insect (perhaps using [UIView animate...]).
Another way to go is to use a 2-d animation framework such as cocos2d - then, you can get an 'update' call linked to the frame refresh rate, inside of which you update the position of your insect using the same equation as from above.

Problem with uiimageView animation: animation stops

Here is my code:
-(void) createNewBall {
UIImage * image = [UIImage imageNamed:#"bulle_03.png"];
bulleBouge = [[UIImageView alloc] initWithImage:image];
[bulleBouge setCenter:[self randomPointSquare]];
[[self view] addSubview:bulleBouge];
}
-(void)moveTheBall{
bulleBouge.center = CGPointMake(imageView.center.x + X, imageView.center.y + Y);
}
createNewBall is called every two seconds. My problem is that every bulleBouge that is created stops moving after two seconds. I don't know why.
How can I solve this please?
It stops moving b/c u are initializing new bulleBouge every two seconds. You are also leakin memory since you never releasy it before assigning new value to it. so what happens is that after u create the imageView you only keep the reference to the lasts instance, hence only the last one is changing position. To fix this store all your new uiImageViews in an array and move them randomly after two seconds.
-(void) createNewBall {
UIImage * image = [UIImage imageNamed:#"bulle_03.png"];
UIImageView *bulleBouge = [[UIImageView alloc] initWithImage:image];
[bulleBouge setCenter:[self randomPointSquare]];
[bulleBougeArray addObject:bulleBouge];
[[self view] addSubview:bulleBouge];
[bulleBouge release];
}
-(void)moveTheBall{
for(int i=0; i< [bulleBougeArray count];i++){
UIImageView *bulleBouge = [bulleBougeArray objectAtIndex:i];
bulleBouge.center = CGPointMake(imageView.center.x + X, imageView.center.y + Y);
}
}
Cyprian is correct. In a simpler form, you're creating a new bulleBouge every two seconds, under the same variable. The program already has one, but since you told it to make a new one under the same ivar it forgets the old one, and thus doesn't move it. You need an array so that each ball can be remembered separately, and thus moved separately as seen in the example code that he posted.