ios-高仿別踩白塊遊戲的實現

先看下效果圖片

  • 前幾天看到一個遊戲叫別踩白塊,下載量還挺大幾百萬了都,下載下來玩了玩看了看,這個遊戲還挺簡單的.俗話說想一千遍,一萬遍不如動手作一遍來的實在.昨晚以及今天白天閒的沒事就開搞了,下午六點鐘終於搞完.中間也遇到了些沒想到的問題,有個難題想了多半個下午,待我一一道來...篇幅較長字體較小但願能耐心看完
  • 代碼沒有過多的進行封裝,只是先實現了基本的功能.一共才200多行代碼➕註釋--只是沒來得及集成音樂git

  • 用的是collectionViewController所有代碼都在.m文件中
  • 對於常常要用到的變量進行了宏定義數組

//
//  ViewController.m
//  PlayPinao
//
//  Created by 裴波波 on 16/4/23.
//  Copyright © 2016年 裴波波. All rights reserved.
//

#import "ViewController.h"

//屏幕寬度
#define kScreenW [UIScreen mainScreen].bounds.size.width
//屏幕高度
#define kScreenH [UIScreen mainScreen].bounds.size.height
//屏幕尺寸
#define kScreenB [UIScreen mainScreen].bounds
#define kLineCount 4  //每行白塊個數
#define kCellCount 200 //白塊總個數
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UICollectionViewFlowLayout *flowLayout;
/** 存儲每一行的cell的可變數組 */
@property (nonatomic, strong) NSMutableArray *arrayM;
/** 記錄取整索引 */
@property (nonatomic, assign) int idx;
/** 初始狀態總體豎直偏移高度 */
@property (nonatomic, assign) CGFloat offsetTotal;
/** 每一個cell高度 */
@property (nonatomic, assign) CGFloat itemH;
/** 點擊黑塊計數器 */
@property (nonatomic, assign) int blackCount;
/** 主界面view */
@property (nonatomic, strong) UIView *mainView;
/** 秒錶計時器 */
@property (nonatomic, strong) NSTimer *timer;
/** 顯示時間的label */
@property (nonatomic, strong) UILabel *lblTime;
/** 累計用時 */
@property (nonatomic, assign) CGFloat useTime;
/** 即將消失 */
@property (nonatomic, assign) int displayNum;

@end
  • 此處的即將消失的意思是:displayNum,因爲collectionView每次滑動的時候,cell即將被銷燬(幹掉)的時候都會調用的方法.可是當程序剛進入初始化的時候也會調用這個方法,因此用一個數字來標記當點擊cell 的時候再判斷是否漏掉了黑色的塊沒有點擊.若是漏掉了黑色塊沒有點擊 -> 遊戲結束
@implementation ViewController

#pragma mark - 懶加載存儲每行cell的可遍數組
-(NSMutableArray *)arrayM{
    
    if (_arrayM == nil) {
        _arrayM = [NSMutableArray array];
    }
    return _arrayM;
}

#pragma mark - 數據源

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    
    return kCellCount;
}

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    
    UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
    /** 綁定tag排序用----並無什麼用處 */
    cell.tag = indexPath.item;
    /** 先統一設置cell背景色白色 */
    cell.backgroundColor = [UIColor whiteColor];
    /** 根據腳標每行隨機設置一個顏色爲黑色的cell */
    /** 將每行的cell 4個存入可變數組,獲取隨機數0-3,根據隨機數取腳標讓其變黑 */
    /** 將cell數組添加到可變數組 */
    [self.arrayM addObject:cell];
    
    /** 記錄索引 = 3的時候從中隨機選擇一個cell讓其背景色變 黑 */
    if (self.idx == 3) {
        /** 產生的隨機腳標 */
        int idxBlcak = arc4random_uniform(4);
        UICollectionViewCell * blackCell = self.arrayM[idxBlcak];
        blackCell.backgroundColor = [UIColor blackColor];
    }
    self.idx ++;
    //idx > 3 重置記錄索引
    if (self.idx > 3) {
        self.idx = 0;
        //當重置idx的時候 令可變數組arrayM removeAllObject
        [self.arrayM removeAllObjects];
    }
    
    return cell;
}
  • 用self.arrayM可變數組來保存每一行的cell,再用產生隨機數的函數來產生一個0-3的一個隨機數,讓後讓以這個隨機數爲腳標的 存在 self.arrayM中的cell的顏色變爲黑色這樣就實現了 每行黑色的版塊的隨機.
  • 當idx > 3 的時候再重置爲0,接着使用
