温馨提示:使用PC端浏览器阅读可获得最佳体验

阅读本文时,请时不时就对照参考图看一下。

什么是overview?

如果你有使用过3D模型制作工具,例如3dsMax等等,在编辑模型时这些软件通常会展示四个视图:

  • 前视图
  • 左视图
  • 顶视图
  • 透视图

overview类似与顶视图。

HL引擎可以给任意一张地图生成overview,为了生成overview,你需要加上 -dev 参数来启动游戏,进入任意一张地图,然后在控制台输入 dev_overview 1 即可。

你会看到游戏画面变成了类似上图这样,这正是当前地图的overview,也就是顶视图。

同时我们还需要注意一下画面顶部显示的一些参数。

Overview: Zoom 1.57, Map Origin (-248.00, 16.00, 192.00), Z Min 673.00, Z Max -296.00, Rotated 0

然后把这个画面截图,就获得了一张overview图片(下文简称OV图),HL和CS已经制作好了一些,它们放在 valve/overviews 或者 cstrike/overviews 目录下。

生成overview的原理

为了正确使用OV图,我们有必要了解一下它是怎么生成的。

我制作了一个简单的地图模型:

上图坐标系中:

横向为 X轴 ,纵向为 Y轴 。

蓝色矩形是 世界区域 ,位置以 O 为中心,大小为 8192×8192 ,这个大小是引擎规定的。 O 是 世界中心点 。

紫色矩形是 overview区域 (下文简称 OV区域 ),位置和大小由地图作者制作的地图决定,引擎会自动计算,图中大小为 3000×3000 。 ORIGIN 是 OV区域 的中心点。

灰色区域是地图模型,仅仅作为观赏用。

当你输入 dev_overview 1 后,引擎就会把 OV区域 显示到游戏窗口:

假设 游戏窗口 大小(不包含窗口的边框)为 800×800 ,那么引擎就是把 OV区域 缩小到 800×800 来显示了。

缩小倍数

再次提醒, OV图 实际上就是 游戏窗口 的截图,所以它们的大小是相同的。

继续之前,需要先了解坐标系统, OV区域 使用 世界坐标 。而 游戏窗口 使用 窗口坐标 。

世界坐标最小值: x = -4096, y = -4096  世界矩形左下角
世界坐标最大值: x = +4096, y = +4096 世界矩形右上角
窗口坐标最小值: x =   0, y =   0  窗口左上角
窗口坐标最大值: x = 800, y = 800 窗口右下角

我们首先需要关注的是,引擎做了一个缩操作。

引擎把 OV区域 缩小到 游戏窗口 大小,也就是 3000×3000 缩小到 800×800 ,我们只需要知道引擎缩小了多少倍,就能将 世界坐标 单位转换为 窗口坐标 单位。

计算方法很简单:

scale.x = overview.width ÷ window.width
scale.y = overview.height ÷ window.height

代入参考图中的数据:

scale.x = 3000 ÷ 800  = 3.75
scale.y = 3000 ÷ 800 = 3.75

参考点

参考图中 P 的坐标是 800,1400 ,这是 世界坐标 ,以 世界中心点 作为参考点。但引擎缩小 OV区域 的时候,显然不是以 世界中心点 为中心缩放的。

引擎会以 ORIGIN 为中心来缩小 OV区域 。这意味着,如果我们要缩小 P 的坐标,就不能以 世界中心点 为参考点来缩小,否则会产生错位。

既然如此,那就把 P 的参考点也变成 ORIGIN 不就行了。

我们已经知道 OV区域 的 中心点 是 ORIGIN ,那就可以计算出 P 以 OV区域 的 中心点 为参考点的新坐标了。

计算如下:

P2.x = P.x - ORIGIN.x
P2.y = P.y - ORIGIN.y

代入参考图中的数据:

P2.x = 800 - 500   = 300
P2.y = 1400 - 500 = 900

好了,现在让我们忘掉 世界中心点 吧。现在 ORIGIN 才是 中心点 。

然后我们把 P2 的坐标按照上文中计算出来的缩小倍数来缩小,就能得到 P2 的 缩小后的OV区域 坐标 。

P3.x = P2.x ÷ scale.x
P3.y = P2.y ÷ scale.y

代入参考图中的数据:

