UIDynamic 物理仿真引擎全面了解

什么是UIDynamic

UIDynamic 是从 iOS 7开始引入的一种新技术,隶属于UIKit框架可以认为是一种物理引擎,能模拟和仿真现实生活中的物理现象,如:重力、弹性碰撞等现象

物理引擎的价值

广泛用于游戏开发,经典成功案例是“愤怒的小鸟”让开发人员可以在远离物理学公式的情况下,实现炫酷的物理仿真效果提高了游戏开发效率,产生更多优秀好玩的物理仿真游戏

知名的2D物理引擎

Box2d
Chipmunk

三个概念

物理仿真元素(Dynamic Item)

谁要进行物理仿真?

物理仿真元素要素:
任何遵守了UIDynamicItem协议的对象

UIView 默认已经遵守了 UIDynamicItem 协议,因此任何 UI 控件都能做物理仿真 UICollectionViewLayoutAttributes 类默认也遵守 UIDynamicItem 协议

注意:不是任何对象都能做物理仿真元素,不是任何对象都能进行物理仿真

物理仿真行为(Dynamic Behavior)

执行怎样的物理仿真效果?怎样的动画效果?

UIDynamic提供了以下几种物理仿真行为:

  • UIGravityBehavior:重力行为
  • UICollisionBehavior:碰撞行为
  • UISnapBehavior:捕捉行为
  • UIPushBehavior:推动行为
  • UIAttachmentBehavior:附着行为
  • UIDynamicItemBehavior:动力元素行为

上述所有物理仿真行为都继承自 UIDynamicBehavior
所有的 UIDynamicBehavior 都可以独立进行,组合使用多种行为时,可以实现一些比较复杂的效果

物理仿真器(Dynamic Animator)

让物理仿真元素执行具体的物理仿真行为,UIDynamicAnimator 类型的对象

1
- (instancetype)initWithReferenceView:(UIView *)view;

view参数:是一个参照视图,表示物理仿真的范围

UIDynamicAnimator 的常见方法

1
2
3
4
5
6
7
8
//添加1个物理仿真行为
- (void)addBehavior:(UIDynamicBehavior *)behavior;  
 
//移除1个物理仿真行为
- (void)removeBehavior:(UIDynamicBehavior *)behavior; 

//移除之前添加过的所有物理仿真行为 
- (void)removeAllBehaviors;

UIDynamicAnimator的常见属性

1
2
3
4
5
6
7
8
9
10
11
//参照视图 
@property (nonatomic, readonly) UIView* referenceView;

//添加到物理仿真器中的所有物理仿真行为
@property (nonatomic, readonly, copy) NSArray* behaviors;

//是否正在进行物理仿真
@property (nonatomic, readonly, getter = isRunning) BOOL running;

//代理对象(能监听物理仿真器的仿真过程,比如开始和结束
@property (nonatomic, assign) id <UIDynamicAnimatorDelegate> delegate;)

使用步骤

要想使用UIDynamic来实现物理仿真效果,大致的步骤如下

  1. 创建一个物理仿真器(顺便设置仿真范围)
  2. 创建相应的物理仿真行为(顺便添加物理仿真元素)
  3. 将物理仿真行为添加到物理仿真器中开始仿真

下面将详细介绍所有的物理仿真行为,并附上示例代码

UIGravityBehavior 重力行为

说明:给定重力方向、加速度,让物体朝着重力方向掉落

常用方法:

1
2
3
4
5
6
7
8
// UIGravityBehavior的初始化,item参数 :里面存放着物理仿真元素
- (instancetype)initWithItems:(NSArray *)items;

// 添加1个物理仿真元素
- (void)addItem:(id <UIDynamicItem>)item;

// 移除1个物理仿真元素
- (void)removeItem:(id <UIDynamicItem>)item;

UIGravityBehavior常见属性

1
2
3
4
5
6
7
8
9
10
11
// 添加到重力行为中的所有物理仿真元素
@property (nonatomic, readonly, copy) NSArray* items;

