【开坑】雷达的写法
寫在前面
閱讀本文,請務必動手寫代碼測試,否則無法愉快玩耍。
如果你還不懂怎么開始編寫一個MetaHook插件,請首先去看使用 MetaHook Plus 繪制 HUD來增加姿勢。
這篇文章假設你已經有最最基礎的以下技能:
正文開始
?我們的游戲世界基于一個三維直角坐標系,如下圖:
電腦端可以右鍵新標簽頁打開大圖,手機端可以保存后查看大圖。
在HL引擎中,X軸和Y軸組成的平面就是地面,當你在游戲中前后左右移動,你的X軸和Y軸的坐標值將會發生變化。當你往高處和低處移動時,Z軸的坐標值將會發生變化。所以可知:XY軸=前后左右 Z軸=上下
我們可以編寫代碼在游戲中實時輸出坐標值來觀察:
int HUD_Redraw(float time, int intermission) {cl_entity_t* local = gEngfuncs.GetLocalPlayer();gEngfuncs.Con_Printf("position=(%f,%f,%f)\n", local->curstate.origin[0], local->curstate.origin[1], local->curstate.origin[2]);return gExportfuncs.HUD_Redraw(time, intermission); }使用?-dev?參數啟動游戲,進入游戲后,在屏幕左上角將會看到輸出的坐標值。
很顯然,我們要實現小地圖,只需要XY這兩個軸就行了,所以我們不妨先作個圖:
我們可以編寫代碼獲取一些實體的坐標試試看:
int HUD_Redraw(float time, int intermission) {// 索引1到32都是玩家for (int i = 1; i <= 32; i++){// 根據索引獲取實體cl_entity_t* entity = gEngfuncs.GetEntityByIndex(i);if (!entity || !entity->model)continue;// 取得實體的XY坐標,通常來說三維坐標值都比較大,所以我們乘以0.2,也就是縮小到20% vec2_t position;position[0] = (int)(entity->curstate.origin[0] * 0.2);position[1] = (int)(entity->curstate.origin[1] * 0.2);// 在HUD上繪制小方塊gEngfuncs.pfnFillRGBA(position[0], position[1], 8, 8, 255, 255, 255, 255);}return gExportfuncs.HUD_Redraw(time, intermission); }然后進入?cs_italy?這個地圖的匪徒出生點,加幾個BOT,并且把它們暫停,你就可以在HUD上看到最簡陋的?小地圖?了,如下:
此時如果你移動位置,白色小方塊也會跟著移動。
你會發現,稍微走走,小方塊就跑到屏幕外面去了。很顯然,我們需要以自己為中心,來顯示其它人的位置。
相對坐標
回顧上面的平面坐標系圖,假如我們要以自己為中心,那就需要知道隊友“在哪”。
自己的坐標是(4,4),隊友A的坐標是(7,6)
如果要以自己為中心,自己的坐標就是(0,0),那么此時隊友A的坐標是多少呢?
你可以看到隊友A的坐標為(3,2),那這個坐標是怎么算出來的呢?
其實就是減法而已,如下:
(7,6)-(4,4)=(3,2)
很好,我們現在已經知道相對于自己來說隊友“在哪”了,現在我們在HUD上取一點,用來顯示自己的位置。
int HUD_Redraw(float time, int intermission) {cl_entity_t* local = gEngfuncs.GetLocalPlayer();// 在HUD上取一點,顯示自己的位置const vec2_t hudPos = { 300, 300 };// 繪制自己的方塊(紅色)gEngfuncs.pfnFillRGBA(hudPos[0], hudPos[1],8, 8,255, 0, 0, 255);// 自己的坐標,通常來說三維坐標值都比較大,所以我們乘以0.2,也就是縮小到20% vec2_t selfPos;selfPos[0] = (int)(local->curstate.origin[0] * 0.2);selfPos[1] = (int)(local->curstate.origin[1] * 0.2);// 索引1到32都是玩家for (int i = 1; i <= 32; i++){// 根據索引獲取實體cl_entity_t* entity = gEngfuncs.GetEntityByIndex(i);if (!entity || !entity->model)continue;// 自己不用畫if (entity == local)continue;// 取得實體的坐標,為了和selfPos單位統一,也要縮放20% vec2_t entPos;entPos[0] = (int)(entity->curstate.origin[0] * 0.2);entPos[1] = (int)(entity->curstate.origin[1] * 0.2);// 算出隊友相對于自己的坐標 vec2_t offset;offset[0] = entPos[0] - selfPos[0];offset[1] = entPos[1] - selfPos[1];// 繪制隊友小方塊(白色)// 自己的坐標 + 相對的隊友坐標 = 以自己為中心時隊友的坐標gEngfuncs.pfnFillRGBA(hudPos[0] + offset[0],hudPos[1] + offset[1],8, 8,255, 255, 255, 255);}return gExportfuncs.HUD_Redraw(time, intermission); }效果如下:
此時無論在地圖何處,自己的位置(紅色小方塊)始終顯示在HUD上(300,300)的位置,此時相對的隊友的位置(白色小方塊)位置也正確。
方向
在上面的例子中,你會發現無論在游戲中朝向哪里看,白色小方塊相對于紅色小方塊的方向都沒有任何改變。
而我們想要的效果是:自己前方的隊友應該顯示在紅色小方塊上面,自己后方的隊友應該顯示在小方塊的下面。
首先我們要知道自己朝向哪個角度:
int HUD_Redraw(float time, int intermission) {cl_entity_t* local = gEngfuncs.GetLocalPlayer();// angles使用歐拉角,詳情可以搜索相關資料。// 引擎的angles定義如下:// angles[0] = PITCH// angles[1] = YAW// angles[2] = ROLL// 我們只關心左右朝向,所以只看yawgEngfuncs.Con_Printf("yaw=%f\n", local->curstate.angles[1]);// ... }相關資料:
https://blog.csdn.net/modestbean/article/details/79135769假設你當前朝向YAW=0°,并且你的正前方有隊友A,那么此時坐標系如下圖:
保持朝向YAW=0°讓隊友A站在你正前方,白色小方塊(隊友A)應該在紅色小方塊上面。為了確認這件事,我們要運行測試代碼看看:
int HUD_Redraw(float time, int intermission) {cl_entity_t* local = gEngfuncs.GetLocalPlayer();float yaw = local->curstate.angles[1];// 引擎里YAW值的范圍是-180~180,我們可以轉換為0~360,小于0時加上360即可if (yaw < 0)yaw += 360;// 在HUD上取一點,顯示自己的位置const vec2_t hudPos = { 300, 300 };// 繪制自己的方塊(紅色)gEngfuncs.pfnFillRGBA(hudPos[0], hudPos[1],8, 8,255, 0, 0, 255);// 自己的坐標,通常來說三維坐標值都比較大,所以我們乘以0.2,也就是縮小到20% vec2_t selfPos;selfPos[0] = (int)(local->curstate.origin[0] * 0.2);selfPos[1] = (int)(local->curstate.origin[1] * 0.2);// 索引1到32都是玩家for (int i = 1; i <= 32; i++){// 根據索引獲取實體cl_entity_t* entity = gEngfuncs.GetEntityByIndex(i);if (!entity || !entity->model)continue;// 自己不用畫if (entity == local)continue;// 取得隊友實體的坐標,為了和selfPos單位統一,也要縮放20% vec2_t entPos;entPos[0] = (int)(entity->curstate.origin[0] * 0.2);entPos[1] = (int)(entity->curstate.origin[1] * 0.2);// 算出隊友相對于自己的坐標 vec2_t diffPos;diffPos[0] = entPos[0] - selfPos[0];diffPos[1] = entPos[1] - selfPos[1];gEngfuncs.Con_Printf("yaw=%f red=(%f,%f) white=(%f,%f)\n",yaw,0.0, 0.0,diffPos[0], diffPos[1]);// 繪制隊友小方塊(白色)gEngfuncs.pfnFillRGBA(hudPos[0]+diffPos[0],hudPos[1]+diffPos[1],8, 8,255, 255, 255, 255);}return gExportfuncs.HUD_Redraw(time, intermission); }運行結果如下:
YAW=0°
紅色小方塊坐標為(0,0)即以自己為中心。
白色小方塊坐標為(25,0)。
跟預想中的效果不一樣。在游戲中,我已經使YAW=0°,并且隊友站在正前方。此時白色小方塊應該在紅色小方塊上面,而它現在在右邊。
觀察白色小方塊的坐標值,可以發現它X軸值比紅色小方塊的大了25。
這說明三維坐標系里的XY軸并不能直接對應HUD二維坐標系的XY軸,我們需要做一點轉換。
為了讓白色小方塊在紅色小方塊上面,我們需要交換白色小方塊的XY軸。
交換之后白色小方塊的坐標為(0,25)。
它的X軸已經和紅色小方塊的X軸對應,都是0。
而它的Y軸卻比紅色小方塊的Y軸大了25,也就是說它會在紅色小方塊的下面,而我們想讓他在紅色小方塊上面。只需要把Y軸取反就行。
實際上,X軸也要取反。
代碼如下:
// 算出隊友相對于自己的坐標 vec2_t diffPos; diffPos[0] = entPos[0] - selfPos[0]; diffPos[1] = entPos[1] - selfPos[1];// 轉換坐標軸 vec2_t finalPos; finalPos[0] = diffPos[1]; finalPos[1] = diffPos[0]; finalPos[1] = -finalPos[1];// 繪制隊友小方塊(白色) gEngfuncs.pfnFillRGBA(hudPos[0] + finalPos[0],hudPos[1] + finalPos[1],8, 8,255, 255, 255, 255);嘗試前后左右走動,確定白色小方塊的坐標已經正確,如圖:
方向(二)
當你在游戲中稍微往右邊看,假設你向右旋轉了30°,小地圖上的隊友A的小方塊應該要稍微往左邊移動,如下圖:
也就是說,我們必須把隊友A的小方塊逆時針旋轉30°,這樣看起來才會正確。
為了計算旋轉后的坐標,我們需要一個函數,如下:
#define _USE_MATH_DEFINES #include <math.h>//---------- // in - 要旋轉的點 // o - 圓心點 // angle - 旋轉角度(單位為弧度,正為順時針,負為逆時針) // out - 旋轉后的點 //---------- void RotateByPoint(const vec2_t in, const vec2_t o, float angle, vec2_t out) {float ca = cos(angle);float sa = sin(angle);out[0] = (in[0] - o[0])*ca - (in[1] - o[1])*sa + o[0];out[1] = (in[0] - o[0])*sa + (in[1] - o[1])*ca + o[1]; }這個函數計算出二維坐標系中一個點繞另一個點旋轉指定角度后的新坐標。
那么現在開始寫旋轉的代碼:
// 轉換坐標軸 vec2_t finalPos; finalPos[0] = diffPos[1]; finalPos[1] = diffPos[0]; finalPos[1] = -finalPos[1];// 計算旋轉 float angle = yaw * (M_PI / 180.0); // 角度轉弧度 vec2_t o = { 0, 0 }; // 圓心點 vec2_t out; // 旋轉后的點 RotateByPoint(finalPos, o, angle, out);// 繪制隊友小方塊(白色) gEngfuncs.pfnFillRGBA(hudPos[0]+ out[0],hudPos[1]+ out[1],8, 8,255, 255, 255, 255);因為引擎的設定是:往右手邊轉,YAW就會減小。往左手邊轉,YAW就變大。
而RotateByPoint函數規定:angle值小了就逆時針轉,大了就順時針轉,所以越往右手邊轉,YAW值就越小,就越往逆時針方向轉。這剛好符合我們的需求,不用取反了。
所以我們直接傳yaw進去就行了。因為RotateByPoint函數要求angle的單位必須是弧度,所以我們先要把yaw的單位轉換為弧度。
運行結果如下:
嘗試往右看,白色小方塊就往另外一邊走了,一切順利 。
所以我們先把完整的代碼貼一下:
#include <metahook.h>cl_enginefunc_t gEngfuncs;int Initialize(struct cl_enginefuncs_s *pEnginefuncs, int iVersion) {memcpy(&gEngfuncs, pEnginefuncs, sizeof(gEngfuncs));return gExportfuncs.Initialize(pEnginefuncs, iVersion); }void HUD_Init(void) {return gExportfuncs.HUD_Init(); }#define _USE_MATH_DEFINES #include <math.h>//---------- // in - 要旋轉的點 // o - 圓心點 // angle - 旋轉角度(單位為弧度,正為順時針,負為逆時針) // out - 旋轉后的點 //---------- void RotateByPoint(const vec2_t in, const vec2_t o, float angle, vec2_t out) {float ca = cos(angle);float sa = sin(angle);out[0] = (in[0] - o[0])*ca - (in[1] - o[1])*sa + o[0];out[1] = (in[0] - o[0])*sa + (in[1] - o[1])*ca + o[1]; }int HUD_Redraw(float time, int intermission) {cl_entity_t* local = gEngfuncs.GetLocalPlayer();float yaw = local->curstate.angles[1];// 引擎里YAW值的范圍是-180~180,我們可以轉換為0~360,小于0時加上360即可if (yaw < 0)yaw += 360;// 在HUD上取一點,顯示自己的位置const vec2_t hudPos = { 300, 300 };// 繪制自己的方塊(紅色)gEngfuncs.pfnFillRGBA(hudPos[0], hudPos[1],8, 8,255, 0, 0, 255);// 自己的坐標,通常來說三維坐標值都比較大,所以我們乘以0.2,也就是縮小到20% vec2_t selfPos;selfPos[0] = (int)(local->curstate.origin[0] * 0.2);selfPos[1] = (int)(local->curstate.origin[1] * 0.2);// 索引1到32都是玩家for (int i = 1; i <= 32; i++){// 根據索引獲取實體cl_entity_t* entity = gEngfuncs.GetEntityByIndex(i);if (!entity || !entity->model)continue;// 自己不用畫if (entity == local)continue;// 取得隊友實體的坐標,為了和selfPos單位統一,也要縮放20% vec2_t entPos;entPos[0] = (int)(entity->curstate.origin[0] * 0.2);entPos[1] = (int)(entity->curstate.origin[1] * 0.2);// 算出隊友相對于自己的坐標 vec2_t diffPos;diffPos[0] = entPos[0] - selfPos[0];diffPos[1] = entPos[1] - selfPos[1];// 轉換坐標軸 vec2_t finalPos;finalPos[0] = diffPos[1];finalPos[1] = diffPos[0];finalPos[1] = -finalPos[1];// 計算旋轉float angle = yaw * (M_PI / 180.0); // 角度轉弧度vec2_t o = { 0, 0 }; // 圓心點vec2_t out; // 旋轉后的點RotateByPoint(finalPos, o, angle, out);// 繪制隊友小方塊(白色)gEngfuncs.pfnFillRGBA(hudPos[0]+ out[0],hudPos[1]+ out[1],8, 8,255, 255, 255, 255);}return gExportfuncs.HUD_Redraw(time, intermission); }?調整
經過以上的操作,就已經完成了一個最簡單的雷達:
你可以繼續修改上面的代碼,讓你的雷達(1.0)變得更好看一些。
?
轉載于:https://www.cnblogs.com/crsky/p/9341842.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
- 上一篇: MAC安装mysql8.0.11以及修改
- 下一篇: python day-15 匿名函数