P3.x = 300 ÷ 3.75  = 80
P3.x = 900 ÷ 3.75 = 240

此时 P3 坐标的单位已经和 窗口坐标 单位一致。

缩小后的OV区域 和 缩小后的P2的坐标 如下:

(什么?地形变了?那是因为我重新画过了-.-)

但是别忘了, 窗口坐标 的坐标值范围是:

窗口坐标最小值: x =   0, y =   0  窗口左上角
窗口坐标最大值: x = 800, y = 800 窗口右下角

而我们上面计算出的 P3 是以 0,0 为参考点的,显然 窗口坐标 的 中心点 不是 0,0 ,我们需要计算出来。

计算 窗口坐标 的 中心点 如下:

O2.x = window.width ÷ 2
O2.y = window.height ÷ 2

代入参考图中的数据:

O2.x = 800 ÷ 2  = 400
O2.y = 800 ÷ 2 = 400

如下图:

然后我们把 P3 的参考点转为 窗口坐标 的 中心点 ,如下:

P4.x = O2.x + P3.x
P4.y = O2.y + P3.y

代入参考图中的数据:

P4.x = 400 + 80 = 480
P4.y = 400 - 240 = 160

得到最终P4的坐标:

因为 OV图 就是 游戏窗口 的截图,所以 P4 在 OV图 中也是一样的坐标。

计算OV区域的大小

如果你还记得

Overview: Zoom 1.57, Map Origin (-248.00, 16.00, 192.00), Z Min 673.00, Z Max -296.00, Rotated 0

你应该会注意到这里并没有提供 OV区域 的大小,而 OV区域 的大小是我们最终计算出 P4 所必需的。

为此,引擎提供了 Zoom 参数。它的计算方法如下:

zoom.x = 世界区域.width ÷ overview.width
zoom.y = 世界区域.height ÷ overview.height

代入参考图中的数据:

zoom.x = 8192 ÷ 3000  = 2.73
zoom.y = 8192 ÷ 3000 = 2.73

我们已经知道 世界区域 的大小,因此计算出 OV区域 的大小非常容易:

overview.width = 世界区域.width ÷ zoom.x
overview.height = 世界区域.height ÷ zoom.y

代入参考图中的数据:

overview.width = 8192 ÷ 2.73  = 3000
overview.height = 8192 ÷ 2.73 = 3000

OV区域的中心点

OV区域 的 中心点 (即 ORIGIN )也是必须的,所以引擎提供了这个值:

Overview: Zoom 1.57, Map Origin (-248.00, 16.00, 192.00), Z Min 673.00, Z Max -296.00, Rotated 0

编写代码

有了计算方法,和必需的已知条件,我们就可以开始写代码了。

定义一个结构体 overview_t 来组织 OV图 的数据:

typedef struct {
GLuint textureId; // GL纹理ID
GLuint width; // OV图宽度
GLuint height; // OV图高度
GLfloat zoom; // 用于计算OV区域大小
GLfloat originX; // OV区域中心点X坐标
GLfloat originY; // OV区域中心点Y坐标
} overview_t;

定义一个变量 g_overview 来存储 OV图 的数据:

overview_t g_overview;

void loadOverviewImage() {
// 这两个函数请自己搞定
loadTexture("overviews/cs_italy.tga", &g_overview.textureId, &g_overview.width, &g_overview.height);
loadInfo("overviews/cs_italy.txt", &g_overview.zoom, &g_overview.originX, &g_overview.originY);
}

将 OV图 绘制到HUD上:

void HUD_Redraw() {
gExportfuncs.HUD_Redraw(); RECT rc;
rc.left = ;
rc.top = ;
rc.right = rc.left + g_overview.width;
rc.bottom = rc.top + g_overview.height; glBindTexture(GL_TEXTURE_2D, g_overview.textureId);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS);
// -------- 左上角 -------
glTexCoord2f(0.0f, 0.0f);
glVertex2f(rc.left, rc.top);
// -------- 右上角 -------
glTexCoord2f(1.0f, 0.0f);
glVertex2f(rc.right, rc.top);
// -------- 右下角 -------
glTexCoord2f(1.0f, 1.0f);
glVertex2f(rc.right, rc.bottom);
// -------- 左下角 -------
glTexCoord2f(0.0f, 1.0f);
glVertex2f(rc.left, rc.bottom);
glEnd();
}

