Menu 类似一个容器,里面可以添加多个按钮,这些按钮的类型可以是文字、图片和Tab切换按钮。分别由不同的类进行处理,但它们有一个共同的基类MenuItem,继承关系如图2-19所示。
图2-19 菜单项继承图
MenuItemLabel类对应的是文字按钮,它有两个子类。MenuItemAtlasFont类通过指定和LabelAtlas 同样的参数来创建按钮;MenuItemFont 只要指定内容就可以创建按钮。加上MenuItemLabel 这种类型的按钮,文字按钮的创建方式一共有 3 种,它们的使用方式如程序片段2-34所示。
程序片段2-34 文字按钮的使用
函数 create 的 CC_CALLBACK_1 指定的参数是当玩家单击该按钮后的回调函数,在这个函数里面实现单击按钮后的操作。MenuItemFont 和 MenuItemAtlasFont 类由于继承自MenuItemLabel,所以在创建按钮时,只要传递按钮的文字即可,类里面已经封装了使用这些文字来创建Label的功能。
游戏中用得最多的还是图片按钮,这种类型的按钮可以通过两种方式创建,分别对应类MenuItemSprite和MenuItemImage。前者通过指定按钮正常、单击和禁用3种状态下的Node来定义,后者由于继承前者,所以它在创建时只要指定在按钮 3 个状态下对于图片的存储位置即可。具休的使用方式如程序片段2-35所示。
程序片段2-35 图片按钮的使用
MenuItemSprite和MenuItemImage类型的按钮在创建时,它们的区别是所传递的参数不一样,其他的操作都类似。
MenuItemToggle类创建的菜单可以配合LayerMultiplex类使用,它有多个按钮,但一次只能有一个按钮处于激活状态。它的创建必须依靠前面介绍的 MenuItem,该类中有一个方法createWithCallback,它用于创建这种类型的按钮,具体使用如程序片段2-36所示。
程序片段2-36 MenuItemToggle类的创建函数
代码中创建了两个MenuItemToggle菜单项和一个LayerMultiplex对象,它让两个层交替显示。当单击菜单中的任何一个按钮时,需要改变它们的状态和切换层的显示,具体实现如程序片段2-37所示。
程序片段2-37 menuToggleCallback函数
对于菜单项的显示状态,通过它的 setSelectedIndex 函数来控制,而对于 LayerMultiplex的操作,则通过switchTo函数。对于这种类型的菜单项,通常和LayerMultiplex配合使用。在游戏中,有很多这样的控制页面。
如果使用一张图片实现按钮的两种状态,即按钮的正常状态和单击状态。对于按钮的单击状态,则可以通过改变图片的颜色来实现,前面在讲解图片按钮时就把单击状态的图片设置为绿色。也可以把单击状态的图片缩放到原来图片的 0.8 或其他值,但是,如果这样做,当你单击使用这种方式实现的按钮时,会发现图片以左下角为锚点进行缩放,而程序需要的效果是,让图片的锚点在(0.5,0.5)的位置。所以,利用引擎提供的类不能实现这种效果。但是,可以把引擎的图片按钮类稍微改一下,即把它的锚点设置在图片的中间位置。
XJMenu 菜单就是实现这种功能的,它实现的效果不只是单纯的缩放。当单击该按钮时,会有一个动画缩放到指定的比例;当按钮松开时,又会以动画的方式恢复到原来状态。实现这种类型的按钮由两个类组成:XJMenu是菜单类,功能同Menu类;XJMenuItem类是菜单项类,功能同MenuItem类。
对于 XJMenu 类,它负责事件的相应,判断单击是否在某个子菜单项上,它由itemForTouch方法来实现这个功能,具体实现如程序片段2-38所示。
程序片段2-38 itemForTouch函数
该方法查询菜单的所有子项,然后返回单击到的子菜单项或返回 nullptr。当单击到菜单项时,让子菜单项执行缩放动画,这个功能在XJMenuItem中实现。
XJMenuItem 类的菜单项有两种状态,即正常和单击。设置它们图片的对应函数是setNormalImage 和 setDisabledImage,在这两个方法中,都把图片按钮的锚点设置在(0.5,0.5)的位置。菜单项单击的相应事件在 selected 函数中实现,具体实现如程序片段 2-39所示。
程序片段2-39 selected函数
单击按钮的实现方式是先停止按钮之前的动画,然后在0.1s的时间里让按钮缩放到原始大小的0.85倍。当按钮松开时,由函数unselected来实现,如程序片段2-40所示。
程序片段2-40 unselected函数
对于按钮松开,同样先是让它停止之前的所有动画,然后让按钮放大到原来的 1.15倍,最后再缩放到1.0,即初始大小,这样会让按钮有一个反弹的效果。
对于这种类型的按钮,读者可以在光盘中找到相应的代码,如果觉得这种动画效果不满意,也可以改成其他的动画效果。
对于这4种类型的菜单,对应的效果如图2-20所示。
图2-20 菜单效果图
图 2-20 中,图 2-20(a)中的 click me 和 456 按钮是 MenuItemLabel 类型的按钮;图2-20(b)返回和关闭按钮是 MenuItemSprite 类型的按钮;图 2-20(c)中,显示文字为标签的是 MenuItemToggle 类型的按钮,中间的文字(标签 1)是一个 Layer,当切换标签页时会显示不同的 Layer;图 2-20(d)中的精彩活动是 MenuItemSprite 类型的按钮,但它的单击状态图片为缩放到初始的0.8,当单击时,发现会以左下角为锚点。
前3种类型的按钮是引擎提供给开发者的,而对于XJMenu类型的按钮,是作者在编写游戏中的改写的一种按钮类型。读者可以根据自己的项目需求选择使用哪种类型的按钮。
对于按钮的响应事件,还有一个很重要的知识点是作者在项目中发现的,菜单的事件监听是逐点响应的,类型为 EventListenerTouchOneByOne。手机屏幕在检测单击事件时,对于同一个按钮,屏幕有可能检测到两次或多次单击,根据事件类型,则该按钮在一帧内就会执行两次或多次按钮的响应事件。对于网络游戏,如果该按钮是向服务器进行通信的,则一个命令会发送两次,这种情况是不应该出现的。所以,在菜单类 Menu 或 XJMenu 的 onTouch Began 函数内加入一个判断,即在一定时间内,如 30ns,该函数对应的代码只会执行一次。对于Menu类,则要更改引擎提供的Menu类代码,具体的判断实现如程序片段2-41所示。
程序片段2-41 onTouchBegan时间判断
程序片段2-41给出的是引擎2.x版本中实现的代码,对于3.x版本,读者使用相应的函数来实现。代码中的变量 m_startTime 记录的是上一次单击的时间。当两次单击的时间间隔在 30ns 之内时,通过返回 false,onTouchBegan 函数后面的代码和 onTouchEnded 等函数就不会执行了。