// 重力方向(是一个二维向量)
@property (readwrite, nonatomic) CGVector gravityDirection;

// 重力方向(是一个角度,以x轴正方向为0°,顺时针正数,逆时针负数)
@property (readwrite, nonatomic) CGFloat angle;

// 量级(用来控制加速度,1.0代表加速度是1000 points /second²)
@property (readwrite, nonatomic) CGFloat magnitude;

code

1
2
3
4
5
6
7
8
// 1.创建重力行为
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] init];

// 2.添加仿真元素
[gravity addItem:self.purperView];

// 3.让物理仿真元素执行仿真行为
[self.animator addBehavior:gravity];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 检测重力属性
*/
- (void)gravityProperty
{
// 1.重力行为
UIGravityBehavior *gravity = [[UIGravityBehavior alloc]init];
// 设置重力的方向(角度)x 轴正方向为0点,顺时针为正,逆时针为负
// gravity.angle = (M_PI_4);
// 设置重力的加速度,重力的加速度越大,碰撞就越厉害
gravity.magnitude = 100;
// 设置重力的方向(是一个二维向量)
gravity.gravityDirection=CGVectorMake(1, 1);
[gravity addItem:self.purperView];

// 碰撞检测行为
UICollisionBehavior *collision = [[UICollisionBehavior alloc] init];
[collision addItem:self.purperView];
[collision addItem:self.block1];
[collision addItem:self.block2];

// 让参照视图的边框成为碰撞检测的边界
collision.translatesReferenceBoundsIntoBoundary = YES;

//3.执行仿真
[self.animator addBehavior:gravity];
[self.animator addBehavior:collision];
}

UICollisionBehavior 碰撞行为

可以让物体之间实现碰撞效果,可以通过添加边界(boundary),让物理碰撞局限在某个空间中

UICollisionBehavior 边界相关的方法

1
2
3
4
5
6
7
8
9
10
11
- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier forPath:(UIBezierPath*)bezierPath;

- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier fromPoint:(CGPoint)p1 toPoint:(CGPoint)p2;

- (UIBezierPath*)boundaryWithIdentifier:(id <NSCopying>)identifier;

- (void)removeBoundaryWithIdentifier:(id <NSCopying>)identifier;

@property (nonatomic, readonly, copy) NSArray* boundaryIdentifiers;

- (void)removeAllBoundaries;

UICollisionBehavior 常见用法

1
2
3
4
5
6
7
8
9
10
11
// 是否以参照视图的bounds为边界
@property (nonatomic, readwrite) BOOL translatesReferenceBoundsIntoBoundary;

// 设置参照视图的bounds为边界,并且设置内边距
- (void)setTranslatesReferenceBoundsIntoBoundaryWithInsets:(UIEdgeInsets)insets;

// 碰撞模式(分为3种,元素碰撞、边界碰撞、全体碰撞)
@property (nonatomic, readwrite) UICollisionBehaviorMode collisionMode;

// 代理对象(可以监听元素的碰撞过程)
@property (nonatomic, assign, readwrite) id <UICollisionBehaviorDelegate> collisionDelegate;

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 重力和碰撞行为
*/
- (void)gravityAndCollision
{
// 重力行为
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] init];

[gravity addItem:self.purperView];

// 碰撞行为
UICollisionBehavior *collision = [[UICollisionBehavior alloc] init];
[collision addItem:self.purperView];
[collision addItem:self.block1];
[collision addItem:self.block2];

// 让参照视图的边框成为碰撞边界
collision.translatesReferenceBoundsIntoBoundary = YES;

// 执行仿真
[self.animator addBehavior:gravity];
[self.animator addBehavior:collision];

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 自定义碰撞边界
*/
- (void)customBounds
{
// 1.重力行为
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] init];
[gravity addItem:self.purperView];