#pragma mark - 代理
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    
    UICollectionViewCell * cell = [collectionView cellForItemAtIndexPath:indexPath];
    
    /** 點擊開始計時 */
    if (self.timer == nil) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60 target:self selector:@selector(addTimeOfUserUse) userInfo:nil repeats:YES];
        NSRunLoop * runloop = [NSRunLoop currentRunLoop];
        [runloop addTimer:self.timer forMode:NSRunLoopCommonModes];
    }
    /** 標記---奠定石系統執行方法didEndDisplayingCell時檢測漏掉的黑色 */
    self.displayNum = 1;
    
    /** 判斷cell顏色爲黑色->變灰 cell爲白色 -> 變紅 */
    /** 若是點擊到了黑塊計數 */
    if (cell.backgroundColor == [UIColor blackColor]) {
        cell.tag = 99999; //此行沒有什麼卵用
        cell.backgroundColor = [UIColor grayColor];
        self.blackCount ++;
    } else {
        //點擊錯誤提示用戶點錯而且返回點擊黑色塊的數量
        cell.backgroundColor = [UIColor redColor];
        /** 彈出提示框 */
        [self showClickError];
        /** 中止計時器 */
        [self.timer invalidate];
    }
    /** 每次點擊黑色塊讓collectionView偏移 - 一個cell的高度 */
    /** 滑動到頭髮生某個事件 */
    if (self.collectionView.contentOffset.y == 0) {
        return;
    }
    //點擊滾動
    [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:YES];
}
  • 當點擊黑色的版塊的時候:1. 要將黑色 -> 灰色 ,而後collectionView向下滑動過點擊的cell,也就是最後一行代碼執行的方法.以前我用的是點擊cell讓總體的collectionView的偏移量 - 最小行間距 - 每一個cell的高度.可是這樣作有bug,就是當你點擊cell collectionVIew向下滑動的時候最下面一行4個cell會直接消失,露出背景色,體驗不好...而後看官方文檔找方法,找到這個滑動的方法,最合適.
  • 此處當點擊cell的時候開啓一個定時器,相似自動圖片輪播器.當點擊到的cell不是黑色的版塊的時候,計時器中止,記錄時間,點擊彈框的肯定按鈕後將時間給了主界面的label來顯示用的時間.
  • self.blackCount點擊到的黑色的版塊的計數器,遊戲結束後統計結果,在彈框顯示點擊黑色的版塊的數量
  • 此時設置self.displayNum = 1;讓下面的方法didEndDisplayingCell,,記錄cell被移除會調用的方法,判斷被移除cell是否包含黑色的cell若是包含了黑色的cell說明玩家漏掉了黑色的cell,而後彈框提醒->遊戲結束
  • 最後判斷當偏移量爲0的時候中止滾動.
#pragma mark - 將要消失的cell
-(void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{

    if (cell.backgroundColor == [UIColor blackColor]) {
        if (self.displayNum == 1) {
            [self showClickError];
            [self.timer invalidate];
            self.displayNum = 0;
        }
    }
}
  • 爲何要用self.displayNum來進行點擊的時候判斷,由於當程序剛啓動的時候也會調用此方法,咱們不但願當程序啓動的時候就調用此方法中的 "彈框" 以及 "中止計時器",因此要用一個至關於啓動器來啓動監視黑色cell是否被幹掉
#pragma mark - 累加時間,每秒執行60次
-(void)addTimeOfUserUse{
    
    self.useTime += 1.0 / 60;
}

#pragma mark - 提示錯誤框
-(void)showClickError{
    
    UIAlertController * alertVc = [UIAlertController alertControllerWithTitle:@"最終結果" message:[NSString stringWithFormat:@"成績是:%zd個",self.blackCount] preferredStyle:UIAlertControllerStyleAlert];
    /** 肯定返回主界面 */
    UIAlertAction * action = [UIAlertAction actionWithTitle:@"肯定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        self.lblTime.text = [NSString stringWithFormat:@"累計用時%f",self.useTime];
        self.blackCount = 0;
        self.displayNum = 0;
        [UIView animateWithDuration:0.2 animations:^{
            self.mainView.alpha = 1;
        }];
    }];
    [alertVc addAction:action];
    [self presentViewController:alertVc animated:YES completion:nil];
}
  • 彈框提醒的同時將點擊的黑色的cell的個數呈現到彈框上.同時重置記錄點擊cell個數的計數器 self.blackCount,以及self.displayNum.
