配置北通168方向盘玩上《尘埃拉力赛》(DiRT Rally)

前不久入了台二手的北通168游戏方向盘。说起来这个方向盘的年代非常久远,比我的年龄都大,卖家也是个称得上爷爷辈的老人了(囧)但它只要80元!真的是相当值的价格!!

于是我毫不犹豫便入手了,千里迢迢把它带回家:

缺点当然也不是没有,抛开本身的老化和定位不准问题,年代久远,兼容性多少也都不如现代手柄。测试了几个赛车和驾驶游戏,欧卡2尚可一试,而Forza地平线索性直接不支持了,必须想办法映射成Xbox手柄……

最近又试了下我最爱的尘埃拉力赛 (DiRT Rally) ,和欧卡2类似,也需要自己设置控制器配置。然而情况有些特殊:每次重新打开游戏,就要重新配置方向盘,甚是不爽,何况,有些菜单操作还是需要用到F区键盘……


前言

在解决这些问题前,首先要说明的是,Windows系统连接控制器外设(通俗意义上的手柄,包括方向盘),分为DirectInput和XInput两种驱动模式:大多数通用控制器采用DirectInput模式,而XInput模式对应Xbox 360及之后的手柄输入,两者区别在于布局关系上。具体而言,后者有明确的XYAB标准布局,而前者数量均无限制(对方向盘排挡杆是刚需),由此影响控制器外设跟游戏的兼容性。

北通168方向盘年代久远,尽管是自动挡娱乐方向盘,没有挡杆那么多按键的要求,但在360主机尚未盛行的年代,市面上清一色也都是通用的DirectInput模式,免驱、但不免配置。没有XInput的Xbox手柄模式,也不能直接用来玩地平线。

即使到了2023年,这样的廉价方向盘外设仍然存世于淘宝上,相同的模具、相似的主控,价格还“上涨”了几百块(如果不考虑转换2006年货币汇率的话)

虽说的确可以考虑用x360ce之类的软件,或者Steam的控制器配置,把方向盘转换映射成Xbox手柄,但考虑到资源占用以及延迟关系,还是尽量用游戏自带的控制器支持吧。比较好的例子是欧卡2,可以清晰分明手柄和方向盘的转向区别。

Google搜索了一番,在Steam上找到一篇教程,发现也有同样问题的玩家:

How to solve Direct Input problems with unsupported wheels :: DiRT Rally General Chat
I was having some problems with my wheel that other people seems to be struggling with too and I’ve found a solution that works. It’s a bit long, but if you’re really aching to use a unsupported wheel, it may be worth it. (Sorry for any spelling mistakes, english is not my native language) The first problem is that my input changes are never saved. I’ve tried some solutions posted here, but none worked. Changing all inputs to my wheel, pressing a button on the wheel at the start screen…
steamcommunity.com

上面的链接教程中,描述了以下三种问题:

  • 无法自动识别方向盘,也不能保存配置
  • 转向、加减速存在线性轴死区(Deadzone)
  • 没有肩键功能控制快速菜单 (键盘对应F3/F4)

简单来说,尘埃拉力赛通过读取游戏目录本身的XML配置文件,实现对DirectInput方向盘控制器的支持,并且内置了多个主流厂商的方向盘适配(如罗技G系列、图马斯特等)

然而不同的控制器,按键、线性轴(axis)的位置也有所差异,不像Xbox手柄那么规范,就需要游戏开发商独一适配。而游戏中没有杂牌手柄和方向盘的配置,索性加载出来就是空的默认配置文件。

尘埃使用通用控制器的情况下,默认设定1、2键对应确认和返回,然而游戏并不认识方向盘的布局,不看说明也不知道按键的位置。在奇葩手柄上这个问题更加严重,连接了英伟达手柄,本用于确认的2号键居然在触控板上……

解决方案就是对症下药,既然游戏不能自动识别,那就自己写一份配置文件,自行针对方向盘布局定制配置。

实践

