游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

万字长文,解析基于触屏操作的虚拟控制按钮设定

发布时间:2015-04-07 10:54:25 Tags:,,

作者Mike Jones

我觉得自己可以使用Pushbutton Engine作为基础来测试某些曾经玩过的虚拟控制器,所以编写了这个有关Pushbutton Engine的系列文章。使用这种方法使我无需将大量时间投入 到代码编写中,能够更专注于真正的控制器及其整合方法。我真切地感受到,要让这种控制器整合尽量标准化,这样才能够被运用到任何游戏中,无需考虑代码的影响。

我还做了些许假设,首先我用自己的拇指来决定控制器拇指杆的大小。我的拇指很大,所以我认为这样的大小应该能够满足多数人的需求。第二个假设显得更为技术化。对于船只 或角色的控制而言,多数PC游戏对键盘输入有很强的依赖(游戏邦注:Flash网页游戏尤为突出),所以我试图模拟上、下、左和右4个方向键,而不是使用坐标转换。使用按键输 入模拟取代坐标转换的原因很多。上述是第一个原因。第二是为了转换的便利性,因为多数使用键盘的Flash游戏已经添加了虚拟控制器,这样就无需对现有代码进行大幅更新。最 后,采用这种方法有利于测试和调试。

virtual joystick(from toucharcade.com)

virtual joystick(from toucharcade.com)

设计控制器

首先要考虑的是控制器的视觉外观,本文将使用的是类似于Playstation或XBox控制器上的拇指杆。为了让用户更容易地使用拇指杆进行触碰输入,你需要两个元素。第一个是拇指 区域,也就是用户放置拇指的地方,第二个元素是环绕。使用环绕有两个原因,既可以在视觉上让用户明白拇指杆移动的范围,也可以用次来作为整个控制器的边界,这样你就能 够知道用户拇指的位置,更重要的是能够知道他们的拇指何时离开控制杆。下图便是整个拇指杆控制器(游戏邦注:包括拇指区域和环绕)的样式。

有趣的是,如果你不想要视觉呈现,你完全可以将其移除。有些游戏隐藏了拇指杆的呈现,只向用户标注拇指可放置位置。这种方法可能会让部分玩家感到困惑,所以是否完全移 除视觉元素的决定应当取决于游戏的目标用户类型。如果不确定,可将控制器设置为透明风格。

因为这属于控制器的视觉层面,所以可以使用Flash Professional CS5来制作。你也可以使用Flash Builder来完成所有的工作,但是使用更具视觉化的工具会让初始化更加简单。 创建新FLA文件,命名为VirtualControllers,将其保存在电脑上。接下来,新建ActionScript 3.0类,命名为ThumbStick.as,将其保存在与VirtualControllers.fla相同的目录 下。我使用程序包结构来呈现类,所以文件夹就如下图所示。如果你不喜欢这种方法,也可以不这么做,但我们在本文将使用略显单调的程序包结构。

folder hierarchy(from flashgen.com)

folder hierarchy(from flashgen.com)

在我们开始编写代码钱,你需要在库中为控制器创建元件,导入环绕和拇指杆图片资产(游戏邦注:可以下载作者的设计,也可以自行设计)。

1、创建新元件(插入>新元件)

2、将新元件命名为ThumbStick

3、定位下载的环绕和拇指杆资产,将它们导入到库中(文件>导入>导入到库)

4、Flash Pro CS5会自动在元件库中创建两个新元件(Symbol1和Symbol2)

5、打开元件库(窗口>库)

6、选择Symbol1,根据其图像来重命名(可能是环绕或拇指杆)

7、对Symbol2重复这个步骤

将元件重命名后,你需要对它们进行编辑,确保所包含的图片位于元件的中心位置,同时确保它们被设置为影片剪辑元件而不是图像元件(游戏邦注:系统默认其为图像元件)。

1、右键点击库中的Surround元件

2、从背景菜单中选择属性

3、当属性对话框打开时,将上面的“类型”从图像切换为影片剪辑

4、点击“OK”应用并关闭这个对话窗口

5、对Thumb元件重复这个过程

6、在选中Thumb元件时双击进入编辑模式

7、面板应当更新为图片资产位于近屏幕中间位置

8、选择图片(周围出现蓝色方框)

9、在属性面板(窗口>属性)中将X和Y的值设为-45,这会将Thumb图片放置在中心位置,因为其大小是90 X 90像素

10、对Surround元件重复这个过程,将X和Y的值设为-75,因为该图片大小为150 X 150像素。

现在,你已经确定了拇指杆控制器的主要元素,你需要将它们组合起来。要实现这个目标,首先编辑你创建的名为ThumbStick的元件:

1、双击库中的ThumbStick元件进入编辑模式

2、将时间轴(窗口>时间轴)中的默认层重命名为Thumb。右键点击层,选择背景菜单中的“属性”

3、从打开的对话框中设置新的层名称。你也可以双击层直接进行编辑

4、在时间轴面板中创建新层(点击时间轴左下方的“新建层”图标),命名为Surround

5、确保选中Surround层

6、从库中拖出Surround元件,确保其位于元件的中心

7、选中Surround元件,打开属性面板,为环绕设置成员名称

8、当你对它的位置感到满意后,选择Thumb层

9、选中Thumb层,将Thumb元件拖动到ThumbStick元件上,确保其位于Thumb层上

10、同样确保Thumb元件位于中心位置(注意图片中位于元件中央的交叉瞄准线)

11、与Surround元件做法相同,打开属性面板(确保其处在选中状态下),为Thumb元件设置拇指成员名称

技巧

如果位图资产看起来有明显的锯齿,你可以用平滑来处理,右键点击库中的每个图片,从背景菜单中选择“属性”。打开对话框后,勾选“平滑”选框,点击“OK”应用。

现在,你已经拥有了FLA和ActionScript文件,我们可以开始创建初始化代码。你需要做的首件事情是确保类扩展精灵(游戏邦注:默认情况下它不会扩展)。你还需要用某些私有 变量来存储控制器信息。我们需要使用下列代码(游戏邦注:只需要复制和粘贴下列代码即可):

定义控制器

现在,你已经拥有了设计,你已经创建了控制器元件,你需要开始考虑添加能够控制该元件的代码,这样用户与之发生互动时才会起作用。最佳方法是将这个过程分解成多个部分 的功能列表。列表很可能如下所示:

1、以圆形动作移动拇指杆

2、将拇指杆移动限制在环绕内

3、当用户移开拇指时让拇指杆回到控制器的中心位置

4、当用户移动拇指杆时发送事件

显然,你可以随意在这个列表中添加更多的内容,以上这4个只是基本功能。接下来,我们开始编写代码。

编写代码

你要做的首件事情是确保你的ThumbStick类扩展精灵,默认情况下Flash Professional创建的是非继承类。现在,你的代码应当如下所示:

package
{
import flash.display.Sprite;

public class ThumbStick extends Sprite
{
public function ThumbStick()
{

}
}
}

回头看看上文我罗列的4个功能点,首个功能是以圆形动作移动拇指杆,所以让我们从移动拇指杆开始。这是个相当复杂的过程,我将留到本系列文章的第2部分作具体的阐述。但 是,你要做的是设置控制器,这样当你触碰到拇指杆时,它会跟随你的拇指而移动,释放拇指后又会自行回到环绕的中心位置。

要实现这个目标,你需要添加几个事件监听器。这些监听器会处理用户用拇指触碰和移开拇指的触碰事件。尽管你可以直接与控制器中的元件互动(游戏邦注:因为在上文中已设 置成员名称),但是Flash Professional代码编辑器中的代码较为复杂,所以我将使用元件中的私有变量使过程变得更加简单。

以下代码包含事件监听器及其处理器,你可以看到整个流程和结构(游戏邦注:这段代码中有个问题,作者稍后将进行解释):

