本文作者 LuckyMore。
前几天, Cee 童鞋把 Co!orMix 开源了。当时刚上架的时候,团队里的另外两个童鞋都发了文,@Cee 讲产品,@Albus 讲设计(这两篇文章都在 V2EX 上发布过)。
既然代码开源了,那是时候说一下项目本身了。
Co!orMix 里面并没有什么特别有技术含量的东西,可以说,任何一个人都可以毫不费力地把它做出来。对于新手来说,也值得一试。不过毫不谦虚地说,如果要达到零错误率,还是有一定难度的,而上线至今,它的 Crash 率一直保持在 0 ,这是我所引以为豪的。(欢迎大家试玩!偷偷说一句,我可是跟组里承诺过,只要出现一个 crash ,就请吃饭的哦!)
无论是多简单的项目,开动之前必须要思考一下大致的架构。 Co!orMix 本身是一个相对很简单的游戏。
从页面考虑:主页、设置页、游戏界面、游戏结果页、引导页;
从游戏本身考虑:题目、选项、得分、策略、游戏模式。
第一天晚上我们三个人商量出了游戏的大致功能和规则,我和 Cee 便直接开始讨论起了实现。由于游戏本身的复杂度不高,我们决定采用最传统的 MVC 来完成这个应用。
Model 层
CMCard :卡片(对应游戏界面上半部分)
CMColor :颜色(游戏中所对应的颜色的概念)
CMQuestion :问题(包含 CMCard ,以及对应的 Option 和 Answer )
CMScene :游戏场景(对应一次游戏,根据游戏模式区分不同表现)
Factory :工厂(工厂模式,生产问题)
View 层
CMCardView :卡片 View
CMQuestionView :问题 View
CMScoreView :得分
Controller 层
CMClassicTutorialViewController/CMFantasyTutorialViewController :新手引导
CMGameResultViewController :游戏结果
CMGameViewController :游戏界面
CMMenuViewController :游戏菜单界面
CMSettingViewController :游戏设置界面
由于项目比较简单,所以并没有按照功能模块进行分类,项目的结构大致如下:
目录 | 职能 |
---|---|
Category | 工具、扩展类 |
ViewController | Controller 层 |
View | View 层 |
Model | Model 层 |
项目并没有使用任何第三方库(除了友盟统计),所以自然也就没有使用 Cocoapods 了。
项目主体编码主要是我负责的,@Cee 在初期做了 Code Review 工作(惊讶吧!这么小的项目还做 Code Review )。开发在 dev
分支做,记得第一次提交的时候,@Cee 写了 N 个 comments ,很多都是关于 Coding Style 的问题(我自认已经很注意了,但是在某个人的强迫症面前根本不值一提),改完了所有的问题。之后就更加小心地完成编码工作。最后通过 Pull Request
合并进主分支。之后又开发了 Android 版本,感兴趣的童鞋可以去 dev_android
的分支看一下安卓版本,实现的大致逻辑和 iOS 差不多。
项目采用 xib + AutoLayout + Size Classes 完成界面搭建,这也是我们第一次尝试使用 Size Classes 。
总体来说,算是不错的一次实践, 0 代码完成全平台适配。我们以游戏界面中的 QuestionView 为例(CMQuestionView
)做一下介绍,这个界面算是相当相当的典型了!
分析一下这个界面,基本可以分三部分:卡片、问题、选项。这里我总共分了三个 View 来区分,分别为 Options 、 Card 、 Question ,其中 Options 是撑满整个 View 的。(参看 Fantasy 模式下的表现)
可以注意一下这里还有一个背景占位符
,它是一个垫在最底层撑满整个屏幕的透明的 View ,用来给其他 subview 做尺寸上的参考,比如 Card
部分的高度所做的约束通过背景占位符
高度的一半来限定。
另外分享一个小的 Tip ,在 xib 中可以直接通过右侧的选项实时查看不同设备下界面的布局:
再有就是 Tutorial 的这个界面,细心的用户也会发现也是全适配的。我们的游戏引导界面并没有单纯地使用图片,而是在原有的游戏界面上加上了对应的引导元素,不管是箭头还是文字都是使用 AutoLayout 拉的约束。
为求最简,我们的分享使用的系统原生的 UIActivityViewController
来实现。不过这里在适配的时候遇到了个坑,直接上代码:
- (IBAction )onShareButtonClicked:(id )sender {
[MobClick event:@"Share"];
CMScoreView *scoreView = [[CMScoreView alloc] initWithScore:self.score];
UIImage *imageToShare = [UIImage captureImageFromView:scoreView];
NSString *stringToShare = [NSString stringWithFormat:@"I scored %ld in the %@ mode, play #Co!orMix with me: %@", (long )self.score, self.gameMode == classicMode ? @"classic" : @"fantasy" , kAppStoreUrl ];
NSArray *activityItems = [[NSArray alloc] initWithObjects:imageToShare,stringToShare, nil];
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
activityVC.excludedActivityTypes = @[UIActivityTypeSaveToCameraRoll];
if (IS_IPAD ) {
UIPopoverController *popup = [[UIPopoverController alloc] initWithContentViewController:activityVC];
self.shareController = popup;
popup.delegate = self;
[popup presentPopoverFromRect:CGRectMake (self.view.frame.size.width / 2, self.shareBtn.frame.size.height + self.shareBtn.frame.origin.y , 0, 0 )inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
} else {
[self presentViewController:activityVC animated:YES completion:nil];
}
}
对于 UIActivityViewController
,在 iPhone
上,我们可以直接使用 present
的方式;但是在 iPad
上,这会引起崩溃,可以使用 UIPopoverController
来包装 UIActivityViewController
。
个人还是很喜欢这个效果的,使用的是 iOS8 自带的毛玻璃效果。上代码:
- (IBAction )onSettingButtonClicked:(id )sender {
[MobClick event:@"Setting"];
self.settingViewController = [[CMSettingViewController alloc] initWithNibName:NSStringFromClass ([CMSettingViewController class]) bundle:nil];
self.settingViewController.view.frame = self.view.bounds;
self.settingViewController.view.alpha = 0;
[self.view addSubview:self.blurView];
[self.view addSubview:self.settingViewController.view];
[self addChildViewController:self.settingViewController];
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_blurView.alpha = 1;
self.settingViewController.view.alpha = 1;
} completion:nil];
}
- (UIView *)blurView {
if (!_blurView ) {
_blurView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
_blurView.frame = [[UIScreen mainScreen] bounds];
_blurView.alpha = 0;
}
return _blurView;
}
这两块都是 @Cee 负责弄的。友盟的接入相对简单,不过 GameCenter @Cee 弄了一晚上,还吐槽网上没有成熟的 GameCenter 接入指南,我觉得有必要让他专门写一份了。
项目虽小,可以用任何方式实现。不过这样一个小项目可以把面向对象思想很好地加以实践。所有的类都实现了对应的职能,隐藏了内部细节。
以前写过不少项目,不过都比较庞大,而且基本都是负面教材。 Co!orMix 是一个小而精的 App ,从萌生想法,到 Demo ,到设计出炉正式完工,整个周期一共就一星期:设计花了一整天做出了可供交互的原型;在代码上只花了两天,但是却不仓促。代码均在 GitHub 上托管,测试上使用 TestFlight 完成分发,通过种子用户的体验修改游戏参数,提高用户体验。同时兼顾各种极端操作,保证代码安全性,做到零错误率。
简单来说,对于新手来说,大家可以先下一下这个 App ,感兴趣的可以自己实现一遍,一点都不困难!然后再对比一下我们的实现。如果 Co!orMix 有任何不合理的地方,也欢迎指正!
最后欢迎大家体验!
Website: http://colormix.cee.moe
Twitter: @ColorMix_Game
二维码下载:
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.