• UIDynamicBehavior的简单使用:接球小游戏


    一、概念扩充:

    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中实例化仿真者以及各个仿真行为,并且将他们赋值为全局属性

     1 -(instancetype)initWithFrame:(CGRect)frame
     2 {
     3     self= [super initWithFrame:frame];   
     4     //给视图添加大的背景
     5     self.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]];
     6     
     7     //初始化小球数量为0
     8     self.ballCount=0;
     9     
    10     //创建仿真者对象
    11     UIDynamicAnimator * animator = [[UIDynamicAnimator alloc]initWithReferenceView:self];
    12     self.animator =animator;
    13  
    14     //创建重力行为,并将其添加到仿真者对象中
    15     UIGravityBehavior * gra = [[UIGravityBehavior alloc]init];
    16     self.gra=gra;
    17     [self.animator addBehavior:gra];
    18   
    19     //创建碰撞行为,并将其添加到仿真者对象中
    20     UICollisionBehavior * col = [[UICollisionBehavior alloc]init];
    21         //将屏幕边缘碰撞检测开启(屏幕边缘是在仿真者对象中规定的)
    22     col.translatesReferenceBoundsIntoBoundary=YES;
    23     col.collisionDelegate=self;
    24     self.col = col;
    25     [self.animator addBehavior:col];
    26     
    27     //创建仿真元素行为(弹性和摩擦),这里摩擦力效果不明显
    28     UIDynamicItemBehavior * dyib=[[UIDynamicItemBehavior alloc]init];
    29         //弹性
    30     dyib.elasticity=ballE;
    31         //摩擦力
    32     dyib.friction=1;
    33     self.dyib=dyib;
    34     [self.animator addBehavior:dyib];
    35     
    36     //构图(生成中间的障碍物)
    37     [self makeMainStructure];
    38     
    39     // 创建计时器、让小球不断生成
    40     NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:timerCount target:self selector:@selector(makeBalls) userInfo:nil repeats:YES];
    41     [timer fire];
    42     
    43     //生成球
    44     [self makeBalls];
    45     
    46     //生成屏幕上方的两个label,显示得分和剩余小球
    47     [self makeTwoLabel];
    48 
    49     //生成接小球的拍子    
    50     [self makeBoard];
    51     
    52     return self;
    53 }

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

    -(void)makeBalls
    {
        CGRect frame = CGRectMake(randomX, 0, ballWH, ballWH); 
        UIImageView * ball = [[UIImageView alloc]initWithFrame:frame];
        ball.backgroundColor = randomColor;
        ball.layer.cornerRadius= ballWH*0.5;
        ball.layer.masksToBounds=YES;
        
        //计数器累加
        self.ballCount +=1;
        [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 =0;
        }
        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(0, 0, 25, 25);
        box1.center=CGPointMake(145, 250);
        [self addSubview:box1];
    //得分的箱子“box2”
        UIImageView * box2 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"RedeemCode"]];
        box2.bounds=CGRectMake(0, 0, 20, 20);
        box2.center=CGPointMake(330, 350);
        [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(0, 0, 25, 25);
        box1.center=CGPointMake(150, 200);
        [self addSubview:box1];   
        
        UIImageView * box2 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"Box1"]];
        box2.bounds=CGRectMake(0, 0, 30, 30);
        box2.center=CGPointMake(300, 300);
        [self addSubview:box2];
       
        UIImageView * box3 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
        [box3 sizeToFit];
        box3.center=CGPointMake(70, 300);
        [self addSubview:box3];
       
        UIImageView * box4 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
        [box4 sizeToFit];
        box4.center=CGPointMake(280, 160);
        [self addSubview:box4];
    
        UIImageView * box5 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
        [box5 sizeToFit];
        box5.center=CGPointMake(100, 160);
        [self addSubview:box5];
        
        UIImageView * box6 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
        [box6 sizeToFit];
        box6.center=CGPointMake(390, 250);
        [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(280, 100, 120, 30)];
        numLabel.backgroundColor=[UIColor greenColor];
        [self addSubview:numLabel];
        self.numLabel=numLabel;
        NSString * str = [NSString stringWithFormat:@"即将开闸:%d",maxCount];
        self.numLabel.text=str;
        
        //创建一个记录分数的label
        
        self.score=0;
        UILabel * scoreLabel = [[UILabel alloc]initWithFrame:CGRectMake(20, 100, 100, 30)];
        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(0, 0, boardW, 10);
        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>=0) {
                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 += 5;        
        }
        //得1分
        else if([str isEqualToString:@"score2"])
        {
            self.score +=1;  
        }
        self.scoreLabel.text=[NSString stringWithFormat:@"得分:%ld",self.score];
    
    }

    至此,案例效果实现。

      

    四、总结

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

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

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

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

  • 相关阅读:
    Android中的httpclient框架发送get请求
    成员函数的重载&amp;&amp;隐藏&amp;&amp;覆盖
    子墨庖丁Android的ActionBar源代码分析 (一)实例化
    Hadoop2.x介绍与源代码编译
    NFS 服务器的配置
    tftp 服务器的配置
    LINUX内核及应用程序移植工作
    u-boot 移植工作目录
    Linux 下工作用户及环境
    zless
  • 原文地址:https://www.cnblogs.com/cleven/p/5245456.html
Copyright © 2020-2023  润新知