package
{
import flash.display.Sprite;
import flash.events.TouchEvent;

public class ThumbStick extends Sprite
{
private var _thumb :Sprite;
private var _surround :Sprite;

public function ThumbStick()
{
_thumb = thumb;
_surround = surround;

_thumb.addEventListener(TouchEvent.TOUCH_BEGIN, onThumbDown);
_thumb.addEventListener(TouchEvent.TOUCH_END, onThumbUp);
_surround.addEventListener(TouchEvent.TOUCH_END, onThumbUp);
}

protected function onThumbDown(e:TouchEvent):void
{
trace(“Thumb Down”);
}

protected function onThumbUp(e:TouchEvent):void
{
trace(“Thumb Up”);
}
}
}

那么,这段代码中的问题是什么呢?与测试有关。你会发现,如果你从Flash Professional内部运行这段代码,你无法对TouchEvents进行测试,因为通常的PC与移动设备并不相同 。如果你手头没有可供测试的实体设备,那么这段代码并不是特别有用。随后,我会强调某些情况下在目标硬件设备上测试游戏和应用程序的重要性。

改进方法是选择将来运行程序的硬件类型,以此为基础使用Capabilities()类调整代码。所以,如果我对上述代码进行更新,我就能够测试其是否能够在基于ARM的处理器(游戏邦 注:多数智能手机和平板电脑使用的处理器)上运行。随后,我也能调整代码初始化的方法。

package
{
import flash.display.Sprite;
import flash.events.TouchEvent;
import flash.system.Capabilities;
import flash.events.MouseEvent;

public class ThumbStick extends Sprite
{
private var _thumb :Sprite;
private var _surround :Sprite;

public function ThumbStick()
{
_thumb = thumb;
_surround = surround;

if(Capabilities.cpuArchitecture == “ARM”)
{
_thumb.addEventListener(TouchEvent.TOUCH_BEGIN, onThumbDown);
_thumb.addEventListener(TouchEvent.TOUCH_END, onThumbUp);
_surround.addEventListener(TouchEvent.TOUCH_END, onThumbUp);
}
else
{
_thumb.addEventListener(MouseEvent.MOUSE_DOWN, onTestThumbDown);
_thumb.addEventListener(MouseEvent.MOUSE_UP, onTestThumbUp);
_surround.addEventListener(MouseEvent.MOUSE_UP, onTestThumbUp);
}
}

protected function onThumbDown(e:TouchEvent):void
{
trace(“Thumb Down”);
}

protected function onThumbUp(e:TouchEvent):void
{
trace(“Thumb Up”);
}

// These are just for testing within Flash Pro
protected function onTestThumbDown(e:MouseEvent):void
{
trace(“Mouse Down”);
}

protected function onTestThumbUp(e:MouseEvent):void
{
trace(“Mouse Up”);
}
}
}

这段代码较为有用,但是我倾向于在使用真正的设备测试之前移除所有的非必要代码。如果你不需要执行,那为何还要添加这些代码呢?让我们继续下去。

你需要执行的下个功能是拇指杆的移动。这很容易实现,我创造了两种方法:一种负责处理当用户触碰拇指杆时的移动,另一种负责当用户移开拇指时让拇指杆回到环绕中心点:

protected function initDrag():void
{
_thumb.startDrag();
}

protected function resetThumb():void
{
_thumb.stopDrag();
_thumb.x = 0;
_thumb.y = 0;
}

要访问这些方法,你需要在事件处理器的initDrag()中添加调用,在用户将拇指放在拇指杆上时作出响应,也就是onThumbDown()和onTestThumbDown()。同样,当用户从控制器上 移开拇指时,系统需要调用resetThumb()方法,所以你需要在onThumbUp()和onTestThumbUp()方法中添加对这项命令的调用。

如果你现在进行测试,它的功能应当同以下范例类似。试着点击和拖动以下拇指杆。释放鼠标左键后,它会回到环绕中心的原位置上。应当注意的是,尽管你可以用鼠标点击和拖 动拇指杆,但其并不受环绕边界的限制。这不是我们想要的结果。不过不用担心,接下来我们就要处理这个问题。

设置控制器边界

正如我之前提到的,现在我们已经实现了虚拟控制器的基本拖动功能,而且在释放鼠标后拇指杆会重新回到原位置。但是,它的移动并没有被限制在控制器环绕内。接下来,我们 要处理的就是这个问题。

首先,你需要做的是添加新事件监听器,在移动拇指杆时对其进行监管,这样当它移动到控制器边界时就会受到限制。现在,通常情况下你可能会使用MouseEvent.MOUSE_MOVE事件 来监管拇指杆的移动。但是,与Event.ENTER_FRAME事件相比,前者在事件分配和使用方面显得相当昂贵。记住这一点,将下列代码添加到initDrag()方法底部:

addEventListener(Event.ENTER_FRAME, onThumbDrag);

同样,当用户停止同控制器互动时,你需要移除这个事件。要实现这个目标,你只需要移除resetThumb()方法中的事件监听器。与initDrag()方法类似,将下列代码添加到 resetThumb()方法前。这样,事件就会被直接移除,其他内务程序就会得到处理:

removeEventListener(Event.ENTER_FRAME, onThumbDrag);

你会注意到,这两行代码都涉及到目前还未执行的方法,也就是onThumbDrag()。这个方法用于处理所有同拇指杆移动边界设置相关的计算。以下是完整的onThumbDrag()方法代码 ,你需要在代码中执行。

protected function onThumbDrag(e:Event):void
{
// Store the current x/y of the knob
var _currentX :Number = _thumb.x;
var _currentY :Number = _thumb.y;

// Store the registration point of the surrounding ‘joystick holder’
var _registrationX :Number = _surround.x;
var _registrationY :Number = _surround.y;

// Subtract the two from each other to get the actual x/y
var _actualX :Number = _currentX – _registrationX;
var _actualY :Number = _currentY – _registrationY;

// Calculate the degrees for use when creating the zones.
_degrees = Math.round(Math.atan2(_actualY, _actualX) * 180/Math.PI);

// Calculate the radian value of the knobs current position
var _angle :Number = _degrees * (Math.PI / 180);

// As we want to lock the orbit of the knob we need to calculate x/y at the maximum distance
var _maxX :Number = Math.round((_radius * Math.cos(_angle)) + _registrationX);
var _maxY :Number = Math.round((_radius * Math.sin(_angle)) + _registrationY);

// Check to make sure that the value is positive or negative
if(_currentX > 0 && _currentX > _maxX || _currentX < 0 && _currentX < _maxX)
_thumb.x = _maxX;

if(_currentY > 0 && _currentY > _maxY || _currentY < 0 && _currentY < _maxY)
_thumb.y = _maxY;
}

在我分析这个方法的各个层面前,你或许会注意到这段代码中提及两个变量域。它们是_degrees和_radius。这两者用以下代码来定义:

private var _degrees :Number;
private var _radius :Number = 25;

_degrees以后再解释,_radius变量处理的是控制器边界内的移动范围。将该变量设置为25的原因在于,拇指杆的大小为50X50像素,一半就是25,环绕的大小为150X150像素,一半 就是75。25加50等于75,很简单的数学。所以,当用户拖动达到最大值时,刚好到达环绕的边界。如果你希望拇指杆移动出边界,只需要增加这个值即可。同样,如果你想要让其 移动范围更小,减小这个值。

数学层面的问题

这些方法从理论上来看确实很棒,但是要如何真正运用到上述代码中呢?让我们逐步解析每个片段的代码,这样你就能够对真正的流程有更为清晰的认识。首先,你有一套本地存 储变量。这些变量涉及到拇指杆和环绕的坐标:

// Store the current x/y of the knob
var _currentX :Number = _thumb.x;
var _currentY :Number = _thumb.y;

// Store the registration point of the surrounding ‘joystick holder’
var _registrationX :Number = _surround.x;
var _registrationY :Number = _surround.y;