// 2.碰撞检测行为
UICollisionBehavior *collision = [[UICollisionBehavior alloc] init];
[collision addItem:self.purperView];

// 添加一个椭圆为碰撞边界 被矩形包裹的圆
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 320, 320)];
// 注意:标识符不能写空。可以写字符串,因为需要标识符需要遵守NSCopying协议,而字符串满足要求。
[collision addBoundaryWithIdentifier:@"circle" forPath:path];

// 3.开始仿真
[self.animator addBehavior:gravity];
[self.animator addBehavior:collision];
}

/**
* 绘制碰撞边界曲线
*/
- (void)makeBoundsPath
{
// 1.重力行为
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] init];
[gravity addItem:self.purperView];

// 2.碰撞检测行为
UICollisionBehavior *collision = [[UICollisionBehavior alloc] init];
[collision addItem:self.purperView];
CGPoint startP = CGPointMake(0, 160);
CGPoint endP = CGPointMake(320, 400);
[collision addBoundaryWithIdentifier:@"line1" fromPoint:startP toPoint:endP];
CGPoint startP1 = CGPointMake(320, 0);
[collision addBoundaryWithIdentifier:@"line2" fromPoint:startP1 toPoint:endP];
// collision.translatesReferenceBoundsIntoBoundary = YES;

// 3.开始仿真
[self.animator addBehavior:gravity];
[self.animator addBehavior:collision];
}

UISnapBehavior 捕捉行为

可以让物体迅速冲到某个位置(捕捉位置),捕捉到位置之后会带有一定的震动

UISnapBehavior的初始化

1
- (instancetype)initWithItem:(id <UIDynamicItem>)item snapToPoint:(CGPoint)point;

UISnapBehavior常见属性

1
2
// 用于减幅、减震(取值范围是0.0 ~ 1.0,值越大,震动幅度越小)
@property (nonatomic, assign) CGFloat damping;

注意:

如果要进行连续的捕捉行为,需要先把前面的捕捉行为从物理仿真器中移除

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取触摸点
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.view];

// 需要两个参数,一个是物理仿真元素,一个是捕捉点
UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:self.purperView snapToPoint:point];

// 设置防震系数,数值越大,震动的幅度越小 范围 0 ~ 1
snap.damping = arc4random_uniform(10) / 10.0;

// 添加新的仿真行为前要删除以前的仿真行为
[self.animator removeAllBehaviors];

// 添加仿真行为
[self.animator addBehavior:snap];

UIPushBehavior 推动行为

需要结合示例来说明如何使用

请看考UIPushBehavior文件夹

初始化方法和常用的属性:

1
2
3
4
5
6
7
8
9
10
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.image] mode:UIPushBehaviorModeInstantaneous];

// vector 矢量 ,控制 push 的方向
pushBehavior.pushDirection = CGVectorMake((velocity.x / 10) , (velocity.y / 10));

// magnitude 的属性控制 push 的快慢 必须慢慢试才能找出最合适的值
pushBehavior.magnitude = magnitude / ThrowingVelocityPadding;

self.pushBehavior = pushBehavior;
[self.animator addBehavior:self.pushBehavior];

UIAttachmentBehavior:附着行为

有关 API 详细的解释见:UIAttachmentBehavior Class Reference

具体来说就是我手怎么动,UIAttachmentBehavior 指定的 view 就怎么动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (IBAction)handleAttachmentGesture:(UIPanGestureRecognizer *)pan
{

// 在手势开始时添加 物理仿真行为
if (pan.state == UIGestureRecognizerStateBegan) {
// 首先要移除物理仿真行为
[self.animator removeAllBehaviors];

// 获取手势在 view 容器中的位置
CGPoint location = [pan locationInView:self.view];
// 获取手势在 lightBlueView 中的位置
CGPoint boxLocation = [pan locationInView:self.lightBlueView];

// 以 lightBlueView 为参考坐标系,计算触摸点到 lightBlueView 中点的偏移量
UIOffset offset = UIOffsetMake(boxLocation.x - CGRectGetMidX(self.lightBlueView.bounds), boxLocation.y - CGRectGetMidY(self.lightBlueView.bounds));


// 创建物理仿真行为
self.attach = [[UIAttachmentBehavior alloc] initWithItem:self.lightBlueView offsetFromCenter:offset attachedToAnchor:location];

[self.animator addBehavior:self.attach];
}

[self.attach setAnchorPoint:[pan locationInView:self.view]];

}

