1. 程式人生 > >【HLSDK系列】服務端實體 edict_t 和 控制類

【HLSDK系列】服務端實體 edict_t 和 控制類

武器 .cpp 成員 常用 blog 生成 指針 但是 dll

我們來了解一下引擎是怎麽管理實體的吧!我們這裏就說說服務端的實體(edict_t)

服務端用 edict_t 這個結構體來保存一個實體,可以說一個 edict_t 就是一個 服務端實體,下文簡稱實體。

我們在 mp.dll 的源碼裏經常看到的那些 CBaseXXX 又和 edict_t 有什麽關系呢?

引擎只管理小部分實體的功能,更多功能需要我們自己寫代碼去實現,這裏就引入了 實體控制類 這個東西(就是那些 CBaseXXX),類就是C++的那個類。下文簡稱控制類。

接下來我們就分析 edict_t 到底是怎麽跟 控制類 掛上勾的。

我們通常用 CREATE_NAMED_ENTITY( MAKE_STRING("weapon_mp5") ); 來創建一個 weapon_mp5 的武器實體,那我們就來分析這個函數到底做了什麽吧!

1. 用戶調用 CREATE_NAMED_ENTITY。

2. 引擎在 mp.dll (的導出函數)裏查找名為“weapon_mp5”的函數。(你可能會有疑問:我從來沒寫過這個函數啊?別急,下文分析)

3. 引擎調用“weapon_mp5”函數來創建出一個CMP5類實例。“weapon_mp5”還調用了 CREATE_ENTITY 來創建出一個 edict_t。(用數學老師的話說:CMP5就是實體weapon_mp5的控制類)

4. 引擎把實例的指針賦值到 edict_t 的 pvPrivateData 成員變量裏。

5. 引擎返回 edict_t 給用戶。

看了上面的步驟,你一定註意到非常關鍵的一步,“weapon_mp5”函數到底是怎麽一回事。

打開 mp5.cpp 你會發現有一行

LINK_ENTITY_TO_CLASS( weapon_mp5, CMP5 );

這行就是關鍵,它會生成一個函數,這個函數起了類似如下代碼的作用:

註:實際上不是這麽簡單的,只是為了更容易理解。

CMP5 *weapon_mp5()
{
    return new CMP5();
}

再往回看上面的步驟3和4,能理解了吧。

引擎先創建一個 edict_t 然後又 new CMP5 把指針存到 pvPreivateData 這個變量裏,到此一個實體就創建出來了。

然後我們還要了解控制類是怎麽工作的。首先請你打開 cbase.cpp 你會看到一堆 Dispatch 開頭的函數,下文簡稱派遣函數。

派遣函數用來幹嘛呢?當一個實體要 Think 的時候,引擎就會調用 mp.dll 裏的 DispatchThink 這個函數,它有一個參數 edict_t *pent 就是要 Think 的實體!

接著才是關鍵!

我們來看 DispatchThink 的源碼:

void DispatchThink( edict_t *pent )
{
    CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent);
    if (pEntity)
    {
        // ...
        pEntity->Think();
    }
}

順便還有 GET_PRIVATE 的源碼:

inline void *GET_PRIVATE( edict_t *pent )
{
    if ( pent )
        return pent->pvPrivateData;
    return NULL;
}

我們可以看到它獲取了 edict_t 裏面的 pvPrivateData 變量,你一定還記得這個變量是怎麽來的吧!不記得請馬上往回看!

沒錯,之前引擎創建實體的時候,把 控制類 的 指針 存這變量裏了,我們這裏就把這個 控制類 拿出來而已!

接著它檢查了一下 控制類 是不是 NULL,然後它在 if 裏面調用了 控制類 的 Think 函數!

整個過程就是這樣的:引擎 -> 派遣函數 -> 控制類 也就是說,引擎是不管 控制類 的,為讓 控制類 工作,我們還需要在派遣函數裏寫東西(雖然HLSDK已經寫好了,但是你一定要去看看他是怎麽寫的)。

如果你寫過 AMXX 你肯定會認識 FM_Think FM_Spawn 這些東西,它們就是HOOK了這些派遣函數!

本來還想仔細講解 LINK_ENTITY_TO_CLASS 的,留到下一篇文章吧!

【HLSDK系列】服務端實體 edict_t 和 控制類