接下来,用环绕的坐标减去拇指杆的坐标,得到的就是存储在控制器内的真正X和Y坐标。

// Subtract the two from each other to get the actual x/y
var _actualX :Number = _currentX – _registrationX;
var _actualY :Number = _currentY – _registrationY;

接下来是_degrees变量。正如我先前提到的,随后我会解释更多的细节。现在你需要知道的就是它能够获取_actualX和_actualY,并将它们转换为弧度(游戏邦注:使用 Math.atan()方法),随后再被转化成角度,比如从0到180和从-179到-1这样的值。

_angles变量只是将_degrees值转化回弧度。但是,我希望自己能够简化这段代码,只使用_degrees变量之类的Math.atan()。这样,至少你可以看到角度和弧度之间如何实现转化 。

这个过程已几近结束,但是还要说明几点。你需要通过两个部分来限制拇指杆X和Y坐标。首先,你需要计算拇指杆能够移动的最大距离。随后,第二部分是查看拇指杆的X或Y值是 否比这个值小。如果它们大于这些值,在超出边界时方法会自动将拇指杆的X或Y坐标限定为最大距离。计算这个距离并不是那么简单。这时要用到三角学。通过计算控制器环绕中 的直角三角形两边,你可以很容易地得出斜边的长度。代码如下所示:

// As we want to lock the orbit of the knob we need to calculate x/y at the maximum distance
var _maxX :Number = Math.round((_radius * Math.cos(_angle)) + _registrationX);
var _maxY :Number = Math.round((_radius * Math.sin(_angle)) + _registrationY);

正如我说过的那样,第二部分是检测拇指杆的坐标,如果大于最大坐标会被重置为最大值:

// Check to make sure that the value is positive or negative
if(_currentX > 0 && _currentX > _maxX || _currentX < 0 && _currentX < _maxX)
_thumb.x = _maxX;

if(_currentY > 0 && _currentY > _maxY || _currentY < 0 && _currentY < _maxY)
_thumb.y = _maxY;

完成这些代码后,你现在可以对控制器进行测试。现在,它被限制在环绕内,onThumbDrag()方法实现了这一点,所以不会再出现上面移动出边界的情况。

拇指的位置

或许你已经注意到,如果你的鼠标移出拇指杆元件,当你释放后,拇指杆并没有回到环绕的中心文职。这是因为,如果你意外将拇指移出环绕范围,那么代码中还没有注册事件来 捕捉拇指杆释放的行为。这个问题需要解决,因为你的用户很可能在玩游戏时会将他们的拇指移出拇指杆环绕边界。因为玩家在游戏过程中可能不会盯着控制器,而且控制器是虚 拟的,他们无法感受到已经移出边界的肢体反馈。

要解决这个问题,你只需要添加用户释放拇指杆的状态和反应。对于电脑和网页应用来说,它们通常无需支持多点触摸,所以你每次只能用鼠标同单个元素互动。但是,在移动设 备上,情况并非如此。因为移动设备上可能存在多点触摸的状况,所以会更加麻烦。

较好的做法是创建隐藏在控制器图像后的不可视区域,监测拇指杆的释放。这样,即便用户将拇指移出拇指杆元件也无关紧要,因为这个区域仍然会对释放做出响应。现在,你需 要决定的是这块响应区域的大小。这方面的决定是完全靠主观感觉的,但是如果你能够知道用户会如何拿着设备来使用控制器,那么这算是个不错的开端。

对于新开发者来说,你不需要设置过大的区域,我认为可以限制在不超过屏幕宽度1/3即可(游戏邦注:游戏横向显示)。毕竟,用户需要拿稳设备,所以不会将拇指移动过大的距 离。如果对此存在疑虑,可以进行测试直到获得满意的大小。

在以下例子中,我在Flash Professional中将ThumbStick元件进行更新,添加另一个称为“Boundary”的层次,放置一个新的25 X 25环形元件。我为其设置边界成员名称,并确保 该层次位于ThumbStick元件所有其他层次之下。

很显然,25 X 25像素的大小并没有多大的作用。但是,在你将元件放入ThumbStick()类中的变量后,你可以很容易地设置其尺寸。这样,你可以很容易地添加设置器,使你可以动 态地修改和设置控制器尺寸。在下列代码中,我为这个边界元件的宽度和高度设置了固定数值。

private var _thumb :Sprite;
private var _surround :Sprite;
private var _boundary :Sprite;

private var _degrees :Number;
private var _radius :Number = 25;

public function ThumbStick()
{
_thumb = thumb;
_surround = surround;
_boundary = boundary;
_boundary.width = 200;
_boundary.height = 200;

if(Capabilities.cpuArchitecture == “ARM”)
{
_thumb.addEventListener(TouchEvent.TOUCH_BEGIN, onThumbDown);
_thumb.addEventListener(TouchEvent.TOUCH_END, onThumbUp);
_surround.addEventListener(TouchEvent.TOUCH_END, onThumbUp);
_boundary.addEventListener(TouchEvent.TOUCH_END, onThumbUp);
}
else
{
_thumb.addEventListener(MouseEvent.MOUSE_DOWN, onTestThumbDown);
_thumb.addEventListener(MouseEvent.MOUSE_UP, onTestThumbUp);
_surround.addEventListener(MouseEvent.MOUSE_UP, onTestThumbUp);
_boundary.addEventListener(MouseEvent.MOUSE_UP, onTestThumbUp);
}
}

我将边界元件的alpha值设为1。现在再尝试下,即便你的鼠标移出拇指杆,只要仍然位于边界区域内,释放后拇指杆就会重新回到中心。显然,如果你觉得这个边界区域不够大, 你可以根据自己的需要进行调整,甚至可以改变其形状,用矩形也可以。

如何组合基本设计和功能“创建模块”,让你能够沿着边缘轨迹拖曳控制器的操纵杆,然后在你放开时,将其送回至控制器的中心点。如下图所示:

virtual joystick (from flashgen.com)

virtual joystick (from flashgen.com)

我们设置完“逃跑”轨迹,剩下的就是调遣事件,让“外部世界”知道控制器的具体操作内容。就如我在文章第一部分中提到的,我选择将键盘投射至控制器中,因为多数网页 Flash游戏主要着眼于键盘控制,而非鼠标移动(游戏邦注:有些情况下,这和Flash运行时间没有提供Mouselook支持有关)。

目前这听起来像是个简单过程。毕竟只有4个方向键,所以我在控制器上将其设置成上下左右(Up、Down、Left和Right)。如果我只是想进行4个方向的操纵杆控制(想想《Dig- Dug》或《Mr. Doo》),这完全没有问题。但多数现代平台游戏都至少支持8个方向的移动操作。这带来一定问题,因为这意味着你需要设计按键操作组合——例如,左上或右下。若是采用实际键盘,这完全不费吹灰之力,因为用户可以组合按下这些按键,它们会给予相应回应。我们需要效仿这一模式,这意味着你需要进行更多设置,确定你的虚拟控制器 要如何通过模拟按键操作进行调遣。

映射按键

首先要做的就是,投射4个方向键。这样你就清楚自己是基于操纵杆的位置调遣事件。要完成这一操作,你需要添加简单的内部循环(或是标记),这会更新有关按键输入模拟的信 息。所以如果你要进行的是推向顶部的操作,那么你多半会调遣代表按下“上”按键的事件。

反向控制器

在未来的逐步发展中,你会想要添加的一个设置是,让用户颠倒控制的Y轴(上/下)。这非常适合基于纵向和横向路径飞行的太空飞船游戏。因此和真实飞机一样,按下控制器的 “上”按键会促使太空船向下移动,按压控制器的“下”按键会令其向上移动。

你需要通过另一方式检查和调遣模拟按键操作。所以你需要创建新的dispatchKeyCombo()保护方式,在onThumbDrag()方法的底部将其设置成调用状态。代码内容应如下(注意:出 于简洁,我移除onThumbDrag()方法中的多数代码内容):

