前段时间,网上出现了一段使用HTC vive观看LoL的视频,相关链接在这里。这是由一位名叫Fire-Proof的大神开发完成的,并且大神公布了源代码。作为一名程序员,对牛掰的代码自然充满了好奇,用VR观看LoL,这到底是如何实现的呢?
查看代码发现,程序完全使用Python开发(再次感受到Python的强大),包含的代码并不多,一共也就10几个文件。
首先查看main.py。在最下方定义了start_app
方法
def start_app():
app = QApplication(sys.argv)
tray_icon = MainDialog()
tray_icon.show()
app.exec_()
程序创建了一个名为MainDialog的对话框,让我们看一下MainDialog中有些什么(代码有删减)
class MainDialog(QDialog, Ui_MainDialog):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.spectate = None
self.spectate_started = False
self.setupUi(self)
self.lol_watcher = ProcessWatcher(b"League of Legends")
self.lol_watcher.running.connect(self.lol_watcher_update, Qt.QueuedConnection)
self.lol_watcher.start()
self.vorpx_watcher = ProcessWatcher(b"vorpControl")
self.vorpx_watcher.running.connect(self.vorpx_watcher_update,Qt.QueuedConnection)
self.vorpx_watcher.start()
self.pushButtonStart.clicked.connect(self.toggle_spectate)
def toggle_spectate(self):
if self.spectate_started:
self.pushButtonStart.setText("Start")
self.stop_spectate()
self.spectate_started = False
else:
self.pushButtonStart.setText("Stop")
self.start_spectate()
self.spectate_started = True
def start_spectate(self):
if self.spectate is None:
self.spectate = SpectateThread()
self.spectate.error.connect(self.spectate_error, Qt.QueuedConnection)
self.spectate.start()
对话框创建了一个start按钮,并将该按钮的点击事件绑定到了toggle_spectate
方法上,该方法会调用start_spectate
方法,并最终通过SpectateThread
创建了一个线程。继续查看SpectateThread
(代码有删减)
class SpectateThread(QThread):
def __init__(self):
super(SpectateThread, self).__init__()
self.spectate = None
def run(self, *args, **kwargs):
self.spectate = VRSpectate()
self.spectate.run()
看来玄机在VRSpectate
方法中。在查看VRSpectate
之前,注意到MainDailog
中开启了两个线程来检测”League of Legends”进程和”vorpControl”进程。前一个很好理解,观看LoL自然需要”League of Legends”进程,那这个”vorpControl”进程又是什么鬼呢?这是vorpX软件的运行进程,用来将非VR游戏的画面转换成VR模式并同步到头显设备中。看到这里是不是有种被欺骗的感觉,通过VR观看LoL是通过软件来实现的?答案确实如此,Python程序并不负责转换LoL的画面到VR设备上,它只是不停的检测VR头显和控制器的状态,并以此调整LoL的画面,这样就可以通过转动头部或触发控制器来以不同的视角观看LoL画面(比如站立时鸟瞰整个战场,蹲下来观看局部战场)。
在VRSepecate
的run
方法中,会不停的调用_next_frame
方法来更新画面
def _next_frame(self):
self.scale = self.z_offset
controller_frame = self.vr.controller_frame()
# Camera movement
if len(controller_frame) == 2:
if all(controller_frame.button_pressed("trigger")):
self.yaw_offset += self.prev_controller_frame.relative_rotation - controller_frame.relative_rotation
self.z_offset += (self.prev_controller_frame.relative_distance - controller_frame.relative_distance) * self.z_offset * 2
self._move_offset(self.prev_controller_frame.position(), controller_frame.position())
elif any(controller_frame.button_pressed("trigger")):
active_controller = [i for i, x in enumerate(controller_frame.button_pressed("trigger")) if x][0]
self._move_offset(self.prev_controller_frame.position(active_controller), controller_frame.position(active_controller))
if len(controller_frame) == 1:
if any(controller_frame.button_pressed("trigger")):
self._move_offset(self.prev_controller_frame.position(), controller_frame.position(), update_z=True)
self.prev_controller_frame = controller_frame
pos = self.vr.hmd.position()
self.lol.yaw = pos.yaw + self.yaw_offset
self.lol.pitch = pos.pitch*-1
self.lol.x = (pos.x*self.scale) + self.x_offset
self.lol.y = (pos.y*self.scale) + self.y_offset
self.lol.z = (pos.z*self.scale) + self.z_offset
该方法会首先来检测VR控制器是否触发,并获取VR头显的位置信息,根据这些数据来更新LoL的画面信息。其中访问VR设备是通过调用openvr库来实现的,并在OpenVR.py中做了进一步的封装。那么更新LoL画面如何实现呢,答案是修改LoL进程的内存。
在LeagueOfLegends.py中,封装了修改LoL进程内存的方法,例如执行self.lol.x = 520,实际上执行的是下面的代码
@x.setter
def x(self, val):
self._x.write(val, type="float")
而更底层的实现是通过memorpy目录中的相关代码来实现的。那么如何知道应该修改哪些内存位置呢(毕竟直接修改内存一个不慎可能进程就挂了)。涉及到画面信息的内存位置都是通过一个cam_base_address
进行偏移得到的,作者给出了cam_base_address
的搜索方法:
HOW TO: Find base addr
1) Move screen to lowest y coord
2) Scan for float = 520
3) Move screen to highest y coord
4) Scan for float = 14765
5) Select the address that will updated the camera pos when changed
6) Right click this address and click “find out what accesses this address”
7) Select an element and press more information
8) Copy the value which is listed at the end of the string “The value of the pointer needed to find this …”
9) Search for this this value as an 8-bit hex value
10) Now search for the first address found
11) You should now have found a “green” address, select this and double click the address
12) You should now be able to see a string similar to “League of Legends.exe”+13D4280 <– base addr
看起来作者是通过某些内存查看软件(例如IDA)来进行分析得到的内存地址(在此膜拜大神)。
了解了这些,基本就明白程序的技术原理了,剩下的部分感兴趣的同学可以自行查看源码,当然有条件可以上设备体验一番。