zoukankan      html  css  js  c++  java
  • ios-高仿别踩白块游戏的实现

    先看下效果图片

    • 前几天看到一个游戏叫别踩白块,下载量还挺大几百万了都,下载下来玩了玩看了看,这个游戏还挺简单的.俗话说想一千遍,一万遍不如动手做一遍来的实在.昨晚以及今天白天闲的没事就开搞了,下午六点钟终于搞完.中间也遇到了些没想到的问题,有个难题想了多半个下午,待我一一道来...篇幅较长字体较小希望能耐心看完

    • 代码没有过多的进行封装,只是先实现了基本的功能.一共才200多行代码➕注释--只是没来得及集成音乐

    • 用的是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

  • 相关阅读:
    CSS多行文字垂直居中的两种方法
    CSS3 选择器——基本选择器
    页面添加锚点的三种方式
    css3动画特效:上下晃动的div
    CSS3图片倒影技术实现及原理
    标准W3C盒子模型和IE盒子模型CSS布局经典盒子模型(转)
    JQuery中操作Css样式的方法
    22.从上往下打印二叉树 Java
    21.栈的压入、弹出序列 Java
    20.包含min函数的栈 Java
  • 原文地址:https://www.cnblogs.com/adampei-bobo/p/5425715.html
Copyright © 2011-2022 走看看