前段時間,網(wǎng)上出現(xiàn)了一段使用HTC vive觀看LoL的視頻,相關鏈接在這里。這是由一位名叫Fire-Proof的大神開發(fā)完成的,并且大神公布了源代碼。作為一名程序員,對牛掰的代碼自然充滿了好奇,用VR觀看LoL,這到底是如何實現(xiàn)的呢?
查看代碼發(fā)現(xiàn),程序完全使用Python開發(fā)(再次感受到Python的強大),包含的代碼并不多,一共也就10幾個文件。
首先查看main.py。在最下方定義了start_app方法
def start_app():
app = QApplication(sys.argv)
tray_icon = MainDialog()
tray_icon.show()
app.exec_()
程序創(chuàng)建了一個名為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()
對話框創(chuàng)建了一個start按鈕,并將該按鈕的點擊事件綁定到了toggle_spectate方法上,該方法會調(diào)用start_spectate方法,并最終通過SpectateThread創(chuàng)建了一個線程。繼續(xù)查看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游戲的畫面轉(zhuǎn)換成VR模式并同步到頭顯設備中??吹竭@里是不是有種被欺騙的感覺,通過VR觀看LoL是通過軟件來實現(xiàn)的?答案確實如此,Python程序并不負責轉(zhuǎn)換LoL的畫面到VR設備上,它只是不停的檢測VR頭顯和控制器的狀態(tài),并以此調(diào)整LoL的畫面,這樣就可以通過轉(zhuǎn)動頭部或觸發(fā)控制器來以不同的視角觀看LoL畫面(比如站立時鳥瞰整個戰(zhàn)場,蹲下來觀看局部戰(zhàn)場)。
在VRSepecate 的run方法中,會不停的調(diào)用_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控制器是否觸發(fā),并獲取VR頭顯的位置信息,根據(jù)這些數(shù)據(jù)來更新LoL的畫面信息。其中訪問VR設備是通過調(diào)用openvr庫來實現(xiàn)的,并在OpenVR.py中做了進一步的封裝。那么更新LoL畫面如何實現(xiàn)呢,答案是修改LoL進程的內(nèi)存。
在LeagueOfLegends.py中,封裝了修改LoL進程內(nèi)存的方法,例如執(zhí)行self.lol.x = 520,實際上執(zhí)行的是下面的代碼
@x.setter
def x(self, val):
self._x.write(val, type="float")
而更底層的實現(xiàn)是通過memorpy目錄中的相關代碼來實現(xiàn)的。那么如何知道應該修改哪些內(nèi)存位置呢(畢竟直接修改內(nèi)存一個不慎可能進程就掛了)。涉及到畫面信息的內(nèi)存位置都是通過一個cam_base_address 進行偏移得到的,作者給出了cam_base_address 的搜索方法:
HOW TO: Find base addr
- Move screen to lowest y coord
- Scan for float = 520
- Move screen to highest y coord
- Scan for float = 14765
- Select the address that will updated the camera pos when changed
- Right click this address and click "find out what accesses this address"
- Select an element and press more information
- Copy the value which is listed at the end of the string "The value of the pointer needed to find this ..."
- Search for this this value as an 8-bit hex value
- Now search for the first address found
- You should now have found a "green" address, select this and double click the address
- You should now be able to see a string similar to "League of Legends.exe"+13D4280 <-- base addr
看起來作者是通過某些內(nèi)存查看軟件(例如IDA)來進行分析得到的內(nèi)存地址(在此膜拜大神)。
了解了這些,基本就明白程序的技術(shù)原理了,剩下的部分感興趣的同學可以自行查看源碼,當然有條件可以上設備體驗一番。