在Steam中浏览游戏本地文件位置,找到Input文件夹(默认位置大概在 C:\Program Files (x86)\Steam\SteamApps\common\DIRT Rally\Input ),可以看到众多方向盘和手柄的适配文件。

上文教程的解决方案是直接编辑 dinput_default.xml 文件,打开可以看到很多命名为</Action>的代码行。写过HTML基本上也能看懂一些,这里我已经修改过了,一般情况下,这个文件内基本上是空的配置。

逐个</Action>代码行内,直接添加DirectInput的按键事件,如 <Axis id="di_button_0"> 代表的就是1号键点击,从0算1起,button_1代表2号键,以此类推。

通过直接编辑XML文件,也可以实现多功能复用同一按键:比如Xbox手柄默认摇杆和方向键都可以转向,就是在一个Action填写两个Axis实现的。

线性轴的操作事件稍有区别,在后面还需填写biDirUpper/Lower上下增减的附加属性,根据方向盘的类比轴 (Axis) 配置不同,添加转向、踏板对应的Axis轴配置。

我的北通方向盘是X轴左右转向、Y轴向上刹车 (biDirUpper) 、向下踩油门加速 (biDirLower) 。如果方向盘是全轴控制的设计(比如从左到右的Z轴),则应该设置为uniDirPos/Neg(反向)

还需要设置转向、油门起始死区 (deadzone) 和最大极限 (saturation) 的值,对于方向盘可以直接关闭死区,设置为0,获得更精细的线性操作;而转向根据我的偏好,设置为极限的85%,也就是0.85,避免90°暴力打方向撞击边缘,伤害方向盘和相关部件的问题(虽说方向盘就是拿来糙的,不过也已经坏了一个拨片了)

缺少的F3/F4用于菜单切换的功能键,在游戏中用于切换涂装、视图之类的功能。通过游戏内无法映射,则可以直接从Xbox手柄配置文件(比如xinput_pad_0.xml) 拿过来,配置识别为肩键。我的方向盘设置为了5和6号键,代码如下:

  <Action id="Menu Left Shoulder">
    <Axis id="di_button_4" />
  </Action>
  <Action id="Menu Right Shoulder">
    <Axis id="di_button_5" />
  </Action>

以此类推,编辑剩下的事件,保存。打开尘埃拉力赛,在Settings>Control设置界面加载默认的Joystick布局,逐一测试每个按键和线性灵敏度。

有时候会出现反向的问题,退出游戏,重复编辑配置文件保存,再次打开尘埃,加载布局测试。改动XML配置后一定要记得手动加载一次!直到配置成正确的布局,基本上就完成啦。

以下是完整配置:

