让OGRE支持中文.docx
- 文档编号:7170216
- 上传时间:2023-01-21
- 格式:DOCX
- 页数:23
- 大小:35.17KB
让OGRE支持中文.docx
《让OGRE支持中文.docx》由会员分享,可在线阅读,更多相关《让OGRE支持中文.docx(23页珍藏版)》请在冰豆网上搜索。
让OGRE支持中文
让OGRE支持中文3
——中文输入
-1.前言
中文输入终于实现了,这并不是一个轻松的过程,足足用了几个月时间才搞定,真正负责中文输入处理的就是一个简单的类,不过为了实现这个类需要的接口对OGRE进行了大量的改动,在此期间读了相当多的OGRE源码,改写尽量保持OGRE自身的风格,零零碎碎改写的地方相当多,这篇文章难免有疏漏的地方,如果各位真的希望了解真正的改写,还是看看源码比较好,这篇文章能做一个源码导读。
我相信大多数OGRE的使用者还是希望直接使用这个输入的,所以我给大家提供了一个例子和相应的代码,大家可以直接拿去用。
0.还是检讨
之前已经写了两篇关于OGRE引擎支持中文的文章,第一篇是位图字体,第二篇是TFF字体,关于位图字体,在字体管理器的管理下,同一字体之产生了一个实例,所以不会产生我在第二篇文章说的用过多字体实例导致内存被占用。
在第二篇TFF字体的程序中,有几个严重错误。
第一个是释放文字,在写的时候我曾认为每个实例维护一个贴图,事实上却是相同字体实例共同使用一个贴图,而且在正常使用的过程中,这个字体类型的实例也只能有一个,但是调用这个字体类的实例进行渲染的TextAreaGuiElement却有很多,把记录字体是否使用的代码改成引用记数才能正常工作,之前的代码会导致错误的释放。
第二个问题是向
贴图画字的时机,如果在申请贴图时候才画字,在某些情况下会无法显示,改成在判断是否使用该字也就是调用引用记数时进行画字便解决了这个问题。
第三个问题是在之前代码的顺序下,首先申请绘字空间然后判断是否在字库中有这个字,这样导致申请空间却没有画字的情况出现。
在修改了顺序之后解决了这个问题。
大体思路没有改变,只是进行了一些修改和优化,有兴趣的朋友可以直接看源码中的OgreFont.h、OgreFont.cpp、OgreTextAreaGuiElement.h,、OgreTextAreaGuiElement.cpp,这四个文件,并和之前的文件对比一下就能发现区别了。
1.捕捉Windows消息
在Windows环境下,如果要进行中文输入,捕捉Windows的祖字消息是必需的,但是OGRE为了其支持多平台的特性,从而拒绝将Windows消息交给用户处理,封装在相应的窗口渲染类中。
上图是处理窗口选的相关类的继承图,其中D3D7RenderWindow(DirectX7.0)、D3D9RenderWindow(DirectX9.0)、Win32Window(OpenGL)三个类是在Windows环境下处理窗口的相应类,Windows消息便封装在这三个类中,在我们所使用的抽象层RenderWindow类中已经无法得知具体的Windows消息,我们要做的就是在RenderWindow中建立一个可以监听Windows消息的接口,并且在D3D7RenderWindow(DirectX7.0)、D3D9RenderWindow(DirectX9.0)、Win32Window(OpenGL)三个类向上传递Windows消息,在Linux和Max环境中没有消息传递(这样的处理破坏了OGRE的多平台性,但是我所知道的中文输入处理只是针对Windows平台的,如果要保持多平台性应该做更多的处理,或者实现自己的输入法,不过这就是一个更大更大的工程了)。
参照OGRE的编码风格,我使用监听者模式,一个向要得知Windows消息的对象,需要具备(继承)Windows消息监听者的接口(RenderWindowListener),然后向RenderWindow实例注册自身,当有Windows消息出现时候RenderWindow实例得到windows消息,并向所有注册的实例发送消息。
我们首先实现两个基础结构和类。
structRenderWindowEvent//这个结构用来封装windows消息
{
HWNDhWnd;
UINTuMsg;
WPARAMwParam;
LPARAMlParam;
};
class_OgreExportRenderWindowListener//消息监听者的接口
{
public:
virtualboolRWUpdate(constRenderWindowEvent&evt){returntrue;}
};
然后在RenderWindow增加
virtualvoid
addRWListener(RenderWindowListener*listener)//注册监听者
virtualvoid
removeAllRWListeners(void)//移除所有监听者
virtualvoid
removeRWListener(RenderWindowListener*listener)//移除某一监听者
virtualbool
RWUpdate(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam)
//系统得到消息时调用的函数,在这里向所有监听者发送消息
具体请参照本文提供的代码。
最后在Windows的回调函数中加入处理代码,这里拿DX9.0为例。
在下面函数中
LRESULTD3D9RenderWindow:
:
WndProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam)
增加
LPCREATESTRUCTlpcs;
D3D9RenderWindow*win=NULL;
//lookupwindowinstance
if(WM_CREATE!
=uMsg)
//Getwindowpointer
win=(D3D9RenderWindow*)GetWindowLong(hWnd,0);
//以下三行是我们增加的
if(win)
if(!
win->RWUpdate(hWnd,uMsg,wParam,lParam))
return0;
这样就把Windows消息导出来了,不用高兴,现在只是开胃菜而已。
2.对中文输入的预处理。
说到输入,就要有输入框,有光标。
这些东西要到哪里搞呢。
看看手上的资源TextBoxGuiElement类就是一个简单的输入框,不过也真够简单,只能支持英文不说,还没有光标,要做到一个类似我们在窗口中的输入框,还有很远的路要走。
我们要实现自己的TextInputGuiElement类,让其可以实现中文输入。
这个类是一个TextBoxGuiElement类的子类,继承了TextBoxGuiElement类的接口,但是我们要进行大规模的手术才可以为我所用。
(相关源文件清参考OgreTextInputGuiElement.h、OgreTextInputGuiElement.cpp)
首先是加入对全角字的预处理,因为如果进行中文输入的话,我们需要判定,现在所指的字节是半角字还是全角字,这样我们删除的时候才可以知道是删除一个字节还是两个字节。
在这里我使用了一个
std:
:
vector
结构来辅助储存信息,
boolb=true;
for(i=text.begin();i!
=iend;++i)//检查整个字符串
{
mCheck.push_back(b);
if((unsignedchar)*i>=CHINESE_FIRST)//如果是全角字
{
++i;
if(i==iend)
break;
mCheck.push_back(b);//下一位的bool和本位相等
}
b=!
b;
}
上面text是String类型字符串,这种运行结果产生了一个判断全/半角的辅助结构,
比如“ab中国c人”这个字符串生成的辅助结构就是“true,false,true,true,false,false,ture,false,false”,当我们发现连续两个相等的布尔值时候,相应位置上的字符就是全角,否则就是半角。
然后我们来加入光标。
加入光标分两个步骤,第一是如何得到目前光标的位置,第二是如何显示光标。
这些在WinAPI中轻而易举的事情要让我们从头做起了。
得到光标位置,我们就需要在渲染时候分析全/半角字,并储存渲染的位置,然后我们要实现这样两个查询函数,第一个是通过第几个字得到这个字渲染的位置,另外一个函数是通过坐标(鼠标点选)得到这个字的位置和这个字是字符串中的几个字。
这些艰巨的任务我们交给TextAreaGuiElement类来完成。
我们首先加入这个数据
typedefstd:
:
vector
RectListmRectList;
来保存相应文字的坐标,Rectangle结构是OGRE自身定义的有四个Real(float)数据定义的矩形。
然后又回到修改
voidTextAreaGuiElement:
:
updateGeometry()
这个函数上了,我们主要是在渲染字的时候把其坐标储存到Rectagle数据中,具体的代码请参看OgreTextAreaGuiElement.cpp文件中
RectList:
:
iteratoriR,iendR;
相关的部分。
然后是增加的两个函数。
第一个是从字数中得到坐标。
//gettherectformthenum
00136inlinevoidgetRect(unsignedint&num,Rectangle&rect)
00137{
00138
00139if(mRectList.empty())//如果没有字
00140{
00141num=0;
00142
00143floatleft=_getDerivedLeft()*2.0-1.0;//得到坐标
00144floattop=-((_getDerivedTop()*2.0)-1.0);
00145
00146rect.left=left;
00147rect.top=top;
00148rect.bottom=top-mCharHeight*2.0;
00149rect.right=left;
00150
00151return;
00152}
00153
00154RectList:
:
iteratoriR,iendR;
00155
00156//如果取一个新字符位置
00157if(num>=mCaption.size())
00158{
00159iendR=mRectList.end();
00160--iendR;//取最后一个矩形
00161num=mCaption.size();
00162
00163rect.left=iendR->right;
00164rect.top=iendR->top;
00165rect.bottom=iendR->bottom;
00166rect.right=rect.left;
00167
00168return;
00169}
00170
00171iR=mRectList.begin();
00172iendR=mRectList.end();
00173//寻找字符位置
00174
00175for(unsignedinti=0;i 00176{ 00177 00178if(unsignedchar(mCaption.at(i))>=CHINESE_FIRST)//如果是汉字 00179++i; 00180++iR; 00181} 00182 00183 00184if(i! =num)//当目前字符为汉字的第二个字节时候产生的情况; 00185num=i; 00186 00187 00188 00189//当目前字符为汉字的第二个字节时 00190//并且为最后一个字符候产生的情况; 00191if(iR>=iendR) 00192{ 00193--iendR;//取最后一个矩形 00194num=mCaption.size(); 00195 00196rect.left=iendR->right; 00197rect.top=iendR->top; 00198rect.bottom=iendR->bottom; 00199rect.right=rect.left; 00200 00201return; 00202} 00203 00204 00205 00206 00207rect=*iR; 00208 00209return; 00210} 另外一个是通过坐标(鼠标点选)得到这个字的位置和这个字是字符串中的几个字。 inlineunsignedintgetNum(Realx,Realy,Rectangle&rect) 00217{ 00218x=x*2.0-1.0; 00219y=-((y*2.0)-1.0); 00220if(mRectList.empty())//如果没有字 00221{ 00222 00223floatleft=_getDerivedLeft()*2.0-1.0;//得到坐标 00224floattop=-((_getDerivedTop()*2.0)-1.0); 00225 00226rect.left=left; 00227rect.top=top; 00228rect.bottom=top-mCharHeight*2.0; 00229rect.right=left; 00230 00231return0; 00232} 00233 00234RectList: : iteratoriR,iendR; 00235 00236iR=mRectList.begin(); 00237iendR=mRectList.end(); 00238 00239 00240unsignedinti=0; 00241 00242for(;iR! =iendR;++iR,++i) 00243{ 00244if(i>=mCaption.size())//未知错误 00245i=mCaption.size()-1; 00246 00247 00248if(x>=iR->left&&x<=iR->right&&y>=iR->bottom&&y<=iR->top) 00249{ 00250 00251rect=*iR; 00252returni; 00253} 00254 00255if(unsignedchar(mCaption.at(i))>=CHINESE_FIRST)//如果是汉字 00256++i; 00257 00258} 00259--iendR;//最后一个矩形 00260rect.left=iendR->right; 00261rect.top=iendR->top; 00262rect.bottom=iendR->bottom; 00263rect.right=rect.left; 00264 00265returnmCaption.size(); 00266 00267} 这样我们便有了光标的位置,但是我们更需要光标。 光标的显示。 因为我们构造的类是TextBoxGuiElement的子类,而TextBoxGuiElement又是GuiContainer的子类,所以我们可以向我们构造的类中添加Element元素,具体概念请参考Ogre源码和相关教程,我们这里只是按照Ogre的风格向我们的类中添加一个2D元素。 void setCursorPanel(constString&templateName,intsize) String getCursorPanelName()const void initCursorPanel() void updateCursor() GuiContainer* mCursorPanel String mCursorPanelTemplateName CmdCursorPanel msCmdCursorPanel 以上就是我们增加的函数和数据成员。 CmdCursorPanel这个是我们为实现StringInterface实现的类,也便是为了实现脚本而作的,具体代码请参考OgreTextInputGuiElement.h和OgreTextInputGuiElement.cpp两个文件。 为了支持脚本系统,我们重载 voidTextInputGuiElement: : addBaseParameters(void) 并添加 dict->addParameter(ParameterDef("cursor_panel", "Thetemplatenameofthepanelisthecursorintext." PT_STRING), &msCmdCursorPanel); 这样我们就可以在脚本中定义光标的样式了。 然后是控制光标和相关操作。 鼠标点选,当我们鼠标点到某个文字上面时候,光标应该以东道这个文字上,并且者时候这个输入框处于激活状态,者时候可以进行键盘操作。 在事件处理函数 voidTextInputGuiElement: : processEvent(InputEvent*e) 中增加 caseKeyEvent: : KE_KEY_FOCUSOUT: //失去焦点 if(mCursorPanel) mCursorPanel->hide();//隐藏光标 break; caseKeyEvent: : KE_KEY_FOCUSIN: //得到焦点 if(mCursorPanel) { initCursorPanel();//初始化光标 mCursorPanel->show();//显示光标 } break; caseMouseEvent: : ME_MOUSE_PRESSED: //鼠标点击事件 //通过鼠标位置得到当前字数和当前光标位置 mNum=mTextArea->getNum(static_cast setCaptionToTextArea();//更新 break; 键盘操纵光标 我们来给我们的输入框增加一些键盘操作,比如删除,橡皮(向后删除),左移动,右移动,home,end这些功能。 00688boolTextInputGuiElement: : key(intkey) 00689{ 00690switch(key) 00691{ 00692caseKC_LEFT: //左移动 00693 00694if(mNum) 00695if(mNum>=2)//看看是否有空间移动全角 00696{//判断是否全角 00697if(mCheck.at(mNum-1)^mCheck.at(mNum-2)) 00698mNum-=1; 00699else 00700mNum-=2;//全角移动两个字节 00701} 00702else 00703mNum-=1;//半角一个 00704setCaptionToTextArea();//更新 00705returntrue; 00706 00707caseKC_RIGHT: //右移动 00708 00709if(mNum 00710if(mNum+2<=mCaption.size()) 00711{ 00712if(mCheck.at(mNum)^mCheck.at(mNum+1)) 00713mNum+=1; 00714else 00715mNum+=2; 00716} 00717else 00718mNum+=1; 00719setCaptionToTextArea(); 00720returntrue; 00721 00722caseKC_DELETE: //删除 00723 00724if(mNum 00725if(mNum+2<=mCaption.size()) 00726{ 00727if(mCheck.at(mNum)^mCheck.at(mNum+1)) 00728mCaption.erase(mNum,1); 00729else 00730mCaption.erase(mNum,2); 00731} 00732else 00733mCaption.erase(mNum,1); 00734setCaptionToTextArea(); 00735returntrue; 00736 00737caseKC_HOME: //移动到头 00738mNum=0; 00739setCaptionToTextArea(); 00740returntrue; 00741 00742caseKC_END: //移动到尾 00743mNum=-1;//因为mNum是非负,这里付给他一个最大值 00744setCaptionToTextArea(); 00745returntrue; 00746 00747caseKC_BACK: //橡皮,向后删除 00748if(mNum) 00749if(mNum>=2) 00750{ 00751if(mCheck.at(mNum-1)^mCheck.at(mNum-2)) 00752{ 00753mNum-=1; 00754mCaption.erase(mNum,1); 00755} 00756else 00757{ 00758mNum-=2; 00759mCaption.erase(mNum,2); 00760} 00761} 00762else 00763{ 00764mNum-=1; 00765mCaption.erase(mNum,1
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- OGRE 支持 中文