protected function onThumbDrag(e:Event):void
{
// Additional code removed for brevity

dispatchKeyCombo();
}

protected function dispatchKeyCombo():void
{

}

现在每次用户移动操纵杆及onThumbDrag()方法被调用时,dispatchKeyCombo()方法也会自动被调用。设置完存根方法后,下面就来着手添加初始逻辑和事件调遣。首先需要进行的 操作是,判断操纵杆是否同边缘存在联系。你可以在onThumbDrag()方法进行调用,这里你有个分层级存储当前操纵杆积极或消极位置的变量_degrees。此前我略过它的具体运用,称后面会进行详述。现在是时候了。你需要层级数值,这样你才能够以当前的层级旋转为基础,将操纵杆的位置投射到方向键。

记住现在你只是添加4个方向的操纵杆支持。要完成这一操作,你需要查看_degrees变量的数值,若这相当于4个方向(上下左右)的必要求值,那么你就可以调遣事件。根据当前 设置,4个操纵杆位置的层级数值分别是:右=0,下=90,左=180,下=-90。把握具体数值后,插入逻辑条件,确定需要调遣的数值则就非常简单。

protected function dispatchKeyCombo():void
{
// Thumb stick position – Right
if(_degrees == 0)
{
trace(“Right”);
}
// Thumb stick position – Down
else if(_degrees == 90)
{
trace(“Down”);
}
// Thumb stick position – Left
else if(_degrees == 180)
{
trace(“Left”);
}
// Thumb stick position – Up
else if(_degrees == -90)
{
trace(“Up”);
}
}

从理论上来说,这就是你需要进行的操作。但若你在调试模式中进行测试,你会发现要在移动操纵杆时达到特地数值非常困难,因为你需要非常准确——这通过鼠标很难完成,想 想如果你要通过自己的拇指,这将会多么棘手。最显而易见的做法就是,通过系列数值判断操纵杆的位置。这样你就创建同上下左右相关的“区域”。下面是通过区域判断操纵杆 位置的更新代码。注意,这里的条件更加复杂,因为我们主要判断操纵杆是否介于数值之间(有时候也需要处理积极和消极数值)。

protected function dispatchKeyCombo():void
{
// Thumb stick position – Right
if(_degrees >= -22 && _degrees <= 22)
{
trace(“Right”);
}
// Thumb stick position – Down
else if (_degrees >= 68 && _degrees <= 112)
{
trace(“Down”);
}
// Thumb stick position – Left
else if((_degrees >= 158 && _degrees <= 180) || (_degrees >= -179 && _degrees <= -158))
{
trace(“Left”);
}
// Thumb stick position – Up
else if(_degrees >= -112 && _degrees <= -68)
{
trace(“Up”);
}
}

如果你在Debug模式中测试这一版本,那么你会看到追踪轨迹更加连贯——显然你可以调节值域,改变操纵杆输出值的准确度。但由于你所瞄准的是触控设备,所以不要设定具体值 域,用户的拇指通常不是那么准确(游戏邦注:特别是当他们处在激烈炮战中的时候)。

现在你已设定基本结构,可以着手下个步骤。将键盘输入值投射到操纵杆位置的对应数值。ActionSCript提供各普通按键数值的常量,这使得执行输入内容变得非常简单。在你将 任何按键输入代码添加至dispatchKeyCombos()方法前,你需要定义4个新变量。这些属于实体变量,而非方法变量,所以不妨将它们置于类顶部的其他实体变量之下。

private var _primaryKeyCode :int;
private var _secondaryKeyCode :int;

private var _previousPrimaryKeyCode :int;
private var _previousSecondaryKeyCode :int = 0;

目前你无需担心这块内容;我会在添加额外功能时解释它们的用途。现在你唯一需要关心的内容是_primaryKeyCode,你需要在此变量中存储当前按键代码。

首先,将各trace()陈述替换成_primaryKeyCode变量的引用。根据自己的检测方向,你可以就相关按键输入常量进行调遣。当这些操作完成后,你只需要调遣_primaryKeyCodeas的 更新数值,这是新KeyboardEvent()的键码属性,就如如下代码块所示:

protected function dispatchKeyCombo():void
{
// Thumb stick position – Right
if(_degrees >= -22 && _degrees <= 22)
{
_primaryKeyCode = Keyboard.RIGHT;
}
// Thumb stick position – Down
else if (_degrees >= 68 && _degrees <= 112)
{
_primaryKeyCode = Keyboard.DOWN;
}
// Thumb stick position – Left
else if((_degrees >= 158 && _degrees <= 180) || (_degrees >= -179 && _degrees <= -158))
{
_primaryKeyCode = Keyboard.LEFT;
}
// Thumb stick position – Up
else if(_degrees >= -112 && _degrees <= -68)
{
_primaryKeyCode = Keyboard.UP;
}

dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _primaryKeyCode));
}

更新的虚拟控制器呈现下图样式。(我插入外部事件处理器,这样你就能够在上方的文本框中看到当前的方向输入内容,我还将红色边界的初始值设置成0,这样它就呈透明状态) 。

 

当前方向 from blog.flashgen.com

当前方向 from blog.flashgen.com

扩展范围

现在你已设定调遣代码的基本结构,下面就来对其进行扩展,这样虚拟控制器代码就能够将控制器变成8个方向的控制器,而不是当前的4个方向模式。鉴于控制器映射的是模拟按 键事件,要在控制中执行附加角度会产生额外要求。

是否还记得我创建的附加变量:_primaryKeyCode和_secondaryKeyCode等?这里你需要用到其中一个,即_secondaryKeyCode。在初始位置中移动操纵杆只需要调遣单个按键事件。 将操纵杆移动到这些离轴角度需要你调遣两个按键事件——例如,若你将操纵杆移至上和右之间的位置,你就需要调遣代表向上箭头键及右箭头键的键控代码。

下面就来看看修改后的代码,同时分析二级事件分配器的添加及针对提供8个方向控制器支持的附加“区域”进行的新检查操作。

protected function dispatchKeyCombo():void
{
_secondaryKeyCode = 0;

// Thumb stick position – Right
if(_degrees >= -22 && _degrees <= 22)
{
_primaryKeyCode = Keyboard.RIGHT;
}
// Thumb stick position – Down
else if (_degrees >= 68 && _degrees <= 112)
{
_primaryKeyCode = Keyboard.DOWN;
}
// Thumb stick position – Left
else if((_degrees >= 158 && _degrees <= 180) || (_degrees >= -179 && _degrees <= -158))
{
_primaryKeyCode = Keyboard.LEFT;
}
// Thumb stick position – Up
else if(_degrees >= -112 && _degrees <= -68)
{
_primaryKeyCode = Keyboard.UP;
}
// Thumb stick position – Up/Left
else if(_degrees >= -157 && _degrees <= -113)
{
_primaryKeyCode = Keyboard.UP;
_secondaryKeyCode = Keyboard.LEFT;
}
// Thumb stick position – Down/Left
else if(_degrees <= 157 && _degrees >= 113)
{
_primaryKeyCode = Keyboard.DOWN;
_secondaryKeyCode = Keyboard.LEFT;
}
// Thumb stick position – Up/Right
else if(_degrees >=-67 && _degrees <= -21)
{
_primaryKeyCode = Keyboard.UP;
_secondaryKeyCode = Keyboard.RIGHT;
}
// Thumb stick position – Down/Right
else if(_degrees >= 23 && _degrees <= 67)
{
_primaryKeyCode = Keyboard.DOWN;
_secondaryKeyCode = Keyboard.RIGHT;
}

if(_secondaryKeyCode > 0)
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _secondaryKeyCode));

dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _primaryKeyCode));
}

