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

时间: 2024-11-03 03:46:10

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

c语言:简单飞机射击小游戏

c语言:简单飞机射击小游戏 使用c语言编写一个打飞机小游戏,使用键盘按键来进行游戏,操作方法是"a""d""w"或者"←""↑""→"来控制攻击.左.右,击中敌机可获得积分,被敌机撞中死亡一次,每次游戏有3次机会,机会用光则游戏结束,之后可选择是否重新开始游戏. 改进:增加了颜色函数,使得游戏看起来更加的舒适:增加了终止函数,使游戏在死亡三次后自动结束游戏,并且可以选择是否重新开始游戏:

JavaScript实现简单贪吃蛇小游戏

之前上Web课,学到JavaScript的时候,老师要求写几个静态页面,要用到JavaScript.想了想就写个贪吃蛇吧.其实之前用pygame写过一次GUI的贪吃蛇,素材都是自己拿画图画的,其丑无比.所以这次还是老老实实用字符吧. 首先,是一些全局变量的定义: 1 <script> 2 var state = 0;//0 wait 1 run 2 over 3 var width = 40; 4 var height = 25; 5 var update = false; 6 var dir

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

using GameDemo.Utils;using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace GameDemo{ class Program { static void Main(string[] args) { int total=0;//计时 Console.WriteLine("开始游戏"); Console.WriteLine("准备好

小游戏●贪吃蛇1(利用二维数组制作)

利用二维数组编写简单贪吃蛇小游戏,由于是初学C#,用的是单线程,所以蛇不会自动前进 代码及简要分析如下: 1 //定义地图,0为空,1为墙,2为蛇,3为食物 2 int[,] map = new int[15, 15]{ 3 {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, 4 {1,2,0,0,0,0,0,0,0,0,0,0,0,0,1}, 5 {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, 6 {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},

简单的猜数字小游戏

/** 简单的猜数字小游戏 要求如下: 用户输入想猜测数字的范围,输入1000则是0~1000之内的数字,程序就会内置一个 1 到 1000 之间的数字作为猜测的结果,由用户猜测此数字,用户每猜测一次,由系统提示猜测结果:大了.小了或者猜对了:直到用户猜对结果,则提示游戏结束.用户可以提前退出游戏,即,游戏过程中,如果用户录入数字0则游戏终止.加入新功能: 记次猜测次数功能,提示游戏开始时间,计猜测总用时功能,提示游戏结束时间 思路:1.用户输入电脑生成的数值取值范围,接收并判断是否是合理数值?

一个简单的“贪吃蛇”小游戏

一个简单的“贪吃蛇”小游戏 页面结构 简单的21x21的方块,页面结构 id为container的div包含所21个class名为row的div,每个row代表贪吃蛇的一整行,每个row中又包含21个div,代表这一行的每一个div方格,如果这个方格是空的话,div的类名为blank,如果这一个方格表示“贪吃蛇”的“食物”,div的类名为food,如果这一个方格表示“蛇”,div的类名为snake. CSS JS 然后我们思考下一个贪吃蛇游戏需要那些参数, 首先,界面中可见的元素无非就是空方格,

Cocos2d-X开发一个简单的小游戏

学了这么久Cocos2d-X,今天终于可以做出一个简单的小游戏了,游戏非常简单,通过菜单项控制精灵运动 在做游戏前,先学一个新概念 调度器(scheduler): Cocos2d-x调度器为游戏提供定时事件和定时调用服务.所有Node对象都知道如何调度和取消调度事件,使用调度器有几个好处: 每当Node不再可见或已从场景中移除时,调度器会停止. Cocos2d-x暂停时,调度器也会停止.当Cocos2d-x重新开始时,调度器也会自动继续启动. Cocos2d-x封装了一个供各种不同平台使用的调度

一个简单用原生js实现的小游戏----FlappyBird

这是一个特别简单的用原生js实现的一个小鸟游戏,比较简单,适合新手练习 这是html结构 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="game"> <div id="b

Java 初学 第一弹--编译并运行书上的简单程序(猜数字小游戏)

(博主原创) 首先说明一下,博主是大一上学期结束寒假时自己看的Java,然后我看的是Head First Java的中文版,因为大一学了c,所以里面的一些基本思想还是了解的,在看这本书时就浏览了一下(就是那种光看没有自己动手去敲代码的),然后看到书上的一个猜数字小游戏,就想手动敲一下,熟悉熟悉Java的语法,但是真正去做时,发现比看起来要困难一些. 首先是Java在建立一个源码文件之前要先建一个package,然后我用的Eclipse写的Java(感觉和pycharm风格差不多),再新建一个文件