iOS9以后又新推出了一些方法,我大概看了一下,基本都是做小游戏的,使用这些方法你可以创造一个接近真实的世界,比如一个大风车在转,而每个扇叶顶端都有一个附着的舀水的东东就可以用 UIAttachmentBehavior 实现。

下面介绍一下 UIAttachmentBehavior 的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@property (readonly, nonatomic) UIAttachmentBehaviorType attachedBehaviorType;

// 该锚点关联物理仿真元素附着的点,在这个例子中,就是你的手指所触摸的那个点,
// lightBlueView 是围绕这个点在运动的。
@property (readwrite, nonatomic) CGPoint anchorPoint;

@property (readwrite, nonatomic) CGFloat length;
// 衰减,经测试,这个值越大,可以减震
@property (readwrite, nonatomic) CGFloat damping;

// 该值越大,物理仿真元素运动越剧烈
@property (readwrite, nonatomic) CGFloat frequency;

// 反抗旋转的程度
@property (readwrite, nonatomic) CGFloat frictionTorque NS_AVAILABLE_IOS(9_0);
@property (readwrite, nonatomic) UIFloatRange attachmentRange NS_AVAILABLE_IOS(9_0);

UIDynamicItemBehavior:动力元素行为

具体作用:给一个仿真元素设置各种物理属性。比如:

allowsRotation :是否允许旋转
resistance: 抗阻力 0CGFLOAT_MAX ,阻碍原有所加注的行为(如本来是重力自由落体行为,则阻碍其下落,阻碍程度根据其值来决定)
friction: 磨擦力 0.0
1.0 在碰撞行为里,碰撞对象的边缘产生
elasticity:弹跳性 0.01.0
density:密度 0
1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- (UIView *) newViewWithCenter:(CGPoint)paramCenter  backgroundColor:(UIColor *)paramBackgroundColor{
UIView *newView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)];
newView.backgroundColor = paramBackgroundColor;
newView.center = paramCenter;
return newView;
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];

UIView *topView = [self newViewWithCenter:CGPointMake(100.0f, 0.0f)
backgroundColor:[UIColor greenColor]];
UIView *bottomView = [self newViewWithCenter:CGPointMake(100.0f, 50.0f)
backgroundColor:[UIColor redColor]];

[self.view addSubview:topView];
[self.view addSubview:bottomView];

//构造动画
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

//gravity
UIGravityBehavior *gravity = [[UIGravityBehavior alloc]
initWithItems:@[topView, bottomView]];
[self.animator addBehavior:gravity];

//collision
UICollisionBehavior *collision = [[UICollisionBehavior alloc]
initWithItems:@[topView, bottomView]];
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];

//指派不同特性值 弹性bounce
UIDynamicItemBehavior *moreElasticItem = [[UIDynamicItemBehavior alloc]
initWithItems:@[bottomView]];
moreElasticItem.elasticity = 1.0f;

UIDynamicItemBehavior *lessElasticItem = [[UIDynamicItemBehavior alloc]
initWithItems:@[topView]];
lessElasticItem.elasticity = 0.5f;
[self.animator addBehavior:moreElasticItem];
[self.animator addBehavior:lessElasticItem];

}

本文所有代码:UIDynamic

文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2015/08/05/UIDynamic-%E7%89%A9%E7%90%86%E4%BB%BF%E7%9C%9F%E5%BC%95%E6%93%8E%E5%85%A8%E9%9D%A2%E4%BA%86%E8%A7%A3/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论