十月长假也就这么过去了,利用假期想磨练一下自己的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回调

全局用的宏

因为在程序中必然要减少数字的使用,面的这种情况,最简单的方法就是定义一个宏,楼主将所有的宏打包成一个头文件,叫做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];//转成urlNSURL * 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;//标志位为0self.score = 0;//初始化分数为0self.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
{//初始化frameself.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++){//获取UIImageUIImage * 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;//获得随机的centerNSInteger 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);//初始化imageViewself.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
{//创建一个重新来过的buttonUIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];//设置framebutton.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]];//获得storyboardViewController * 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

iOS开发-------MVC架构思想-植物大战僵尸相关推荐

  1. 植物2 IOS 怎么实名认证_【植物大战僵尸2国际版最新iOS版】植物大战僵尸2国际版iOS版下载...

    植物大战僵尸2国际版iOS版下载如遇网络信号问题,可切换至WIFI网络或4G网络. 植物大战僵尸2国际版iOS版下载游戏更新: [月卡焕新 限时特惠] 潘妮时空车进行了大改造,带来了更多超棒的月卡新特 ...

  2. C语言手写-植物大战僵尸

    植物大战僵尸,是一个非常经典的小游戏,初学者从零开始,开发一个自己的植物大战僵尸,还是非常值得期待的!可以作为自己的课设,也可以用来快速提升自己的项目开发能力. 项目效果(详细视频教程-下载素材-点这 ...

  3. 基于python开发植物大战僵尸

    目录 摘要 2 一, 引言 3 1.1中国游戏产业的现状 3 1.2中国游戏产业的未来发展局势 4 1.3植物大战僵尸游戏的发展状况 4 二.系统结构 5 2.1 Python3.8.2 IDLE 简 ...

  4. 植物2 IOS 怎么实名认证_植物大战僵尸2未来世界22天困难怎么过关 植物阵容推荐...

    植物大战僵尸2未来世界22天困难攻略 植物大战僵尸2未来世界22天有很多小伙伴都卡在这里,难度是一定的,那么到底要怎么过呢?下面就和小编一起去了解一下吧! 植物大战僵尸未来世界第二十二天过关条件是指挥 ...

  5. Python开发简单植物大战僵尸

    运行效果 1.引入需要的模块,配置图片路径,设置界面,创建游戏主入口. #1 引入需要的模块 import os import timeimport pygame import random #1 配 ...

  6. 原生JS实现的h5小游戏-植物大战僵尸

    代码地址如下: http://www.demodashi.com/demo/12755.html 项目介绍 本项目是利用原生js实现的h5小游戏-植物大战僵尸,主要结合了一下自己对于h5小游戏的理解, ...

  7. 女友让我破解植物大战僵尸!我干脆撸了一款一样的....翻身舔狗把歌唱呀

    今天给大家分享的开源项目可以说非常适合入门,还比较好玩,更是一个有故事的项目.既能满足想学习的读者,又能满足那些喜欢八卦的读者. 提到植物大战僵尸相信大部分读者都不陌生,可以说是塔防类游戏的鼻祖.就鸟 ...

  8. iOS开发的架构模式

    iOS开发的架构模式 0.VIPER 为了减轻Controller层负担的方法,而VIPER架构其实是将Controller再细分成三层,分别是View.Interactor.Presenter,已达 ...

  9. [日记]游长白遇梅花,植物大战僵尸

    "旅客朋友们--"我因为坐在面包车副驾驶的位置,所以假装自己是导游,"您即将经过的景点是'董眼镜修车铺',请向车窗的左侧观看--" 董眼镜修车铺 "董 ...

最新文章

  1. Elasticsearch之深入了解Search的运行机制
  2. 数据中心人员短缺,行业仍然充满挑战
  3. 经济学中的定量分析python_Sargent定量经济学(3):Python
  4. vue 使用fs_node.js中常用的fs文件系统
  5. a*算法的时间复杂度_算法的时间和空间复杂度,就是这么简单
  6. JavaWeb笔记02-Tomcat
  7. 百度再显管理变革决心 副总裁郑子斌离职
  8. 各种与视频编解码以及视频图像处理的应用相关的新技术,新方法,各种软件开发相关的算法,思想。...
  9. linux安装qq_一分钟,轻松上手 Linux 安装QQ,让你简单开启聊天办公新环境
  10. 陈天石吴翰清顾嘉唯光速对话(汤晓鸥今天没有晒娃)
  11. 云服务器里面安装虚拟服务器,云服务器里面安装虚拟服务器
  12. 编译exe不弹窗口_详解matlab mbuild -setup找不到C++编译器解决方案
  13. Eclipse IDE 2022的下载与安装
  14. PS黑作坊人像磨皮 精修 快速伪商业修图 插件 扩展面板
  15. CSS 标签权重判断的方式
  16. SAS数据分析之聚类分析
  17. MySQL8.0超细致下载安装教程
  18. 流利阅读 2019.1.26 The maturing of the smartphone industry should be celebrated, not lamented
  19. docker的目录挂载
  20. UG CAM 开发获取工序导航器当前选择的操作、程序组、几何体、刀具方法,获得名字并修改名字

热门文章

  1. 4.4.2 将拉取偏移量作为提交偏移量
  2. 微信小程序生成小程序码以及参数的获取
  3. 公众号引流效果好吗?会操作引流效果超好!
  4. NOJ 题目1645 聊天止于呵呵(模拟)
  5. vue+Echarts 实时大屏看板
  6. P7470-[NOI Online 2021 提高组]岛屿探险【Trie,CDQ分治】
  7. 加密的PDF怎么解密?其实很简单,只要掌握这个技巧
  8. 用C++ 封装linux下的互斥锁MutexLock和条件变量Condition
  9. 第七天:《LeetCode一天一例》-----将矩阵进行螺旋式输出(python实现)
  10. Pygame实战:记忆差怎么办?别急,增强记忆力的小游戏送给你~【越玩越上瘾】