7

In iOS 9 Apple introduced the collisionBoundsType to UIKit-Dynamics.

I have no issue when setting this UIDynamicItemCollisionBoundsTypeRectangle or when I set this to UIDynamicItemCollisionBoundsTypeEllipse.

The screenshot below is from a game I am making where the collisionBoundsType of the player is set to rectangle and the ball is set to ellipse:

enter image description here

However, when I set the player's collisionBoundsType to path I get weird behavior as seen here:

enter image description here

The view appears higher than it should and the collision body is to the right of where it should be.

Currently I have collisionBoundingPath set to this:

- (UIBezierPath *)collisionBoundingPath
{
    maskPath = [[UIBezierPath alloc] init];
    [maskPath addArcWithCenter:CGPointMake(SLIME_SIZE, SLIME_SIZE) radius:SLIME_SIZE startAngle:0*M_PI endAngle:M_PI clockwise:NO];
    return maskPath;
}

Additionally, my drawRect function looks like this:

- (void) drawRect:(CGRect)rect
{
    if (!_color){
    [self returnDefualtColor];
    }
    if (!maskPath) maskPath = [[UIBezierPath alloc] init];
    [maskPath addArcWithCenter:CGPointMake(SLIME_SIZE, SLIME_SIZE) radius:SLIME_SIZE startAngle:0*M_PI endAngle:M_PI clockwise:NO];
    [_color setFill];
    [maskPath fill];
}

Why is this happening? How do I set the path of the collision body to be the same as the drawing in the view?

Additionally, the red is just the background of the view (i.e. view.backgroundColor = [UIColor redColor];).

Wyetro
  • 8,439
  • 9
  • 46
  • 64
  • Not a drawing expert, but the documentation suggests the path should be a polygon. Your code is adding an arc but not closing the path. Perhaps you should be calling `[maskPath closePath]` to create a closed path which may make the difference? – Rory McKinnel Oct 21 '15 at 10:25
  • It closes automatically – Wyetro Oct 21 '15 at 16:49
  • For the fill in `drawRect` this will be the case. Are you sure for your `collisionBoundingPath`? Where does it say in the docs that it closes the path? – Rory McKinnel Oct 21 '15 at 19:14
  • I mean, that's just clearly not the issue. If it didn't close itself then the ball would be inside of the slime in the second image, given that the bottom is the edge that wouldn't be closed. – Wyetro Oct 21 '15 at 19:17
  • My assumption was it would not work properly at all with a non closed path and hence potentially show odd behavior as you see. You noted it works Ok with a rectangle and does not when you change to a path, which implies the path or the path mechanism is broken. You probably need to post more code or an example project. – Rory McKinnel Oct 21 '15 at 21:05
  • @RoryMcKinnel, I got the answer. If you can explain why this is the case (in another answer) I'll award you the bounty. – Wyetro Oct 22 '15 at 04:52
  • Having you tried using SKSpriteKit? It's practically made for what it looks like you're building. Plus there is a built in physics engine that is easy to use. – ChrisHaze Oct 22 '15 at 04:56
  • I'm specifically using the UIDynamics library and was definitely not looking to use SpriteKit. – Wyetro Oct 22 '15 at 04:57
  • I once read somewhere that the only really useful use of SKShapeNode is to debug physics by using it to draw outlines for the actual physics body. It's part of Sprite Kit, and has HORRID performance. CAShapeLayer is sort of an equivalent, but better. You could, in future situations like this, use it to do a drawing of the actual path, and you might have found this odd anchor position nuisance easier. Or Apple could make a visual editor with their 100's of billions... – Confused Oct 22 '15 at 05:17
  • @Confused & WMios I'm glad I asked. Just trying to help out and now I am excited to dig into Core Animation for fixes to some of the issues I have with Sprite Kit. – ChrisHaze Oct 22 '15 at 06:07
  • check out shouldRasterize, it's a way of turning drawn objects into bitmaps within the API/Framework of Core Animation. It didn't work reliably until iOS 8.3.x, but has been pretty good since then. CAShapeLayer is (in my uses) far faster than Sprite Kit. And all of CA is generally speaking faster than Sprite Kit. Somewhere on SO is a question by a guy that found CA's handling of bitmaps/textures/sprites to be better than SpriteKit, too. – Confused Oct 22 '15 at 07:40
  • @WMios Posted answer for what I believe is the reason you need to adjust the coordinates. However I think you may have set the view frame wrongly to SLIME_SIZE wide/high rather than SLIME_SIZE*2. See the answer, but everything has to be relative to the center of the dynamic item for collision paths and that will depend in its width/height. – Rory McKinnel Oct 22 '15 at 10:55

