在Cocos2d中实现能够惯性拖动的选择界面
蘋果的應(yīng)用講究用戶體驗
有的時候仔細想想
的確,很多細節(jié)決定了用戶體驗
比如說慣性拖動
可以說之前沒有任何一家廠商能把觸摸慣性拖動做的像蘋果的UI那么流暢
?
Cocos2D中實現(xiàn)能夠慣性拖動的選擇界面
完成的效果:
制作一個簡單的圖層,通過傳入許多的節(jié)點,圖層自動將節(jié)點排版,并能夠通過物理拖拽來選擇其中的某一個節(jié)點,并通知節(jié)點的代理來處理
?
首先新建一個cocos2d項目,我用的版本是2.0,命名為SimplePhysicsDragSelectorTest
新建一個objective-c class,我這里命名為SZSimplePhysicsDragSelector
在SimplePhysicsDragSelector.h文件里添加以下代碼:
?
#import "cocos2d.h"@class SZSimplePhysicsDragSelector; @protocol SZSimplePhysicsDragSelectorDelegate <NSObject> @optional // call when the selected icon changes -(void)onSelectedIconChanged:(SZSimplePhysicsDragSelector*)selector; @end@interface SZSimplePhysicsDragSelector : CCLayer {CCNode *s_content;//所有節(jié)點圖標的父節(jié)點NSMutableArray *s_icons;//節(jié)點圖標清單CCNode *s_selectedIcon;//選定的節(jié)點 BOOL isDragging;//是否在拖拽狀態(tài)CGPoint lastTouchPoint;//上一個觸摸點float lastx;//上一個圖層內(nèi)容x坐標float xvel;//內(nèi)容在x軸上的速度int maxX;//內(nèi)容可以自然移動到的最大極限x坐標int minX;//內(nèi)容可以自然移動到的最小極限x坐標float acceleration;//加速度float f;//合外力id<SZSimplePhysicsDragSelectorDelegate> s_delegate;//代理 }@property (nonatomic, readonly) NSMutableArray *Icons; @property (nonatomic, readonly) CCNode *SelectedIcon; @property (nonatomic, assign) id<SZSimplePhysicsDragSelectorDelegate> Delegate;- (id)initWithIcons:(NSArray*)icons;@end?
?
?
這里聲明了SZSimplePhysicsDragSelector需要使用到的變量和方法,同時聲明了SZSimplePhysicsDragSelector代理的方法
變量的作用如注釋里描述的,后面將會詳細說到
解釋下代理方法:
-(void)onSelectedIconChanged:(SZSimplePhysicsDragSelector*)selector;
?在圖層選擇的節(jié)點發(fā)生改變時將會發(fā)送此消息給代理,如果改變?yōu)闆]有選擇節(jié)點也會發(fā)送此消息
?
初始化
在SZSimplePhysicsDragSelector.m文件中添加以下代碼:
@implementation SZSimplePhysicsDragSelector@synthesize Delegate = s_delegate; @synthesize Icons = s_icons; @synthesize SelectedIcon = s_selectedIcon;- (id)initWithIcons:(NSArray *)icons {self = [super init];if (self) {s_icons = [[NSMutableArray alloc] initWithArray:icons];s_content = nil;s_selectedIcon = nil;isDragging = NO;lastTouchPoint = CGPointZero;lastx = 0.0f;xvel = 0.0f;minX = 0;maxX = 0;acceleration = 0.0f;f = 0.0f;self.isTouchEnabled = true;// 啟用接收觸摸事件 s_delegate = nil;}return self; }- (void)dealloc {[s_icons release];[super dealloc]; }#pragma mark Override methods-(void) onEnter {[super onEnter];s_content = [[CCSprite alloc]init];[self addChild:s_content];[self scheduleUpdate];//開啟計時器 }-(void) onExit {[self unscheduleUpdate];//關(guān)閉計時器 [self removeChild:s_content cleanup:YES];[s_content release];s_content = nil;s_selectedIcon = nil;[super onExit]; }@end?
以上代碼實現(xiàn)了初始化&內(nèi)存釋放以及onEnter和onExist方法
在選擇器被添加到某一個節(jié)點中時,將會自動創(chuàng)建一個內(nèi)容節(jié)點s_content,用來存放所有的節(jié)點,并一起移動
?
布局節(jié)點
在onEnter方法中布局視圖,并實現(xiàn)layout方法-(void) onEnter
-(void) onEnter {[super onEnter];s_content = [[CCSprite alloc]init];[self addChild:s_content];[self layout];[self scheduleUpdate]; }-(void) layout {int i = 1;for (CCNode *icon in s_icons) {CGPoint position = ccp((i-1) * 180, 0);float distance = fabsf(icon.position.x)/100;icon.position = position;if (![s_content.children containsObject:icon]) {[s_content addChild:icon];}i++;}s_selectedIcon = [s_icons lastObject];if ([s_delegate respondsToSelector:@selector(onSelectedIconChanged:)]) {[s_delegate onSelectedIconChanged:self];}minX = - (i-1) * 180 - 100;maxX = 100; }?
解釋下layout方法
將180pt作為每兩個節(jié)點之間的間距,同時第一個節(jié)點在s_content中的位置應(yīng)該是(0,0)所以計算得出位置的公式(間距和初始位置可以根據(jù)需要更改)
position = ccp((i-1) * 180, 0)
之后添加節(jié)點到s_content,并且設(shè)置最后一個為初始選定的節(jié)點,最后通知代理選定節(jié)點發(fā)生更改
關(guān)于極限位置(minX,maxX)是這樣設(shè)定的,前面說到180作為間距,(0,0)為初始節(jié)點位置,所以最后一個節(jié)點的x坐標為(i-1) *?180(i為節(jié)點個數(shù)),當(dāng)需要選擇右邊的節(jié)點時實際上是將s_content的位置向左移動,所以選擇到最后一個節(jié)點時s_content的位置應(yīng)該是-(i-1) *?180,同理第一個選擇到第一個節(jié)點時s_content的位置應(yīng)該是(0,0),此外我希望極限位置能夠比頭尾節(jié)點位置的范圍稍大,所以最終我設(shè)定
minX = - (i-1) * 180 - 100;
maxX = 100;
?
觸摸記錄?
布局完成,接下來我們需要實現(xiàn)觸摸事件消息來記錄數(shù)據(jù)供模擬物理使用
在SZSimplePhysicsDragSelector.m文件中添加以下代碼:
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; {s_selectedIcon = nil;if ([s_delegate respondsToSelector:@selector(onSelectedIconChanged:)]) {[s_delegate onSelectedIconChanged:self];}UITouch *touch = [touches anyObject];CGPoint position = [self convertTouchToNodeSpace:touch];lastTouchPoint = position;isDragging = true; }- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; {UITouch *touch = [touches anyObject];CGPoint position = [self convertTouchToNodeSpace:touch];CGPoint translate = ccpSub(position, lastTouchPoint);translate.y = 0;s_content.position = ccpAdd(s_content.position, translate);lastTouchPoint = position; }- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; {isDragging = false; }- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; {isDragging = false; }這里分開說下4個觸摸事件
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
在方法中我們清空了選擇的節(jié)點并通知代理選擇的節(jié)點改變,標記自身狀態(tài)為拖拽中
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
在方法中根據(jù)此刻觸摸點與上一次觸摸點的位置差,來移動s_content的位置,從而使內(nèi)容跟隨觸摸移動,最后在記錄下此刻的位置為上一次觸摸位置,供下一次計算使用
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
標記自身狀態(tài)為未拖拽
- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
標記自身狀態(tài)為未拖拽
這樣我們已經(jīng)能夠辨別自身是否在拖拽狀態(tài)以及正確拖拽內(nèi)容
?
模擬物理計算
首先說明一下思路:
我們在udpate方法中我們需要檢測圖層的狀態(tài):
若圖層在被拖拽狀態(tài),則不需要模擬物理,只需要計算出用戶觸摸拖拽內(nèi)容在x軸上的速度
若圖層在未拖拽狀態(tài),則根據(jù)已經(jīng)記錄下的x軸移動速度,和通過受力計算出的加速度,改變x軸移動速度,最后在根據(jù)計算出的移動速度來計算實際位移
?
在SZSimplePhysicsDragSelector.m文件中添加以下代碼:
-(void) update:(ccTime)dt; {[self updateMove:dt]; }- (void) updateMove:(ccTime)dt {if ( !isDragging ){// *** CHANGE BEHAVIOR HERE *** // float F1 = 0.0f;float F2 = 0.0f;float F3 = 0.0f;CGPoint pos = s_content.position;//F1// frictionF1 = - xvel * 0.1;//F2// prevent icons out of rangeif ( pos.x < minX ){F2 = (minX - pos.x);}else if ( pos.x > maxX ){F2 = (maxX - pos.x);}//F3// suck planetif (fabsf(xvel) < 100 && !s_selectedIcon) {CCNode *nearestIcon = nil;for (CCNode *icon in s_icons) {if (nearestIcon) {CGPoint pt1 = [icon.parent convertToWorldSpace:icon.position];float distance1 = fabsf(pt1.x - [CCDirector sharedDirector].winSize.width/2);CGPoint pt2 = [nearestIcon.parent convertToWorldSpace:nearestIcon.position];float distance2 = fabsf(pt2.x - [CCDirector sharedDirector].winSize.width/2);if (distance1 < distance2) {nearestIcon = icon;}}else {nearestIcon = icon;}}if (nearestIcon) {s_selectedIcon = nearestIcon;if ([s_delegate respondsToSelector:@selector(onSelectedIconChanged:)]) {[s_delegate onSelectedIconChanged:self];}}}if (s_selectedIcon) {CGPoint pt = [s_selectedIcon.parent convertToWorldSpace:s_selectedIcon.position];;float distance = pt.x - [CCDirector sharedDirector].winSize.width/2;F3 = - distance;}//CALCULATEf = F1 + F2 + F3;acceleration = f/1;xvel += acceleration;pos.x += xvel*dt;s_content.position = pos;}else{xvel = ( s_content.position.x - lastx ) / dt;lastx = s_content.position.x;} }在onEnter方法中,我們已經(jīng)啟用了計時器,所以udpate方法將會在每個最小時間間隔被調(diào)用?
其他就如同剛才整理的那樣,沒什么問題,主要使這個受力問題,這個受力是我經(jīng)過了好多數(shù)值的嘗試后,得出的比較能符合要求的效果
內(nèi)容受到的力分為
F1阻力:方向與內(nèi)容移動速度方向相反,大小與移動速度快慢呈正比
F1 = - xvel * 0.1;
F2超出邊界的額外受力:方向與超出邊界的方向相反,大小與超出邊界的距離呈正比
F2 = (minX - pos.x);或者F2 = (maxX - pos.x);
F3將選定節(jié)點吸至屏幕中央的吸力:方向從選定節(jié)點指向屏幕中央,大小與選定節(jié)點到屏幕中央的距離呈正比:
F3 = - distance;
此外有個細節(jié),如果我們不斷的施加吸力,會出現(xiàn)一種情況:很難將選定的節(jié)點拖拽出去,因為吸力太大了,所以在代碼中添加了一個條件
fabsf(xvel) < 100,當(dāng)移動速度小于100時,才產(chǎn)生吸力,這樣你會發(fā)現(xiàn)拖拽順暢多了,并且也能夠在選定了節(jié)點后短時間內(nèi)變?yōu)殪o止
?
還有什么?
最后在添加一個隨著移動而變化節(jié)點大小的效果,讓拖拽看起來更加舒服
在原有代碼內(nèi)添加以下內(nèi)容:
-(void) layout {int i = 1;for (CCNode *icon in s_icons) {CGPoint position = ccp((i-1) * 180, 0);float distance = fabsf(icon.position.x)/100;float scale = 1/(1+distance);icon.position = position;icon.scale = scale;//初始化縮放比例if (![s_content.children containsObject:icon]) {[s_content addChild:icon];}i++;}s_selectedIcon = [s_icons lastObject];if ([s_delegate respondsToSelector:@selector(onSelectedIconChanged:)]) {[s_delegate onSelectedIconChanged:self];}minX = - (i-1) * 180 - 100;maxX = 100; }-(void) update:(ccTime)dt; {[self updateMove:dt]; [self updateScale:dt];//更新縮放比例}-(void) updateScale:(ccTime)dt; {for (CCNode *icon in s_icons) {CGPoint pt = [self convertToNodeSpace:[icon.parent convertToWorldSpace:icon.position]];float distance = fabsf(pt.x)/100;icon.scale = 1/(1+distance);} }
?
測試
好了,代碼完成了,接下來測試一下效果
把HelloWorldLayer的初始化方法替換為以下代碼:
// create and initialize a LabelCCLabelTTF *label = [CCLabelTTF labelWithString:@"Sawyer's Test" fontName:@"Marker Felt" fontSize:64];// ask director for the window sizeCGSize size = [[CCDirector sharedDirector] winSize];// position the label on the center of the screenlabel.position = ccp( size.width /2 , size.height/2 );// add the label as a child to this Layer [self addChild: label];// add the test selector to the layerNSMutableArray *icons = [NSMutableArray array];int i = 10;while (i) {[icons addObject:[CCSprite spriteWithFile:@"Icon@2x.png"]];i--;}SZSimplePhysicsDragSelector *selector = [[[SZSimplePhysicsDragSelector alloc] initWithIcons:icons] autorelease];selector.position = self.anchorPointInPoints;selector.Delegate = self;[self addChild:selector];
運行ios模擬器,你應(yīng)該看到以下效果:
?
還算滿意,希望大家能夠用到各位的游戲中
?
測試代碼
測試代碼可以在以下鏈接下載
SimplePhysicsDragSelectorTest.zip
?
轉(zhuǎn)載于:https://www.cnblogs.com/sawyerzhu/archive/2012/08/13/2636275.html
總結(jié)
以上是生活随笔為你收集整理的在Cocos2d中实现能够惯性拖动的选择界面的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: arm开发tq2440上的c++裸奔程序
- 下一篇: IIS相关问题及解决方案