计算 OV区域 大小:

typedef struct {
GLfloat width;
GLfloat height;
} SIZE_t; SIZE_t overview_size;
overview_size.width = 8192.0f / g_overview.zoom;
overview_size.height = 8192.0f / g_overview.zoom / 1.3333; // 4÷3=1.3333

你应该注意到了计算 OV区域 高度时,额外再除了一个 1.3333 ,这是因为引擎只给出了 zoom.x 和 zoom.y 的其中一个。

引擎总是认为,游戏窗口的宽度一定会大于高度(比例是4:3),而 OV区域 总是正方形。

为了保证生成 OV区域 显示在游戏窗口中不会变形(把正方形拉成长方形显示肯定会变形呀),实际上缩小 OV区域 的高度时会缩得比宽度更多一点。

所以我们计算 OV区域 的高度时,也要这么做。

补充:无论实际 游戏窗口 的宽高是多少,显示 OV区域 时,引擎都始终认为宽高比例是 : ,所以写固定的 1.333 就行了。如果你用宽屏模式去查看 OV区域 ,将会是变形的(被拉宽了)。

计算缩小比例:

float scaleX = overview_size.width / g_overview.width;
float scaleY = overview_size.height / g_overview.height;

取一个 世界坐标 来测试:

typedef struct {
GLfloat x;
GLfloat y;
} POINT_t; cl_entity_t* local = gEngfuncs.GetLocalPlayer(); // 取本机客户端对应的玩家实体 POINT_t P;
P.x = local->curstate.origin[]; // X
P.y = local->curstate.origin[]; // Y

将 P 的 参考点 转换为 OV区域 的 中心点 :

POINT_t P2;
P2.x = P.x - g_overview.originX;
P2.y = P.y - g_overview.originY;

将 世界坐标 的 单位 转换为 窗口坐标 的 单位 :

POINT_t P3;
P3.x = P2.x / scaleX;
P3.y = P2.y / scaleY;

计算 窗口坐标 的 中心点 :

POINT_t overview_image_origin;
overview_image_origin.x = g_overview.width / 2.0f;
overview_image_origin.y = g_overview.height / 2.0f;

将 P3 的 参考点 转换为 窗口坐标 的 中心点 :

POINT_t P4;
P4.x = overview_image_origin.x + P3.x;
P4.y = overview_image_origin.y - P3.y;

绘制 P4 到HUD上:

gEngfuncs.pfnFillRGBA(P4.x - , P4.y - ,  // X,Y
, , // width,height
, , , ); // R,G,B,A

参考代码