3 Answers3

2

From the documentation on the UIDynamicItem here, the following statement about the coordinate system for paths seems to represent what is wrong:

The path object you create must represent a convex polygon with counter-clockwise or clockwise winding, and the path must not intersect itself. The (0, 0) point of the path must be located at the center point of the corresponding dynamic item. If the center point does not match the path’s origin, collision behaviors may not work as expected.

Here it states that the (0,0) for the path MUST be the center point.

I would think that the center of your arc path should be (0,0) and not (SLIME_SIZE/2,SLIME_SIZE/2). Have you perhaps set the width and height of the UIView frame to SLIME_SIZE rather than SLIME_SIZE*2?

SLIME_SIZE really seems to define the radius, so the frame width should be SLIME_SIZE*2. If it is set as SLIME_SIZE, then that would explain why you need to translate by SLIME_SIZE/2 as a correction.

Wyetro
  • 8,439
  • 9
  • 46
  • 64
Rory McKinnel
  • 7,936
  • 2
  • 17
  • 28
1

I was able to answer this by changing:

- (UIBezierPath *)collisionBoundingPath
{
    maskPath = [[UIBezierPath alloc] init];
    [maskPath addArcWithCenter:CGPointMake(SLIME_SIZE, SLIME_SIZE) radius:SLIME_SIZE startAngle:0*M_PI endAngle:M_PI clockwise:NO];
    return maskPath;
}

to:

- (UIBezierPath *)collisionBoundingPath
{
    maskPath = [[UIBezierPath alloc] init];
    [maskPath addArcWithCenter:CGPointMake(SLIME_SIZE / 2, SLIME_SIZE / 2) radius:SLIME_SIZE startAngle:0*M_PI endAngle:M_PI clockwise:NO];
    return maskPath;
}

The key difference is that I modified the center of the arc by dividing the x and y values by 2.

Wyetro
  • 8,439
  • 9
  • 46
  • 64
1

Debugging physics is a thing. It's probably not something that iOS users have tended to think a lot about as they've generally done very simple things with UIKit Dynamics. This is a bit of a shame, as it's one of the best aspects of the recent editions of iOS, and offers a truly fun way to make compelling user experiences.

So... how to debug physics?

One way is to mentally imagine what's going on, and then correlate that with what's going on, and find the dissonance between the imagined and the real, and then problem solve via a blend of processes of elimination, mental or real trial & error and deduction, until the problem is determined and solved.

Another is to have a visual depiction of all that's created and interacting presenting sufficient feedback to more rapidly determine the nature and extents of elements, their relationships and incidents/events, and resolve issues with literal sight.

To this end, various visual debuggers and builders of physics simulations have been created since their introduction.

Unfortunately iOS does not have such a screen based editor or "scene editor" for UIKit Dynamics, and what is available for this sort of visual debugging in Sprite Kit and Scene Kit is rudimentary, at best.

However there's CALayers, which are present in all UIKit Views, into which CAShapeLayers can be manually created and drawn to accurately represent any and all physical elements, their bounds and their anchors and relationships.

CAShapeLayers are a "container" for CGPaths, and can have different colours for outline and fill, and more than one CGPath element within a single CAShapeLayer.

And, to quote the great Rob:

"If you add a CAShapeLayer as a layer to a view, you don't have to implement any drawing code yourself. Just add the CAShapeLayer and you're done. You can even later change the path, for example, and it will automatically redraw it for you. CAShapeLayer gets you out of the weeds of writing your own drawRect or drawLayer routines."

If you have an enormous number of interacting elements and want to debug them, CAShapeLayer's performance issues might come into play, at which point you can use shouldRasterize to convert each to a bitmap, and get a significant performance improvement when hitting limits created by the "dynamic" capabilities of CAShapeLayers.

Further, for representing things like constraints and joints, there's a simple process of created dashed lines on CAShapeLayers, by simply setting properties. Here's the basics of setting up a CAShapeLayer's properties, and the way to use an array to create a 5-5-5 dashed outline with a block stroke, width of 3, no fill.

CAShapeLayer *shapeLayer = [CAShapeLayer layer];
[shapeLayer setBounds:self.bounds];
[shapeLayer setPosition:self.center];
[shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
[shapeLayer setStrokeColor:[[UIColor blackColor] CGColor]];
[shapeLayer setLineWidth:3.0f];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setLineDashPattern:
 [NSArray arrayWithObjects:[NSNumber numberWithInt:10],
  [NSNumber numberWithInt:5],nil]];
Confused
  • 6,048
  • 6
  • 34
  • 75