【HLSDK系列】groupinfo的基本用法
如果你經常寫AMXX,你應該會知道有個 pev->groupinfo 變量,但我猜大部分人都不會用這個變量,這個變量涉及很多實體處理功能,下面列舉幾個最常用的。
① 玩家與非玩家實體之間的碰撞檢測
② 非玩家實體之間的碰撞檢測
③ Trace系列檢測函數的目標過濾
下面我一個個介紹這些功能具體怎麽實現和利用。
一、玩家與非玩家實體之間的碰撞檢測(包括玩家與玩家)
玩家的移動處理是在pm_shared.c裏PM_Move函數實現的(PM意為PlayerMove,HLSDK包含此文件),如果你走路撞上一些實體或者地圖的墻體,你會無法前進,被擋住,這是因為
PM檢查到你前方有障礙物,不讓你繼續前進。那麽PM是怎麽實現這個檢查的呢?原來引擎為PM提供了一個專用的Trace函數,叫做PM_PlayerTrace,這個函數可以
模擬一個玩家大小的BOX朝指定位置移動,並返回途中碰到的實體,如果途中碰到了某個實體,這個實體正是“障礙物”,那麽你將被這個障礙物阻擋,無法繼續前進。
像下圖這樣:
藍色的Box是玩家的Hull(Hull在HL引擎中意為碰撞盒子),huang色Box是某個實體或者地圖的Hull。具體實現原理讓我們暫時忽略吧,如果有需要可以留言。
如果我們有一些特殊的需求,例如:讓玩家不要碰到某些實體(就是讓玩家可以穿過某些實體),這怎麽辦呢?沒事,引擎為我們留下了可以定制的接口。
首先我們要進一步了解PM(PlayerMove)這個東西,引擎每次只處理一個玩家,也就是說,引擎會依次給每個玩家調用PM_Move函數來實現每個玩家的移動處理,
調用PM_Move函數之前,引擎會先準備一個叫做physents的數組(這個數組在pm_defs.h文件裏的playermove_t結構體裏定義),而調用PM_PlayerTrace來檢查
障礙物時,PM_PlayerTrace只會檢查physents裏的實體,聰明的你已經猜到了,要想讓玩家穿過某些實體,這些實體就一定不能出現在physents這個數組裏。
這個數組由引擎管理的,我們必須按照引擎的規定來處理這個數組(歪門邪道快離開)。我們有必要了解一下PM_Move是在什麽時機執行的,如下所示:
CmdStart -> PlayerPreThink -> [填充physents數組] -> PM_Move
我們現在已經知道引擎是什麽時候準備physents數組了,有個非常好的消息是,引擎填充這個數組的時候,提供了一種方法讓我們可以控制哪些實體可以被填充
到這個數組裏(默認是全部pev->solid不為SOLID_NOT和SOLID_TRIGGER的實體)。這就是 groupinfo 派上用場的一個地方。
引擎會逐個檢查所有pev->solid符合上述條件的實體的groupinfo,與玩家的(再次註意!引擎每次只處理一個玩家!所以請把這裏的“玩家”當成“自己”)groupinfo
進行對比,如果符合條件,這個實體就會被加入到physents數組裏。引擎使用位與(&)運算符來計算實體的groupinfo和玩家的groupinfo,根據結果和判斷方法
來決定是否把實體加入數組。引擎定義了兩種判斷方法,使用 g_engfuncs.pfnSetGroupMask 函數的 op 參數來設置,分別是 GROUP_OP_AND 和 GROUP_OP_NAND,
默認為 GROUP_OP_AND 。
#define GROUP_OP_AND 0 #define GROUP_OP_NAND 1
void (*pfnSetGroupMask) ( int mask, int op );
如果是 GROUP_OP_AND ,兩個實體的groupinfo的計算結果為零,則不加入數組,代碼像這樣:
if ( mode == GROUP_OP_AND && (check->pev->groupinfo & player->pev->groupinfo) == 0 ) continue;
如果是 GROUP_OP_NAND,兩個實體的groupinfo的計算結果不為零,則不加入數組,代碼像這樣:
if ( mode == GROUP_OP_NAND && (check->pev->groupinfo & player->pev->groupinfo) != 0 ) continue;
如果你不懂位與(&)計算,那我就舉個簡單的例子:
int groupinfoA = (1<<1) int groupinfoB = (1<<1) // 結果 groupinfoA & groupinfoB > 0 ------------------------------------ int groupinfoA = (1<<1) int groupinfoB = (1<<2) // 結果 groupinfoA & groupinfoB = 0
上面我已經知道了引擎會在PlayerPreThink之後、PM_Move之前檢查那些要加入數組的實體,所以我們可以在PlayerPreThink的時候把該玩家要穿過的所有實體的groupinfo設置一個值,該玩家則設置一個不同的值,例如:
void PlayerPreThink( player ) { player->pev->groupinfo = ( 1<<1 ); for ( int i = 0; i < num; i++ ) { // 這個數組是要穿透的實體列表 entities[i]->pev->groupinfo = ( 1<<2 ); } }
我們使用 GROUP_OP_AND 判斷方法,所以要設置一下:
g_engfuncs.pfnSetGroupMask( 0, GROUP_OP_AND );
註:AMXX可以使用fakemeta模塊的engfunc函數
引擎執行完PlayerPreThink函數,我們就設置好了所有需要處理的實體的groupinfo,接著引擎就會檢查所有實體,因為我們知道 (1<<1) & (1<<2) 結果是等於0的,所以根據GROUP_OP_AND判斷方法,
與該玩家的groupinfo計算結果等於0的實體將不會加入physents數組,也就不會被PM_PlayerTrace檢測,自然也就不會成為“障礙”。(可以穿過)
當引擎執行完PM_Move之後,“玩家”已經移動了,你已經實現了目的,所以你必須要清理你設置過的實體的groupinfo,以免造成意外,可以在PlayerPostThink裏進行清理。
void PlayerPostThink( player ) { player->pev->groupinfo = 0; for ( int i = 0; i < num; i++ ) { entities[i]->pev->groupinfo = 0; } }
請註意!如果你要穿透一個玩家,你必須讓你穿透的那個玩家也穿透你,不然當你穿進去,與那個玩家的模型重疊,那麽你可以自由移動,但是那個玩家會卡住無法移動(非玩家並且可移動的實體同理),甚至可能會被GameRules Kill掉。
以上是服務端的處理辦法,此時已經可以正常進行穿透,但是還有些瑕疵,因為客戶端也會運行一份PM_Move(同樣也有一個physents數組),當客戶端運行PM_Move時,你上面處理的那些實體還是會被
加入客戶端的physents數組進行碰撞檢測。造成的結果就是,你可以穿透這些實體,但是穿過去的時候會“卡”一下因為服務端沒有檢查那個實體,但是客戶端卻檢查了它。這很不正常(除非你特意這樣做),為了不讓客戶端把我們已經穿
透的這些實體加入physents數組,我們要在 AddFullPack 函數裏把該實體的state->solid改成SOLID_NOT,這樣客戶端就不會把該實體加入physents數組了。
int AddToFullPack( state, e, ent, host, ... ) { // state 該state會被發送到host端 // e 當前要發送的實體索引(實體ID) // host state將會被發送到此host for ( int i = 0; i < num; i++ ) { // 這個entities還是該玩家需要穿透的實體數組,判斷當前發送的實體是否在列表裏 if ( e == host->entities[i] ) { state->solid = SOLID_NOT;
break; } } }
OK,一切處理完成,你可以完美穿越任何想要穿過的實體(除了World實體,因為World是無條件添加到physents裏的,不然你就無法站在地上了,會往下掉)
二、非玩家實體之間的穿透
【HLSDK系列】groupinfo的基本用法