疯狂ios讲义之实例:贪吃蛇
实例:贪食蛇 轻扫手势处理器在游戏中的应用十分广泛,比如在经典的贪食蛇游戏中,可以让用户通过轻扫手势处理器来改变蛇的移动方向。 贪食蛇的关键数据就是记录蛇的身体,蛇身包含多个点,多个点的数据用于确定蛇身的位置,由于蛇身的长度会动态改变,因此程序将会使用NSMutableArray来记录蛇身的每个点。 除此之外,贪食蛇只有两个需要处理的逻辑。 蛇的移动:程序使用一个定时器控制蛇身的移动。对于蛇身的移动来说,蛇头受移动方向的控制,当移动方向为向左时,代表蛇头的点的X坐标减1;当移动方向为向右时,代表蛇头的点的X坐标加1;当移动方向为向上时,代表蛇头的点的Y坐标减1;当移动方向为向下时,代表蛇头的点的Y坐标加1。除此之外,蛇身的其他点总是去“追逐”前一个点,也就是将第i个点的坐标设置为第i+1个点的坐标。 蛇的方向改变:通过轻扫手势处理器检测用户的轻扫手势,根据轻扫手势的方向来改变蛇的移动方向。 创建一个SingleView Application,该应用将会包含一个应用程序委托类和一个视图控制器类,但程序还需要一个自定义UIView控件类,该控件类负责绘制贪食蛇的游戏界面。 下面是自定义的FKSnakeView类(该类继承了UIView)的接口代码。 程序清单:codes/01/1.3/Snake/Snake/FKSnakeView.h //记录地图上的宽和高有多少个格子 #define WIDTH 15 #define HEIGHT 22 //定义每个格子的大小 #define CELL_SIZE 20 typedefenum { kDown = 0, kLeft, kRight, kUp } Orient; @interface FKSnakeView : UIView<UIAlertViewDelegate> //定义蛇的移动方向 @property (nonatomic , assign) Orient orient; @end 上面接口部分定义了一些宏变量,并使用了一个枚举类型,该枚举类型用于定义蛇的移动方向。 程序清单:codes/01/1.3/Snake/Snake/FKSnakeView.m @implementation FKSnakeView //记录蛇的点,最后一个点代表蛇头 NSMutableArray* snakeData; //定义食物所在的点 FKPoint* foodPos; NSTimer* timer; UIColor* bgColor; UIImage* cherryImage; UIAlertView * overAlert; //代表游戏音效变量 SystemSoundID gu; SystemSoundID crash; @synthesize orient; - (id)initWithFrame:(CGRect)frame { self =[super initWithFrame:frame]; if(self) { //加载食物图片 cherryImage = [UIImage imageNamed:@"cherry.png"]; //加载游戏背景图片,并将背景图片转换为平铺形式的颜色 bgColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"grass.png"]]; //获取两个音效文件的URL NSURL* guUrl = [[NSBundle mainBundle] URLForResource:@"gu" withExtension:@"mp3"]; NSURL* crashUrl = [[NSBundle mainBundle] URLForResource:@"crash" withExtension:@"wav"]; //加载两个音效文件 AudioServicesCreateSystemSoundID((__bridge CFURLRef)guUrl , &gu); AudioServicesCreateSystemSoundID((__bridgeCFURLRef)crashUrl , &crash); overAlert = [[UIAlertView alloc] initWithTitle:@"游戏结束" message:@"您输了,是否重新再来?" delegate:self cancelButtonTitle:@"不来了"otherButtonTitles:@"再来一盘!", nil]; [self startGame]; } returnself; } - (void) startGame { //FKPoint的第1个参数控制位于水平第几格,第2个参数控制位于垂直第几格 snakeData = [NSMutableArray arrayWithObjects: [[FKPoint alloc] initWithX:1 y:0], [[FKPoint alloc] initWithX:2 y:0], [[FKPoint alloc] initWithX:3 y:0], [[FKPoint alloc] initWithX:4 y:0], [[FKPoint alloc] initWithX:5 y:0],nil]; //定义蛇的初始移动方向 orient =kRight; timer = [NSTimerscheduledTimerWithTimeInterval:0.3 target:self selector:@selector(move) userInfo:nilrepeats:YES]; //① } - (void) move { //除了蛇头受方向控制之外,其他点都是占它的前一个点 //获取最后一个点,作为蛇头 FKPoint*first = [snakeData objectAtIndex: snakeData.count - 1]; FKPoint*head = [[FKPoint alloc] initWithX:first.x y:first.y]; switch(orient) { case kDown: //代表向下 //新蛇头的位置 head.y = head.y + 1; break; case kLeft: //代表向左 //新蛇头的位置 head.x = head.x - 1; break; case kRight: //代表向右 //新蛇头的位置 head.x = head.x + 1; break; case kUp: //代表向上 //新蛇头的位置 head.y = head.y - 1; break; } //如果移动后蛇头超出界面或与蛇身碰撞,则游戏结束 if(head.x < 0 || head.x > WIDTH - 1 ||head.y < 0 || head.y > HEIGHT - 1 ||[snakeData containsObject:head]) { //播放碰撞的音效 AudioServicesPlaySystemSound(crash); [overAlert show]; [timer invalidate]; }; //表明蛇头与食物点重合 if([headisEqual:foodPos]) { //播放吃食物的音效 AudioServicesPlaySystemSound(gu); //将食物点添加成新的蛇头 [snakeData addObject:foodPos]; //食物清空 foodPos = nil; } else { //从第1个点开始,控制蛇身向前 for (int i = 0 ; i < snakeData.count- 1; i++) { //将第i个点的坐标设置为第i+1个点的坐标 FKPoint* curPt = [snakeDataobjectAtIndex:i]; FKPoint* nextPt = [snakeDataobjectAtIndex:i + 1]; curPt.x = nextPt.x; curPt.y = nextPt.y; } //重新设置蛇头坐标 [snakeData setObject:headatIndexedSubscript:(snakeData.count - 1)]; } if(foodPos == nil) { while(true) { FKPoint* newFoodPos = [[FKPoint alloc] initWithX:arc4random() % WIDTH y:arc4random() % HEIGHT]; //如果新产生的食物点没有位于蛇身上 if(![snakeData containsObject:newFoodPos]) { foodPos = newFoodPos; break; //成功生成了食物的位置,跳出循环 } } } [selfsetNeedsDisplay]; } //定义绘制蛇头的方法 - (void) drawHeadInRect:(CGRect)rectcontext:(CGContextRef)ctx { CGContextBeginPath(ctx); //根据蛇头的方向,决定开口的角度 CGFloatstartAngle; switch(orient) { casekUp: startAngle = M_PI * 7 / 4; break; casekDown: startAngle = M_PI * 3 / 4; break; casekLeft: startAngle = M_PI * 5 / 4; break; casekRight: startAngle = M_PI * 1 / 4; break; default: break; } //添加一段弧作为路径 CGContextAddArc(ctx, CGRectGetMidX(rect), CGRectGetMidY(rect) ,CELL_SIZE / 2, startAngle, M_PI * 1.5 + startAngle, 0); //将绘制点移动到中心 CGContextAddLineToPoint(ctx, CGRectGetMidX(rect), CGRectGetMidY(rect)); //关闭路径 CGContextClosePath(ctx); CGContextFillPath(ctx); } - (void)drawRect:(CGRect)rect { //获取绘图API CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(ctx, [bgColor CGColor]); //绘制背景 CGContextFillRect(ctx, CGRectMake(0 , 0 ,WIDTH * CELL_SIZE , HEIGHT * CELL_SIZE)); //绘制文字 [@"疯狂贪食蛇" drawAtPoint:CGPointMake(50,20) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIFont fontWithName:@"Heiti SC" size: 40] , NSFontAttributeName, [UIColor colorWithRed:1 green:0 blue:1 alpha:.4], NSForegroundColorAttributeName,nil]]; [@"www.fkjava.org" drawAtPoint:CGPointMake(50 ,60) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIFont fontWithName:@"Heiti SC" size: 26] ,NSFontAttributeName, [UIColor colorWithRed:1 green:0 blue:1 alpha:.4], NSForegroundColorAttributeName, nil]]; //设置绘制蛇的填充颜色 CGContextSetRGBFillColor(ctx, 1, 1, 0, 1); //遍历蛇的数据,绘制蛇的数据 for (int i = 0 ; i < snakeData.count ;i++ ) { //为每个蛇的点(记录的是在数组中的位置),在屏幕上绘制一个圆点 FKPoint* cp = [snakeDataobjectAtIndex:i]; //定义将要绘制蛇身点的矩形 CGRect rect = CGRectMake(cp.x * CELL_SIZE, cp.y * CELL_SIZE , CELL_SIZE , CELL_SIZE); //绘制蛇尾巴,让蛇的尾巴小一些 if(i < 4) { CGFloat inset =(4 - i); CGContextFillEllipseInRect(ctx,CGRectInset(rect,inset, inset)); } //如果是最后一个元素,代表蛇头,绘制蛇头 else if (i == snakeData.count - 1) { [self drawHeadInRect:rectcontext:ctx]; } else { CGContextFillEllipseInRect(ctx,rect); } } //绘制“食物”图片 [cherryImage drawAtPoint:CGPointMake(foodPos.x * CELL_SIZE ,foodPos.y * CELL_SIZE)]; } -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { //如果用户单击了第2个按钮,则重新开始游戏 if(buttonIndex== 1) [self startGame]; } @end 上面程序中,move方法中第1段粗体字代码负责根据移动方向控制蛇头的移动;move方法中第2段粗体字代码采用循环将第i个点的坐标设置为第i+1个点的坐标。 该自定义类还在①号粗体字代码处启动了定时器,该定时器会控制程序每隔0.3秒执行一次move方法,move方法负责控制蛇身的移动,并在蛇身移动完成后通知FKSnakeView重绘自身。 接下来只要在视图控制器中创建并添加FKSnakeView控件即可,除此之外,还需要在视图控制器中使用轻扫手势处理器来改变蛇的移动方向。下面是视图控制器类的实现代码(接口部分几乎是空的)。 程序清单:codes/01/1.3/Snake/Snake/FKViewController.m @implementation FKViewController FKSnakeView* snakeView; - (void)viewDidLoad { [super viewDidLoad]; //创建FKSnakeView控件 snakeView = [[FKSnakeView alloc] initWithFrame: CGRectMake(10, 10, WIDTH*CELL_SIZE , HEIGHT * CELL_SIZE)]; //为snakeView控件设置边框和圆角 snakeView.layer.borderWidth = 3; snakeView.layer.borderColor = [[UIColor redColor] CGColor]; snakeView.layer.cornerRadius = 6; snakeView.layer.masksToBounds = YES; //设置self.view控件支持用户交互 self.view.userInteractionEnabled = YES; //设置self.view控件支持多点触碰 self.view.multipleTouchEnabled = YES; for(int i = 0 ; i < 4 ; i++) { //创建手势处理器,指定使用该控制器的handleSwipe:方法处理轻扫手势 UISwipeGestureRecognizer*gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleSwipe:)]; //设置该手势处理器只处理i个手指的轻扫手势 gesture.numberOfTouchesRequired = 1; //指定该手势处理器只处理1<< i方向的轻扫手势 gesture.direction = 1 << i; //为self.view控件添加手势处理器 [self.viewaddGestureRecognizer:gesture]; } [self.view addSubview:snakeView]; } //实现手势处理器的方法,该方法应该声明一个形参 //当该方法被激发时,手势处理器会作为参数传给该方法的参数 - (void)handleSwipe:(UISwipeGestureRecognizer*)gesture { //获取轻扫手势的方向 NSUInteger direction = gesture.direction; switch (direction) { caseUISwipeGestureRecognizerDirectionLeft: if(snakeView.orient!= kRight) //只要不是向右,即可改变方向 snakeView.orient = kLeft; break; caseUISwipeGestureRecognizerDirectionUp: if(snakeView.orient!= kDown) //只要不是向下,即可改变方向 snakeView.orient = kUp; break; caseUISwipeGestureRecognizerDirectionDown: if(snakeView.orient != kUp) //只要不是向上,即可改变方向 snakeView.orient = kDown; break; caseUISwipeGestureRecognizerDirectionRight: if(snakeView.orient!= kLeft) //只要不是向左,即可改变方向 snakeView.orient = kRight; break; } } @end 上面程序中第1段粗体字代码为该控制器的UIView(即整个界面)注册了4个方向的手势处理器,第2段粗体字代码位于手势处理方法中,这些粗体字代码会根据轻扫方向改变蛇的移动方向。编译、运行该程序,即可看到如图1.7所示的游戏界面。 图1.7 贪食蛇游戏界面 本文转自fkJava李刚51CTO博客,原文链接:http://blog.51cto.com/javaligang/1388754,如需转载请自行联系原作者