W3D引擎游戏开发----从入门到精通【22】
配置完成基本DT物体项后,在这个DT物体项中开始添加这个玩家的动画信息,如下所示。UseAnim设置是否使用动画功能,这里开启。AnimTypeN设置总共的动画类型数,当前只有一个待机动画,因此设置为1。AnimType1FrameN设置1号动画的帧数,在这里即为待机动画的帧数,共13帧(0.obj~12.obj共13个帧物体)。AnimType1Frame1~AnimType1Frame13依次设置这13个帧物体的obj模型路径。
...
[DTMod3]
texnum = 1
mtl=model\LearnW3D\Chapter1\DT\Anim\standby\mod.mtl
mod=model\LearnW3D\Chapter1\DT\Anim\standby\mod.obj
tex1=model\LearnW3D\Chapter1\DT\Anim\standby\mod.bmp
UseAnim=1
AnimTypeN=1
AnimType1FrameN=13
AnimType1Frame1=model\LearnW3D\Chapter1\DT\Anim\standby\0.obj
AnimType1Frame2=model\LearnW3D\Chapter1\DT\Anim\standby\1.obj
AnimType1Frame3=model\LearnW3D\Chapter1\DT\Anim\standby\2.obj
AnimType1Frame4=model\LearnW3D\Chapter1\DT\Anim\standby\3.obj
AnimType1Frame5=model\LearnW3D\Chapter1\DT\Anim\standby\4.obj
AnimType1Frame6=model\LearnW3D\Chapter1\DT\Anim\standby\5.obj
AnimType1Frame7=model\LearnW3D\Chapter1\DT\Anim\standby\6.obj
AnimType1Frame8=model\LearnW3D\Chapter1\DT\Anim\standby\7.obj
AnimType1Frame9=model\LearnW3D\Chapter1\DT\Anim\standby\8.obj
AnimType1Frame10=model\LearnW3D\Chapter1\DT\Anim\standby\9.obj
AnimType1Frame11=model\LearnW3D\Chapter1\DT\Anim\standby\10.obj
AnimType1Frame12=model\LearnW3D\Chapter1\DT\Anim\standby\11.obj
AnimType1Frame13=model\LearnW3D\Chapter1\DT\Anim\standby\12.obj
[DTMod]
num = 3
...
设置完DT物体后,开始设置此DT物体派生出的PT物体。然后在[ScenePTMod1]字段中添加第3个PT物体信息如下:
...
3x=0.0
3y=0.0
3z=0.0
3sx=0.38089
3sy=0.38089
3sz=0.38089
3AbsPos=1
3ShowTip=0
3type=3
3SelMode=0
3DyPhyCtrlMod=1
3DyPhyCtrlModID=2
num = 3
...
之前我们讲过,用户控制玩家,是控制玩家的包络模型,因为如果直接控制玩家的动画模型,玩家动画模型的面数过多、是否为封闭物体这些因素都是不可控因素,都将导致玩家的碰撞检测可能出现问题,因此我们直接控制玩家包络模型。那么如何让玩家的动画模型跟随着包络模型?这里只需要设置此玩家的动画模型受物理模型控制即可。
此字段中,DyPhyCtrlMod设置此动画模型是一个受物理物体控制的物体,DyPhyCtrlModID设置这个物体具体受哪个物理物体控制,为物体的ID即序号,这里为2即玩家的包络模型。type为此物体的DT物体类型(序号),即为3号。sx、sy、sz设置此模型的缩放系数x分量、y分量和z分量,2.5.1节中已经把玩家的动画模型缩放入包络模型中,缩放比例为38.089%,因此这里设置缩放为0.38089。
AbsPos设置这个物体的位置为绝对位置,那么这个物体的初始位置即为x、y、z位置所表示,若不设置为绝对位置,那么这个物体的初始位置即为x+场景偏移x、y+场景偏移y、z+场景偏移z的位置所表示(场景偏移见应用篇)。
因为此物体完全由玩家包络模型控制,那么此物体的姿态将与玩家包络模型完全一致,此时x、y、z将表示此物体相对于被控物理模型的位置偏移,这里x、y、z设置为0表示没有偏移,与包络模型完全一致。但偏移还取决于是否设置绝对位置(AbsPos),当设置了则偏移为x、y、z表示,若不设置则偏移为x+场景偏移x、y+场景偏移y、z+场景偏移。
随后在组中添加此3号PT物体,如下所示:
...
[Scene1Group]
Group1ID1=1
Group1ID2=2
Group1ID3=3
Group1Num=3
...
到此用户控制玩家包络的运动时,将同时控制玩家动画模型,但是玩家是不应该看到包络模型的,因此需要隐藏包络模型,我们为2号包络PT物体添加如下配置项:
...
2x=-114.234
2y=39.009
2z=127.017
2ShowTip=0
2type=2
2SelMode=0
2DyPhyMod=1
2ModCanColldSelfRot=0
2HideMod=1
...
HideMod设置此物体隐藏(并非不绘制),这时包络不可见,只有玩家动画模型。运行测试如下图所示,图2.11显示了待机动画模型的第一帧动画。
在这一部分,我们将在场景中添加按钮,玩家接近此按钮时此按钮发出提示音,同时在屏幕中央显示操作提示,按照提示操作后一个不停旋转的圆石盘将缓慢上升,托着玩家到达另一处高台。因为此按钮是固定位置,因为按钮物体可以是场景物体。在[SceneMod1]中添加按钮场景物体,添加的配置信息如下,那么按钮的场景物体序号为2。
...
2mod_lv1=model\LearnW3D\Chapter1\anniu.obj
2tex=model\LearnW3D\Chapter1\anniu.bmp
2mtl=model\LearnW3D\Chapter1\anniu.mtl
2GenShadow=0
num = 2
...
在插件代码中,我们新建一个线程,用于控制游戏的剧情,在W3DCustomEntry()中创建一个剧情控制线程,同时添加一个全局变量dwStage作为游戏的当前剧情号,初始化为0表示此时游戏剧情从头开始。
代码如下:
...
//剧情从头开始
dwStage=0;
//剧情线程
_h = CreateThread(0,0,StageCtrlThread,0,0,0);
CloseHandle(_h);
...
建立剧情控制线程框架,代码如下:
...
//剧情控制线程
DWORD WINAPI StageCtrlThread(LPVOID p)
{
while(on)
{
DWORD _dwStatus;
DWORD _dwInfo;
//等待加载完毕
W3DGetSystemStatus(&_dwStatus,&_dwInfo);
if(_dwStatus == 1 && _dwInfo == 2)
{
break;
}
Sleep(10);
}
//开始工作
while(on)
{
//此处添加剧情处理代码
//...
Sleep(10);
}
return TRUE;
}
...
要完成玩家接近此按钮时此按钮发出提示音这一个工作,我们需要知道此按钮模型当前的位置和玩家当前的位置。按钮模型是场景物体,可以使用W3DGetSceneModPos函数获得位置,函数原型如下:
函数定义 | BOOL W3DGetSceneModPos(DWORD dwModID,vec3* pvPos) |
功能描述 | 获得场景物体当前的中心点位置。dwModID为查询的场景物体序号,pvPos返回此场景物体的中心点位置。 提示:pvPos[0],pvPos[1],pvPos[2]分别为返回的x,y,z坐标。 |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
需要注意的是,场景物体的中心点指的是此物体的边界盒中心点,场景物体边界框和中心点位置如下图所示,图中场景物体为一个茶壶,长方体为此茶壶的编辑框。
当到达触发半径后,将发出提示音。W3D引擎声音播放方法主要有三种:背景音乐、场景物体声音、PT物体声音。
背景音乐:不是场景中某个物体发出的声音,而是一种泛在的声音,因此不存在音量随距离的增大减少而发生改变。
场景物体声音:场景中某个场景物体发出的声音,可以设置音量随距离此物体的增大减少而发生改变。
PT物体声音:场景中某个PT物体发出的声音,可以设置音量随距离此物体的增大减少而发生改变。
在这里,因为按钮是场景物体,因此需要添加一个场景物体声音,可以使用函数W3DAddModSoundByPlayPos为一个场景物体增加一个声音播放请求,此函数原型如下:
函数定义 | BOOL W3DAddModSoundByPlayPos(DWORD dwModID, DWORD dwModType, DWORD dwSoundVolume, char* pszSoundFileName, BOOL bPlayByDis, DWORD dwPlayPosID, DWORD dwPlayDeviceID, BOOL bAddExist, DWORD dwDisMode) |
功能描述 | 为场景或PT物体添加一个播放声音请求。 dwModID为物体序号。 dwModType为物体类型,场景物体为W3D_SCENE_MOD,PT物体为W3D_PT_MOD(此枚举类型定义在W3DEngShell.h中)。 dwSoundVolume为声音的初始音量,最小音量为0。 pszSoundFileName为需要播放的声音文件路径(路径为ascii字符格式),声音文件格式可以为wav或mp3等格式。 bPlayByDis指定此声音的音量是否随距离此物体的距离变化,true为随距离变化,距离此物体越远声音越小,越近声音越大,false不随距离变化,一直保持为初始音量。 dwPlayDeviceID指定一个声音播放设备ID用于播放此声音,引擎内置了20个播放器,因此编号ID可以为1~20。 dwPlayPosID指定此声音播放请求在播放队列中的位置,引擎内置了一个500大小的播放队列,任何一个声音需要播放时,需要将此播放请求填写到这个队列的其中一个位置。参数设置范围为0~499。 bAddExist指定当dwPlayPosID位置上已经存在了声音播放请求时,是否仍然要添加播放请求。 dwDisMode指定随声音随距离的衰减模式, 当为0时,声音随距离非线性衰减(衰减较快),当前音量=初始音量/距离 当为1时,声音随距离线性衰减(衰减较慢),当前音量=初始音量-距离。 当为2时,声音随距离非线性衰减(衰减较快),当前音量=初始音量-距离2。 |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
注意此函数仅向引擎申请一次播放请求,引擎是否播放此声音取决于以下情况是否满足:
- dwPlayDeviceID指定了正确的播放器ID
- dwPlayPosID指定了正确的播放队列位置
- dwPlayPosID位置处没有正在播放的声音
bAddExist可以设置当此播放位置已经有声音请求时,是否仍然添加声音请求。当此播放位置有声音请求但未播放时,如果仍然添加声音将覆盖原先的播放请求,那么当播放到此播放位置时,将播放覆盖后的那个声音;当此播放位置有声音请求且正在播放时,强行添加声音将导致声音播放出现错误。
引擎同样支持播放队列位置的声音状态的查询,调用函数W3DInquireSoundStatus,函数定义原型如下所示:
函数定义 | BOOL W3DInquireSoundStatus(DWORD dwPlayPosID, DWORD* dwStatus) |
功能描述 | 查询播放队列位置处的声音播放状态。 dwPlayPosID指定需要查询的播放队列位置。dwStatus返回状态,状态可以是:W3D_SOUND_STATUS_PLAY(正在播放),W3D_SOUND_STATUS_END(播放结束)(此枚举类型定义在W3DEngShell.h中) |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
可以调用W3DStopModSound函数立即停止指定播放队列位置处的声音,函数定义原型如下所示:
函数定义 | BOOL W3DStopModSound(DWORD dwPlayPosID) |
功能描述 | 停止播放队列位置处的声音。dwPlayPosID指定需要停止的播放队列位置。 |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
在距离触发剧情处添加代码实现播放提示音,添加的代码如下所示:
...
//如果小于指定距离触发剧情
if(_dis < 30)
{
W3DAddModSoundByPlayPos(2,
W3D_SCENE_MOD,
1000,
"sound\\1.mp3",
1,
0,
1,
0,
1);
dwStage=1;
}
...
代码为按钮场景物体(序号为2)添加一个声音(声音文件路径为sound\1.mp3),使用最大音量(即此声音文件的默认音量),设置声音随距离衰减,使用1号播放器,0号播放队列位置,存在声音不添加,声音播放模式为线性衰减。代码最后设置剧情号为1,表示声音播放后进入1号剧情,即开始下一阶段的游戏剧情。运行游戏进行测试,如下图所示,包含声音的视频演示资料在本书配套学习资源包中。
在2号剧情中,我们需要将提示标中的文字改为“请按下E键”,可以使用函数W3DSetFMModWord完成,函数原型如下:
函数定义 | BOOL W3DSetFMModWord(DWORD dwModID, BOOL bChangeWord, WCHAR* word, BOOL bChangeWordCol, float fWordR,float fWordG,float fWordB, BOOL bChangeWordTran, float fTranX,float fTranY,float fTranZ, BOOL bChangeWordRot, float fRotRa,float fRotX, float fRotY,float fRotZ, BOOL bChangeWordScale, float fSclX,float fSclY,float fSclZ) |
功能描述 | 设置封面物体的文字信息。dwModID为封面物体序号ID。 bChangeWord设置是否改变文字内容,设置为true则word有效。 word设置文字内容,unicode字符串形式。 bChangeWordCol设置是否改变文字颜色,设置为true则fWordR,fWordG,fWordB有效。 fWordR,fWordG,fWord设置文字的颜色红,绿,蓝分量,每一个分量取值范围为[0.0,1.0]。 bChangeWordTran设置是否改变文字的位置信息,fTranX、fTranY和fTranZ分别是x、y、z位置分量。 bChangeWordRot设置是否改变文字的旋转信息,fRotRa、fRotX、fRotY和fRotZ是旋转角度、旋转x轴分量、y轴分量、z轴分量。 bChangeWordScale设置是否改变文字的缩放信息,fSclX、fSclY和fSclZ是缩放x分量、y分量、z分量。 (文字位置、旋转、缩放信息含义见3.2节封面物体配置信息中相关描述) |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
添加2号剧情代码如下所示,首先设置文字内容为“请按下E键”同时将文字颜色设置为红色,位置不改变。代码最后进入3号剧情。
...
else if(dwStage == 2)
{
W3DSetFMModWord(1,
1,TEXT("请按下E键"),//改变文字内容
1,1.0f,0.0f,0.0f,//初始设置为红色
0,0,0,0,0,0,0,0,0,0,0,0,0);
dwStage = 3;
}
...
4.5 无阻塞延时方法
在进入3号剧情后,需要不断红白闪烁文字,同时文字缓慢飞入屏幕指定位置。闪烁文字需要间隔一个时间将文字颜色变换为红色和白色,如果间隔时间使用阻塞型函数,如sleep等函数,那么剧情线程将阻塞,无法执行飞入代码,因此我们需要无阻塞的延时方法。无阻塞的延时方法可以使用多种第三方库或自行编写,也可以使用引擎自带无阻塞延迟函数完成,函数原型如下:
函数定义 | BOOL W3DEventDelay(BOOL bReset,DWORD dwIndex, DWORD dwTime) |
功能描述 | 无阻塞延时。dwIndex指定事件序号,引擎支持5000种事件的独立超时检查,因此可设置为0~4999。 dwTime设置此事件的超时时间(ms为单位)。 bReset重置此事件,那么将强制记录当前时间为开始时间。 |
返回值 | 当前时间超时则返回true,否则返回false |
当第一次调用此函数时,引擎将记录当前时间为此事件的开始时间并返回false。随后对此事件的函数调用将检测当前时间距离记录的上一次开始时间是否超过指定的超时时间,未超过则返回false,若超过返回true且记录当前时间为此事件的当前时间。当重置此事件后,将结束此事件的无阻塞延时内部事件记录并重置内部参数并返回true,即相当于清除了此事件的超时时间,下次对此事件的函数调用可以像第一次调用那样设置新的超时时间。
那么使用这样的无阻塞延迟函数,我们可以完成不阻塞其他代码的延时。在3号剧情中添加如下代码。代码中使用0号事件,延时100ms将颜色设置变量反转一次,然后调用W3DSetFMModWord只设置文字颜色。
...
else if(dwStage == 3)
{
static bool _ys=0;
if(W3DEventDelay(0,0,100))
{
_ys = !_ys;
}
if(_ys)
{
W3DSetFMModWord(1,
0,0,
1,1.0f,0.0f,0.0f,//设置为红色
0,0,0,0,0,0,0,0,0,0,0,0,0);
}
else
{
W3DSetFMModWord(1,
0,0,
1,1.0f,0.0f,0.0f,//设置为白色
0,0,0,0,0,0,0,0,0,0,0,0,0);
}
}
...
4.6 封面物体飞入动画实现
封面物体的文字闪烁开始后,我们让封面物体从屏幕外飞入屏幕内指定位置,即从左侧(-0.093,0.032,-0.11)移动到右侧(-0.06,0.032,-0.11)。在剧情3中继续添加代码如下所示:
...
//提示标慢慢进入屏幕指定位置
static float _cswz=-0.093f;
W3DSetFMModPos(1,
0,
vec3(_cswz,0.032,-0.11),
vec4(0.0f,0.0f,0.0f,0.0f),
vec3(1.0f,1.0f,1.0f));
//位置自增
_cswz+=0.0005f;
//到指定位置
if(_cswz >= -0.06f)
{
W3DSetFMModWord(1,
0,0,
1,1.0f,0.0f,0.0f,//设置为红色
0,0,0,0,0,0,0,0,0,0,0,0,0);
dwStage = 4;
}
...
代码不断调用W3DSetFMModPos设置提示标位置,读者可以修改参数实现不同速度的飞入效果,当到达指定位置后,将提示标文字颜色设置为红色,同时进入4号剧情。
4.7 场景物体移动开发方法
进入4号剧情后,等待用户按下’E’键,按钮消失,这时从地面下方升上来一个圆盘拖着玩家上升到一个高台,在上升过程中地面不断震动。
对按键的检测,可以使用函数W3DGetSingleKeyInfo完成(2.5.4节中描述了函数原型),在4号剧情中添加代码如下:
...
else if(dwStage == 4)
{
bool _eResult;
//获得E键的状态
W3DGetSingleKeyInfo('E',&_eResult);
//如果按下
if(_eResult)
{
//...
}
}
...
检测到按下E键后,让按钮消失。让按钮消失可以用两种方法实现:按钮模型不绘制和按钮模型隐藏。模型不绘制与隐藏有一定的区别:
按钮模型不绘制:引擎不会为此模型分配渲染和物理资源,系统开销最小。
按钮模型隐藏:引擎会为此模型分配物理和其他资源,只是最终不进行图像渲染,有一定系统资源开销。
设置一个场景物体或PT物体是否绘制可以使用函数W3DSetSceneModShow或W3DSetPTModShow,函数原型如下:
函数定义 | BOOL W3DSetSceneModShow(DWORD dwModID,DWORD dwMode) |
功能描述 | 设置场景物体显示状态。dwModID指定场景物体序号ID。dwMode设置此物体的显示状态,true为绘制,false为不绘制。 |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
函数定义 | BOOL W3DSetPTModShow(DWORD dwModID,DWORD dwMode) |
功能描述 | 设置PT物体显示状态。dwModID指定PT物体序号ID。dwMode设置此物体的显示状态,true为绘制,false为不绘制。 |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
设置一个场景物体或PT物体是否隐藏可以使用函数W3DSetSceneModHide或W3DSetPTModHide,函数原型如下:
函数定义 | BOOL W3DSetSceneModHide(DWORD dwModID,DWORD dwMode) |
功能描述 | 设置场景物体隐藏状态。dwModID指定场景物体序号ID。dwMode设置此物体的隐藏状态,true为隐藏,false为不隐藏。 |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
函数定义 | BOOL W3DSetPTModHide(DWORD dwModID,DWORD dwMode) |
功能描述 | 设置PT物体隐藏状态。dwModID指定PT物体序号ID。dwMode设置此物体的隐藏状态,true为隐藏,false为不隐藏。 |
返回值 | 函数执行成功为true,失败为false,调用W3DGetLastError了解更多错误信息 |
因为此按钮仅显示一个模型,不进行物理运算,因此我们不绘制此按钮模型。添加代码如下所示,按下E键后不绘制2号场景物体(即为按钮物体),完成后进行5号剧情。
...
//如果按下
if(_eResult)
{
W3DSetSceneModShow(2,0);
dwStage = 5;
}
...
在5号剧情中,开始将地面下方的圆盘升上来拖着玩家上升到一个高台,同时地面震动。一个物体托着或推着一个物体移动,一般可以采用动画或物理运算方法。
动画方法:实时计算两个物体的距离,当距离为接触时,一个物体跟着另一个物体移动,这种方法程序实现比较复杂,且移动灵活性低、效果不自然。
物理运算方法:将两个物体设置为刚性物体物体,一个物理物体对另一个物体物体的碰撞、挤压等作用所发生的形变、位置移动由物理引擎实时计算。这种方法对游戏开发者来说较为简单,且移动灵活性高、效果更好。
这里我们使用物理运算方法实现。玩家包络已经是动态物理物体,那么我们只需要将此圆盘模型也设置为物理物体即可,但设置为静态还是动态物理物体?由于在此游戏中,我们可以认为此圆台的托举是对于玩家是个不可抗力,即玩家的力量无法让圆台发生位置改变,那么我们可以将此圆台设置为一个静态物理模型。制作圆台与高台模型如下图所示: