作者简介:何金源,腾讯Android手Q开发工程师,2011年本科毕业,负责Android手Q无障碍优化工作,对Android无障碍系统原理及开发技术有深入了解。
手机应用无障碍化逐渐受到重视,这项技术为盲人或者视力有障碍的人士带来了很大便利。那么对于,同样也应该进行无障碍化,本文将以盲人猜地鼠游戏为例,讲解如何对进行无障碍化设计,如何让原本无法操作变成可在无障碍模式下正常使用,最后总结无障碍化的大体思路。
前言
目前市场上针对盲人进行无障碍化的几乎没有,这对于障碍用户来讲,是一大遗憾。实际上,他们对游戏的渴望跟对应用无障碍化的渴望一样强烈,他们也希望能在手机上体验一把游戏的乐趣。下面,我们来探讨无障碍化。
由于是首次针对进行无障碍化,所以这里挑选了一款较为简单的游戏——猜地鼠。猜地鼠是基于MasterMind游戏的玩法(如图1)。原本玩法是两个人对玩,其中一人是出谜者,摆好不同颜色的球的位置;另一个人是猜谜者,每个回合猜各个位置应该放什么颜色的球。每回合结束后会有结果提示。 图1 MasterMind游戏
而猜地鼠则是会有不同颜色的地鼠,用户每个回合要猜地鼠的颜色排列顺序。这是使用Cocos2d-x引擎开发的,引擎并没对无障碍模式做出优化,所以游戏开发完成后只有一个大焦点在界面上,当用户点击这个焦点时,没法进行下一步操作,怎么点也进不了游戏界面。如图2所示。 图2 优化前的游戏界面
构造无障碍虚拟节点
Cocos2d-x引擎是支持跨平台的,所以我们可以针对不同平台区分处理无障碍。首先看下Android平台,在游戏中,Cocos2d-x是把游戏里的界面、图片和文字等素材画到GLSurfaceView上,而GLSurfaceView是添加到Cocos2dxActivity的布局中。Cocos2dxActivity是Cocos2d-x引擎在Android平台上最主要的Activity,我们要对游戏进行无障碍化,就需要让这个Activity支持无障碍。
在Android平台,可以对自定义View进行无障碍化支持。具体的原理可以参考官网上的介绍,或者《程序员》8月发布的《Android无障碍宝典》。所以我们第一步,就是为Cocos2dxActivity增加一个自定义View。
- public class MasterMind extends Cocos2dxActivity{
-
- private AccessibilityGameView mGameView;
-
- protected void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
-
- Rect rectangle = new Rect();
- getWindow().getDecorView().getWindowVisibleDisplayFrame(rectangle);
- AccessibilityHelper.setScreen(rectangle.width(), rectangle.height());
-
- @SuppressWarnings(deprecation)
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
- getWindowManager().getDefaultDisplay().getWidth(),
- getWindowManager().getDefaultDisplay().getHeight());
- mGameView = new AccessibilityGameView(this);
- addContentView(mGameView, params);
-
- AccessibilityHelper.setGameView(mGameView);
-
- ViewCompat.setImportantForAccessibility(mGLSurfaceView, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
- }
-
- static {
- System.loadLibrary(game);
- }
- }
菜单场景 图4 游戏场景
图5 结束场景
图6 帮助场景
接着来看下如何对场景无障碍化,举个例子,对菜单场景进行无障碍化。首先,需要去掉原来的大焦点;接着,为开始游戏按钮和怎么玩按钮提供无障碍焦点和无障碍信息,也就是说,要构造两个无障碍节点。对AccessibilityGameView添加两个无障碍虚拟节点。这样,用户就能操作到场景中这两个按钮。
进入到游戏代码中,两个按钮是CCMenuItemLabel控件,通过调用rect()方法,可以获得两个按钮在屏幕中的大小。
- m_pItemMenu = CCMenu::create();
- for (int i = 0; i < menuCount; ++i) {
- CCLabelTTF* label;
- label = textAddOutline(menuNames[i].c_str(),
- fonts/akaDylan Plain.ttf, 30, ccWHITE, 1);
- CCMenuItemLabel* pMenuItem = CCMenuItemLabel::create(label, this,
- menu_selector(HelloWorld::menuCallback));
- m_pItemMenu->addChild(pMenuItem, i + 10000);
- pMenuItem->setPosition(
- ccp( VisibleRect::center().x, (VisibleRect::bottom().y + (menuCount - i) * LINE_SPACE) ));
- CCRect rect = pMenuItem->rect();
- const char * str = GameConstants::getMenuNodeDesc(i);
- AccessibilityWrapper::getInstance()->addMenuSceneRect(i, str, rect.getMinX(),rect.getMaxX(),rect.getMinY(),rect.getMaxY());
- }
- AccessibilityWrapper::getInstance()->addMenuSceneRect(i, str, rect.getMinX(),rect.getMaxX(),rect.getMinY(),rect.getMaxY());
- const char * str = GameConstants::getMenuNodeDesc(i);
- CCRect rect = pMenuItem->rect();
- ccp( VisibleRect::center().x, (VisibleRect::bottom().y + (menuCount - i) * LINE_SPACE) ));
- pMenuItem->setPosition(
- m_pItemMenu->addChild(pMenuItem, i + 10000);
- menu_selector(HelloWorld::menuCallback));
- CCMenuItemLabel* pMenuItem = CCMenuItemLabel::create(label, this,
- fonts/akaDylan Plain.ttf, 30, ccWHITE, 1);
- label = textAddOutline(menuNames[i].c_str(),
- CCLabelTTF* label;
- for (int i = 0; i < menuCount; ++i) {
图7 引擎无障碍化通讯流程
然而,在游戏世界中的坐标系与在手机中坐标系有些区别,如图8所示,在游戏中坐标原点是在左下角,而在手机屏幕中坐标原点在左上角。所以按钮的边框R1要转换成边框R2,需要对左上角坐标点和右下角坐标点进行变换。 图8 坐标转换图
比如左上角的点,在游戏中是(x1, y1),y1的值是红线段的长度。在右边,左上角的(x2,y2),y2的值是蓝色线段的长度。x2的值通过图8中的公式求出,其中SW是手机屏幕的宽度,而游戏场景设定屏幕大小为480 * 720。
要求y2的值,先算出RH的值,RH是游戏场景在手机屏幕中的高度。由于游戏是宽度适应,手机上下会有黑边,RH是手机屏幕高度SH减去黑边BH,然后就可以根据公式求出y2的值。在代码中转换方法如下:
- private static int getScreenX(int x){
- int ret = x * sWidth / sGameWith;
- return ret;
- }
-
- private static int getScreenY(int y){
- // 针对不同的拉伸方式要有不同的转换,这里是kResolutionShowAll
- int realHeight = sGameHeight * sWidth / sGameWith;
- int blackHeight = (sHeight - realHeight) / 2;
- int y1 = sGameHeight - y;
- int ret = y1 * realHeight / sGameHeight + blackHeight;
- return ret;
- }
- return ret;
- int ret = y1 * realHeight / sGameHeight + blackHeight;
- int y1 = sGameHeight - y;
- int blackHeight = (sHeight - realHeight) / 2;
- int realHeight = sGameHeight * sWidth / sGameWith;
- // 针对不同的拉伸方式要有不同的转换,这里是kResolutionShowAll
-
- }
- return ret;
- int ret = x * sWidth / sGameWith;
复制代码
经过坐标转换后,屏幕上的无障碍焦点就能完美的盖在菜单按钮上。当无障碍用户双击操作则会触发菜单按钮的点击操作。当用户触摸到菜单按钮位置时,则菜单按钮获取无障碍焦点。
总结
从猜地鼠游戏无障碍化看出,要实现无障碍化并非难事。首先要为游戏界面添加自定义View,并且对这个自定义View进行无障碍化,使得它具有构造出虚拟无障碍节点的能力。然后,就要在游戏代码中计算出要展示给用户的无障碍节点的边框大小,经过坐标的转换,计算出在手机屏幕中展示的边框大小。将边框大小与描述信息,通过JNI层,通知到界面。以此类推,在各个场景中都进行无障碍焦点边框的处理,并且在切换场景时,把上个场景的无障碍焦点都清除掉。每个场景中,随着游戏操作变化,界面上的元素也可能发生变化,这时只要动态的维护场景的无障碍焦点,则用户就能感知游戏的变化。
当然,这种实现方式,目前仅支持变化不多、比较简单的游戏(比如像猜地鼠这样的智力游戏),对于其他类型的游戏,可能需要更多的无障碍化实现,希望这个例子能给予游戏开发者解决灵感,为更多的游戏实现无障碍化。
via:《程序员》
锐亚教育,游戏开发论坛|游戏制作人|游戏策划|游戏开发|独立游戏|游戏产业|游戏研发|游戏运营| unity|unity3d|unity3d官网|unity3d 教程|金融帝国3|8k8k8k|mcafee8.5i|游戏蛮牛|蛮牛 unity|蛮牛
- 还没有人评论,欢迎说说您的想法!