一、概念扩充:

1、在开发中,我们可以使用UIKit中提供的仿真行为,实现与现实生活中类似的物理仿真动画,UIKit动力学最大的特点是将现实世界动力驱动的动画引入了UIKit,比如重力,铰链连接,碰撞,悬挂等效果。我们使用仿真引擎(UIDynamicAnimator)或者叫仿真者来管理和控制各种仿真行为,同时各种仿真行为可以叠加使用,可以实现力的合成。

2、只有遵守了UIDynamicItem协议的对象才可以参与到UI动力学仿真中,从iOS 7开始,UIView和UICollectionViewLayoutAttributes 类默认实现了该协议。

二、主要涉及到系统提供的类(API):

1、UIDynamicBehavior:仿真行为。是基本动力学行为的父类,可以理解为抽象类,用以生成子类。

2、UIDynamicAnimator :动力学仿真者。程序运行过程中要保证对象一直存在,用来控制所有仿真行为。

3、基本的动力学行为类:UIGravityBehavior(重力)、UICollisionBehavior(碰撞)、UIAttachmentBehavior(链接)、UISnapBehavior(吸附)、UIPushBehavior(推动)以及UIDynamicItemBehavior(元素),本例中不讨论链接、吸附、推动。

4、UICollisionBehavior的代理协议方法:-collisionBehavior: beganContactForItem: withBoundaryIdentifier: atPoint:

5、UIResponder的触摸响应事件: -touchesMoved: withEvent:

三、案例开始,实现效果:

1、一些宏定义

//小球掉落的随机X坐标
#define randomX arc4random_uniform([UIScreen mainScreen].bounds.size.width-ballWH) //随机颜色
#define randomColor [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0] //球大小
#define ballWH 20
//计时器间隔
#define timerCount 1 //球拍宽度
#define boardW 100 //到多少球开闸(小球阈值)
#define maxCount 50 //球的弹性系数
#define ballE 0.8

2、私有类扩展,全局变量声明,并遵循了碰撞检测的代理协议

@interface ZQpingPangView ()<UICollisionBehaviorDelegate>

//仿真者,用来管理仿真行为
@property(nonatomic,strong) UIDynamicAnimator * animator; //仿真重力行为
@property(nonatomic,strong) UIGravityBehavior * gra ; //仿真碰撞行为
@property(nonatomic,strong) UICollisionBehavior * col; //仿真元素行为
@property(nonatomic,strong)UIDynamicItemBehavior * dyib; //小球阈值
@property(nonatomic,weak) UILabel * numLabel;
//得分栏
@property(nonatomic,weak) UILabel * scoreLabel;
//得分
@property(nonatomic,assign) NSInteger score; //球拍
@property(nonatomic,weak) UIImageView * board; //用于球拍边缘检测的路径
@property(nonatomic,strong) UIBezierPath * path; //记录小球数目的变量
@property(nonatomic,assign) NSInteger ballCount; @end

3、在自定义View的initWithFrame中实例化仿真者以及各个仿真行为,并且将他们赋值为全局属性

 -(instancetype)initWithFrame:(CGRect)frame
{
self= [super initWithFrame:frame];
//给视图添加大的背景
self.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]]; //初始化小球数量为0
self.ballCount=; //创建仿真者对象
UIDynamicAnimator * animator = [[UIDynamicAnimator alloc]initWithReferenceView:self];
self.animator =animator; //创建重力行为,并将其添加到仿真者对象中
UIGravityBehavior * gra = [[UIGravityBehavior alloc]init];
self.gra=gra;
[self.animator addBehavior:gra]; //创建碰撞行为,并将其添加到仿真者对象中
UICollisionBehavior * col = [[UICollisionBehavior alloc]init];
//将屏幕边缘碰撞检测开启(屏幕边缘是在仿真者对象中规定的)
col.translatesReferenceBoundsIntoBoundary=YES;
col.collisionDelegate=self;
self.col = col;
[self.animator addBehavior:col]; //创建仿真元素行为(弹性和摩擦),这里摩擦力效果不明显
UIDynamicItemBehavior * dyib=[[UIDynamicItemBehavior alloc]init];
//弹性
dyib.elasticity=ballE;
//摩擦力
dyib.friction=;
self.dyib=dyib;
[self.animator addBehavior:dyib]; //构图(生成中间的障碍物)
[self makeMainStructure]; // 创建计时器、让小球不断生成
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:timerCount target:self selector:@selector(makeBalls) userInfo:nil repeats:YES];
[timer fire]; //生成球
[self makeBalls]; //生成屏幕上方的两个label,显示得分和剩余小球
[self makeTwoLabel]; //生成接小球的拍子
[self makeBoard]; return self;
}