- (void)viewDidLoad {
    
    [super viewDidLoad];
    self.collectionView.backgroundColor = [UIColor blueColor];
    /** 初始化佈局參數 */
    [self initFlowLayout];
    /** 初始化主界面 */
    [self initMainView];
}

#pragma mark - 初始化主界面
-(void)initMainView{
    
    UIView * mainView = [[UIView alloc] initWithFrame:kScreenB];
    self.mainView = mainView;
    mainView.backgroundColor = [UIColor orangeColor];
    [self.view addSubview:mainView];
    UIButton * button = [[UIButton alloc] init];
    [button setTitle:@"進入" forState:UIControlStateNormal];
    button.bounds = CGRectMake(0, 0, 50, 30);
    button.backgroundColor = [UIColor blackColor];
    button.center = mainView.center;
    [mainView addSubview:button];
    [button addTarget:self action:@selector(hideMainView) forControlEvents:UIControlEventTouchUpInside];
    
    //顯示遊戲時長label
    UILabel * lblTime = [[UILabel alloc] init];
    lblTime.frame = CGRectMake(0, 0, kScreenW, 200);
    self.lblTime = lblTime;
    lblTime.font = [UIFont systemFontOfSize:22];
    lblTime.textAlignment = NSTextAlignmentCenter;
    [mainView addSubview:lblTime];
}

#pragma mark - 隱藏主界面進入遊戲
-(void)hideMainView{
    
    /** 將計時器重置爲nil */
    self.timer = nil;
    /** 將累計用時重置nil */
    self.useTime = 0;
    /** 每次進入遊戲將偏移量重置 */
    CGFloat offset = (kCellCount / kLineCount - 1) * self.flowLayout.minimumLineSpacing + (kCellCount / kLineCount) * self.itemH - kScreenH;
    self.offsetTotal = offset;
    self.collectionView.contentOffset = CGPointMake(0, offset);
    /** 每次進入遊戲將紅塊重置爲白塊 -> 刷新collectionView */
    [self.collectionView reloadData];
    [UIView animateWithDuration:0.5 animations:^{
        self.mainView.alpha = 0;
    }];
}

#pragma mark - 初始化佈局參數
-(void)initFlowLayout{
    
    self.flowLayout.minimumLineSpacing = 1;
    self.flowLayout.minimumInteritemSpacing = 0; //cell最小間距
    CGFloat itemH = (kScreenH - (kLineCount -1) * self.flowLayout.minimumLineSpacing) / 4;
    self.itemH = itemH;
    self.flowLayout.itemSize = CGSizeMake((kScreenW / kLineCount) - 1,itemH);
    //偏移量 = (行數 - 1) * 行間距 + 行數 * 每一個cell高度 - 屏幕高度
    CGFloat offset = (kCellCount / kLineCount - 1) * self.flowLayout.minimumLineSpacing + (kCellCount / kLineCount) * itemH - kScreenH;
    self.offsetTotal = offset;
    self.collectionView.contentOffset = CGPointMake(0, offset);
    self.collectionView.showsVerticalScrollIndicator = NO;
    self.collectionView.bounces = NO; //取消彈簧效果
    self.collectionView.scrollEnabled = NO; //取消滾動
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

寫完才明白想了那麼多遍沒想到會遇到的問題.有想法就動手.

注意點:1. 最重要的一個邏輯判斷以及方法就是cell消失的時候系統要調用的方法,一個下午就被坑在這個上面了,中間想了好多方法例如:把界面上能夠看到的黑色的cell篩選出來,通過冒泡排序經過cell.tag來排序,從小到大,每次點擊判斷你是否點擊的是腳標最大的一個cell,,,然而不可行,緣由是,你能夠點擊tag最小的,再趕忙點擊tag最大的也能夠. 還有一個方法是計算腳標最大的cell的偏移量是否越過了屏幕的最底邊,然而測試的時候是每一個cell的偏移量不固定,貌似沒規律,就算用if進行判斷也很複雜..也行不通. 等等吧...最少想了三個方法都不行

最簡單最實用的也就是系統的方法,當cell被幹掉的時候調用的方法,直接判斷黑色的cell是否被幹掉便可.其餘沒什麼難度畢竟就一個collectionView而已.

源代碼demo下載地址:https://git.oschina.net/alexpei/PBPlayPiano.git

相關文章
相關標籤/搜索