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

苹果的应用讲究用户体验

有的时候仔细想想

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

比如说惯性拖动

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

 

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

完成的效果:

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

 

首先新建一个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;//所有节点图标的父节点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<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;

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

 

初始化

在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// 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方法中,我们已经启用了计时器,所以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 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模拟器,你应该看到以下效果:

 

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

 

测试代码

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

SimplePhysicsDragSelectorTest.zip

 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/296741.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SignalR在React/Go技术栈的实践

哼哧哼哧半年&#xff0c;优化改进了一个运维开发web平台。本文记录SignalR在react/golang 技术栈的生产小实践。01背景有个前后端分离的运维开发web平台&#xff0c; 后端会间隔5分钟同步一次数据&#xff0c;现在需要将最新一次同步的时间推送到web前端。说到[web服务端推送]…

16张扎心漫画,戳中女生私密日常,每一幕都很真实

全世界只有3.14 % 的人关注了爆炸吧知识比利时的插画师Planet Prudence&#xff0c;画了很多女生的真实日常&#xff0c;每一幕都很戳心&#xff0c;一起来看看吧。别人的痘痘一长就是一个&#xff0c;我一长就是一片。买买买的时候爽得要命&#xff0c;要穿的时候总觉得自己没…

Magicodes.IE 2.5.6.2发布

2.5.6.22021.10.13支持自定义列字体颜色&#xff0c;具体见PR#342&#xff0c;感谢xiangxiren修复日期格式化的问题&#xff0c;具体见PR#344&#xff0c;感谢ccccccmd2.5.6.12021.10.06修复 #337&#xff0c;bool?类型导出的映射问题2.5.6.02021.10.05合并Magicodes.EPPlus到…

一个让我很不爽的外包项目——奔驰Smart2015新官网

七月份的下半个月&#xff0c;有幸做了奔驰 Smart 2015年新官网&#xff0c;包括手机端和PC端的宣传页&#xff0c;地址&#xff1a; PC端手机端这里&#xff0c;为了证明这个是一个事实&#xff0c;我还特意的留存了两张截图&#xff1a; 这里只想说明这么几个问题&#xff1a…

为什么饮料瓶大都是圆的,牛奶盒却是方的?原因你想不到

全世界只有3.14 % 的人关注了爆炸吧知识每日看着电视玩着手机再喝着饮料那是相当美好但是喝了这么久的饮料你有没有想过一个问题为什么饮料瓶一般都是圆的&#xff1f;而牛奶盒却是方的&#xff1f;图片来源网络有的人可能会说饮料瓶要拿在手上当然是圆的舒服啊拿着一个方形的饮…

IIS相关问题及解决方案

1、问题&#xff1a;HTTP 错误 500.19 - Internal Server Error无法访问请求的页面,因为该页的相关配 可能原因&#xff1a;在自己使用IIS发布网站之后&#xff0c;自己所使用的当前账户的密码改变了。 解决方法&#xff1a;将用户才密码改回来即可.... 2. 转载于:https://www.…

博客园html源码编辑出错,博客园小技巧

作者&#xff1a;Vamei 出处&#xff1a;http://www.cnblogs.com/vamei 欢迎转载&#xff0c;也请保留这段声明。谢谢&#xff01;在博客园写博的半年中&#xff0c;我有时会纠结于一些诸如写作格式和显示效果之类的小问题。我想任何一个热衷于在这里写博客的人都可能会遇到类似…

使用零代码平台构建应用,应该怎样转变思路?

最近两年&#xff0c;越来越多的各类零代码产品在市场上出现&#xff0c;与此同时&#xff0c;企业的数字化转型的速度也越来越快&#xff0c;零代码产品已然成为了帮助企业数字化转型的利器。技术也在不断地演进&#xff0c;其核心目的就是让开发人员能够更专注于业务逻辑&…

看了这几幅图,感觉自己物理白学了!

全世界只有3.14 % 的人关注了爆炸吧知识看完觉得自己即将成为21世纪最伟大的科学家&#xff01;据说&#xff0c;物理老师看到这几幅图&#xff0c;会说不出话来……不信&#xff0c;转给你们老师看看&#xff0c;嘿嘿嘿……来源&#xff1a;物理知识大全

在Web应用中使用localforage存储离线数据

在现代Web应用中&#xff0c;我们经常会需要在本地存储一些数据&#xff0c;一方面记住用户的一些状态&#xff0c;或个性化设置&#xff0c;尤其是可以缓存一些常用&#xff08;甚至全部&#xff09;的数据&#xff0c;实现更加强大和丰富的本地交互体验。传统上说&#xff0c…