4、生成球的方法,生成小球以后就将小球添加到各个仿真行为中去

-(void)makeBalls
{
CGRect frame = CGRectMake(randomX, , ballWH, ballWH);
UIImageView * ball = [[UIImageView alloc]initWithFrame:frame];
ball.backgroundColor = randomColor;
ball.layer.cornerRadius= ballWH*0.5;
ball.layer.masksToBounds=YES; //计数器累加
self.ballCount +=;
[self addSubview:ball];
//添加到各个行为中
[self.gra addItem:ball];
[self.col addItem:ball];
[self.dyib addItem:ball]; //小球达到阈值,就“倾倒”出屏幕
if (self.ballCount==maxCount) {
self.col.translatesReferenceBoundsIntoBoundary=NO;
//计数器归零
self.ballCount =;
}
else{
self.col.translatesReferenceBoundsIntoBoundary=YES;
} NSInteger num =maxCount - self.ballCount % maxCount;
NSString * numStr = [NSString stringWithFormat:@"即将开闸:%ld",num];
self.numLabel.text=numStr; }

5、生成屏幕中的构图:能得分的箱子,障碍物,障碍点。这些障碍物的边缘都要加入到碰撞检测当中去,而不是自身加入到碰撞检测中(这样自己也会被撞飞的)

#pragma mark - 生成构图

-(void)makeMainStructure
{
//生成中间的障碍物
[self makeBarrier]; //生成得分障碍物
[self makeScoreBarrier]; } #pragma mark - 生成得分障碍物
-(void)makeScoreBarrier
{
//得分的箱子“box1”
UIImageView * box1 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"RedeemCode"]];
box1.bounds=CGRectMake(, , , );
box1.center=CGPointMake(, );
[self addSubview:box1];
//得分的箱子“box2”
UIImageView * box2 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"RedeemCode"]];
box2.bounds=CGRectMake(, , , );
box2.center=CGPointMake(, );
[self addSubview:box2]; //通过箱子的frame,创建路径对象
UIBezierPath * path1 = [UIBezierPath bezierPathWithRect:box1.frame];
UIBezierPath * path2 = [UIBezierPath bezierPathWithRect:box2.frame];
//通过路径 ,添加边缘碰撞检测,而不是把箱子添加进检测
[self.col addBoundaryWithIdentifier:@"score1" forPath:path1];
[self.col addBoundaryWithIdentifier:@"score2" forPath:path2]; } #pragma mark - 生成中间不得分的障碍物
-(void)makeBarrier
{
// 创建6个箱子当做障碍物
UIImageView * box1 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"Box1"]];
box1.bounds=CGRectMake(, , , );
box1.center=CGPointMake(, );
[self addSubview:box1]; UIImageView * box2 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"Box1"]];
box2.bounds=CGRectMake(, , , );
box2.center=CGPointMake(, );
[self addSubview:box2]; UIImageView * box3 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
[box3 sizeToFit];
box3.center=CGPointMake(, );
[self addSubview:box3]; UIImageView * box4 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
[box4 sizeToFit];
box4.center=CGPointMake(, );
[self addSubview:box4]; UIImageView * box5 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
[box5 sizeToFit];
box5.center=CGPointMake(, );
[self addSubview:box5]; UIImageView * box6 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
[box6 sizeToFit];
box6.center=CGPointMake(, );
[self addSubview:box6]; //创建6个路径
UIBezierPath * path1 = [UIBezierPath bezierPathWithRect:box1.frame];
UIBezierPath * path2 = [UIBezierPath bezierPathWithRect:box2.frame];
UIBezierPath * path3 = [UIBezierPath bezierPathWithRect:box3.frame];
UIBezierPath * path4 = [UIBezierPath bezierPathWithRect:box4.frame];
UIBezierPath * path5 = [UIBezierPath bezierPathWithRect:box5.frame];
UIBezierPath * path6 = [UIBezierPath bezierPathWithRect:box6.frame]; //添加6个路径到碰撞检测
[self.col addBoundaryWithIdentifier:@"box1" forPath:path1];
[self.col addBoundaryWithIdentifier:@"box1" forPath:path2];
[self.col addBoundaryWithIdentifier:@"box3" forPath:path3];
[self.col addBoundaryWithIdentifier:@"box4" forPath:path4];
[self.col addBoundaryWithIdentifier:@"box5" forPath:path5];
[self.col addBoundaryWithIdentifier:@"box6" forPath:path6]; }

6、生成屏幕上方两个显示数据的label