typedef struct {
GLfloat x;
GLfloat y;
} POINT_t; typedef struct {
GLfloat width;
GLfloat height;
} SIZE_t; typedef struct {
GLuint textureId; // OV图文理ID
GLuint width; // OV图宽度
GLuint height; // OV图高度
GLfloat zoom; // 用于计算OV区域大小
GLfloat originX; // OV区域中心点X坐标
GLfloat originY; // OV区域中心点Y坐标
bool rotated; // OV区域是否需要旋转
} overview_t; overview_t g_overview = { }; void HUD_Init(void)
{
gExportfuncs.HUD_Init(); LoadTexture("overviews/cs_siege.tga",
&g_overview.textureId,
&g_overview.width,
&g_overview.height); LoadInfo("overviews/cs_siege.txt",
&g_overview.zoom,
&g_overview.originX,
&g_overview.originY,
&g_overview.rotated);
} int HUD_Redraw(float time, int intermission)
{
gExportfuncs.HUD_Redraw(time, intermission); RECT rc;
rc.left = ;
rc.top = ;
rc.right = rc.left + g_overview.width;
rc.bottom = rc.top + g_overview.height; // ------------- 把OV图绘制到HUD上 -------------
glBindTexture(GL_TEXTURE_2D, g_overview.textureId);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS);
// -------- 左上角 -------
glTexCoord2f(0.0f, 0.0f);
glVertex2f(rc.left, rc.top);
// -------- 右上角 -------
glTexCoord2f(1.0f, 0.0f);
glVertex2f(rc.right, rc.top);
// -------- 右下角 -------
glTexCoord2f(1.0f, 1.0f);
glVertex2f(rc.right, rc.bottom);
// -------- 左下角 -------
glTexCoord2f(0.0f, 1.0f);
glVertex2f(rc.left, rc.bottom);
glEnd(); // -------------- 计算OV区域大小 ---------------
SIZE_t overview_size;
overview_size.width = 8192.0f / g_overview.zoom;
overview_size.height = 8192.0f / g_overview.zoom / 1.3333f; // --------------- 计算缩小比例 ----------------
float scaleX = overview_size.width / g_overview.width;
float scaleY = overview_size.height / g_overview.height; // --------------- 取自己的坐标 ----------------
cl_entity_t* local = gEngfuncs.GetLocalPlayer();
POINT_t P;
P.x = local->curstate.origin[];
P.y = local->curstate.origin[]; // ------- 将P的参考点转为OV区域的中心点 --------
POINT_t P2;
P2.x = P.x - g_overview.originX;
P2.y = P.y - g_overview.originY; // ------- 将P2的坐标单位转为窗口坐标单位 -------
POINT_t P3;
P3.x = P2.x / scaleX;
P3.y = P2.y / scaleY; // -------------- 计算OV图中心点 ---------------
POINT_t overview_image_origin;
overview_image_origin.x = g_overview.width / 2.0f;
overview_image_origin.y = g_overview.height / 2.0f; // -------- 将P3的参考点转为OV图的中心点 --------
POINT_t P4;
if (g_overview.rotated) {
P4.x = overview_image_origin.x + (P3.x);
P4.y = overview_image_origin.y + (-P3.y);
} else {
P4.x = overview_image_origin.x + (-P3.y);
P4.y = overview_image_origin.y + (-P3.x);
} // -------------- 把P4绘制到HUD上 --------------
gEngfuncs.pfnFillRGBA(rc.left + (P4.x - ), rc.top + (P4.y - ), // X,Y
, , // width,height
, , , ); // R,G,B,A return ;
}

载入HL的OV的配置文件

bool LoadOverviewInfo(const char* fileName, overview_t* data) {
char* buffer = (char*)gEngfuncs.COM_LoadFile((char*)fileName, , nullptr);
if (!buffer) {
return false;
}
char* parsePos = buffer;
char token[];
bool parseSuccess = false;
while (true) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
if (!parsePos) {
break;
}
if (!stricmp(token, "global")) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
if (!parsePos) {
goto error;
}
if (strcmp(token, "{")) {
goto error;
}
while (true) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
if (!parsePos) {
goto error;
}
if (!stricmp(token, "zoom")) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
data->zoom = atof(token);
}
else if (!stricmp(token, "origin")) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
data->originX = atof(token);
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
data->originY = atof(token);
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
}
else if (!stricmp(token, "rotated")) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
data->rotated = atoi(token) != ;
}
else if (!stricmp(token, "}")) {
break;
}
else {
goto error;
}
}
}
else if (!stricmp(token, "layer")) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
if (!parsePos) {
goto error;
}
if (strcmp(token, "{")) {
goto error;
}
while (true) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
if (!stricmp(token, "image")) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
strcpy(data->image, token);
}
else if (!stricmp(token, "height")) {
parsePos = gEngfuncs.COM_ParseFile(parsePos, token);
}
else if (!stricmp(token, "}")) {
break;
}
else {
goto error;
}
}
}
else {
goto error;
}
}
parseSuccess = true;
error:
if (buffer) {
gEngfuncs.COM_FreeFile(buffer);
}
return parseSuccess;
}

