您所在的位置:主页 > iOS开发培训 >

疯狂iOS讲义之实例:贪吃蛇

时间:2015-01-14 11:35来源:未知 作者:疯狂java 点击:

  

  实例:贪食蛇

  轻扫手势处理器在游戏中的应用十分广泛,比如在经典的贪食蛇游戏中,可以让用户通过轻扫手势处理器来改变蛇的移动方向。

  贪食蛇的关键数据就是记录蛇的身体,蛇身包含多个点,多个点的数据用于确定蛇身的位置,由于蛇身的长度会动态改变,因此程序将会使用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

  // 定义蛇的移动方向

  @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个点的坐标。

  wKiom1M7Z9zAEgZ1AAErNWsWh-k617.jpg

  该自定义类还在①号粗体字代码处启动了定时器,该定时器会控制程序每隔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段粗体字代码位于手势处理方法中,这些粗体字代码会根据轻扫方向改变蛇的移动方向。