<!-- Default action map for Direct Input -->
<ActionMap name="betop_3168" device_type="{00060079-0000-0000-0000-504944564944}" priority="0">

  <!-- In Game Actions -->
  <Action id="Accelerate">
    <Axis id="di_y_axis" type="biDirLower" deadzone="0.0" saturation="1.0" />
  </Action>
  <Action id="Brake">
    <Axis id="di_y_axis" type="biDirUpper" deadzone="0.0" saturation="1.0" />
  </Action>
  <Action id="Clutch">
  </Action>
  <Action id="Handbrake">
    <Axis id="di_button_2" />
  </Action>
  <Action id="Steer Left">
    <Axis id="di_x_axis" type="biDirLower" deadzone="0.0" saturation="0.85" />
  </Action>
  <Action id="Steer Right">
    <Axis id="di_x_axis" type="biDirUpper" deadzone="0.0" saturation="0.85" />
  </Action>
  <Action id="Change View">
    <Axis id="di_button_5" />
  </Action>
  <Action id="Gear Up">
    <Axis id="di_button_0" />
  </Action>
  <Action id="Gear Down">
    <Axis id="di_button_1" />
  </Action>
  <Action id="Look Left">
    <Axis id="di_dpad_0_left" />
  </Action>
  <Action id="Look Right">
    <Axis id="di_dpad_0_right" />
  </Action>
  <Action id="Look Up">
    <Axis id="di_dpad_0_up" />
  </Action>
  <Action id="Look Down">
    <Axis id="di_dpad_0_down" />
  </Action>
  <Action id="Look Back">
    <Axis id="di_button_4" />
  </Action>
  <Action id="Pause">
    <Axis id="di_button_9" />
  </Action>
  <Action id="Instant Replay">
    <Axis id="di_button_1" />
  </Action>
  <Action id="Horn">
  </Action>
  <Action id="Reset Vehicle">
    <Axis id="di_button_7" />
  </Action>
  <Action id="Gear 1">
  </Action>
  <Action id="Gear 2">
  </Action>
  <Action id="Gear 3">
  </Action>
  <Action id="Gear 4">
  </Action>
  <Action id="Gear 5">
  </Action>
  <Action id="Gear 6">
  </Action>
  <Action id="Gear 7">
  </Action>
  <Action id="Gear Reverse">
  </Action>
  <Action id="Push To Speak">
  </Action>
  <Action id="Headlights">
  </Action>
  <Action id="Wipers">
  </Action>
  <Action id="SeatMoveForward">
  </Action>
  <Action id="SeatMoveBackward">
  </Action>
  <Action id="SeatMoveUp">
  </Action>
  <Action id="SeatMoveDown">
  </Action>
  <Action id="SeatTiltUp">
  </Action>
  <Action id="SeatTiltDown">
  </Action>
  <Action id="SeatReset">
  </Action>

  <!-- Menu Actions -->
  <Action id="Menu Left">
    <Axis id="di_dpad_0_left" />
  </Action>
  <Action id="Menu Right">
    <Axis id="di_dpad_0_right" />
  </Action>
  <Action id="Menu Up">
    <Axis id="di_dpad_0_up" />
  </Action>
  <Action id="Menu Down">
    <Axis id="di_dpad_0_down" />
  </Action>
  <Action id="Menu Select">
    <Axis id="di_button_3" />
  </Action>
  <Action id="Menu Back">
    <Axis id="di_button_2" />
  </Action>
  <Action id="Menu Button3">
    <Axis id="di_button_0" />
  </Action>
  <Action id="Menu Button4">
    <Axis id="di_button_1" />
  </Action>
  <Action id="Menu Start Button">
    <Axis id="di_button_9" />
  </Action>
  <Action id="Menu Select Button">
    <Axis id="di_button_8" />
  </Action>
  <Action id="Menu Left Shoulder">
    <Axis id="di_button_4" />
  </Action>
  <Action id="Menu Right Shoulder">
    <Axis id="di_button_5" />
  </Action>
  <Action id="Fe View Tweak Left">
    <Axis id="di_x_axis" type="biDirLower" deadzone="0.0" saturation="1.0" />
  </Action>
  <Action id="Fe View Tweak Right">
    <Axis id="di_x_axis" type="biDirUpper" deadzone="0.0" saturation="1.0" />
  </Action>
  <Action id="Fe View Tweak Up">
  </Action>
  <Action id="Fe View Tweak Down">
  </Action>
  <Action id="Fe View Tweak In">
    <Axis id="di_y_axis" type="biDirLower" deadzone="0.0" saturation="1.0" />
  </Action>
  <Action id="Fe View Tweak Out">
    <Axis id="di_x_axis" type="biDirUpper" deadzone="0.0" saturation="1.0" />
  </Action>

  <!-- Replay Actions -->
  <Action id="Replay Rewind">
    <Axis id="di_y_axis" type="biDirUpper" deadzone="0.0" saturation="1.0" />
    <Axis id="di_x_axis" type="biDirLower" deadzone="0.0" saturation="1.0" />
  </Action>
  <Action id="Replay Fast Forward">
    <Axis id="di_y_axis" type="biDirLower" deadzone="0.0" saturation="1.0" />
    <Axis id="di_x_axis" type="biDirUpper" deadzone="0.0" saturation="1.0" />
  </Action>
  <Action id="Replay Pause">
    <Axis id="di_button_3" />
  </Action>
  <Action id="Replay Next Camera">
    <Axis id="di_button_5" />
  </Action>
  <Action id="Replay Prev Camera">
    <Axis id="di_button_4" />
  </Action>
  <Action id="Replay Jump In">
  </Action>
  <Action id="Replay Audio Mute">
    <Axis id="di_button_6" />
  </Action>
  <Action id="Replay Exit">
    <Axis id="di_button_2" />
  </Action>
  <Action id="Replay UI On Off">
    <Axis id="di_button_8" />
  </Action>
  <Action id="Replay Youtube">
  </Action>
  <Action id="Youtube Drag Left">
  </Action>
  <Action id="Youtube Drag Right">
  </Action>
  <Action id="Youtube Speed Up">
  </Action>
  <Action id="Youtube Speed Down">
  </Action>
  <Action id="Youtube Upload">
  </Action>
  <Action id="Youtube Exit">
  </Action>

  <!-- Spectator actions -->
  <Action id="Spectator Next Camera">
    <Axis id="di_button_5" />
  </Action>
  <Action id="Spectator Previous Camera">
    <Axis id="di_button_4" />
  </Action>
  <Action id="Spectator UI On Off">
    <Axis id="di_button_8" />
  </Action>
  <Action id="Spectator List On Off">
    <Axis id="di_button_1" />
  </Action>
  <Action id="Cycle Forward On Board Cameras">
    <Axis id="di_button_4" />
  </Action>
  <Action id="Activate Replay System">
    <Axis id="di_button_5" />
  </Action>


