• 【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函数忽略任何想忽略的实体,是不是非常⑥呢?

  • 相关阅读:
    新年初六
    新年初五
    新年初四
    Who moved my cheese?
    红螺寺踏春
    JSP基本语法--实例演练
    JSP基础语法--跳转指令 jsp:forward page
    JSP基本语法--包含指令<%@include file="路径"%> <jsp:include page>
    JSP基本语法--Page指令 <%@page 属性=”内容“%>
    JSP注释及scriptlet <%局部%><%!全局%><%=输出%>
  • 原文地址:https://www.cnblogs.com/crsky/p/7744854.html
Copyright © 2020-2023  润新知