【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) == )
continue;
如果是 GROUP_OP_NAND,两个实体的groupinfo的计算结果不为零,则不加入数组,代码像这样:
if ( mode == GROUP_OP_NAND && (check->pev->groupinfo & player->pev->groupinfo) != )
continue;
如果你不懂位与(&)计算,那我就举个简单的例子:
int groupinfoA = (<<)
int groupinfoB = (<<) // 结果
groupinfoA & groupinfoB > ------------------------------------ int groupinfoA = (<<)
int groupinfoB = (<<) // 结果
groupinfoA & groupinfoB =
上面我已经知道了引擎会在PlayerPreThink之后、PM_Move之前检查那些要加入数组的实体,所以我们可以在PlayerPreThink的时候把该玩家要穿过的所有实体的groupinfo设置一个值,该玩家则设置一个不同的值,例如:
void PlayerPreThink( player )
{
player->pev->groupinfo = ( << ); for ( int i = ; i < num; i++ )
{
// 这个数组是要穿透的实体列表
entities[i]->pev->groupinfo = ( << );
}
}
我们使用 GROUP_OP_AND 判断方法,所以要设置一下:
g_engfuncs.pfnSetGroupMask( , 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 = ; for ( int i = ; i < num; i++ )
{
entities[i]->pev->groupinfo = ;
}
}
请注意!如果你要穿透一个玩家,你必须让你穿透的那个玩家也穿透你,不然当你穿进去,与那个玩家的模型重叠,那么你可以自由移动,但是那个玩家会卡住无法移动(非玩家并且可移动的实体同理),甚至可能会被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 = ; 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( , GROUP_OP_AND ); // me 表示自己
me->pev->groupinfo = ( << ); for ( int i = ; < num; i++ )
{
// friends里是队友们
friends[i]->groupinfo = ( << );
} // me传到ignoreEntity参数,所以会被Trace忽略掉
// 然后Trace还会拿me的groupinfo和其它碰撞到的实体的groupinfo进行对比 TraceLine( start, end, ..., me, &result ); // 不要忘了清理
me->pev->groupinfo = ; for ( int i = ; < num; i++ )
{
friends[i]->groupinfo = ;
}
有了如上的代码,你就可以让Trace函数忽略任何想忽略的实体,是不是非常⑥呢?
【HLSDK系列】groupinfo的基本用法的更多相关文章
- Spring3系列5-Bean的基本用法
Spring3系列5-Bean的基本用法 本篇讲述了Bean的基本配置方法,以及Spring中怎样运用Bean. 主要内容如下: 一. Spring中Bean的相互引用 二. Sp ...
- 学习javascript基础知识系列第二节 - this用法
通过一段代码学习javascript基础知识系列 第二节 - this用法 this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在javascr ...
- SpringBoot系列之@Conditional注解用法简介
SpringBoot系列之@Conditional注解用法简介 引用Spring官方文档的说法介绍一下@Conditional注解:Spring5.0.15版本@Conditional注解官方文档 @ ...
- SpringBoot系列之外部配置用法简介
SpringBoot系列之外部配置用法简介 引用Springboot官方文档的说法,官方文档总共列举了如下用法: 1.Devtools global settings properties on yo ...
- Go基础系列:nil channel用法示例
Go channel系列: channel入门 为select设置超时时间 nil channel用法示例 双层channel用法示例 指定goroutine的执行顺序 当未为channel分配内存时 ...
- Go基础系列:双层channel用法示例
Go channel系列: channel入门 为select设置超时时间 nil channel用法示例 双层channel用法示例 指定goroutine的执行顺序 双层通道的解释见Go的双层通道 ...
- SpringBoot系列之YAML配置用法
1.全局配置 SpringBoot的全局配置文件有两种: application.properties application.yml 配置文件的作用:修改SpringBoot自动配置的默认值,主要是 ...
- SpringBoot系列之集成Thymeleaf用法手册
目录 1.模板引擎 2.Thymeleaf简介 2.1).Thymeleaf定义 2.2).适用模板 3.重要知识点 3.1).th:text和th:utext 3.2).标准表达式 3.3).Thy ...
- 精通awk系列(4):awk用法入门
回到: Linux系列文章 Shell系列文章 Awk系列文章 awk用法入门 awk 'awk_program' a.txt awk示例: # 输出a.txt中的每一行 awk '{print $0 ...
随机推荐
- 自适应浏览器分辨率的javascript函数[转]
function changeWidth(now,target) { //now是现在代码所适应的宽度,如800:target是想要达到的显示器分辨率宽度var widthStr; var flag ...
- 开箱即用 - Memcache缓存
废话少说,先上代码C# memcache Demo memcache 是服务器缓存系统,以键值对方式保存数据到内存中,把对象序列化后,理论上可支持所有的数据类型. 使用情景:怎么用都可以,注意的是它只 ...
- C++实现从一个文件夹中读出所有txt文件
前段时间做项目需要读取一个文件夹里面所有的txt文件,查询资料后得到以下实现方法:首先了解一下这个结构体struct _finddata_t { unsigned attrib; t ...
- TPO-23 C1 Post a student announcement
第 1 段 1.Listen to a conversation between a student and the director of campus activities. 请听一段学生与校园活 ...
- Dubbo问题处理集合
1 . 启动微服务的时候,报错信息如下: 核心:Can not lock the registry cache file /root/.dubbo/dubbo-registry-127.0.0.1.c ...
- 【LDAP安装】在已编译安装的PHP环境下安装LDAP模块
在已编译安装的PHP环境下安装LDAP模块 (乐维温馨提示:其他模块也能以这个方式安装) 1.在PHP源码包内找到ldap模块文件 cd php-5.6.37 cd ext/ldap/ 2.phpiz ...
- gulp: Did you forget to signal async completion? 解决方案
背景 学习gulp的前端自动化构建,按照示例代码,跑了一个简单的task,控制台打出如下提示: The following tasks did not complete: testGulp Did y ...
- 对于新手来说,Python 中有哪些难以理解的概念?
老手都是从新手一路过来的,提起Python中难以理解的概念,可能很多人对于Python变量赋值的机制有些疑惑,不过对于习惯于求根究底的程序员,只有深入理解了某个事物本质,掌握了它的客观规律,才能得心应 ...
- JQuery ajax请求struts action实现异步刷新的小实例
这个样例是用JQuery ajax和struts来做的一个小样例,在这个样例中采用两种方式将java Util中的list转换成支json的格式,第一种是用json-lib.jar这个jar包来转换, ...
- Spring Cloud限流思路及解决方案
转自: http://blog.csdn.net/zl1zl2zl3/article/details/78683855 在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Clo ...