-(void)makeTwoLabel
{
//创建一个记录球数的label UILabel * numLabel = [[UILabel alloc]initWithFrame:CGRectMake(, , , )];
numLabel.backgroundColor=[UIColor greenColor];
[self addSubview:numLabel];
self.numLabel=numLabel;
NSString * str = [NSString stringWithFormat:@"即将开闸:%d",maxCount];
self.numLabel.text=str; //创建一个记录分数的label self.score=;
UILabel * scoreLabel = [[UILabel alloc]initWithFrame:CGRectMake(, , , )];
scoreLabel.backgroundColor=[UIColor yellowColor];
[self addSubview:scoreLabel];
self.scoreLabel=scoreLabel;
self.scoreLabel.text=@"得分:0"; }

7、生成球拍,这里同样也要将球拍的边缘添加进碰撞检测

-(void)makeBoard
{
UIImageView * board = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"RedButton"]];
board.bounds=CGRectMake(, , boardW, );
board.center=CGPointMake(self.center.x, self.frame.size.height*0.75);
[self addSubview:board]; //获取球拍边缘
UIBezierPath * path = [UIBezierPath bezierPathWithRect:board.frame];
//添加碰撞检测
[self.col addBoundaryWithIdentifier:@"board" forPath:path]; //做全局记录
self.board=board;
self.path=path; }

8、触摸响应事件:(1)更新球拍的位置,让他能够根据手指触摸移动,并且不能移动到屏幕的上半部。

        (2)更新球拍的碰撞检测的边缘,时刻保持球拍可以“击打”小球。

-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//获取屏幕触摸点的一些数据
//当前点
CGPoint currentPoint = [touches.anyObject locationInView:self];
//上一刻的点
CGPoint prePoint = [touches.anyObject previousLocationInView:self];
//便宜亮
CGPoint offset = CGPointMake(currentPoint.x-prePoint.x, currentPoint.y-prePoint.y);
CGFloat boardX = self.board.center.x;
CGFloat boardY = self.board.center.y; //给拍子添加一个 移动上边界
//拍子在下半部,正常移动
if (self.board.center.y>= self.center.y) {
boardX += offset.x;
boardY += offset.y;
self.board.center=CGPointMake(boardX, boardY);
}
else
{
//拍子到达边界,下一刻向下移动,则正常移动
if (offset.y>=) {
CGFloat boardX = self.board.center.x;
CGFloat boardY = self.board.center.y;
boardX += offset.x;
boardY += offset.y;
self.board.center=CGPointMake(boardX, boardY);
}
//拍子到达边界,下一刻向上移动,则竖直方向上不可移动(不能再向上移动了)
else{
CGFloat boardX = self.board.center.x;
boardX += offset.x;
self.board.center=CGPointMake(boardX, boardY);
}
} //先移除原来的拍子的边界碰撞
[self.col removeBoundaryWithIdentifier:@"board"]; //添加拍子新的边界碰撞 self.path= [UIBezierPath bezierPathWithRect:self.board.frame];
[self.col addBoundaryWithIdentifier:@"board" forPath:self.path]; }

9、重写碰撞代理方法, 实现得分。在设定障碍物的边缘碰撞检测时,都有标记了“Identifier”,所以根据这个标识,区分不同的障碍物,可以设定不同的得分

-(void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
NSString * str = (NSString *)identifier;
//得5分
if ( [str isEqualToString:@"score1"]){
self.score += ;
}
//得1分
else if([str isEqualToString:@"score2"])
{
self.score +=;
}
self.scoreLabel.text=[NSString stringWithFormat:@"得分:%ld",self.score]; }

至此,案例效果实现。

四、总结

1、UIKit动力学的引入,并不是为了替代CA或者UIView动画,在绝大多数情况下CA或者UIView动画仍然是最优方案,只有在需要引入逼真的交互设计的时候,才需要使用UIKit动力学它是作为现有交互设计和实现的一种补充。

2、使用物理仿真比较消耗性能。

3、个人感觉,物理仿真看似功能强大,还是有很多小问题以及不够灵活,比如碰撞检测的边缘添加方式不够灵活,而且容易出现小bug。拖拽和刚性附着等行为也会有小bug出现,本人功力不够,还没有很好解决。

4、本例中还遗留了一些问题,包括游戏本身交互性没有完成,只是做一个仿真行为使用的小练习,而且小球的释放问题也没处理(没有影响,就懒得弄了),球拍的击打效果,球拍的摩擦效果都没有完善,大神勿喷。。