【HLSDK系列】overview(俯视图)的更多相关文章

  1. 【HLSDK系列】groupinfo的基本用法

    如果你经常写AMXX,你应该会知道有个 pev->groupinfo 变量,但我猜大部分人都不会用这个变量,这个变量涉及很多实体处理功能,下面列举几个最常用的. ① 玩家与非玩家实体之间的碰撞检 ...

  2. 【HLSDK系列】HL引擎入门篇

    如果你打算拿HL的源码(也就是HLSDK)来改出一个自己的游戏,那你就非常有必要理解一些HL引擎的工作方式. HL引擎分成两个部分,服务端和客户端.服务端管理所有玩家的状态和游戏规则,客户端负责显示U ...

  3. 【HLSDK系列】服务端实体 edict_t 和 控制类

    我们来了解一下引擎是怎么管理实体的吧!我们这里就说说服务端的实体(edict_t) 服务端用 edict_t 这个结构体来保存一个实体,可以说一个 edict_t 就是一个 服务端实体,下文简称实体. ...

  4. 【HLSDK系列】怎么增加一种新实体

    你平常肯定接触到很多比如 info_player_start hostage info_target 之类的实体,这里就解释一下怎么创建一种新的实体. 首先建立一个新的 .h 文件(当然你写在现有的文 ...

  5. 【HLSDK系列】Delta 详解

    服务端和客户端总是需要互相交换数据,来做到实时的游戏体验. 很久之前,我们的网速都不是很快,甚至带宽只有 1Mbps (128KB/s)这样的速度,作为当时一个网络实时对战游戏,每时每刻都要传递数据, ...

  6. 【HLSDK系列】服务端 AddToFullPack 函数

    服务端会给客户端发送一些数据,其中两大种类数据是 clientdata_t 和 entity_state_t 这里我们说说 entity_state_t 这个结构体. 你在丢在地上的枪.C4等等是服务 ...

  7. 【HLSDK系列】服务端 UpdateClientData 函数

    首先说明下,这个函数是写在 mp.dll 里的. 服务器会给每个客户端发送一些数据,其中两大数据种类就是 clientdata_t 和 entity_state_t 这里要说的是 clientdata ...

  8. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  9. 用SignalR 2.0开发客服系统[系列4:负载均衡的情况下使用SignalR]

    前言 交流群:195866844 目录: 用SignalR 2.0开发客服系统[系列1:实现群发通讯] 用SignalR 2.0开发客服系统[系列2:实现聊天室] 用SignalR 2.0开发客服系统 ...

随机推荐

  1. .Net Core和.Net Standard直观理解

    .NET framework和.NET Core里面有一些部分,内容是相同的. 这部分相同的内容,就被称为标准库...即NET Standard Library. 而那些不同的部分,则分别叫做.NET ...

  2. 关于恶意说说自动在QQ空间转发的机制

    有些很讨厌的带链接说说,只要你在手机打开它,就会自动转发,内容极其不雅 一怒之下我决定看个究竟首先,在此页开头有此关键语句: <iframe src="http://rtb.map.q ...

  3. loadrunner脚本编写经验

    最近写了不少loadrunner脚本,记录一下心得:1 loadrunner脚本基本可以认为就是c语言代码(loadrunner支持不同语言的脚本,默认生成的是用c语言写的脚本)2 loadrunne ...

  4. renren_fast性能测试平台的安装部署

    1.从GitHub下载源码: https://github.com/zyanycall/stressTestPlatform git clone https://github.com/zyanycal ...

  5. vue-scroller实现vue单页面的上拉加载和下拉刷新问题

    在vue中如何简单的实现页面的上拉加载和下拉刷新,在这里我推荐使用vue-scrolle插件. vue-scrolle的基本使用方法: 1.下载 npm i vue-scroller -D 2.导包 ...

  6. C语言学习之路之基础变量

    Hello,大家好,今天又和大家见面了!前两天,我看到了几款游戏引擎渲染效果的对比的视频,https://www.bilibili.com/video/av5113296?from=search&am ...

  7. .Net 如何访问主流的各大数据库

    做过开发的都知道,.NET基本可以理解是和MSSQL,windows服务器属于一个好的搭档,正如PHP和MYSQL,LIUNX等也可以理解是一个完美搭配:但是在实际的开发中并不完全是这样的,如果你是学 ...

  8. Vs2012 编写代码规则

    FxCop编写规则 VS2012 下更方便,所需的DLL在: D:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Static ...

  9. (第六周)课上Scrum站立会议演示

    组名:连连看 组长:张政 组员:张金生.李权.武志远 时间:2016.10.13   20:20——20:40 会议内容: 已完成的内容: 1.选定编译语言,安装软件并配置环境,完成了游戏的基本模型. ...

  10. Scrum Meeting 7 -2014.11.13

    之前srcum没写好是我的错.以后会每天更新的. 老师反映之前项目小组从pdf中提取作者效果不好,我们讨论决定进行一定的优化.在整合测试的同时开始服务器程序部署. Member Today’s tas ...