博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在Cocos2d中实现能够惯性拖动的选择界面
阅读量:5316 次
发布时间:2019-06-14

本文共 10165 字,大约阅读时间需要 33 分钟。

苹果的应用讲究用户体验

有的时候仔细想想

的确,很多细节决定了用户体验

比如说惯性拖动

可以说之前没有任何一家厂商能把触摸惯性拖动做的像苹果的UI那么流畅

 

Cocos2D中实现能够惯性拖动的选择界面

完成的效果:

制作一个简单的图层,通过传入许多的节点,图层自动将节点排版,并能够通过物理拖拽来选择其中的某一个节点,并通知节点的代理来处理

 

首先新建一个cocos2d项目,我用的版本是2.0,命名为SimplePhysicsDragSelectorTest

新建一个objective-c class,我这里命名为SZSimplePhysicsDragSelector

在SimplePhysicsDragSelector.h文件里添加以下代码:

 

#import "cocos2d.h"@class SZSimplePhysicsDragSelector;@protocol SZSimplePhysicsDragSelectorDelegate 
@optional// call when the selected icon changes-(void)onSelectedIconChanged:(SZSimplePhysicsDragSelector*)selector;@end@interface SZSimplePhysicsDragSelector : CCLayer{ CCNode *s_content;//所有节点图标的父节点 NSMutableArray *s_icons;//节点图标清单 CCNode *s_selectedIcon;//选定的节点 BOOL isDragging;//是否在拖拽状态 CGPoint lastTouchPoint;//上一个触摸点 float lastx;//上一个图层内容x坐标 float xvel;//内容在x轴上的速度 int maxX;//内容可以自然移动到的最大极限x坐标 int minX;//内容可以自然移动到的最小极限x坐标 float acceleration;//加速度 float f;//合外力 id
s_delegate;//代理}@property (nonatomic, readonly) NSMutableArray *Icons;@property (nonatomic, readonly) CCNode *SelectedIcon;@property (nonatomic, assign) id
Delegate;- (id)initWithIcons:(NSArray*)icons;@end

 

 

 

这里声明了SZSimplePhysicsDragSelector需要使用到的变量和方法,同时声明了SZSimplePhysicsDragSelector代理的方法

变量的作用如注释里描述的,后面将会详细说到

解释下代理方法:

-(void)onSelectedIconChanged:(SZSimplePhysicsDragSelector*)selector;

 在图层选择的节点发生改变时将会发送此消息给代理,如果改变为没有选择节点也会发送此消息

 

初始化

在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];//关闭计时器    [self removeChild:s_content cleanup:YES];    [s_content release];    s_content = nil;        s_selectedIcon = nil;        [super onExit];}@end

 

以上代码实现了初始化&内存释放以及onEnter和onExist方法

在选择器被添加到某一个节点中时,将会自动创建一个内容节点s_content,用来存放所有的节点,并一起移动

 

布局节点

在onEnter方法中布局视图,并实现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作为每两个节点之间的间距,同时第一个节点在s_content中的位置应该是(0,0)所以计算得出位置的公式(间距和初始位置可以根据需要更改)

position = ccp((i-1) * 180, 0)

之后添加节点到s_content,并且设置最后一个为初始选定的节点,最后通知代理选定节点发生更改

关于极限位置(minX,maxX)是这样设定的,前面说到180作为间距,(0,0)为初始节点位置,所以最后一个节点的x坐标为(i-1) * 180(i为节点个数),当需要选择右边的节点时实际上是将s_content的位置向左移动,所以选择到最后一个节点时s_content的位置应该是-(i-1) * 180,同理第一个选择到第一个节点时s_content的位置应该是(0,0),此外我希望极限位置能够比头尾节点位置的范围稍大,所以最终我设定

minX = - (i-1) * 180 - 100;

maxX = 100;

 

触摸记录 

布局完成,接下来我们需要实现触摸事件消息来记录数据供模拟物理使用

在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;

在方法中我们清空了选择的节点并通知代理选择的节点改变,标记自身状态为拖拽中

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;