你会发现dispatchKeyCombo()方法出现系列变化。最大变化就是我在此代码块前提到的:我添加围绕操纵杆全方位移动的按键输入映射(游戏邦注:而非仅仅围绕其进行上下左右 移动)。其次现在出现对变量_secondaryKeyCode的引用。这是应用存储二级按键输入代码的位置。就如我之前提到的,如果你将操纵杆向右上方移动,那么从按键输入角度来看, 你就需要在上和右按键中调遣KeyboardEvent()。在方法初始阶段,将_secondaryKeyCode变量的默认值设置成0,只有当用户将虚拟控制器移至需要两个键码数值的位置时,数值才 会发生更新。

最后一部分是添加检查操作,以查看_secondaryKeyCode变量值是否大于0,如果大于0,就调遣二级KeyboardEvent()。

if(_secondaryKeyCode > 0)
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _secondaryKeyCode));

只剩下一块内容,整个方法就大功告成。你也许会发现,你调遣的是KeyboardEvent.KEY_DOWN事件,但你如何让对象关注你的控制器,知道你何时改变方向。你需要调遣 KeyboardEvent.KEY_UP事件。这就引出最后两个变量。它们是:_previousPrimaryKeyCode和_previousSecondaryKeyCode。这些用于存储之前调遣的键码。但为避免调遣不必要的 事件,它们只有在初级和次级键码的新数值发生改变时会被调用,像下面这样:

if(_primaryKeyCode != _previousPrimaryKeyCode)
{
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousPrimaryKeyCode));
_previousPrimaryKeyCode = _primaryKeyCode;
}

if(_previousSecondaryKeyCode != _secondaryKeyCode)
{
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousSecondaryKeyCode));
_previousSecondaryKeyCode = _secondaryKeyCode;
}

若用户改变操纵杆的位置,那么相关输入内容的KeyboardEvent.KEY_UP事件就被调遣。由于这一方法包含众多内容,因此我呈现如下完整版本,这样你就能够看到具体操作流程:

protected function dispatchKeyCombo():void
{
_secondaryKeyCode = 0;

// Thumb stick position – Right
if(_degrees >= -22 && _degrees <= 22)
{
_primaryKeyCode = Keyboard.RIGHT;
}
// Thumb stick position – Down
else if (_degrees >= 68 && _degrees <= 112)
{
_primaryKeyCode = Keyboard.DOWN;
}
// Thumb stick position – Left
else if((_degrees >= 158 && _degrees <= 180) || (_degrees >= -179 && _degrees <= -158))
{
_primaryKeyCode = Keyboard.LEFT;
}
// Thumb stick position – Up
else if(_degrees >= -112 && _degrees <= -68)
{
_primaryKeyCode = Keyboard.UP;
}
// Thumb stick position – Up/Left
else if(_degrees >= -157 && _degrees <= -113)
{
_primaryKeyCode = Keyboard.UP;
_secondaryKeyCode = Keyboard.LEFT;
}
// Thumb stick position – Down/Left
else if(_degrees <= 157 && _degrees >= 113)
{
_primaryKeyCode = Keyboard.DOWN;
_secondaryKeyCode = Keyboard.LEFT;
}
// Thumb stick position – Up/Right
else if(_degrees >=-67 && _degrees <= -21)
{
_primaryKeyCode = Keyboard.UP;
_secondaryKeyCode = Keyboard.RIGHT;
}
// Thumb stick position – Down/Right
else if(_degrees >= 23 && _degrees <= 67)
{
_primaryKeyCode = Keyboard.DOWN;
_secondaryKeyCode = Keyboard.RIGHT;
}

if(_primaryKeyCode != _previousPrimaryKeyCode)
{
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousPrimaryKeyCode));
_previousPrimaryKeyCode = _primaryKeyCode;
}

if(_previousSecondaryKeyCode != _secondaryKeyCode)
{
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousSecondaryKeyCode));
_previousSecondaryKeyCode = _secondaryKeyCode;
}

if(_secondaryKeyCode > 0)
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _secondaryKeyCode));

dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, _primaryKeyCode));
}

到目前为止,具体例子都在浏览器或桌面测试过,但我们的目标是让此控制器能够在触控装置上运作。也就是说,如果你现在将此控制器放入设备中,它将无法运作。

为什么?因为你需要告诉游戏,控制器执行多点触控事件,这样它才知道如何进行操作。现在你可以将这些代码放入虚拟控制器中——确保执行多点触控事件,或者你可以将其具 体化,放入实际游戏中。就个人而言,我倾向将此放入控制器的构造函数中,主要因为在编码集中阶段,我常常会忘记植入多点触控代码,然后在发现其无法于设备上运作时再匆 忙进行设置。显然如果有多个控制器,这些操作会有些重复,但这些代码并不多,完成初始化后,再次进行设置不会产生什么额外费用。

所以最后一个步骤就是将这些代码粘贴至你虚拟控制器的构造函数中(游戏邦注:就在你添加事件处理程序的位置上方)中,然后就大功告成。

Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;

值得一提的是,在于屏幕检测点击和放开操作时,你需要利用MultitouchInputMode.TOUCH_POINT事件。若你需要追踪各种输入内容,以执行各种手势,那么你就需要利用 MultitouchInputMode.GESTURE事件。关于控制器,我建议你不要将其纳入其中,否则将产生负面影响。

内容梳理

最后你需要做的是,整理系列松散的字符串。虽然现在如果你将拇指移开控制器,控制器会调遣正确事件,但它们会持续处于调遣状态中。原因很简单,在用户将拇指移开控制器 时,全类型事件不会终止其他所有事件。这个问题很容易解决。只需创建新方法killAllEvents(),在其中植入如下代码:

protected function killAllEvents():void
{
if(_primaryKeyCode)
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousPrimaryKeyCode));

if(_secondaryKeyCode > 0)
dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, _previousSecondaryKeyCode));
}

在resetThumb()方法中将其设置成调用状态,就在你移除事件处理器之后。你的resetThumb()方法应该呈此状态:

protected function resetThumb():void
{
removeEventListener(Event.ENTER_FRAME, onThumbDrag);

killAllEvents();

_thumb.stopDrag();
_thumb.x = 0;
_thumb.y = 0;

就是这样。若你现在进行测试,你会发现,当操纵杆以任何方向移动时,事件就会被调遣,如果你的拇指从控制器上移开,事件就停止传播。

总结

在这部分文章内容里,我们主要谈论如何轻松通过虚拟控制器模拟键盘输入,然后进行调遣,这样任何注意这些事件的外部对象就能够在接收到事件时做出反应。

相关拓展阅读:篇目1篇目2(本文由游戏邦编译,转载请注明来源,或咨询微信zhengjintiao)

virtual controllers for touch based devices (pt.1)

Mike Jones

As I’m working on a series of articles on Pushbutton Engine I thought I’d use that as a basis for testing some virtual controllers I have be playing about with. For one, it saves me for having to create the majority of the scaffold code and concentrate on the actual controllers and how they integrate. One thing I was acutely aware of was keeping the integration of the controller as generic as possible with the idea that it could be used in any game, regardless of
the code that supports it.

I also made a few assumptions, firstly I used my thumbs as the basis for determining the size of the controller’s thumb stick ‘head’ – I have quite big thumbs so I assumed that this would suffice for most people. The second one was more technical. As most PC games tend to rely heavily on keyboard input (and Flash web games even more so) for controlling your ship / player; I opted to emulate the 4 arrow keys – Up, Down, Left and Right instead of using co- ordinate translation. There were a few reasons for key input emulation over co-ordinate translation. The first one I have already outlined. The second was to do with ease of conversion; as most Flash games use the keyboard already adding in the virtual controller shouldn’t require a major update to any pre- existing code. Lastly, there is the testing and debugging.

Designing The Controller

The first thing to consider is the actual visual appearance of your controller – in my case this was going to be a thumb stick like those found on the Playstation or XBox controllers. For ease of mapping the users touch input on the thumb stick you really need two elements. First the thumb area, i.e. the place where the user places their thumb; secondly you need a surround. The surround is useful for two reasons. It provides a visual indication to the user on how far the thumb stick moves as well as allowing you to use this as the boundaries of your entire controller for the purpose of determining where the users thumb actually is and more importantly when they lift their thumb up. You can see a simple design for the entire thumb stick controller (including both the thumb and surround) in the image on the right.

Interestingly you don’t actually need to have any visuals if you don’t want them. Some games forego them and just map the users thumb location. This can be confusing for some so depending on type of users you are expecting to attract to your game may determine whether you can remove the visual elements entirely. If in doubt make the controllers slightly transparent.

As their is a visual aspect to this controller it makes sense to leverage Flash Professional CS5 – you can do this all in Flash Builder if you wish, but it makes the initial set up easier using a more visual tool. Create a new FLA file and save it somewhere on your machine with the file name VirtualControllers. Next create a new ActionScript 3.0 class, call it ThumbStick.as and save it in the same directory as the one that contains VirtualControllers.fla. I use a package structure for my classes so I have nested folders as the image shows. You don’t have to do this if you don’t want to and in this article you’ll be using a flat package structure.

Before we start writing code though you need to create a symbol in the Library for the controller, and import the image assets for both the surround and the thumb stick (which you can download via the link at the bottom of this article, or create your own).

Create a new symbol (Insert > New Symbol…)

Call this new symbol ThumbStick

Locate the downloaded assets for the surround and the thumb stick and import them in to your library (File > Import > Import to library)

Flash Pro CS5 will automatically create two new symbols (Symbol1 and Symbol2) in your Symbol library

Open the Symbol library (Window > Library)

Select Symbol1 and rename it based on the graphic it contains (it’ll either be the surround or the thumb image)

Repeat this process for Symbol2

Once you have renamed the symbols you need to edit them both to make sure that the enclosed image is centred in the middle of the symbol, as well as making sure that they are set as MovieClip symbols not Graphic symbols (which is what they will be by default).

Right click on the Surround symbol in your library

Select Properties from the context menu

When the properties dialog opens change the top Type drop down from Graphic to MovieClip

Click OK to apply and close this dialog window

Repeat this for the Thumb symbol too

With the Thumb symbol still selected double click it to enter edit mode

The stage should update with the image asset placed approximately in the middle of the screen

Select the image (a bounding blue box should appear)

From the Properties panel (Window > Properties) set the X and Y values to -45, this will centre the Thumb image as it’s 90×90 pixels

Repeat this for the Surround symbol, except set the X and Y values to -75 as the Surround image is 150×150. Again this will centre it

Now you have the main elements of your Thumb Stick controller defined you’ll need to combine them. To do this first edit the symbol you created called ThumbStick

Double click on the ThumbStick symbol in your Library to enter edit mode

Rename the default layer in your Timeline (Window > Timeline) to Thumb by right clicking on the layer and selecting Properties from the context menuA dialog will open where you can set the new layer name. You can also just double click the layer to edit it directlyCreate a new layer in the Timeline panel (click the New Layer icon in the bottom left of the Timeline) and name it Surround

Make sure you still have the Surround layer selected

Drag the Surround symbol from your library and make sure it is centred in the middle of your symbol

With the Surround symbol still selected open the Properties panel and give it an instance name of surround

Once you are happy with its position select the Thumb layer

With the Thumb layer selected drag the Thumb symbol on to the stage of the ThumbStick symbol, making sure it’s on the Thumb layer

Again centre the Thumb symbol so it is in the exact centre (note the cross hair in the middle of the symbols in the image)

And as with the Surround symbol, open the Properties panel (making sure it is still selected) and give the Thumb symbol the instance name of thumbQuick Tip

If the bitmap assets are looking a bit ‘jaggy’ you can always apply smoothing to them by right clicking on each image in the library and selecting Properties from the context menu. When the dialog opens just tick the Smoothing checkbox and click OK to apply.

Now you have the FLA and ActionScript files saved we can get about creating the initial code. The first thing you need to do is make sure your class extends Sprite (by default it doesn’t extend anything). You’re also going to need some private variables to store some of the controllers info in. To that end add the following (or just copy and paste the code below):

Defining The Controller

Now that you have a design, and you have created your actual controller symbol, you need to start thinking about adding the code to actually control it when the user interacts with it. The best approach is to break this process down initially as a bulleted list of its functional parts. The list will likely look something like this:

Move thumb stick in a circular motion

Limit thumb stick movement to within the surround

Return thumb stick to centre of controller when the user removes their thumb

Dispatch an event when the user moves the thumb stick

Obviously you are free to add more items to the list but these four cover the basic functionality. With that in mind lets start coding…

Writing The Code

The first thing you need to do is make sure that your ThumbStick class extends Sprite – by default Flash Professional creates non-inheriting classes. Now your code will look like this (I’ve applied a bit of formatting to my structure in case yours is formatted slightly differently):

Looking back at the four functional points I laid down earlier, the first related to moving the thumb stick in a circular motion let’s start by moving the thumb stick. This is a fairly complex process so I’m going to save that for part 2 of this article. However what you are going to do is set the controller so when you press down on the thumb stick it will drag in the direction of your thumb movement and on release it will re-centre itself back to the middle of the surround.

To do this you need to add a couple of event listeners. These will deal with responding to the touch events when the user presses down with their thumb as well as when they remove it. Even though you can directly interact with the symbols you have within your controller (as you gave them instance names), the code hinting in Flash Professional’s code editor doesn’t kick in – so I tend to make a private variable that I map to the symbol to make this easier.

The code below has the handlers included as well as the event listeners so you can see the flow and structure (there is a problem with this code and I’ll explain what it is in a moment):

So what’s the problem then? Well it has to do with testing. You see if you run this from inside Flash Professional you won’t be able to test the TouchEvents as there is no equivalent on a normal desktop PC. Not particularly useful if you don’t have a physical device at hand to test on (at this point I will emphasise how important it is to test you games, applicationsetc on the target hardware at some point).

One approach is to check the type of hardware you are running on and adjusting the code flow based on that by using the Capabilities() class. So if I update the code above further I can test to see if this is running on an ARM based processor or not (i.e most Smart phones and tablets). I can then tailor how the code is initialized.

This is useful, but I tend to remove all of the non essential code just before I test on the actual device – after all why include code you will never execute? Moving on…

THe next functionality you need to implement is the actual ability to move the thumb stick. This is pretty easy to do and I’ve just created two methods to control this – one to deal with the actual movement when the user is touching the thumb stick, and one to reposition the thumb stick back to the centre of the surround:

To access these methods you need to place a call to initDrag() inside your event handlers that respond to the user placing their thumb on the actual thumb stick – onThumbDown() and onTestThumbDown(). Likewise the resetThumb() method needs to be called when the user removes their thumb from the controller, so you need to add a call to it within the onThumbUp() and onTestThumbUp() methods.

If you test it now it should function in a similar manner to the example below. Clicking and dragging the thumb stick moves it (try the example below). Releasing the mouse button will centre it back to its original position in the middle of the surround – note that while you can click and drag the thumb stick with your mouse, but it isn’t confined by the boundaries of the surround. This isn’t exactly what we want. Worry not though, you’ll be addressing that next.

Setting Controller Boundaries

As I mentioned earlier, this gives you basic drag functionality within your virtual controller and repositions the thumb stick on releasing the mouse button. However it doesn’t restrict the movement to within the actual controller surround. So with out further ado let’s jump right in the deep end and start with some Trigonometry :p. I’m not going to give you a trig primer here, but I will explain where applicable what is actually happening – if you want to know more there are many good resources online (a simple google search should suffice)

The first thing you need to do is add in a new event listener to monitor the thumb stick as you move it so it can be restricted when it hits the desired position at the boundaries of the controller surround. Now, normally you’d probably use the MouseEvent.MOUSE_MOVE event to monitor the movement of the thumb stick. However this event is quite expensive in terms of event dispatching and usage compared to the Event.ENTER_FRAME event. With that in mid you need to add the following line to end of the initDrag() method:

Likewise you need to remove this event when the user stops interacting with the controller. To achieve this you just need to remove the event listener in the resetThumb() method. Like the initDrag() method add the following line to the beginning of the resetThumb() method. That way the event is removed straight away and any other house keeping routines can be processed once this has happened:

You’ll notice that both lines of code refer to an as yet unimplemented method called onThumbDrag(). This method is where all of calculations relating to the thumb stick’s movement boundaries are set. Below is the entire onThumbDrag() method code you’ll need to implement in your code.

Before I go through the various aspects of this method you’ve probably noticed that there are two variable fields being referenced beyond all of the local variables. They are _degrees and _radius. Both of which are defined below

The _degrees variable I’ll cover later on, but the _radius variable deals with the amount of movement within the boundary of your controller. The reason it is set to 25 here is that the thumb stick is 50×50 pixels therefore half of that is 25 and the surround is 150×150 pixels – again half of that is 75. If you add 25 to the thumbs 50×50 dimensions you 75. Simple really. So when the user drags it the most it can move is to the actual edge of the surround. If you want it to move beyond the edge slightly just increase the value. Likewise if you want it to move a shorter distance reduce this amount.

The Math ‘Bit’

That’s all well and good in theory, but how does that actually get applied in the code above? Well let’s start at the beginning of the method and look at each block of code so you can get a clearer understanding of the actual flow. First up you have a set of local storage variables. These just contain references to the position of the thumb stick and the surround:

Next up are the variables that store the actual X/Y position within the controller itself by subtracting the thumb stick’s position from the surround’s position.

Next up is the _degrees variable. As I mentioned earlier I’ll be going in to more detail on what you’ll be using this for shortly. All you need to know at this point is that it takes the values of _actualX and _actualY and converts them to radians (using the Math.atan() method) which are them in turn converted in to degrees – i.e. a value from 0 to 180 and -179 to -1

The _angles variable just converts the _degrees value back in to radian. While I appreciate that I could have simplified this code and just used Math.atan() like the _degrees variable. This way you can at least see how to convert between degrees and radians (should you ever need to)

You’re almost at the end of this process so hang on in there. There are two parts to restricting the thumb sticks X and Y position. First of all you need to calculate the maximum distance the thumb stick can move. The second part then checks to see if the thumb stick’s actual X or Y values are within or beyond those values. If they are beyond those values it automatically sets the X or Y of the thumb stick to the maximum distance for that property if outside of the boundary. To calculate the distance isn’t that obvious as you are measuring the edge of a circle. That’s where the trigonometry I mentioned comes in. By calculating the two sides of a right angle triangle within the controller surround you can easily then work out the hypotenuse (longest side) of the triangle from the centre. And that is all that is happening here:

As I said the second part then actually checks the position of the thumb stick and if it is beyond the maximum position it resets it to that maximum:

With this code in place you can now test your controller. Now it is restricted to the orbit that defined within the onThumbDrag() method, so it doesn’t move beyond the bounds of the actual surround just like the version below

Where’s The Thumb Again?

One thing you will have already noticed is that if your mouse moves outside of the thumb stick symbol, when you release it, the thumb stick doesn’t return to the centred position in the middle of the surround. This is because there is no event registered to capture the release of the thumb stick if you accidentally move out of it. This needs resolving as it is highly likely that your user will move their thumbs beyond the bounds of the thumb stick when playing your game (it’s just one of those things as they aren’t looking at the controller and as it’s virtual there isn’t any real feedback that they
have extended beyond the desired bounds).

To fix this you could just register the stage and respond when the users releases the thumb stick. This is fine desktop / web apps as they don’t generally support multi-touch so you can only interact with one element at a time with the mouse. On devices this isn’t the case. Because you can have multiple touch points if you listened for the release via the stage if you have a virtual button – for firing your ships guns for example – then every time you lifted your finger off the button it would reset the thumb stick. Not ideal really.

A better way would be to create a invidsible area that sits behind the actual controller graphics and listens for the release. That way it doesn’t matter if the user moves off the actual thumb stick symbol as this area would still respond to the release. Now one thing you would need to decide upon if you chose to implement this is how big should this ‘run off’ area be? Well that can be a bit subjective, but if you think about how the user will hold their device to use the controller then that should give you a good starting point.

For starters you don’t need it to cover a massive area – if fact I would say no more than a third of the width of the screen (working on the premise that the game is probably displayed as landscape). After all it’s unlikely the user will move their thumb a big distance given that they will be holding the device at the same time. If in doubt, test it out until you get a happy medium.

In the example below I have updated the ThumbStick symbol in Flash Professional by adding another layer called ‘Boundary’ and placed a new 25×25 pixel filled (red) circlular symbol in it. I gave it the instance name of boundary and made sure the layer was underneath all of the other layers in the ThumbStick symbol.

Obviously 25×25 pixels in size isn’t going to be that useful. However once you map the symbol to a variable in your ThumbStick() class you can easily set its dimensions. That way you could easily add in a getter/setter that would allow you to dynamically set the dimensions of the controller. In the code below I’ve just set this boundary symbol’s width and height with hard values within the constructor.

I’ve left the boundary symbols alpha value at 1 so you can see it (as shown in the SWF below – it’s the red circle :p). Try it out thius time and even though you may drag off the actual thumb stick, as long as ou are still over the boundary area it will still reset the thumb stick back to the centre once released. Obviously if the boundary area isn’t big enough you can enlarge it as you feel is necessary – it doesn’t even need to be circular. A rectangle would work just as well.

Summary

In this article you got a good foundation in how to approach creating a virtual controller that can be used on touch based devices (and we’ll be looking in to that part of the process in more detail in part 2). You also saw how to restrict the movement of the thumb stick to within the actual controller surround; as well as what should happen if the user accidentally moves their thumb off of the thumb stick and then removes their thumb from the screen.

With the foundation set you can now leverage this to add further enhancements such as dispatching events when the user moves the controller so that you can wire it in to your game easily.

In the first part of this article you saw how to put together the basic design and functional ‘building blocks’ that allowed you to drag the thumb stick of the controller around the orbit of the surround and return it back to the centre point in the controller once you released it. As the example below shows:

If you’ve not read the first part of this article yet, I suggest you go and have a read through it now (I’ll wait :p)

Hello… Is This Thing On?

With the ‘run off’ boundary in place all that is left to do is dispatch events to let the ‘outside world’ know what the controller is doing. As I mentioned in the first part of this article, I opted to just map keystrokes to the controller as most web-based Flash games tend to concentrate on keyboard controls over mouse movement (in some instances this is partly to do with the fact that the Flash runtime doesn’t provide support for Mouselook).

Now this sounds like a simple process. After all there are four arrow keys therefore I can just map those to Up, Down, Left, and Right on my controller. However that’s fine if I just want 4-way joystick control, (think Dig-Dug or Mr. Doo). However, most modern platformers tend to support 8-way movement at the very least. This poses a bit of a problem as it means you need to map combinations of key presses – for example Up and Left or Down and Right. This is painless when using a physical keyboard as the user just presses those keys in combination and they respond accordingly. We’ll have to emulate that and that means you need to do a bit more work in how you determine what your virtual controller is dispatching in the way of emulated key presses.

Mapping The Keys

The first thing to do is map the four arrow keys. That way you know that you are dispatching the events based on the position of the thumb stick. To do this you need to add in a simple internal loop (or tick if you prefer) that just updates the information about which key input you are emulating. So if you were to push the to the top you would like want to dispatch an event that represents the UP key being pressed for example.

Inverting Controls

One thing you may wish to implement in a future enhancement is allowing the user to invert the Y-axis of your controller (Up / Down). This is generally useful for games that relate to the flying of air- (or space-) based craft using a pitch and yaw approach. Therefore, as in a real aircraft, pushing up on the controller would in fact make the craft move down (or descend) and pulling the controller down would make it move up (or ascend).

You’re going to need another method to handle the checking and dispatching of the emulated key presses. So create a new protected method named dispatchKeyCombo() and put a call to it at the very bottom of the onThumbDrag() method. You should now have something that looks similar to the code below (note I’ve removed the main bulk of the code in the onThumbDrag() method for brevity):

Now every time the user moves the thumb stick and the onThumbDrag() method is invoked it will automatically invoke the dispatchKeyCombo() method as well. With the stub method in place let’s set about adding in the initial logic and event dispatching. The first thing you need to do is determine where the thumb stick is in relation to the surround. You may recall that in the onThumbDrag() method you had a variable named _degrees that stored the current positive or
negative position of the thumb stick in degrees. At the time I skipped over its use and promised to explain its purpose later on. Well, now’s the time. You need the degree values so that you can use the current degree rotation as a basis for mapping the thumb stick location to the arrow keys.

Remember you are just adding in 4-way joystick support at this point. To achieve this you need to check what the value of the _degrees variable is and if it is equal to the required value for each of the four directions (Up, Down, Left and Right) you can dispatch the event. Based on the current setup, the degree values for each of the four thumb stick positions would be: Right = 0, Down = 90, Left = 180 and Up = -90. Now that you know what the values are it is a
simple process of adding in the logical conditions to determine what values need to be dispatched.

In theory this is exactly what you need. However if you test this in debug mode you’ll see that actually hitting that single value when moving the thumb stick is very difficult as you need to be very precise – it’s tricky with a Mouse imagine how hard it would be if you were using your thumb… The obvious course of action is to make use of a range of values to determine the position of the thumb stick. This way you can create ‘zones’ that relate to Up, Down, Left and Right. Below is the updated code that now uses zones to determine where the thumb stick is. Notice that the conditions are a little more complex as we are checking to see if the thumb stick is between values (some deal with both positive and negative values).

If you test this version in Debug mode you’ll see the traces are produced in a more consistent way – obviously you can tweak the range to vary the precision of the thumb stick output. However as you are targeting touch based devices don’t try and make the range too as user’s thumbs aren’t that precise – especially when they are in the heat of a fire fight.

Now that you have the basic structure in place you can move on to the next step. Mapping the keyboard input values to those of your thumb stick’s position. ActionSCript provides constants for all of the common key values, making implementing the inputs easy. Before you add any key input code to the dispatchKeyCombos() method you’ll need to declare four new variables. These are instance variables not method variables so just place them under all of the
other instance variables at the top of the class.

As with most things you don’t have to worry about all of them at this point in time; I’ll explain each one’s purpose as we add additional functionality. The only one you need to concern yourself with at this point is _primaryKeyCode, within this variable you’re going to store the current keyboard key code.

First up, replace each of the trace() statements with a reference to the _primaryKeyCode variable. Depending on which direction you are looking to monitor you can then assign the relevant keyboard input constant. Once these are in place you just need to dispatch the updated value of _primaryKeyCodeas the keyCode property of a new KeyboardEvent() as you can see in the following code block:

The updated virtual controller is below, (I’ve added in an external event handler so you can see the current direction output in a text field above it)*.

*I’ve also set the alpha on the red boundary to 0 (zero) so it is no longer visible.

Expanding the Scope

Now that you have the basic structure for dispatching code let’s expand it so the virtual controller code allows you to use the controller as an 8-way controller as opposed to the 4-way you currently have. Considering that under the hood the controller is mapping to emulated keyboard events, dealing with the additional angles within your controller raises an additional requirement.

Remember those additional variables I made you declare: _primaryKeyCode, _secondaryKeyCode, and so on? Well, you are going to use another of those now – _secondaryKeyCode. Moving the thumb stick in the primary positions (as the code currently supports) requires only a single keyboard event to be dispatched. Moving the thumb stick to these off axis angles requires you to dispatch two keyboard events – for example if you move the thumb stick halfway between Up
and Right you’re going to have to dispatch key codes that represent an Up Arrow key press as well as a Right Arrow key press.

Let’s look at the revised code and break down what is going on with the addition of the second event dispatcher and the inclusion of the new checks for those additional ‘zones’ that provide 8-way controller support.

You’ll notice that there have been a few changes to the dispatchKeyCombo() method. The biggest change is the one I mentioned prior to this code block: I’ve added support for key input mapping in relation to the full movement of the thumb stick, not just when it is moved Up, Down, Left, or Right. Secondly there is now a reference to the variable _secondaryKeyCode. This is where the application stores the secondary key input code. As I mentioned earlier if you push the thumb stick up and right then from a key input perspective you need to dispatch a KeyboardEvent() for both the Up key and the Right key. By default the _secondaryKeyCode variable is reset to 0 (zero) at the beginning of the method and only gets updated should the user move the virtual controller to a position that requires two key code values.

The last part is to add a check to see if the _secondaryKeyCode variable value is greater than 0 (zero) – if it is then dispatch the second KeyboardEvent().

Only one thing is left to do, and then the method will be finished. You may have noticed that you are dispatching KeyboardEvent.KEY_DOWN events, but how do you let any object listening to your controller know when you’ve changed direction. Well you need to dispatch the KeyboardEvent.KEY_UP event. This is where those final two variable come in. They are: _previousPrimaryKeyCode and _previousSecondaryKeyCode. These are used to store the (as the name implies) previous key codes dispatched. However to avoid unnecessary event dispatching they are only triggered if the new values for both the primary and secondary key codes have changed, like so:

If the user changes the position of the thumb stick then the KeyboardEvent.KEY_UP event for the relevant input is dispatched. Because there’s a lot going on in this method, I’ve included the complete version below so you can see how it flows:

So far the examples have been tested in the browser or on the desktop – however, the intention is that this controller will work on a touch based device (the title gives it away a bit :p). That said, if you were to put this on a device now it wouldn’t work.

Why? Well it’s because you need to tell your game that it is using multitouch events so it knows how to handle them. Now you could place this code within the virtual controller – making sure it always enables the multitouch events, or you can keep it externalized and place it in the actual game. Personally I tend to put this in the constructor of my controllers, mainly because in the heat of coding I tend to forget to put the multitouch code in and then sit their dumbfounded when it fails to work on the device. Obviously this could be conceived as repetitious if I have more than one controller (dual controllers, additional buttons etc), but it’s not a big bit of code and once it’s initialized, setting it again won’t cause any real additional overhead.

So in this final step just paste this code in to the constructor of your virtual controller just above where you added the event handlers and you are done.

It’s worth pointing out that you need to use the MultitouchInputMode.TOUCH_POINT event as you are just monitoring for the press and release on the screen. If you needed to track multiple inputs so you could perform gestures (pinch zoom for example) then you’d need to use the MultitouchInputMode.GESTURE event instead. For controllers I would suggest you avoid mixing them otherwise you will get undesirable side effects.

Tidying Up

The last thing you need to do is tidy up a few loose strings. While your controller now dispatches the correct events if you remove your thumb from the controller they continue to get dispatched. The simple reason is that there is no catch all event to kill all of the events should the user remove their thumb from the controller. This is an easy fix. Just create a new method called killAllEvents() and within it place the following code:

Now place a call to it from within the resetThumb() method, right after you remove the event handler. Your resetThumb() method should now look like this:

That’s it. If you test it now you’ll see the events are dispatched when the thumb stick is moved in any direction and if you remove your thumb from the controller the events will stop being broadcast.

Summary

In the final part of this article you have seen how you can easily emulate keyboard inputs through the virtual controller and dispatch them so that any external object listening for these events can react when they are received.

If you want to see this controller in action check out my Pushbutton engine article on integrating controls with Pushbutton Engine.


上一篇:

下一篇: