iOS开发-------MVC架构思想-植物大战僵尸
2015-10-07 16:12
543 查看
十月长假也就这么过去了,利用假期想磨练一下自己的MVC架构的思想,于是找了一个相对比较麻烦的Demo来锻炼下,在之前很喜欢植物大战僵尸这个游戏,也算为游戏致敬了,但是这个植物大战僵尸肯定不是之前玩过的那个游戏,只是致敬,类似打地鼠,但是代码感觉非常多,所以也算楼主的目前博客里面最有料的了,不得不说,想逻辑的时候直接的虐心啊,先看一下效果吧
然后看一下如果家中的僵尸达到60个,那么就会跳出一个失败的动画,并伴随着僵尸的奸笑声,这里就不给动态图了
要用MVC架构,逻辑最重要,在写代码的时候明显的感觉的到,没有逻辑步步为艰,并且还会出现写不下去的情况,首先看看以我个人见解建构的MVC目录
Model类:
Zombie(僵尸类): 考虑到以后会更加僵尸种类,所以先创建了一个BaseZombie(僵尸的基类),拥有各种僵尸都有的属性,因为这个小游戏中有四种类型的僵尸,所以上面的图可以看到,有4种类型的僵尸。这里是先理理思路,代码后面会有的。
Manager(管理器):单例
ZomibieManager(僵尸管理器):顾名思义,里面存的是僵尸的对象
MusicManager(背景音乐管理器):能够控制音乐的播放
View类:
BackgroundView(背景视图):继承于UIView,是负责显示背景,也就是绿草坪,标签上的僵尸数以及得分,会根据得分以及僵尸数,分别向VC汇报是否结束游戏,什么时候提高僵尸移动加速
ZombieView(单个僵尸视图):继承于UIImageView,是负责每个僵尸的显示,接收点击事件回调以及动画的形成
Zombies_View(所有僵尸视图):继承于UIView,负责僵尸被点击后的操作、将是进入家中的回调以及负责加快僵尸移动速度
GameOverView(游戏结束视图):继承于UIView,负责展示游戏结束的视图,并能够将重新开始按钮点击实现回调给VC
Controller类:
全局的一个boss,负责指挥所有的子控件,并实现最终的回调
大体的逻辑就如上了,只挂图和讲理论不适合我,还是上代码,会更直接简明一些,那就按照MVC的顺序,注:本demon中所有的汇报回调都是Block回调
不需要实现任何的方法,其他四种类型的model类中,也只需重写init方法即可,这里只给出zombieType1(僵尸类型1)的init方法,其他的一样,只需记住图片的数量,以及高和宽即可。
首先会声明一个单例方法
因为要存储僵尸对象,但是又不希望外界能够轻易的改动,那么设置一个开放的数组属性,只能读,即readOnly
接着需要一个加载僵尸的方法,来加载 宏 规定的僵尸数
这个Manager需要一个回调,是告知VC,他的僵尸数组已经加载完毕,并且返回加载完毕的数组,并用该数组完成下一步的操作
有回调比有回调属性,有回调属性必然会有设置回调属性的方法
声明文件完毕,接下来就是实现文件了,延展中的代码块属性不赘余,但是延展中需要一个存放对象的可变数组
首先实现init方法以及单例方法
加载数组的属性,这里唯一一个小心的地方就是每次加载前需要清空数组,不然会因为单例属性而出现对象数量的累加
添加僵尸,貌似只是简单的一句话,但是添加僵尸是随机添加的,不能规定那种僵尸是几个,所以有一个随机返回僵尸对象的方法
那么添加僵尸的方法就很简单了,只需给数组添加一个对象即可
这里说一下设置代码块的方法,因为他的回调是通过这个设置方法启动的,设置之后立马进行加载,因为是同步的,所以加载完毕后就会通过代码块的行回调给VC
僵尸管理器完成。
实现方法,首先延展中需要声明四个音乐播放器属性
楼主比较懒,因此依旧初始化方法都是通过一个方法返回的,自定义的播放器初始方法如下
因此自定义的初始化方法就简单多了
在init方法中调用即可,单例方法不再说明,和上一个Manager一样
播放音乐的方法,就是控制播放器开始播放,停止播放即可
音乐管理器完成。
接着需要在延展中拖入两个输出口
它负责回调游戏结束以及为僵尸加速的回调,所以定义两个Block,当然这里也可以用其他的方法,但是楼主最近一直在练习Block,所以这次依旧用的Block
然后需要随着打到僵尸以及僵尸过线需要改变label上的值,所以声明文件上声明两个方法
然后自然有设置回调的方法
会不会只有这么点的方法呢,不一定,因为延展中会有更多的方法处理,接下来看实现文件
首先需要记录得分以及进入家中的僵尸的个数
如何判定僵尸该不该加速呢,所以必然存在一个标志位
因为需要回调,所以楼主的习惯必然会存在两个属性
延展中属性如此,接下来看方法,既然是继承于UIView,那么必然会有两个初始化方法,谁也不能肯定一定会用哪种方法,所以必先重写两个方法,以后的视图类不再做过多的赘余,记得写即可
接下来就是自定义的创建方法了
然后就是为block的属性赋值的方法,以后的block属性也必然会有赋值,只不过如果不特殊,也就不再赘余了
加分的方法,当分数达到某个数值(即加速标准)的时候会告知VC,需要加速了,这个判断会通过标志位bgTag来判断
增加家里的僵尸的数量,一样,当到达某个数值(即允许进入僵尸的最大数量) 的时候,即告知VC,游戏需要结束了
背景视图完成。
首先是定义在声明文件中的代码块
因为在后面的地方需要用到它是什么类型的僵尸,所以需要一个属性来存储僵尸类型,但是不能修改,所以用readOnly
在Zombie的时候里面存取着相应的frame的高和宽,以及图片的数量,所以选择整体传入,并多一个参数,用来描述中心点位置的point
接着就是实现文件,延展中只需要一个代码块属性即可
因为这个类只能通过代码创建,所以直接写了便利构造方法
因为上面的方法看起来比较简单,是因为分别将三个功能的方法封装起来了,首先是框架的初始化方法,根据zombie中存的数据frame,来创建该视图
接着是相关属性的设置方法
稍微麻烦点的就是设置图片,设置图片需要根据僵尸的类型以及图片的类型来设置,但是这个type是通过方法获得的,先来看设置图片的方法
里面的type是一个NSInteger类型,会根据僵尸的类型来判断返回哪一个数字,毕竟我们僵尸的名字也是有规律的
继承于UIImageView的原因是因为他有自带的图帧动画效果,所以必然会有图片数组的加载,即方法中的接收数组
毕竟不是button,UIImageView没有被点击的功能,但是并不能代表不能添加,既然是UIView,必然会存在响应触摸事件的方法,如下
单个僵尸视图完毕。
因为这个视图上会出现多个单个僵尸视图,所以需要通过ZombiesManager返回的数组进行加载视图
接着有两个为回调代码块赋值的方法
他之所以能够让僵尸按时间段移动,必然存在一个计时器,但当游戏结束后,不需要动了,那么还需要要一个关闭定时器的方法
游戏结束,僵尸不能动,还希望不能点,还需要一个失去响应的方法
如果backgroundView(背景视图)告知VC需要加速,那么VC就会让这个视图加速,所以需要一个加速方法
Speed只是自定义的一个类型,实际是CGFloat
接下来就是更麻烦的实现方法了
首先在延展中定义如下属性
重写init方法
接着实现加载僵尸视图的方法,因为每种僵尸的视图大小是不固定的,所以需要通过知道僵尸视图的frame来控制视图,避免出现在屏幕的边缘
传进来的参数数组里面存的是Zombie对象,所以需要一个模型转视图的过程,因此有一个loadZomiesViewsWithArray方法
上面的方法用到一个随机中心点的方法,需要传入一个Zombie对象,根据frame来创建,即randomPointWithZombieView:(BaseZombie *)zombie方法,如下
这就就是需要僵尸移动了,这个示例里,一共是30个僵尸,所以不会一下子移动30个,必然会有一个移动一只僵尸的方法,30个僵尸的移动便由一个循环即可搞定,首先是一个僵尸移动
多个僵尸移动就简单化了,只需要一个循环即可
僵尸被点击后放大,并且一段时候又变小,接着会被移动到最右侧,方法如下
剩下的三个方法就显得很简单了,让僵尸不能再移动,关闭定时器即可
游戏结束后,背面的僵尸不能进行点击,实际就是不能进行人机交互,将属性设置为NO即可
加速呢,只需要改变速度属性即可
多个僵尸视图完成
在实现文件只,定义一个Block回调,负责告知玩家需要重新开始游戏
接着需要声明两个方法,这个是根据父视图,可以确定在父视图的哪个位置
接着就是最后设置回调的方法了
延展中需要一个UIImageView来显示最后失败的那个图片
不要忘记两个初始化方法
自定义的初始化方法
失败的时候动画弹出的视图,实现方法如下
因为那么button的属性比较多,所以楼主打包成一个方法了,如下
button的目标动作回调其实就是该视图的回调方法
整个视图类完成。
viewDidLoad中初始化即可
在其他的组件加载中,初始化其他的组件loadExceptManager
最后就是最爽的回调过程了,不要忘记强引用循环
首先是Zombies_View(多个僵尸视图)的回调设置
然后是ZombieManager(僵尸管理器)的回调方法
接着是BackgroundView(背景视图)的回调方法
最后是GameOverView(结束视图)的回调方法
不要忘记音乐播放器播放音乐以及组件的添加
注:如果不想最上面的信号以及电量栏挡住,一下方法即可解决
如果有事情,想暂停怎么办呢,就按home键吧,就会暂停了,这就需要在appDelegate中进行设置了
那么当被挂起的时候会执行如下方法
那么再次进入游戏后开始就需要写下面的方法
这样的话,想暂停就直接home键吧,后台就自动暂停了哦,祝大家国庆快乐0.0
GitHub:https://github.com/YRunIntoLove/BeatZombies
然后看一下如果家中的僵尸达到60个,那么就会跳出一个失败的动画,并伴随着僵尸的奸笑声,这里就不给动态图了
要用MVC架构,逻辑最重要,在写代码的时候明显的感觉的到,没有逻辑步步为艰,并且还会出现写不下去的情况,首先看看以我个人见解建构的MVC目录
Model类:
Zombie(僵尸类): 考虑到以后会更加僵尸种类,所以先创建了一个BaseZombie(僵尸的基类),拥有各种僵尸都有的属性,因为这个小游戏中有四种类型的僵尸,所以上面的图可以看到,有4种类型的僵尸。这里是先理理思路,代码后面会有的。
Manager(管理器):单例
ZomibieManager(僵尸管理器):顾名思义,里面存的是僵尸的对象
MusicManager(背景音乐管理器):能够控制音乐的播放
View类:
BackgroundView(背景视图):继承于UIView,是负责显示背景,也就是绿草坪,标签上的僵尸数以及得分,会根据得分以及僵尸数,分别向VC汇报是否结束游戏,什么时候提高僵尸移动加速
ZombieView(单个僵尸视图):继承于UIImageView,是负责每个僵尸的显示,接收点击事件回调以及动画的形成
Zombies_View(所有僵尸视图):继承于UIView,负责僵尸被点击后的操作、将是进入家中的回调以及负责加快僵尸移动速度
GameOverView(游戏结束视图):继承于UIView,负责展示游戏结束的视图,并能够将重新开始按钮点击实现回调给VC
Controller类:
全局的一个boss,负责指挥所有的子控件,并实现最终的回调
大体的逻辑就如上了,只挂图和讲理论不适合我,还是上代码,会更直接简明一些,那就按照MVC的顺序,注:本demon中所有的汇报回调都是Block回调
全局用的宏
因为在程序中必然要减少数字的使用,面的这种情况,最简单的方法就是定义一个宏,楼主将所有的宏打包成一个头文件,叫做DEFINE.h,下面就是宏文件#ifndef ____DEFINE_h #define ____DEFINE_h #define GAMEOVERCOUNT 60 //家里的僵尸达到60,游戏结束 #define ADD_SPEED_STAND 20 //加速标准,达到20即加速 #define ZOMBIE_MOVE_SPEED 0.5 //僵尸的移动速度 #define TIMER_SPEED 0.05 //计时器回调的间隔 #define ZOMBIE_COUNT 15 //每轮僵尸的个数 #define ZOMBIE_TYPE_COUNT 4 //僵尸的种类 #define ZOMBIE_ADD_SPEED 0.1 //每次加速时增量 #endif
Model(模型)类
Zombie(僵尸类)
这里面最简单的也就是僵尸(模型)类了,里面只需记住相对应的僵尸类型的 图片数量(zombiesFrameCount) 僵尸图片的宽度(zombiesFrameWidth) 以及僵尸图片的高度(zombiesFrameHeight),全部定义在BaseZombie类中,如下// // BaseZombie.h // 打僵尸 // // Created by YueWen on 15/10/5. // Copyright (c) 2015年 YueWen. All rights reserved. // #import <Foundation/Foundation.h> @interface BaseZombie : NSObject @property(nonatomic,assign)NSInteger zombiesFrameCount;//僵尸图片的张数 @property(nonatomic,assign)NSInteger zombiesFrameWidth;//僵尸图片的宽度 @property(nonatomic,assign)NSInteger zombiesFrameHeight;//僵尸图片的高度 @end
不需要实现任何的方法,其他四种类型的model类中,也只需重写init方法即可,这里只给出zombieType1(僵尸类型1)的init方法,其他的一样,只需记住图片的数量,以及高和宽即可。
- (instancetype)init { self = [super init]; if (self) { //为框架赋值 self.zombiesFrameHeight = 72; self.zombiesFrameWidth = 83; //图片的数量 self.zombiesFrameCount = 31; } return self; }
ZombiesManager(僵尸对象管理器)
zombiesManager(僵尸对象管理器)是一个存储僵尸对象的类,它不管视图的东西,只负责管理僵尸的类对象,它能够告知VC他的僵尸对象是否已经加载完毕,如果加载完毕,将数组返回交给Zombies_View来继续包装成视图;楼主的习惯,只要是管理者,一般情况下,都是会定义成单例的,这次必然也不例外首先会声明一个单例方法
/** * 获取单例对象 * * @return 单例对象 */ +(instancetype)shareZombieManager;
因为要存储僵尸对象,但是又不希望外界能够轻易的改动,那么设置一个开放的数组属性,只能读,即readOnly
/** * 存放僵尸对象的数组 */ @property(nonatomic,strong,readonly)NSArray * zombies;
接着需要一个加载僵尸的方法,来加载 宏 规定的僵尸数
/** * 加载僵尸 */ -(void)loadZombies;
这个Manager需要一个回调,是告知VC,他的僵尸数组已经加载完毕,并且返回加载完毕的数组,并用该数组完成下一步的操作
typedef void(^ZombieManagerLoadFinishBlock)(NSArray * zombies);
有回调比有回调属性,有回调属性必然会有设置回调属性的方法
/** * 为回调的代码块赋值 * * @param zombieMangerLoadFinishBlock 回调的代码块 */ -(void)setZombieManagerLoadFinishHandleBlock:(ZombieManagerLoadFinishBlock)zombieMangerLoadFinishBlock;
声明文件完毕,接下来就是实现文件了,延展中的代码块属性不赘余,但是延展中需要一个存放对象的可变数组
@property(nonatomic,strong)NSMutableArray * zombies_m;
首先实现init方法以及单例方法
-(instancetype)init { if (self = [super init]) { //初始化数组 self.zombies_m = [NSMutableArray array]; } return self; } +(instancetype)shareZombieManager { static ZombieManager * zombieManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ zombieManager = [[ZombieManager alloc]init]; }); return zombieManager; }
加载数组的属性,这里唯一一个小心的地方就是每次加载前需要清空数组,不然会因为单例属性而出现对象数量的累加
/** * 加载僵尸数组 */ -(void)loadZombies { //开始之前清除数组 [self.zombies_m removeAllObjects]; //开始循环加载僵尸 for (NSInteger i = 0; i < ZOMBIE_COUNT; i++) { [self addZombie]; } }
添加僵尸,貌似只是简单的一句话,但是添加僵尸是随机添加的,不能规定那种僵尸是几个,所以有一个随机返回僵尸对象的方法
/** * 随机返回僵尸对象 * * @return 增加的僵尸 */ -(BaseZombie *)arcdToModeZombie { NSInteger temp = arc4random() % ZOMBIE_TYPE_COUNT + 1;//随机数 1~4,并根据数组返回僵尸类型 switch (temp) { case 1: return [[ZombieType1 alloc]init]; break; case 2: return [[ZombieType2 alloc]init]; break; case 3: return [[ZombieType3 alloc]init]; break; case 4: return [[ZombieType4 alloc]init]; break; default: break; } return nil; }
那么添加僵尸的方法就很简单了,只需给数组添加一个对象即可
//增加一个僵尸 -(void)addZombie { [self.zombies_m addObject:[self arcdToModeZombie]]; }
这里说一下设置代码块的方法,因为他的回调是通过这个设置方法启动的,设置之后立马进行加载,因为是同步的,所以加载完毕后就会通过代码块的行回调给VC
/** * 设置回调代码块,并触发加载僵尸数据 * * @param zombieMangerLoadFinishBlock 回调的代码块 */ -(void)setZombieManagerLoadFinishHandleBlock:(ZombieManagerLoadFinishBlock)zombieMangerLoadFinishBlock { self.b = zombieMangerLoadFinishBlock; [self loadZombies];//加载数据 self.b(self.zombies_m);//返回处理好的数组 }
僵尸管理器完成。
MusicManager(音乐管理器)
它的职责很简单,虽然是一个单例,但是只管音乐的播放,VC告诉他放什么音乐,它放音乐即可,单例方法的生命不再赘余,下面是声明各种音乐播放的方法即可/** * 播放开始时的背景音乐 */ -(void)playStartMusic; /** * 播放结束的音乐 */ -(void)playEndMusic; /** * 播放失败的音乐 */ -(void)playLoseMusic; /** * 播放僵尸被敲打的声音 */ -(void)playBeatMusic;
实现方法,首先延展中需要声明四个音乐播放器属性
@property(nonatomic,strong)AVAudioPlayer * backgrountPlayer; @property(nonatomic,strong)AVAudioPlayer * endMusicPlayer; @property(nonatomic,strong)AVAudioPlayer * loseMusicPlayer; @property(nonatomic,strong)AVAudioPlayer * beatMusicPlayer;
楼主比较懒,因此依旧初始化方法都是通过一个方法返回的,自定义的播放器初始方法如下
/** * 加载播放器 * * @param musicName 音乐名称 * @param musicType 音乐类型 * * @return 播放器地址 */ -(AVAudioPlayer *)loadPlayWithMusicName:(NSString *)musicName Type:(NSString *)musicType WithNumbersOfLoop:(NSInteger)loop { NSString * path = [[NSBundle mainBundle]pathForResource:musicName ofType:musicType]; //转成url NSURL * url = [NSURL fileURLWithPath:path]; //加载播放器 AVAudioPlayer * player = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil]; //设置播放次数 player.numberOfLoops = loop; //准备播放 [player prepareToPlay]; //返回加载好的播放器 return player; }
因此自定义的初始化方法就简单多了
/** * 加载所有的音乐器 */ -(void)loadAllMusic { self.backgrountPlayer = [self loadPlayWithMusicName:@"bg" Type:@"mp3" WithNumbersOfLoop:-1]; self.endMusicPlayer = [self loadPlayWithMusicName:@"end" Type:@"mp3" WithNumbersOfLoop:0]; self.loseMusicPlayer = [self loadPlayWithMusicName:@"lose" Type:@"mp3" WithNumbersOfLoop:0]; self.beatMusicPlayer = [self loadPlayWithMusicName:@"shieldhit2" Type:@"caf" WithNumbersOfLoop:0]; }
在init方法中调用即可,单例方法不再说明,和上一个Manager一样
- (instancetype)init { self = [super init]; if (self) { //初始化音乐 [self loadAllMusic]; } return self; }
播放音乐的方法,就是控制播放器开始播放,停止播放即可
/******************** 播放音乐的方法 ************************/ -(void)playStartMusic { //背景音乐开启 [self.backgrountPlayer play]; } -(void)playEndMusic { //背景音乐关闭 [self.backgrountPlayer stop]; //开启结束音乐 [self.endMusicPlayer play]; } -(void)playLoseMusic { //背景音乐关 [self.backgrountPlayer stop]; //开启失败音乐 [self.loseMusicPlayer play]; } -(void)playBeatMusic { //开启敲打音乐 [self.beatMusicPlayer play]; }
音乐管理器完成。
Views(视图类)
BackgroundView(背景视图)
在之前的逻辑简述中也说过了,他负责展示背景、控制标签上的数量,以及 汇报是否游戏结束 与 僵尸什么时候该加速,为了好做,所以结合了xib,xib如下,不要忘记绑定即可。接着需要在延展中拖入两个输出口
@property (strong, nonatomic) IBOutlet UILabel *lblNumberOfZombiesInHome; @property (strong, nonatomic) IBOutlet UILabel *lblScore;
它负责回调游戏结束以及为僵尸加速的回调,所以定义两个Block,当然这里也可以用其他的方法,但是楼主最近一直在练习Block,所以这次依旧用的Block
typedef void(^GameOverBlock)(void);//游戏结束的回调 typedef void(^AddZombiesMoveSpeed)(void);//需要增加速度的回调,这两个作用一样,只不过为了好区分,所以定义了两个代码块
然后需要随着打到僵尸以及僵尸过线需要改变label上的值,所以声明文件上声明两个方法
/** * 增加得分 */ -(void)addScore; /** * 增加在家的僵尸 */ -(void)addNumberOfZombieInHome;
然后自然有设置回调的方法
/** * 设置游戏结束时候的回调 * * @param grameOverBlock 游戏结束的回调 */ -(void)backgroundGameOverBlockHandle:(GameOverBlock)gameOverBlock; /** * 设置加速的回调 * * @param addZombiesMoveSpeed 加速回调 */ -(void)backgroundAddZombiesMoveSpeed:(AddZombiesMoveSpeed)addZombiesMoveSpeed;
会不会只有这么点的方法呢,不一定,因为延展中会有更多的方法处理,接下来看实现文件
首先需要记录得分以及进入家中的僵尸的个数
@property (nonatomic,assign)NSInteger score;//得分 @property (nonatomic,assign)NSInteger numberOfZombiesInHome;//在家中的僵尸数量
如何判定僵尸该不该加速呢,所以必然存在一个标志位
@property (nonatomic,assign)NSInteger bgTag;//得分速度的标志位
因为需要回调,所以楼主的习惯必然会存在两个属性
@property (nonatomic,strong)GameOverBlock gameOverBlock; @property (nonatomic,strong)AddZombiesMoveSpeed addZombiesMoveSpeed;
延展中属性如此,接下来看方法,既然是继承于UIView,那么必然会有两个初始化方法,谁也不能肯定一定会用哪种方法,所以必先重写两个方法,以后的视图类不再做过多的赘余,记得写即可
//代码创建是运行的方法 -(instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self backgroundViewInit]; } return self; } //用xib或者stroyboard加载运行的方法 -(void)awakeFromNib { [self backgroundViewInit]; }
接下来就是自定义的创建方法了
//自定义的背景视图加载方法 -(void)backgroundViewInit { self.bgTag = 0;//标志位为0 self.score = 0;//初始化分数为0 self.numberOfZombiesInHome = 0;//初始化进入房屋的僵尸个数为0 /*********初始化标签的title*********/ self.lblNumberOfZombiesInHome.text = [NSString stringWithFormat:@"家中的僵尸数量: %ld",self.numberOfZombiesInHome]; self.lblScore.text = [NSString stringWithFormat:@"得分: %ld",self.score]; }
然后就是为block的属性赋值的方法,以后的block属性也必然会有赋值,只不过如果不特殊,也就不再赘余了
//为游戏结束代码块赋值 -(void)backgroundGameOverBlockHandle:(GameOverBlock)gameOverBlock { self.gameOverBlock = gameOverBlock; } //为增加僵尸速度代码块赋值 -(void)backgroundAddZombiesMoveSpeed:(AddZombiesMoveSpeed)addZombiesMoveSpeed { self.addZombiesMoveSpeed = addZombiesMoveSpeed; }
加分的方法,当分数达到某个数值(即加速标准)的时候会告知VC,需要加速了,这个判断会通过标志位bgTag来判断
/** * 加分的方法 */ -(void)addScore { self.score ++; self.lblScore.text = [NSString stringWithFormat:@"得分是: %ld",self.score]; //如果满 宏 定义的数量,那么取余必然会与标志位不符合 if (self.score % ADD_SPEED_STAND != self.bgTag) { //重置标志位 self.bgTag = self.score % ADD_SPEED_STAND; //回调加速 if (self.addZombiesMoveSpeed) { self.addZombiesMoveSpeed(); } } }
增加家里的僵尸的数量,一样,当到达某个数值(即允许进入僵尸的最大数量) 的时候,即告知VC,游戏需要结束了
-(void)addNumberOfZombieInHome { self.numberOfZombiesInHome++; self.lblNumberOfZombiesInHome.text = [NSString stringWithFormat:@"家中的僵尸数量: %ld",self.numberOfZombiesInHome]; //在家的僵尸等于游戏结束的数值的时候 if (self.numberOfZombiesInHome == GAMEOVERCOUNT) { //执行回调 if (self.gameOverBlock) { self.gameOverBlock(); } } }
背景视图完成。
ZombieView(单个僵尸视图)
它是继承于UIImageView,就是演示僵尸行走动画以及被点击的时候进行回调,告知多个僵尸视图,被点击,然后视图告知接下来需要干什么首先是定义在声明文件中的代码块
typedef void(^zombiePressedBlock)(void);
因为在后面的地方需要用到它是什么类型的僵尸,所以需要一个属性来存储僵尸类型,但是不能修改,所以用readOnly
/** * 存储展示的僵尸类型 */ @property(nonatomic,strong,readonly)BaseZombie * zombie;
在Zombie的时候里面存取着相应的frame的高和宽,以及图片的数量,所以选择整体传入,并多一个参数,用来描述中心点位置的point
/** * 根据僵尸图片的大小进行创建 * * @param zombie 僵尸对象 * @param origin 中心店 */ -(instancetype)initWithZombie:(BaseZombie *)zombie WithOrigin:(CGPoint)origin;
接着就是实现文件,延展中只需要一个代码块属性即可
@property(nonatomic,strong)zombiePressedBlock b;
因为这个类只能通过代码创建,所以直接写了便利构造方法
/** * 自定义的便利方法 * * @param zombie 僵尸对象,里面存着大小 * @param origin 中心点 * */ -(instancetype)initWithZombie:(BaseZombie *)zombie WithOrigin:(CGPoint)origin { if (self = [super init]) { //初始化僵尸属性 _zombie = zombie; //框架初始化 [self frameSetWithZombie:zombie WithOrigin:origin]; //设置图片 [self setImageToImageView:self withType:[self tagForZomieButton:zombie] withZombie:zombie]; //初始化相关属性 [self attributeSet]; } return self; }
因为上面的方法看起来比较简单,是因为分别将三个功能的方法封装起来了,首先是框架的初始化方法,根据zombie中存的数据frame,来创建该视图
/** * 初始化位置框架的属性 * * @param zombie 僵尸类型 * @param origin 中心点 */ -(void)frameSetWithZombie:(BaseZombie *)zombie WithOrigin:(CGPoint)origin { //初始化frame self.frame = CGRectMake(0, 0, zombie.zombiesFrameWidth, zombie.zombiesFrameHeight); //初始化中心点 self.center = origin; }
接着是相关属性的设置方法
/** * 初始化相关属性 */ -(void)attributeSet { //可以人机交互 self.userInteractionEnabled = YES; //设置动画属性 self.animationDuration = 1; //循环播放 self.animationRepeatCount = -1; //开启动画 [self startAnimating]; }
稍微麻烦点的就是设置图片,设置图片需要根据僵尸的类型以及图片的类型来设置,但是这个type是通过方法获得的,先来看设置图片的方法
/** * 为imageView设置图片 * * @param ImageView 设置图片的imageView * @param type 图片的类型 */ -(void)setImageToImageView:(UIImageView *)imageView withType:(NSInteger)type withZombie:(BaseZombie *)zombie { //表示返回的类型合法 if (type != 0) { //接收数组 NSArray * pic = [self zombiesPictureWithType:type withZombie:zombie]; //加到imageView上 self.animationImages = pic; } }
里面的type是一个NSInteger类型,会根据僵尸的类型来判断返回哪一个数字,毕竟我们僵尸的名字也是有规律的
/** * 根据僵尸类的名字获得僵尸类型的后缀数字 * * @param baseZomebie 僵尸的父类 * * @return 僵尸的数字类型的后缀数字,如果不存在,返回0 */ -(NSInteger)tagForZomieButton:(BaseZombie *)baseZomebie { //获得僵尸的类名字符串 NSString * zomebieType = NSStringFromClass([baseZomebie class]); //循环匹配 for (NSInteger i = 1; i <= 4; i++) { if ([zomebieType isEqualToString:[NSString stringWithFormat:@"ZombieType%ld",i]]) { return i; } } return 0; }
继承于UIImageView的原因是因为他有自带的图帧动画效果,所以必然会有图片数组的加载,即方法中的接收数组
/** * 根据僵尸的类型和图片数加载图片数组 * * @param type 僵尸的类型 * @param zombie 僵尸的对象 * * @return 图片数组 */ -(NSArray *)zombiesPictureWithType:(NSInteger)type withZombie:(BaseZombie *)zombie { //存储图片的数组 NSMutableArray * pics = [NSMutableArray array]; //开始循环添加图片 for (NSInteger i = 1; i <= zombie.zombiesFrameCount ; i++) { //获取UIImage UIImage * image = [UIImage imageNamed:[NSString stringWithFormat:@"%ld_%ld.tiff",type,i]]; //添加到可变数组中 [pics addObject:image]; } //返回 return [NSArray arrayWithArray:pics]; }
毕竟不是button,UIImageView没有被点击的功能,但是并不能代表不能添加,既然是UIView,必然会存在响应触摸事件的方法,如下
//touch事件 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //捕获touch事件 UITouch * touch = [touches anyObject]; //获取touch的点 CGPoint point = [touch locationInView:self]; //判断在该区域内 if (CGRectContainsPoint(self.bounds, point)) { //点击之后的操作,由上一个视图决定,回调,即多个僵尸视图 if (self.b) { self.b(); } } }
单个僵尸视图完毕。
Zombies_View(多个僵尸的视图)
这个视图算是比较麻烦的一个视图了,因为他需要移动每个僵尸视图,并且还需要汇报僵尸视图被点击,僵尸进入了家,所以这个视图需要一点点的计算,说道回调,必然会存在两个回调typedef void(^ZombiesBeatBlock)(void);//僵尸被点击的回调 typedef void(^ZombiesIntoHomeBlock)(void);//僵尸进入家的回调
因为这个视图上会出现多个单个僵尸视图,所以需要通过ZombiesManager返回的数组进行加载视图
/** * 加载僵尸视图 * * @param zombies 存储僵尸对象的数组 */ -(void)startLoadZombiesView:(NSArray *)zombies;
接着有两个为回调代码块赋值的方法
/** * 设置僵尸被敲击的回调 * * @param zombieBeatBlock 被敲击时执行的方法 */ -(void)zombiesBeatBlockHandle:(ZombiesBeatBlock)zombieBeatBlock; /** * 设置僵尸进入家中的回调 * * @param zombieIntoHomeBlock 进入家中时执行的回调 */ -(void)zombiesIntoHomeBlockHandle:(ZombiesIntoHomeBlock)zombieIntoHomeBlock;
他之所以能够让僵尸按时间段移动,必然存在一个计时器,但当游戏结束后,不需要动了,那么还需要要一个关闭定时器的方法
/** * 计时器停止 */ -(void)zombiesStopMove;
游戏结束,僵尸不能动,还希望不能点,还需要一个失去响应的方法
/** * 结束所有的僵尸视图的响应 */ -(void)endAllZombiesEditing;
如果backgroundView(背景视图)告知VC需要加速,那么VC就会让这个视图加速,所以需要一个加速方法
/** * 加快僵尸移动的速度 * * @param speed 加快的速度 */ -(void)addZombiesMoveSpeed:(Speed)speed;
Speed只是自定义的一个类型,实际是CGFloat
typedef CGFloat Speed;
接下来就是更麻烦的实现方法了
首先在延展中定义如下属性
@property(nonatomic,strong)NSMutableArray * zombieViews;//里面存的是僵尸view对象 @property(nonatomic,strong)NSTimer * timer;//计时器 @property(nonatomic,assign)Speed speed;//控制僵尸移动的速度 @property(nonatomic,strong)ZombiesBeatBlock zombiesBeatBlock; @property(nonatomic,strong)ZombiesIntoHomeBlock zombiesIntoHomeBlock;
重写init方法
#pragma mark - 相关创建的方法 -(instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { //初始化数组 self.zombieViews = [NSMutableArray array]; //初始化僵尸的速度 self.speed = ZOMBIE_MOVE_SPEED; } return self; }
接着实现加载僵尸视图的方法,因为每种僵尸的视图大小是不固定的,所以需要通过知道僵尸视图的frame来控制视图,避免出现在屏幕的边缘
/** * 开始加载僵尸视图 * * @param zombies 存僵尸对象的数组 */ -(void)startLoadZombiesView:(NSArray *)zombies { //加载僵尸视图对象 [self loadZombiesViewsWithArray:zombies]; //启动定时器 self.timer = [NSTimer scheduledTimerWithTimeInterval:TIMER_SPEED target:self selector:@selector(moveAllZombie) userInfo:nil repeats:YES]; }
传进来的参数数组里面存的是Zombie对象,所以需要一个模型转视图的过程,因此有一个loadZomiesViewsWithArray方法
/** * 根据存放僵尸对象数组 处理成 存储僵尸视图的 数组 * * @param zombies 存放僵尸对象的数组 */ -(void)loadZombiesViewsWithArray:(NSArray *)zombies { for (NSInteger i = 0; i < zombies.count; i++) { //初始化一个僵尸视图 ZombieView * zombieView = [[ZombieView alloc]initWithZombie:zombies[i] WithOrigin:[self randomPointWithZombieView:zombies[i]]]; //报告VC已经被敲击 [zombieView doneWithZombiePressedHandleBlock:^{ //实现回调,敲击声音 if (self.zombiesBeatBlock) { self.zombiesBeatBlock(); } //被点击之后的动画效果 [self zombiesDidBeat:zombieView]; }]; //添加视图数组 [self.zombieViews addObject:zombieView]; //添加视图 [self addSubview:zombieView]; } }
上面的方法用到一个随机中心点的方法,需要传入一个Zombie对象,根据frame来创建,即randomPointWithZombieView:(BaseZombie *)zombie方法,如下
/** * 根据僵尸类型创建随机点 * * @param zombie 僵尸对象 * * @return 随机创建的点 */ -(CGPoint)randomPointWithZombieView:(BaseZombie *)zombie { //屏幕的宽 NSInteger width = self.bounds.size.width; //屏幕的高 NSInteger height = self.bounds.size.height; //获得随机的center NSInteger x = arc4random()%(width - zombie.zombiesFrameWidth) + width;//让其移动到右侧屏幕外 NSInteger y = arc4random()%(height - zombie.zombiesFrameHeight) + zombie.zombiesFrameHeight/2; return CGPointMake(x, y); }
这就就是需要僵尸移动了,这个示例里,一共是30个僵尸,所以不会一下子移动30个,必然会有一个移动一只僵尸的方法,30个僵尸的移动便由一个循环即可搞定,首先是一个僵尸移动
/** * 单个僵尸移动 * * @param zombieView 移动的僵尸视图对象 */ -(void)moveZombie:(ZombieView *)zombieView { //首先获得视图的中心点 CGPoint point = zombieView.center; //说明已经进入了家,重新分配位置 if (point.x <= -1 * zombieView.bounds.size.width / 2) { //重新分配位置 zombieView.center = [self randomPointWithZombieView:zombieView.zombie]; //并且执行进入家的回调方法 if (self.zombiesIntoHomeBlock) { self.zombiesIntoHomeBlock(); } } //前进 else { //point坐标减 point.x -= self.speed; zombieView.center = point; } }
多个僵尸移动就简单化了,只需要一个循环即可
/** * 所有的僵尸视图移动 */ -(void)moveAllZombie { for (ZombieView * zombieView in self.zombieViews) { [self moveZombie:zombieView]; } }
僵尸被点击后放大,并且一段时候又变小,接着会被移动到最右侧,方法如下
/** * 僵尸被点击的时候实现的动画效果 * * @param zombieView 被点击的僵尸 */ -(void)zombiesDidBeat:(ZombieView *)zombieView { //实现缩放2倍大小.0.25秒之内完成 [UIView animateWithDuration:0.25 animations:^{ //获取仿射对象 CGAffineTransform transForm = CGAffineTransformMakeScale(2, 2); //赋值 zombieView.transform = transForm; } completion:^(BOOL finished) { //实现缩放0.5,0.25秒完成 [UIView animateWithDuration:0.25 animations:^{ //获取仿射对象 CGAffineTransform transform = CGAffineTransformMakeScale(0.5, 0.5); //赋值 zombieView.transform = transform; } completion:^(BOOL finished) { //重新分配位置 zombieView.center = [self randomPointWithZombieView:zombieView.zombie]; /*恢复原来大小*/ zombieView.transform = CGAffineTransformIdentity; }]; }]; }
剩下的三个方法就显得很简单了,让僵尸不能再移动,关闭定时器即可
/** * 僵尸停止移动 */ -(void)zombiesStopMove { //销毁计时器 [self.timer invalidate]; }
游戏结束后,背面的僵尸不能进行点击,实际就是不能进行人机交互,将属性设置为NO即可
/** * gameOver之后让僵尸不可点 */ -(void)endAllZombiesEditing { for (ZombieView * zombieView in self.zombieViews) { //不能人机交互 zombieView.userInteractionEnabled = NO; } }
加速呢,只需要改变速度属性即可
/** * 加快僵尸移动的速度 * * @param speed 加快的速度 */ -(void)addZombiesMoveSpeed:(Speed)speed { if (speed >= 0) { self.speed += speed; } }
多个僵尸视图完成
GameOverView(游戏结束视图)
最后一个视图了,前边的代码估计已经疯了,坚持一下吧,楼主依旧如此,这个视图负责游戏结束后,弹出,并且在弹出之后,会根据 重新开始 按钮进行相应的回调在实现文件只,定义一个Block回调,负责告知玩家需要重新开始游戏
typedef void(^gameOverRestartBlock)(void);
接着需要声明两个方法,这个是根据父视图,可以确定在父视图的哪个位置
/** * 开始出现动画 * * @param superView 父视图 */ -(void)startAnimateWithView:(UIView *)superView;
接着就是最后设置回调的方法了
/** * 设置重新开始游戏的回调 * * @param gameOverRestartBlock 回调方法 */ -(void)gameOverRestartBlockHandle:(gameOverRestartBlock)gameOverRestartBlock;
延展中需要一个UIImageView来显示最后失败的那个图片
//显示失败头像的imageView @property(nonatomic,strong)UIImageView * imageView;
不要忘记两个初始化方法
//代码实现创建的方法 -(instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self gameOverViewInit]; } return self; } //xib和storyboard创建的方法 -(void)awakeFromNib { [self gameOverViewInit]; }
自定义的初始化方法
/** * 自定义的创建方法 */ -(void)gameOverViewInit { //重置大小 self.frame = CGRectMake(0, 0, 141, 180); //初始化imageView self.imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 141, 117)]; //设置图片 [self.imageView setImage:[UIImage imageNamed:@"ZombiesWon.png"]]; //添加图片 [self addSubview:self.imageView]; }
失败的时候动画弹出的视图,实现方法如下
//最后失败跳出动画 -(void)startAnimateWithView:(UIView *)superView; { //设置自己的中心点 self.center = CGPointMake(superView.bounds.size.width / 2, superView.bounds.size.height / 2); //将自己添加到父视图上 [superView addSubview:self]; //实现动画 [UIView animateWithDuration:1.0 animations:^{ //创建一个仿射对象 CGAffineTransform transform = CGAffineTransformMakeScale(2, 2); //赋值 self.transform = transform; } completion:^(BOOL finished) { //创建一个重新来过的按钮 UIButton * button = [self loadRestartButton]; //添加button [self addSubview:button]; }]; }
因为那么button的属性比较多,所以楼主打包成一个方法了,如下
/** * 创建一个重新来过的按钮 * * @return 创建备好的按钮 */ -(UIButton *)loadRestartButton { //创建一个重新来过的button UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom]; //设置frame button.frame = CGRectMake(0, 0, 126, 26); //设置图片 [button setBackgroundImage:[UIImage imageNamed:@"a.png"] forState:UIControlStateNormal]; [button setBackgroundImage:[UIImage imageNamed:@"aHighlight.png"] forState:UIControlStateHighlighted]; //设置中心 button.center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2 + 50); //设置目标动作回调 [button addTarget:self action:@selector(restartGame) forControlEvents:UIControlEventTouchUpInside]; //设置title [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; button.titleLabel.font = [UIFont systemFontOfSize:10]; [button setTitle:@"大侠请重新来过0.0" forState:UIControlStateNormal]; return button; }
button的目标动作回调其实就是该视图的回调方法
//重新开始游戏 -(void)restartGame { //回调方法 if (self.b) { self.b(); } }
整个视图类完成。
ViewController(控制层)
经过一系列的铺垫与吐血,那么控制层的逻辑就很简单了,下面是属性@property(nonatomic,strong)BackgroundView * backgroundView;//背景view,负责报告加速以及结束游戏 @property(nonatomic,strong)MusicManager * musicManager;//负责控制播放音乐 @property(nonatomic,strong)ZombieManager * zombieManager;//负责控制僵尸对象 @property(nonatomic,strong)Zombies_View * zombies_view;//负责显示显示僵尸对象以及敲击事件 @property(nonatomic,strong)GameOverView * gameOverView;//负责显示结束游戏界面以及重新游戏的回调
viewDidLoad中初始化即可
- (void)viewDidLoad { [super viewDidLoad]; //加载音乐管理器 self.musicManager = [MusicManager shareMusicManager]; //加载僵尸管理器 self.zombieManager = [ZombieManager shareZombieManager]; //加载除了管理器的其他组件 [self loadExceptManager]; }
在其他的组件加载中,初始化其他的组件loadExceptManager
//加载背景视图 self.backgroundView = [[[NSBundle mainBundle] loadNibNamed:@"BackgroundView" owner:nil options:nil] lastObject]; self.backgroundView.frame = self.view.bounds; //加载僵尸管理控制器 self.zombies_view = [[Zombies_View alloc]initWithFrame:self.view.bounds]; //加载结束视图 self.gameOverView = [[GameOverView alloc]initWithFrame:CGRectZero];
最后就是最爽的回调过程了,不要忘记强引用循环
//避免强引用循环 __block __weak ViewController * copy_self = self;
首先是Zombies_View(多个僵尸视图)的回调设置
//僵尸视图被点击的时候回调 [self.zombies_view zombiesBeatBlockHandle:^{ //音乐管理器播放被敲击的声音 [copy_self.musicManager playBeatMusic]; //背景分数增加 [copy_self.backgroundView addScore]; }]; //僵尸进入家进行的回调 [self.zombies_view zombiesIntoHomeBlockHandle:^{ //背景进入家中的僵尸数量增加 [copy_self.backgroundView addNumberOfZombieInHome]; }];
然后是ZombieManager(僵尸管理器)的回调方法
//加载完毕僵尸后的回调 [self.zombieManager setZombieManagerLoadFinishHandleBlock:^(NSArray *zombies) { //开始转换成视图僵尸 [copy_self.zombies_view startLoadZombiesView:zombies]; }];
接着是BackgroundView(背景视图)的回调方法
//游戏加速的回调 [self.backgroundView backgroundAddZombiesMoveSpeed:^{ //僵尸视图加速 [copy_self.zombies_view addZombiesMoveSpeed:ZOMBIE_ADD_SPEED]; }]; //游戏结束时候的回调 [self.backgroundView backgroundGameOverBlockHandle:^{ //结束游戏音乐开启 [copy_self.musicManager playLoseMusic]; //僵尸原地不动 [copy_self.zombies_view zombiesStopMove]; //结束僵尸视图的响应 [copy_self.zombies_view endAllZombiesEditing]; //结束视图播放 [copy_self.gameOverView startAnimateWithView:copy_self.view]; }];
最后是GameOverView(结束视图)的回调方法
//结束后点击重新开始的方法 [self.gameOverView gameOverRestartBlockHandle:^{ [self loadExceptManager]; }];
不要忘记音乐播放器播放音乐以及组件的添加
[self.musicManager playStartMusic]; //添加控件 [self.view addSubview:self.backgroundView]; [self.backgroundView addSubview:self.zombies_view];
注:如果不想最上面的信号以及电量栏挡住,一下方法即可解决
/** * 隐藏偏好状态栏 * * @return YES表示隐藏,默认为NO */ -(BOOL)prefersStatusBarHidden { return YES; }
如果有事情,想暂停怎么办呢,就按home键吧,就会暂停了,这就需要在appDelegate中进行设置了
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { UIWindow * windows = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];//创建一个window对象 UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];//获得storyboard ViewController * viewController = [storyboard instantiateViewControllerWithIdentifier:@"ViewController"]; windows.rootViewController = viewController;//设置根视图 self.window = windows; [self.window makeKeyAndVisible];//被适用对象的主窗口显示到屏幕的最前端 return YES; }
那么当被挂起的时候会执行如下方法
- (void)applicationDidEnterBackground:(UIApplication *)application { ViewController * viewController = (ViewController *)self.window.rootViewController; [viewController.zombies_view zombiesStopMove];//停止移动 [viewController.musicManager closeBackPlayerMusic];//关闭背景音乐 }
那么再次进入游戏后开始就需要写下面的方法
- (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. ViewController * viewController = (ViewController *)self.window.rootViewController; [viewController.zombies_view zombiesStartMove];//开始移动 [viewController.musicManager playStartMusic];//播放开始音乐 }
这样的话,想暂停就直接home键吧,后台就自动暂停了哦,祝大家国庆快乐0.0
GitHub:https://github.com/YRunIntoLove/BeatZombies
相关文章推荐
- 我的常用网站整理
- Storm系列(十二)架构分析之Worker-心跳信息处理
- 大型网站之分布式会话管理
- 求助!求助!网站页面图片太多,加载时间太长该怎么办?
- Android平台架构及特性
- 【Machine Learning in Action --4】朴素贝叶斯过滤网站的恶意留言
- 网站运维常用小技巧,排错必备
- iOS开发常用国外网站清单
- 架构师内练基础
- 网站关键词优化--长尾关键6词的优化方法
- 网站地图
- HA集群配置
- 网站典型故障案例
- 飞鸽传飞官方网站
- 开源代码网站
- 网站对话框开源脚本--ArtDialog V6.0
- 学习网站
- 深入 HBase 架构解析(2)
- 深入 HBase 架构解析(1)
- 支付宝中的手机网站支付接口,php版 notify_url.php 异步通知页面未成功执行