1. 程式人生 > >【HLSDK系列】groupinfo的基本用法

【HLSDK系列】groupinfo的基本用法

全部 int solid src mask str 返回 正常 服務

如果你經常寫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

-> PlayerPostThink -> CmdEnd

我們現在已經知道引擎是什麽時候準備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的基本用法