UIDynamicBehavior的简单使用:接球小游戏的更多相关文章

  1. js实现简单的俄罗斯方块小游戏

    js实现简单的俄罗斯方块小游戏 开始 1. 创建一个宽为 200px,高为 360px 的背景容器 <!DOCTYPE html> <html lang="en" ...

  2. 使用 App Inventor 2 开发简单的安卓小游戏

    App Inventor2 是一个简单的在线开发安卓应用程序的工具,通过此工具,我们可以很轻松地开发安卓应用. 这里介绍的是笔者自己写的一个小游戏,游戏中玩家通过左右倾斜手机控制“水库”的左右移动,收 ...

  3. c#实现简单金山打字小游戏(源码)

    using GameDemo.Utils;using System;using System.Collections.Generic;using System.Linq;using System.Te ...

  4. 利用c语言做简单的迷宫小游戏

                       #include <stdio.h> #define ROW 6 #define COL 6 // 封装打印地图的函数 void printMap(c ...

  5. python 基础(实例1——登陆小游戏)

    一个简单的登陆小游戏,输入用户名和密码,如果和user_passwd.txt中内容匹配,则打印“welcome to login...”,如果三次输入错误则将该用户列入黑名单,无法再用该用户名登陆. ...

  6. 如何开发一个简单的HTML5 Canvas 小游戏

    原文:How to make a simple HTML5 Canvas game 想要快速上手HTML5 Canvas小游戏开发?下面通过一个例子来进行手把手教学.(如果你怀疑我的资历, A Wiz ...

  7. 分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”

    这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业 ...

  8. 一个简单的猜大小的小游戏 python

    初学python,用python写了一个简单的猜大小的小游戏 #!/usr/bin/env python #-*- coding:utf-8 -*- print "------------- ...

  9. 一个简单的基于canvas小游戏

    GDOI2016是我的退役战,不知道是题目画风不对,还是我自身的问题. 不过没关系啦,反正已经进过一次队OI生涯就没有什么遗憾的了. 这几天尝试着去做了个所谓的html5小游戏,略显简陋,但还是写个总 ...

随机推荐

  1. 3星|《商业周刊中文版:2017商业人物(下)》:酒店才应该是出行住宿的最佳选择,Airbnb不是

    商业周刊/中文版:2017商业人物(下) 对一些知名商业人物的访谈的合辑. 总体评价3星,有一些参考价值. 以下是本期一些内容的摘抄: 1:段永平是一位隐秘的亿万富豪,去年,他创立的智能手机姊妹品牌O ...

  2. (转) 分布式文件存储FastDFS(一)初识FastDFS

    http://blog.csdn.net/xingjiarong/article/details/50559849 一.FastDFS简介 FastDFS是一款开源的.分布式文件系统(Distribu ...

  3. CAD把一个命令当着一个函数调用,不执行(com接口VB语言)

    主要用到函数说明: MxDrawXCustomFunction::Mx_SendStringToExecuteFun 把一个命令当着一个函数调用,不执行,详细说明如下: 参数 说明 CString s ...

  4. 【剑指Offer】剑指offer题目汇总

      本文为<剑指Offer>刷题笔记的总结篇,花了两个多月的时间,将牛客网上<剑指Offer>的66道题刷了一遍,以博客的形式整理了一遍,这66道题属于相对基础的算法题目,对于 ...

  5. sysbench测试阿里云ECS云磁盘的IOPS,吞吐量

    测试阿里云ECS 对象:在aliyun上买了一个ECS附加的云盘,使用sysbench测试云盘的IOPS和吞吐量 sysbench prepare 准备文件,10个文件,1个1G [root@iZwz ...

  6. time、datatime模块

    python中时间日期格式化符号 %Y 年份(4位数表示) %y 年份(2位数表示) %m 月份(01-12) %d 月内中的一天(0-31) %H 24小时制小时数 %I 12小时制小时数 %M 分 ...

  7. 大专生自学Python到找到工作的心得

    先做个自我介绍,我13年考上一所很烂专科民办的学校,学的是生物专业,具体的学校名称我就不说出来献丑了.13年我就辍学了,我在那样的学校,一年学费要1万多,但是根本没有人学习,我实在看不到希望,我就退学 ...

  8. 用Twebbrowser做可控编辑器与MSHTML(调用js)

    记得intraweb的网页设计也程序开发分开中,是怎么定义的变量的.就是在网页中插入占位符.我们规定占位符是{%Name%} {%Birthday%}单页面装载之前or之后,我们用自己的js查找占位符 ...

  9. 【Codeforces 1106D】Lunar New Year and a Wander

    [链接] 我是链接,点我呀:) [题意] 让你遍历n个节点,访问过的节点不操作. 如果是没有访问过的点,那就把它加到序列的末尾. 问你形成的最小字典序的序列是多少. [题解] 显然每次找最小的标号 用 ...

  10. JPA学习(基于hibernate)

    参考博客:https://blog.csdn.net/baidu_37107022/article/details/76572195 常用注解: https://blog.csdn.net/eastl ...