【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 -> 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里进行清理。

注:你可以任意选择一种判断方法,如果你选择 GROUP_OP_NAND 你可以把 player 和 entities 的groupinfo都设为同一个值,计算结果将不为0,实体将不会加入数组。

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里的,不然你就无法站在地上了,会往下掉)

二、非玩家实体之间的穿透

非玩家实体之间的碰撞,大多数情况是 SOLID_BBOX SOLID_SLIDEBOX SOLID_BPS 这些有Hull的实体之间的碰撞,例如一个箱子可以叠在另一个箱子上。

由于引擎实现的原因,使用groupinfo来控制显得比较困难,所以引擎也提供了专用的接口,即 ShouldCollide 接口 ,参考:http://tieba.baidu.com/p/5384669344 这里不多做介绍了。

三、Trace系列函数中groupinfo的使用

我们经常用TraceLine TraceHull等方法来检查是否能击中一个目标,我们知道如果Trace从一个实体内部出发(作为起点),我们必须要在ignoreEntty写上该实体,以忽略它本身,否者Trace将会被该实体本身挡住。

此用法通常来说没有什么大问题,但是有些时候,我们希望忽略许多实体(例如模拟射击时不让Trace检测到队友),但ignoreEntity参数只有一个,这可麻烦了。

此时groupinfo再次派上用场,与上面的PlayerMove类似,我们需要在Trace前设置想忽略的实体的groupinfo,但我们上面的PlayerMove是以一个玩家作为基准,来对比其它实体的,那么TraceLine这些函数

是以什么作为基准呢?答案就是Trace函数的ignoreEntity参数。这个参数所指定的实体将会被Trace函数用作对比其它实体的基准,类似上文中的PM一样,我们只需要在Trace前随便选一个需要忽略的实体,并且像PM

一样设置好groupinfo,即可忽略任何想忽略的实体,例如:

g_engfuncs.pfnSetGroupMask( 0, GROUP_OP_AND );

// me 表示自己
me->pev->groupinfo = ( 1<<1 );

for ( int i = 0; < num; i++ )
{
    // friends里是队友们
    friends[i]->groupinfo = ( 1<<2 );
}

// me传到ignoreEntity参数,所以会被Trace忽略掉
//  然后Trace还会拿me的groupinfo和其它碰撞到的实体的groupinfo进行对比

TraceLine( start, end, ..., me, &result );

// 不要忘了清理
me->pev->groupinfo = 0;

for ( int i = 0; < num; i++ )
{
    friends[i]->groupinfo = 0;
}

有了如上的代码,你就可以让Trace函数忽略任何想忽略的实体,是不是非常⑥呢?

原文地址:https://www.cnblogs.com/crsky/p/7744854.html