手把手教你如何實現iOS消消樂小游戲Demo
引言
做消消樂Demo屬于一個意外,本想借助學習iOS游戲開發把CoreAnimation學好,并完成第一個游戲Demo:俄羅斯方塊。卻在這過程中發現了一些實現消消樂的小技巧,于是興起完成了這個小Demo,供大家參考。
當然,這個Demo不是平白無故產生的,筆者也是參考了一些資料,其中就包括斯坦福大學的iOS公開課,這里放上百度云的鏈接(含字幕): Dynamic Animation 。視頻是用swift講的,筆者從視頻中獲取了幫助和靈感,大家英語好的話也可以嘗試學習一下。
本文將會講解如何實現這個消消樂小游戲,相信你一定會有所收獲。
項目地址
歡迎一切fork,issue,pull request來幫助該項目做得更好。
效果演示
如下圖,和大多數消消樂一樣,Demo根據顏色,進行垂直,水平以及兩個斜向的三消。用戶可以上下左右自由交換兩個方塊的位置。
消消樂Demo效果
基本思路
先講解一下基本思路。主要分如下幾個部分:
- 首先,大家可以看到這個消消樂需要一些動畫,以及一些諸如碰撞和重力下落等物理特性的支持。
- 其次,我們需要能夠正確計算出三消,并以美觀的動畫樣式將其消除。
- 接著,我們需要響應用戶的移動方塊的操作,實現方塊位置的調換。
- 最后,我們添加一些美化效果。
物理特性及其對應的動畫
很顯然,物理特性實現的好壞,直接關系到消消樂游戲的體驗。在Demo中筆者使用了 UIDynamicAnimator 和 UIDynamicBehavior 這兩個基于 UIKit 的類來進行管理。
通過 UIDynamicAnimator 來實現各種物理特性發生時的動畫,如下落加速動畫和碰撞反彈動畫。而其中涉及的物理特性則使用 UIDynamicBehavior 。
KMAnimatorManager
動畫管理器: KMAnimatorManager 繼承自 UIDynamicAnimator ,用來管理各種物理特性對應的動畫效果。它會關聯到一個UIView,這個UIView是我們動畫展現的場所,之后所有的物理特性和動畫顯示都在這個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 ,筆者向其中封裝了諸如 重力,碰撞檢測,彈性系數,是否圍繞質心旋轉等特性 。這可能需要你有一些相關物理學方面的基礎。但幸好Apple已經做好了封裝,我們大可以放心地使用它提供的接口。如下:
- (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];
}</code></pre>
我們向 KMCubeBehavior 類中加入了所需的各種物理特性,使得之后基于此生成的每一個小方塊都有這些效果。如下:
_cubeBehavior = [[KMCubeBehavior alloc] init];
[_animator addBehavior:_cubeBehavior];
三消計算及消除動畫
消除的時機
在Demo中,我們以隨機下落不同顏色方塊的形式來累積磚塊,供用戶調換位置來消除。因此,需要在兩個情況下進行三消判斷。一個是在方塊下落動畫結束后,一個是在用戶執行完調換操作。
對于前者我們可以利用 <UIDynamicAnimatorDelegate> 中的接口 - (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator 。每當物理動畫執行完畢,我們都可以進入該方法,在其中執行我們的三消計算。
消除的計算
整個的計算,我們會多次調用 - (NSArray *)checkCrossAt:(KMDropView *)centerView 方法。該方法類似于一個掃描,傳入一個方塊視圖,然后執行四個方向的掃描,發現可以三消的方塊后,將它們進行標記,最后以數組統一返回。供外部程序進行消除。
這其中涉及到如何識別方塊是否屬于同一個類型。雖然Demo是通過顏色區分,但在更多實際場景中,我們可以加載各種圖片,比如各種顏色的糖果點心等。因此對于視圖中所有小方塊,筆者讓它們繼承于自定義的 KMDropView 類,其中封裝了方塊所需的各種屬性,詳細內容我們放到下一小節講。
這里,你需要知道,我們通過任一方塊的 type 屬性來進行識別, type 是一個字符串,其內容會在方塊創建時進行賦值,不同類型的方塊有不同的 type 。用戶看到的僅僅是視圖樣式,背后真正的匹配可以和視圖樣式完全獨立。例如,我們把匹配三消的方塊加入消除數組中:
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 檢測可消除的方塊,不斷地進行消除。 該算法思路比較簡單,可以后續進一步優化。
消除動畫
有了需要消除的方塊,我們就可以執行消除動畫,將它們從視圖中移除。在 - (void)kickAwayDrops:(NSArray *)drops 方法中進行響應的實現。我們將這些視圖移動至視圖的視野外側,然后從父視圖上移除。最后我們的動畫管理器 KMAnimatorManager 的實例 _cubeBehavior 會移除這些方塊。方塊就會以美觀的動畫形式消除。如下:
[UIView animateWithDuration:0.5 animations:^{
for (UIView *drop in drops) {
//設定移除后的位置
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];
}</code></pre>
用戶調換操作
KMPanGestureRecognizer
消消樂需要響應用戶對于方塊調換的操作,筆者在這里首先想到了使用 Gesture 。為了能夠更好地響應用戶操作,并簡化View的代碼,我自己封裝了一個手勢 KMPanGestureRecognizer ,并將其添加到游戲主視圖 _gameView 中。
查看其頭文件,可以看到一些外部需要的屬性和接口。其中比較重要的就是對于手勢的判斷。用戶移動方塊屬于一種 Pan 操作,而不是簡單的 Swipe 。這表明,用戶除了常規的輕掃屏幕,也可以先按住一個方塊,然后再慢慢悠悠地往一個方向滑動。因此,系統原生的 UISwipeGestureRecognizer 可能就不能很好滿足需求了。特自定義一個。
在自定義的手勢中,對于手指滑動的方向,我們需要設定閾值,某些范圍內的滑動我們需要將其標記為無效滑動,即該操作不匹配我們的手勢。通過枚舉 KMPanGestureRecognizerDirection ,筆者定義了一系列方向類型,并通過 direction 這個 @property 供外部讀取。
此外,筆者來提供了一些接口,供特定情況下的使用,如可以在手指按住方塊時進行回調接口,通知外部代碼讓改方塊高亮,以達到更好的顯示效果。
KMDropView
有了調換手勢,我們就可以在手勢提供的幫助下,正確得知移動兩個方塊的時機。上文提到過,我們的方塊的視圖和背后的 type 是分離的。 type 確定了,方塊的類型就確定了,用戶看到的顯示效果可以額外設置,與 type 獨立。所以調換兩個方塊,最根本的是調換它們的 type ,而顯示的視圖效果是可以通過動畫來“偽裝”的。
因此,在自定義的 KMDropView 中。筆者提供了一系列 @property 來正確設置方塊的屬性。對于方塊使用的思路,筆者經過思考,認為如下是比較合理的:對于方塊屬性的設置并不直接體現在方塊的樣式上,方塊通過 state 字段的設置才最終完成樣式的繪制。而這個 state 也是通過枚舉,舉出了方塊所有可能的狀態。因此一個方塊最終顯示的效果,其實是取決于它當前所處的狀態的。例如普通狀態或者高亮狀態。
調換動畫
有了調換的時機和調換所需改變的東西,我們就可以實現最終的調換動畫了。這里筆者使用了一些“偽裝”。筆者并沒有真的移動兩個方塊的位置,而是在底層的模型中簡單地調換 type ,而上層的用戶視圖中,臨時生成兩個方塊,覆蓋在兩個原方塊上方。然后將這兩個臨時方塊進行位移操作,在動畫完成后消除,從而產生方塊調換的假象。為此,我特地在 KMDropView 中加入了一個 深拷貝 (KMDropView
)duplicateFrom:(KMDropView *)originView; 類方法 ,使得臨時生成的方塊能夠和原來的看起來一模一樣。</p>
美化操作
有了上述三步最關鍵的操作,剩下的就是一些美化和代碼整理。例如高亮選中的方塊,把游戲主視圖封裝起來,獨立于ViewConroller等等。
總結
這個消消樂小Demo的編寫,還是涉及到了不少新內容。并且含有很多可以值得優化算法的地方。越往后學習真的越感覺到基礎的重要性,甚至出現了跨學科的需求。希望大家對于編程,能夠靜下心打好基礎,避免急于求成。
希望我的這篇文章能夠給大家帶來幫助,也非常歡迎大家提出寶貴意見,幫助改進這個Demo。感謝您的閱讀,歡迎分享~
來自:http://www.jianshu.com/p/55cc6908d7dd