</ActionMap>

另外研究了一下如何能自动识别方向盘的方法,像罗技、图马斯特那种大厂产品一样。不过丑话说在前:实测自行配置会导致无法使用RaceNet线上功能、无法保存游戏等诸多游戏问题

Input文件夹下包含各种方向盘的配置文件,每个都打开看了一下,又发现了一些新的思路:所有的方向盘布局配置文件,实际上都链接在paths.xml文件下,调取对应的ActionMap参数识别方向盘布局,有点类似于网站的sitemap(毕竟都是XML):

仅有文件超链接还不够,若要让方向盘能够即插即用,又不影响其他的手柄,便需要修改对应的ActionMap参数——device_type,而自动识别实际上就是基于device_type中的设备GUID实现的:

设备的GUID可以通过ditool获取。打开会出现一个命令行窗口,找到方向盘控制器的名称,下方的guidProduct便是游戏识别需要的设备ID:

ditool二进制exe可以通过sourceforge地址下载:sourceforge.net/projects/x360ce/files/ditool.zip/download

(参考:granitedevices.com/wiki/Simucube_product_USB_interface_documentation

将guidProduct的值填入到device_type中,另存为新的xml文件。随后打开paths.xml文件,添加下面内链接代码:

<xmlreader processor="Input" filename="input/betop_3168.xml"                      map="UPDATE" pool="UPDATE_TEMPORARY" userdata="presets" />

再打开尘埃,设置控制器,便可以看到新的配置了:

名称前缀的LNG好像有些奇怪,可能是没有i18n/mui翻译字符串之类的关系。还是要提醒一下:自行修改游戏文件会导致RaceNet以及游戏进度无法保存!


如此一番设置以后,享受精细控制带来的驾驶乐趣吧!原本称之为“性能怪兽”的Group B组赛车,也能够轻松驾驭。

开着蓝旗亚Delta S4冲进第一名也非常容易,就像开斯巴鲁STI和奥迪Quattro一样,然而R4组的STI和三菱EVO X又感到慢了半拍,像是开前驱车一样,莫得灵魂,没有以前的驾驶乐趣了。这不是个游戏嘛?我是谁我在哪?(大雾)

下次研究一下怎么更完美地玩上《极限竞速:地平线》系列吧,实话说地平线的调教更偏向手柄。地平线默认好像不支持北通的方向盘,即使用的是x360ce映射也能感受到明显的延迟。

另外,两个方向拨片已经整坏了一个,本来绑定的也是默认A和B键的确认/手刹,看来还是把它们设置成肩键,老老实实用在欧卡转向灯上吧(逃)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注