建议读者对应 HGE 的官方的例子:Tutorial 02 - Using input, sound and rendering 来阅读本文
渲染:
在 HGE 中,四边形是一种图元,对应了结构体 hgeQuad,另外还有三角形图元,对应 hgeTriple,为了渲染,我们现在需要使用 hgeQuad 结构体,这个结构体如下:
struct hgeQuad
{
hgeVertex v[4]; // 顶点描述了这个四边形
HTEXTURE tex; // 纹理的句柄或者为0
int blend; // 混合模式(blending mode)
};
HGE 中图元对应的结构体总含有这3个部分:顶点,纹理句柄,混合模式
struct hgeVertex
{
float x, y; // 屏幕的 x,y 坐标
float z; // Z-order,范围 [0, 1]
DWORD col; // 顶点的颜色
float tx, ty; // 纹理的 x,y 坐标(赋值前需要规格化坐标间隔,使得 tx,ty 取值范围在[0,1])
};
规格化坐标间隔在后面的例子中会谈到。不过先要谈到的一点是 tx,ty 的值超过 1 也是合法的
1. 颜色的表示:
颜色使用32位表示,从左开始,8位为 Alpha 通道,8位红色,8位绿色,8位蓝色
对于后24位,如果全部为0,表示黑色,如果全部为1,表示白色
2. 定义颜色的运算:
我们把颜色看成一个四维向量,即 alpha 通道,红色,绿色,蓝色这四个分量
<1> 颜色是可以相乘的
颜色的相乘是对应的四个分量分别相乘的结果,即:alpha 通道的值与 alpha 通道的值相乘,红色的值与红色的值相乘,绿色的值与绿色的值相乘,蓝色的值与蓝色的值相乘。
<2> 颜色是可以相加的
同上,对应分量相加。
颜色的每个分量使用浮点数表示,范围是[0-1],相加操作可能导致溢出,一种处理的方式就是,如果溢出,则设定值为1。
3. 混合模式:
1)BLEND_COLORADD
表示顶点的颜色与纹理的纹元(texel)颜色相加,这使得纹理变亮,可见顶点颜色为 0x00000000 将不造成任何影响。
2)BLEND_COLORMUL
表示顶点的颜色与纹理的纹元颜色相乘,这使得纹理变暗,可见顶点颜色为 0xFFFFFFFF 将不造成任何影响。
注意:必须在1),2)中做一个选择,且只能选择1),2)中的一个。处理的对象是纹理颜色和顶点颜色。
这里有一个技巧:
如果我们需要在程序中显示一个气球,这个气球的颜色不断变化,这时候我们并不需要准备多张不同颜色的气球纹理,而只需要一张白色的气球纹理,设置 blend 为 BLEND_COLORMUL,白色的R,G,B值被表示成 1.0,也就是说,纹理颜色和顶点颜色相乘的结果是顶点的颜色,那么就可以通过修改顶点颜色,得到任意颜色的气球了。
3)BLEND_ALPHABLEND
渲染时,将对象的像素颜色(而非顶点的颜色)与当前屏幕的对应像素颜色进行 alpha 混合。alpha 混合使用到 alpha 通道,对于两个像素颜色进行如下操作,得到一个颜色:
R(C)=alpha*R(B)+(1-alpha)*R(A)
G(C)=alpha*G(B)+(1-alpha)*G(A)
B(C)=alpha*B(B)+(1-alpha)*B(A)
这里的BLEND_ALPHABLEND使用的是对象像素的颜色的 alpha 通道。可见如果对象像素颜色 alpha 通道为 0,那么结果就是只有当前屏幕的像素颜色,也就是常常说的 100% 透明,因此,我们可以理解 alpha 混合就是一个是图像透明的操作,0 表示完全透明,255 表示完全不透明。
4)BLEND_ALPHAADD
渲染时,将对象的像素颜色与当前屏幕的对应像素颜色相加,结果是有了变亮的效果。
注意:这里的3),4)必选其一,且只能选其一。处理的对象是对象像素颜色和屏幕像素颜色。选择 BLEND_ALPHABLEND 并且设定对象的 alpha 通道为 FF,可使此组参数无效。
5)BLEND_ZWRITE
渲染时,写像素的 Z-order 到 Z-buffer
6)BLEND_NOZWRITE
渲染时,不写像素的 Z-order 到 Z-buffer
这里一样是二者选一
设置举例:
quad.blend=BLEND_ALPHAADD | BLEND_COLORMUL | BLEND_ZWRITE; // quad 为 hgeQuad 变量
4. HGE 渲染
1)定义和初始化 hgeQuad 结构体:
hgeQuad quad; // 定义四边形
2)初始化 hgeQuad 变量:
// 设置混合模式
quad.blend=BLEND_ALPHAADD | BLEND_COLORMUL | BLEND_ZWRITE;
// 加载纹理
quad.tex = pHGE->Texture_Load("particles.png");
注意,读取硬盘上资源的时候,可能会失败,因此通常都需要检查,例如:
if (!quad.tex)
{
MessageBox(NULL, "Load particles.png", "Error", 0);
}
// 初始化顶点
for(int i=0;i<4;i++)
{
// 设置顶点的 z 坐标
quad.v[i].z=0.5f;
// 设置顶点的颜色,颜色的格式为 0xAARRGGBB
quad.v[i].col=0xFFFFA000;
}
// 这里假定载入的纹理大小为 128*128,现在截取由点(96,64),(128,64),(128,96),(96,96)这四个点围成的图形。
quad.v[0].tx=96.0/128.0; quad.v[0].ty=64.0/128.0; // 规格化坐标间隔
quad.v[1].tx=128.0/128.0; quad.v[1].ty=64.0/128.0;
quad.v[2].tx=128.0/128.0; quad.v[2].ty=96.0/128.0;
quad.v[3].tx=96.0/128.0; quad.v[3].ty=96.0/128.0;
注意,对于 hgeQuad 结构体,顶点 quad.v[0] 表示左上那个点,quad.v[1] 表示右上的点,quad.v[2] 表示右下的点,quad.v[3] 表示左下的点。
// 设置 hgeQuad 在屏幕中的位置
float x=100.0f, y=100.0f;
quad.v[0].x=x-16; quad.v[0].y=y-16;
quad.v[1].x=x+16; quad.v[1].y=y-16;
quad.v[2].x=x+16; quad.v[2].y=y+16;
quad.v[3].x=x-16; quad.v[3].y=y+16;
3)设置渲染函数(render function):
System_SetState(HGE_RENDERFUNC,RenderFunc);
RenderFunc 原型和帧函数一样:
bool RenderFunc();
4)编写 RenderFunc 函数:
bool RenderFunc()
{
pHGE->Gfx_BeginScene(); // 在如何渲染之前,必须调用这个函数
pHGE->Gfx_Clear(0); // 清屏,使用黑色,即颜色为 0
pHGE->Gfx_RenderQuad(&quad); // 渲染
pHGE->Gfx_EndScene(); // 结束渲染,并且更新窗口
return false; // 必须返回 false
}
补充:Load 函数是和 Free 函数成对出现的,即在硬盘上加载了资源之后,需要 Free 它们,例如:
quad.tex = pHGE->Texture_Load("particles");
// ...
pHGE->Texture_Free(quad.tex);
这里不得不谈一下规格化坐标间隔,而这之前,需要说说 Texture_GetWidth(xxx) 和 Texture_GetHeight(xxx) 函数,如果这样调用:Texture_GetWidth(xxx) 获取的是处于显存中的纹理宽度,而 Texture_GetWidth(xxx, true) 获取到的是图像文件的宽度,需要特别主义的是,对于同一张纹理来说,这两个值可能是不一样的,那么在规格化坐标间隔的时候,应该明确的是,对于一个 w*h 图像的图片,那么对于图中点(x,y)应该转换成为:
tx = x / pHGE->GetWidth(xxx);
ty = y / pHGE->GetHeight(xxx);
而不能写成:
tx = x / pHGE->GetWidth(xxx, true);
ty = y / pHGE->GetHeight(xxx, true);
这里要注意一下 x,y 的含义
最后再谈一下 tx 和 ty,实际上 tx,ty 大于 1 也是合法的,例如:
tx = 800 / 64;
ty = 600 / 64;
这会使得图片重复,而具体的含义,可以通过实现来体会
音效:
使用音效是很简单的
1. 载入音效:
HEFFECT hEffect = pHGE->Effect_Load("sound.mp3");
2. 播放:
pHGE->Effect_PlayEx(hEffect);
或者 pHGE->Effect_Play(hEffect);
1)Effect_Play 函数只接受一个参数就是音效的句柄 HEFFECT xx;
2)Effect_PlayEx 函数较为强大,一共有四个参数:
HCHANNEL Effect_PlayEx(
HEFFECT effect, // 音效的句柄
int volume = 100, // 音量,100为最大,范围是[0, 100]
int pan = 0, // 范围是[-100, 100],-100表示只使用左声道,100表示只使用右声道
float pitch = 1.0, // 播放速度,1.0 表示正常速度,值越大播放速度越快,值越小播放越慢。这个值要大于0才有效(不可以等于0)
bool loop = false // 是否循环播放,false表示不循环
);
输入:
仅仅需要调用函数 pHGE->Input_GetKeyState(HGEK_xxx); 来判断输入,应该在帧函数中调用它,例如:
bool FrameFunc()
{
if (pHGE->Input_GetKeyState(HGEK_LBUTTOM))
// ...
if (pHGE->Input_GetKeyState(HGEK_UP))
// ...
}