出差一定要给孩子带特产

1 这就是热恋期管不住自己的情侣▼2 热恋期过了...▼3 这该夸你腰细呢&#xff1f;还是脸大呢&#xff1f;▼4 听起来...不是挺好的吗▼5 由于新冠病毒&#xff0c;美国GCW选手都保持着一定距离改用原力来进行比赛▼6 她们太菜了你跟她们好以后肯定疯狂掉分▼7 出差一定…

计算机用户名密码策略,设置域用户帐户密码策略

从安全和易用考虑&#xff0c;普通域用户的帐户策略必须满足一下要求&#xff1a;u 密码长度至少3位u 最长使用期限60天u 密码必须符合复杂性要求u 密码最短使用0天u 帐户锁定阀值7次u 帐户锁定时间30分钟u 复位帐户锁定计数器30分钟任务:u 使用默认域策略设置域用户帐户策略u …

优秀的硕博士们,他们的朋友圈都有什么特点?

全世界只有3.14 % 的人关注了爆炸吧知识很多同学都会有这种感觉&#xff0c;读了硕士博士后&#xff0c;兴趣会突然间发生很大变化&#xff0c;发朋友圈也会不一样了。例如&#xff0c;合格的学术研究者&#xff0c;要快速、全面的获取各种最新文献和学界动态&#xff1b;还要持…

未来教育计算机二级Excel解析,Excel操作小技巧,助你学好计算机二级office!

原标题&#xff1a;Excel操作小技巧&#xff0c;助你学好计算机二级office&#xff01;Office考试中最难的是什么&#xff1f;当然是Excel函数啊&#xff01;小编辛苦整理了excel10大懒人技巧&#xff0c;让你考试速提分&#xff01;还不赶紧收藏起来一、填充合并单元格在工作表…

当你女朋友向你索吻的时候。。

1 当你女朋友向你索吻的时候。。2 可把我厉害坏了&#xff0c;叉会腰&#xff01;3 这脸大的&#xff0c;超想捏4 网友制作的饭团&#xff0c;拍个照都怕打扰他的美梦5 有些话我嘴上不说&#xff0c;但希望你心里有数&#xff01;6 这是一道很神奇的计算题。。你点的每个赞&…

要来吗,不错的WPF技术交流群!(大批干货今日自取)

欢迎加入我的编程技术交流群今日分享&#xff1a;1 《WPF-微软官方文档》2 《WPF客户端应用系统开发实战》全集3 《2021秋招WPF高频面试题-附答案》欢迎加入我的编程技术交流群MVP答疑解惑超多技术干货免费获取优质岗位和技术兼职内推和数百.NET/WPF 开发者一起进阶众多资料今…

用计算机配置打印机IP,网络打印机怎么设置ip(手把手教你设置打印机IP地址)...

如何设置打印机IP地址&#xff0c;是一个长期以来困好多小伙伴的一个问题&#xff0c;那今天我们就以 HP Laserjet Pro 100 MFP M128系列打印机为例&#xff0c;为大家介绍一下打印机IP地址的设置方法~步骤一&#xff1a;打印配置报告查看有效IP地址M128fp&#xff0c;M128fna …

河流为什么是弯曲的?

全世界只有3.14 % 的人关注了爆炸吧知识我们看到的河流&#xff0c;特别是在平原上的河流&#xff0c;总是弯弯曲曲&#xff0c;很少有笔直的河道。这是为什么呢&#xff1f;这得从力学上做一点解释。图1 弯弯曲曲的河流首先我们来关注一个流体力学中的二次流现象。取一口宽底的…

poj--2019 Cornfields 2维RMQ

题目大意&#xff1a; 给定一个 N*N 的数组 求以&#xff08;x1&#xff0c; y1&#xff09; 为左上角 &#xff08;x1 b -1 &#xff0c;y1 b -1)为右下角 这个b*b的范围内最大值减最小值 看到最大值最小值当然想到RMQ啦 View Code //Accepted 27392K 594MS C …

学生渐进片add如何给_渐进镜片的说明与镜架选择

近年来&#xff0c;许多眼镜店都进行了渐进多焦点镜片(下面简称&#xff1a;渐进镜片)的推广与应用&#xff0c;产品也越来越普及&#xff0c;但由于很多眼镜店并没有对从业人员进行系统培训&#xff0c;同时对渐进镜片的了解又甚少&#xff0c;在一些注意事项上经验不足&#…