文章也同時在個人博客 http://kimihe.com/更新
引言
做消消樂Demo屬于一個意外,本想借助學(xué)習(xí)iOS游戲開發(fā)把CoreAnimation學(xué)好煌抒,并完成第一個游戲Demo:俄羅斯方塊。卻在這過程中發(fā)現(xiàn)了一些實現(xiàn)消消樂的小技巧袍榆,于是興起完成了這個小Demo您旁,供大家參考。
當(dāng)然荆永,這個Demo不是平白無故產(chǎn)生的,筆者也是參考了一些資料国章,其中就包括斯坦福大學(xué)的iOS公開課具钥,這里放上百度云的鏈接(含字幕):Dynamic Animation。視頻是用swift講的液兽,筆者從視頻中獲取了幫助和靈感骂删,大家英語好的話也可以嘗試學(xué)習(xí)一下。
本文將會講解如何實現(xiàn)這個消消樂小游戲四啰,相信你一定會有所收獲宁玫。
項目地址
Cubee Game
歡迎一切fork,issue柑晒,pull request來幫助該項目做得更好欧瘪。
效果演示
如下圖,和大多數(shù)消消樂一樣匙赞,Demo根據(jù)顏色恋追,進行垂直,水平以及兩個斜向的三消罚屋。用戶可以上下左右自由交換兩個方塊的位置。
基本思路
先講解一下基本思路嗅绸。主要分如下幾個部分:
- 首先脾猛,大家可以看到這個消消樂需要一些動畫,以及一些諸如碰撞和重力下落等物理特性的支持鱼鸠。
- 其次猛拴,我們需要能夠正確計算出三消羹铅,并以美觀的動畫樣式將其消除。
- 接著愉昆,我們需要響應(yīng)用戶的移動方塊的操作职员,實現(xiàn)方塊位置的調(diào)換。
- 最后跛溉,我們添加一些美化效果焊切。
物理特性及其對應(yīng)的動畫
很顯然,物理特性實現(xiàn)的好壞芳室,直接關(guān)系到消消樂游戲的體驗专肪。在Demo中筆者使用了UIDynamicAnimator和UIDynamicBehavior這兩個基于UIKit的類來進行管理。
通過UIDynamicAnimator來實現(xiàn)各種物理特性發(fā)生時的動畫堪侯,如下落加速動畫和碰撞反彈動畫嚎尤。而其中涉及的物理特性則使用UIDynamicBehavior。
KMAnimatorManager
動畫管理器:KMAnimatorManager繼承自UIDynamicAnimator伍宦,用來管理各種物理特性對應(yīng)的動畫效果芽死。它會關(guān)聯(lián)到一個UIView,這個UIView是我們動畫展現(xiàn)的場所次洼,之后所有的物理特性和動畫顯示都在這個view上進行关贵。如下:
_animator = [[KMAnimatorManager alloc] initWithReferenceView:self];
_animator.delegate = self;
Demo中,我們所有的游戲場景都在KMGameView的實例_gameView中滓玖,上述代碼的self就是_gameView坪哄。而封裝好的_gameView就可以直接添加到任意ViewController了。如下:
_gameView = [[KMGameView alloc] initWithFrame:self.view.frame];
UIImage *background = [UIImage imageNamed:@"background"];
_gameView.contentMode = UIViewContentModeScaleAspectFill;
_gameView.layer.contents = (__bridge id _Nullable)(background.CGImage);
_gameView.delegate = self;
[self.view addSubview:_gameView];
KMCubeBehavior
通過自定義UIDynamicBehavior的子類KMCubeBehavior势篡,筆者向其中封裝了諸如重力翩肌,碰撞檢測,彈性系數(shù)禁悠,是否圍繞質(zhì)心旋轉(zhuǎn)等特性念祭。這可能需要你有一些相關(guān)物理學(xué)方面的基礎(chǔ)。但幸好Apple已經(jīng)做好了封裝碍侦,我們大可以放心地使用它提供的接口粱坤。如下:
- (instancetype)init
{
self = [super init];
[self addChildBehavior:self.gravity];
[self addChildBehavior:self.collider];
[self addChildBehavior:self.animationOptions];
return self;
}
- (void)addItem:(id<UIDynamicItem>)item
{
[self.gravity addItem:item];
[self.collider addItem:item];
[self.animationOptions addItem:item];
}
我們向KMCubeBehavior類中加入了所需的各種物理特性,使得之后基于此生成的每一個小方塊都有這些效果瓷产。如下:
_cubeBehavior = [[KMCubeBehavior alloc] init];
[_animator addBehavior:_cubeBehavior];
三消計算及消除動畫
消除的時機
在Demo中站玄,我們以隨機下落不同顏色方塊的形式來累積磚塊,供用戶調(diào)換位置來消除濒旦。因此株旷,需要在兩個情況下進行三消判斷。一個是在方塊下落動畫結(jié)束后,一個是在用戶執(zhí)行完調(diào)換操作晾剖。
對于前者我們可以利用<UIDynamicAnimatorDelegate>
中的接口- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator
锉矢。每當(dāng)物理動畫執(zhí)行完畢,我們都可以進入該方法齿尽,在其中執(zhí)行我們的三消計算沽损。
消除的計算
整個的計算,我們會多次調(diào)用- (NSArray *)checkCrossAt:(KMDropView *)centerView
方法循头。該方法類似于一個掃描绵估,傳入一個方塊視圖,然后執(zhí)行四個方向的掃描贷岸,發(fā)現(xiàn)可以三消的方塊后壹士,將它們進行標(biāo)記,最后以數(shù)組統(tǒng)一返回偿警。供外部程序進行消除躏救。
這其中涉及到如何識別方塊是否屬于同一個類型。雖然Demo是通過顏色區(qū)分螟蒸,但在更多實際場景中盒使,我們可以加載各種圖片,比如各種顏色的糖果點心等七嫌。因此對于視圖中所有小方塊少办,筆者讓它們繼承于自定義的KMDropView類,其中封裝了方塊所需的各種屬性诵原,詳細(xì)內(nèi)容我們放到下一小節(jié)講英妓。
這里,你需要知道绍赛,我們通過任一方塊的type屬性來進行識別蔓纠,type是一個字符串,其內(nèi)容會在方塊創(chuàng)建時進行賦值吗蚌,不同類型的方塊有不同的type腿倚。用戶看到的僅僅是視圖樣式,背后真正的匹配可以和視圖樣式完全獨立蚯妇。例如敷燎,我們把匹配三消的方塊加入消除數(shù)組中:
NSString *centerColor = centerView.type;
NSString *leftColor = (leftView)?leftView.type : @"#$%^&*";
NSString *rightColor = (rightView)?rightView.type : @"#$%^&*";
...
...
NSMutableArray *totalArr = [NSMutableArray new];
if ([centerColor isEqualToString:leftColor] && [centerColor isEqualToString:rightColor]) {
NSArray *arr = [NSArray arrayWithObjects:leftView, centerView, rightView, nil];
[totalArr addObjectsFromArray:arr];
}
因此,整個的三消計算思路是遍歷所有的方塊箩言,利用- (NSArray *)checkCrossAt:(KMDropView *)centerView
檢測可消除的方塊硬贯,不斷地進行消除。該算法思路比較簡單陨收,可以后續(xù)進一步優(yōu)化饭豹。
消除動畫
有了需要消除的方塊,我們就可以執(zhí)行消除動畫,將它們從視圖中移除墨状。在- (void)kickAwayDrops:(NSArray *)drops
方法中進行響應(yīng)的實現(xiàn)。我們將這些視圖移動至視圖的視野外側(cè)菲饼,然后從父視圖上移除肾砂。最后我們的動畫管理器KMAnimatorManager的實例_cubeBehavior會移除這些方塊。方塊就會以美觀的動畫形式消除宏悦。如下:
[UIView animateWithDuration:0.5 animations:^{
for (UIView *drop in drops) {
//設(shè)定移除后的位置
int x = self.bounds.size.width+DROP_SIZE.width;
int y = - DROP_SIZE.height;
drop.center = CGPointMake(x, y);
}
} completion:^(BOOL finished) {
[drops makeObjectsPerformSelector:@selector(removeFromSuperview)];
}];
for (UIView *drop in drops) {
[_cubeBehavior removeItem:drop];
}
用戶調(diào)換操作
KMPanGestureRecognizer
消消樂需要響應(yīng)用戶對于方塊調(diào)換的操作镐确,筆者在這里首先想到了使用Gesture。為了能夠更好地響應(yīng)用戶操作饼煞,并簡化View的代碼源葫,我自己封裝了一個手勢KMPanGestureRecognizer,并將其添加到游戲主視圖_gameView中砖瞧。
查看其頭文件息堂,可以看到一些外部需要的屬性和接口。其中比較重要的就是對于手勢的判斷块促。用戶移動方塊屬于一種Pan操作荣堰,而不是簡單的Swipe。這表明竭翠,用戶除了常規(guī)的輕掃屏幕振坚,也可以先按住一個方塊,然后再慢慢悠悠地往一個方向滑動斋扰。因此渡八,系統(tǒng)原生的UISwipeGestureRecognizer可能就不能很好滿足需求了。特自定義一個传货。
在自定義的手勢中屎鳍,對于手指滑動的方向,我們需要設(shè)定閾值损离,某些范圍內(nèi)的滑動我們需要將其標(biāo)記為無效滑動哥艇,即該操作不匹配我們的手勢。通過枚舉KMPanGestureRecognizerDirection僻澎,筆者定義了一系列方向類型貌踏,并通過direction這個@property供外部讀取。
此外窟勃,筆者來提供了一些接口祖乳,供特定情況下的使用,如可以在手指按住方塊時進行回調(diào)接口秉氧,通知外部代碼讓改方塊高亮眷昆,以達到更好的顯示效果。
KMDropView
有了調(diào)換手勢,我們就可以在手勢提供的幫助下亚斋,正確得知移動兩個方塊的時機作媚。上文提到過,我們的方塊的視圖和背后的type是分離的帅刊。type確定了纸泡,方塊的類型就確定了,用戶看到的顯示效果可以額外設(shè)置赖瞒,與type獨立女揭。所以調(diào)換兩個方塊,最根本的是調(diào)換它們的type栏饮,而顯示的視圖效果是可以通過動畫來“偽裝”的吧兔。
因此,在自定義的KMDropView中袍嬉。筆者提供了一系列@property來正確設(shè)置方塊的屬性境蔼。對于方塊使用的思路,筆者經(jīng)過思考冬竟,認(rèn)為如下是比較合理的:對于方塊屬性的設(shè)置并不直接體現(xiàn)在方塊的樣式上欧穴,方塊通過state字段的設(shè)置才最終完成樣式的繪制。而這個state也是通過枚舉泵殴,舉出了方塊所有可能的狀態(tài)涮帘。因此一個方塊最終顯示的效果,其實是取決于它當(dāng)前所處的狀態(tài)的笑诅。例如普通狀態(tài)或者高亮狀態(tài)调缨。
調(diào)換動畫
有了調(diào)換的時機和調(diào)換所需改變的東西,我們就可以實現(xiàn)最終的調(diào)換動畫了吆你。這里筆者使用了一些“偽裝”弦叶。筆者并沒有真的移動兩個方塊的位置,而是在底層的模型中簡單地調(diào)換type妇多,而上層的用戶視圖中伤哺,臨時生成兩個方塊,覆蓋在兩個原方塊上方者祖。然后將這兩個臨時方塊進行位移操作立莉,在動畫完成后消除,從而產(chǎn)生方塊調(diào)換的假象七问。為此蜓耻,我特地在KMDropView中加入了一個深拷貝(KMDropView *)duplicateFrom:(KMDropView *)originView;
類方法,使得臨時生成的方塊能夠和原來的看起來一模一樣械巡。
美化操作
有了上述三步最關(guān)鍵的操作刹淌,剩下的就是一些美化和代碼整理饶氏。例如高亮選中的方塊,把游戲主視圖封裝起來有勾,獨立于ViewConroller等等疹启。
總結(jié)
這個消消樂小Demo的編寫,還是涉及到了不少新內(nèi)容蔼卡。并且含有很多可以值得優(yōu)化算法的地方皮仁。越往后學(xué)習(xí)真的越感覺到基礎(chǔ)的重要性,甚至出現(xiàn)了跨學(xué)科的需求菲宴。希望大家對于編程,能夠靜下心打好基礎(chǔ)趋急,避免急于求成。
希望我的這篇文章能夠給大家?guī)韼椭卜浅g迎大家提出寶貴意見苗傅,幫助改進這個Demo滑潘。感謝您的閱讀,歡迎分享~