在方法中根据此刻触摸点与上一次触摸点的位置差,来移动s_content的位置,从而使内容跟随触摸移动,最后在记录下此刻的位置为上一次触摸位置,供下一次计算使用

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

标记自身状态为未拖拽

- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

标记自身状态为未拖拽

这样我们已经能够辨别自身是否在拖拽状态以及正确拖拽内容

 

模拟物理计算

首先说明一下思路:

我们在udpate方法中我们需要检测图层的状态:

若图层在被拖拽状态,则不需要模拟物理,只需要计算出用户触摸拖拽内容在x轴上的速度

若图层在未拖拽状态,则根据已经记录下的x轴移动速度,和通过受力计算出的加速度,改变x轴移动速度,最后在根据计算出的移动速度来计算实际位移

 

在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        // friction        F1 = - xvel * 0.1;                //F2        // prevent icons out of range        if ( pos.x < minX )        {            F2 = (minX - pos.x);        }        else if ( pos.x > maxX )        {            F2 = (maxX - pos.x);        }                //F3        // suck planet        if (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;        }                //CALCULATE        f = 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方法中,我们已经启用了计时器,所以udpate方法将会在每个最小时间间隔被调用 

其他就如同刚才整理的那样,没什么问题,主要使这个受力问题,这个受力是我经过了好多数值的尝试后,得出的比较能符合要求的效果

内容受到的力分为

F1阻力:方向与内容移动速度方向相反,大小与移动速度快慢呈正比

F1 = - xvel * 0.1;

F2超出边界的额外受力:方向与超出边界的方向相反,大小与超出边界的距离呈正比

F2 = (minX - pos.x);或者F2 = (maxX - pos.x);

F3将选定节点吸至屏幕中央的吸力:方向从选定节点指向屏幕中央,大小与选定节点到屏幕中央的距离呈正比:

F3 = - distance;

此外有个细节,如果我们不断的施加吸力,会出现一种情况:很难将选定的节点拖拽出去,因为吸力太大了,所以在代码中添加了一个条件

fabsf(xvel) < 100,当移动速度小于100时,才产生吸力,这样你会发现拖拽顺畅多了,并且也能够在选定了节点后短时间内变为静止

 

还有什么?

最后在添加一个随着移动而变化节点大小的效果,让拖拽看起来更加舒服

在原有代码内添加以下内容:

-(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 Label        CCLabelTTF *label = [CCLabelTTF labelWithString:@"Sawyer's Test" fontName:@"Marker Felt" fontSize:64];        // ask director for the window size        CGSize size = [[CCDirector sharedDirector] winSize];            // position the label on the center of the screen        label.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 layer        NSMutableArray *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模拟器,你应该看到以下效果:

 

还算满意,希望大家能够用到各位的游戏中

 

测试代码

测试代码可以在以下链接下载

 

转载于:https://www.cnblogs.com/sawyerzhu/archive/2012/08/13/2636275.html

你可能感兴趣的文章
介绍Win7 win8 上Java环境的配置
查看>>
移动、联通和电信,哪家的宽带好,看完你就知道该怎么选了!
查看>>
Linux设置环境变量的方法
查看>>
Atitit.进程管理常用api
查看>>
构建自己的项目管理方案
查看>>
利用pca分析fmri的生理噪声
查看>>
div水平居中且垂直居中
查看>>
怎么在windows7系统我的电脑中添加快捷方式
查看>>
epoll使用具体解释(精髓)
查看>>
AndroidArchitecture
查看>>
原生JavaScript第六篇
查看>>
安装Endnote X6,但Word插件显示的总是Endnote Web"解决办法
查看>>
python全栈 计算机硬件管理 —— 硬件
查看>>
大数据学习
查看>>
简单工厂模式
查看>>
Delphi7编译的程序自动中Win32.Induc.a病毒的解决办法
查看>>
Objective-C 【关于导入类(@class 和 #import的区别)】
查看>>
倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-点击运行按钮进入到运行状态报错Error starting TwinCAT System怎么办 AdsWarning1823怎么办...
查看>>
【转】javascript 中的很多有用的东西
查看>>
Centos7.2正常启动关闭CDH5.16.1
查看>>