doom3的UI系统是纯数据驱动的,例如
windowDef TextTitle2 { rect 20,341,600,55 visible 1 text "#str_00073" forecolor 0.6,1,1,0 textscale 0.8 font "fonts/micro" textalign 1 notime 1 onTime 0 { transition "forecolor" "1 1 1 0" "1 1 0.8 1" "500" ; } onTime 1000 { transition "forecolor" "1 1 0.8 1" "1 1 1 0" "500" ; } onTime 2000 { resetTime "0" ; } }
这段文本定义了一个简单的窗口。windowDef说明这是一个普通窗口,后面跟着这个窗口的名字。
其他的窗口类型定义还有editDef,choiceDef,sliderDef,listDef,fieldDef等等,都是在普通窗口的
基础上增加了一些行为和属性,本文不打算详述。
后面紧接着是一些窗口属性值,这些属性可以分为两类,InternalVar和WinVar。
InternalVar是一些常量,定义后就不能改变,例如textalignx,texaligny,bordersize,font等
而WinVar属性,是在运行时可以被改变的属性,例如
visible,
backColor,
matColor,
foreColor,
hoverColor,
borderColor,
textScale,
rotate,
text
等。这些值可以在后面要提到的GUI脚本中被改变。
onTime是一个event,event会在某种条件下被调用。例如onTime event是在时间到达后面的值(毫秒)时被调用。
调用执行的内容是中括号中描述的GUI脚本。
event的类型有
onTime,
onMouseEnter ,
onMouseExit ,
onAction ,
onActivate ,
onDeactivate ,
onESC ,
onEvent ,
onTrigger ,
onActionRelease ,
onEnter ,
onEnterRelease
transition是一个GUI脚本命令,它描述了一个WinVar跟据时间被平滑的插值的过程
transition "forecolor" "1 1 1 0" "1 1 0.8 1" "500" ;
表示前景色forecolor在500毫秒内从 "1 1 1 0"过渡到"1 1 0.8 1"
到这里你应该已经能够看出来,这段文本数据描述了一个以两秒为周期不断变换颜色的文字lable。
好,我们再来看一个复杂一点的例子
windowDef Desktop { rect 0 ,0 ,640 ,480 backcolor 0, 0, 0, 0.9 windowDef CircleClamp { rect 7, 7, 626, 466 visible 1 windowDef Circle0 { rect -95,-174,820,820 visible 1 background "gui/spin1" matcolor 0.5, 0, 0, 0.5 } } windowDef TextButton2 { rect 0,0,0,0 text "#str_00010" textscale 1.2 forecolor 1,1,1,0 visible 1 textalign 1 font "fonts/micro" noevents 1 onMouseEnter { transition "forecolor" "1 1 1 0.6" "1 1 1 1" "300" ; transition "btn2_top::matcolor" "1 1 1 1" "0.8 1 1 0.7" "300" ; transition "btn2_corner1::matcolor" "1 1 1 1" "0.4 1 1 0.7" "300" ; transition "btn2_corner2::matcolor" "1 1 1 1" "0.4 1 1 0.7" "300" ; transition "btn2_bottom::matcolor" "1 1 1 1" "0.4 1 1 0.7" "300" ; transition "btn2_corner3::matcolor" "1 1 1 1" "0.4 1 1 0.7" "300" ; transition "btn2_corner4::matcolor" "1 1 1 1" "0.4 1 1 0.7" "300" ; transition "btn2_right::matcolor" "1 1 1 1" "0.4 1 1 0.7" "300" ; transition "btn2_left::matcolor" "1 1 1 1" "0.4 1 1 0.7" "300" ; transition "btn2_fill::backcolor" "1 1 1 0.465" "0.4 1 1 0.32" "300" ; } onMouseExit { transition "forecolor" "1 1 1 1" "1 1 1 0.6" "200" ; transition "btn2_top::matcolor" "0.8 1 1 0.7" "1 1 1 0.5" "300" ; transition "btn2_corner1::matcolor" "0.4 1 1 0.7" "0.4 1 1 0.5" "300" ; transition "btn2_corner2::matcolor" "0.4 1 1 0.7" "0.4 1 1 0.5" "300" ; transition "btn2_bottom::matcolor" "0.4 1 1 0.7" "0.4 1 1 0.5" "300" ; transition "btn2_corner3::matcolor" "0.4 1 1 0.7" "0.4 1 1 0.5" "300" ; transition "btn2_corner4::matcolor" "0.4 1 1 0.7" "0.4 1 1 0.5" "300" ; transition "btn2_right::matcolor" "0.4 1 1 0.7" "0.4 1 1 0.5" "300" ; transition "btn2_left::matcolor" "0.4 1 1 0.7" "0.4 1 1 0.5" "300" ; transition "btn2_fill::backcolor" "0.4 1 1 0.32" "0.4 1 1 0.232" "300" ; } onAction { if ("gui::gui_parm4" == 1) { set "cmd" "play guisounds_error" ; resetTime "AccessDenied" "0" ; set "noevents" "1" ; } else { set "cmd" "activate ; play guisounds_click" ; resetTime "AccessLocked" "0" ; set "noevents" "1" ; } } } windowDef Static { rect -10 ,-10 ,-660 ,500 visible 1 background "gui/static" matcolor 1, 1, 1, pdhalffade[ time * 0.001 ] / 8 } }
首先,ui的定义是嵌套的,实际上,游戏中的用到的ui都是比较复杂的层次结构。任意多层的各种组件嵌套组合起来,理论上
可以形成任意复杂的UI效果。
然后我们看到了更多的event的使用,onMouseEnter,和onMouseExit实现了鼠标移入移出时的变化效果。
在onAction event中我们看到GUIScript中是可以带逻辑的,不过GUIScript中也只有“if”这一种逻辑。
GUI脚本中的命令有
set,
setFocus,
endGame,
resetTime,
showCursor,
resetCinematics,
transition,
localSound,
runScript,
evalRegs,
set命令可以直接设置某个WinVar。比如
set "Circle7::visible" "0" ;
将窗口Circle7的WinVar visible的值设为0,即隐藏这个窗口。
但如果set的参数是cmd,则其功能是让触发这个GUI event的对象执行后面的GUI command,可谓“脚本套脚本”,例如
一个player对象(某个玩家或NPC),触发了这个onEvent,event中调用了
set "cmd" "activate ; play guisounds_click" ;
则这个player对象执行GUI command
activate
play guisounds_click
效果是,这个player作为activator激活了这个GUI所在的entity,并播放音效guisounds_click
常用的GUI command有
activate
runScript
play
setkeyval
setshaderparm等
最后.....
matcolor 1, 1, 1, pdhalffade[ time * 0.001 ] / 8
WinVar的值可以是一个表达式并且还能查表有木有
总结
doom3引擎的UI系统通过WinVar,GUI event,GUI Script,GUI Command等几个核心抽象,用最少的代码,
实现了极其灵活而强大的功能。