iOS开发-------MVC架构思想-植物大战僵尸
十月长假也就这么过去了,利用假期想磨练一下自己的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回调
全局用的宏
#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(僵尸类)
//
// 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(僵尸对象管理器)
/*** 获取单例对象** @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;
-(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]];
}
/*** 设置回调代码块,并触发加载僵尸数据** @param zombieMangerLoadFinishBlock 回调的代码块*/
-(void)setZombieManagerLoadFinishHandleBlock:(ZombieManagerLoadFinishBlock)zombieMangerLoadFinishBlock
{self.b = zombieMangerLoadFinishBlock;[self loadZombies];//加载数据self.b(self.zombies_m);//返回处理好的数组
}
MusicManager(音乐管理器)
/*** 播放开始时的背景音乐*/
-(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(背景视图)
@property (strong, nonatomic) IBOutlet UILabel *lblNumberOfZombiesInHome;@property (strong, nonatomic) IBOutlet UILabel *lblScore;
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];}
//为游戏结束代码块赋值
-(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(单个僵尸视图)
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];
}
/*** 为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(游戏结束视图)
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;
//僵尸视图被点击的时候回调[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;
}
- (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
iOS开发-------MVC架构思想-植物大战僵尸相关推荐
- 植物2 IOS 怎么实名认证_【植物大战僵尸2国际版最新iOS版】植物大战僵尸2国际版iOS版下载...
植物大战僵尸2国际版iOS版下载如遇网络信号问题,可切换至WIFI网络或4G网络. 植物大战僵尸2国际版iOS版下载游戏更新: [月卡焕新 限时特惠] 潘妮时空车进行了大改造,带来了更多超棒的月卡新特 ...
- C语言手写-植物大战僵尸
植物大战僵尸,是一个非常经典的小游戏,初学者从零开始,开发一个自己的植物大战僵尸,还是非常值得期待的!可以作为自己的课设,也可以用来快速提升自己的项目开发能力. 项目效果(详细视频教程-下载素材-点这 ...
- 基于python开发植物大战僵尸
目录 摘要 2 一, 引言 3 1.1中国游戏产业的现状 3 1.2中国游戏产业的未来发展局势 4 1.3植物大战僵尸游戏的发展状况 4 二.系统结构 5 2.1 Python3.8.2 IDLE 简 ...
- 植物2 IOS 怎么实名认证_植物大战僵尸2未来世界22天困难怎么过关 植物阵容推荐...
植物大战僵尸2未来世界22天困难攻略 植物大战僵尸2未来世界22天有很多小伙伴都卡在这里,难度是一定的,那么到底要怎么过呢?下面就和小编一起去了解一下吧! 植物大战僵尸未来世界第二十二天过关条件是指挥 ...
- Python开发简单植物大战僵尸
运行效果 1.引入需要的模块,配置图片路径,设置界面,创建游戏主入口. #1 引入需要的模块 import os import timeimport pygame import random #1 配 ...
- 原生JS实现的h5小游戏-植物大战僵尸
代码地址如下: http://www.demodashi.com/demo/12755.html 项目介绍 本项目是利用原生js实现的h5小游戏-植物大战僵尸,主要结合了一下自己对于h5小游戏的理解, ...
- 女友让我破解植物大战僵尸!我干脆撸了一款一样的....翻身舔狗把歌唱呀
今天给大家分享的开源项目可以说非常适合入门,还比较好玩,更是一个有故事的项目.既能满足想学习的读者,又能满足那些喜欢八卦的读者. 提到植物大战僵尸相信大部分读者都不陌生,可以说是塔防类游戏的鼻祖.就鸟 ...
- iOS开发的架构模式
iOS开发的架构模式 0.VIPER 为了减轻Controller层负担的方法,而VIPER架构其实是将Controller再细分成三层,分别是View.Interactor.Presenter,已达 ...
- [日记]游长白遇梅花,植物大战僵尸
"旅客朋友们--"我因为坐在面包车副驾驶的位置,所以假装自己是导游,"您即将经过的景点是'董眼镜修车铺',请向车窗的左侧观看--" 董眼镜修车铺 "董 ...
最新文章
- Elasticsearch之深入了解Search的运行机制
- 数据中心人员短缺,行业仍然充满挑战
- 经济学中的定量分析python_Sargent定量经济学(3):Python
- vue 使用fs_node.js中常用的fs文件系统
- a*算法的时间复杂度_算法的时间和空间复杂度,就是这么简单
- JavaWeb笔记02-Tomcat
- 百度再显管理变革决心 副总裁郑子斌离职
- 各种与视频编解码以及视频图像处理的应用相关的新技术,新方法,各种软件开发相关的算法,思想。...
- linux安装qq_一分钟,轻松上手 Linux 安装QQ,让你简单开启聊天办公新环境
- 陈天石吴翰清顾嘉唯光速对话(汤晓鸥今天没有晒娃)
- 云服务器里面安装虚拟服务器,云服务器里面安装虚拟服务器
- 编译exe不弹窗口_详解matlab mbuild -setup找不到C++编译器解决方案
- Eclipse IDE 2022的下载与安装
- PS黑作坊人像磨皮 精修 快速伪商业修图 插件 扩展面板
- CSS 标签权重判断的方式
- SAS数据分析之聚类分析
- MySQL8.0超细致下载安装教程
- 流利阅读 2019.1.26 The maturing of the smartphone industry should be celebrated, not lamented
- docker的目录挂载
- UG CAM 开发获取工序导航器当前选择的操作、程序组、几何体、刀具方法,获得名字并修改名字
热门文章
- 4.4.2 将拉取偏移量作为提交偏移量
- 微信小程序生成小程序码以及参数的获取
- 公众号引流效果好吗?会操作引流效果超好!
- NOJ 题目1645 聊天止于呵呵(模拟)
- vue+Echarts 实时大屏看板
- P7470-[NOI Online 2021 提高组]岛屿探险【Trie,CDQ分治】
- 加密的PDF怎么解密?其实很简单,只要掌握这个技巧
- 用C++ 封装linux下的互斥锁MutexLock和条件变量Condition
- 第七天:《LeetCode一天一例》-----将矩阵进行螺旋式输出(python实现)
- Pygame实战:记忆差怎么办?别急,增强记忆力的小游戏送给你~【越玩越上瘾】