概述
本软件使用VS2010为框架,采用MFC框架,以及DirectDraw作为画图的工具。网络库与消息调度使用live555底层的模块实现。关于DirectDraw和live555的细节,可以自行上网查阅资料。
界面元素
图 1. 界面
如上图所示,是整个游戏客户端的界面。其中的每一个元素都是一个对象,使用组合模式来管理。界面每一秒种刷新30次(可以根据需要调整),每次刷新时每个元素根据需要重绘。这样达到动画效果。
图 2. 界面元素类图
如上图所示,界面中每一个元素都是一个CGraph的子类,不同的元素根据自身需要,重载onDraw函数,完成不同的绘图。CContainer是一个容器,本身不显示图像,只是保存有其他多个CGraph的对象,同时负责这些对象的位置管理,每次重绘的时候所做的事情就是调用他们的onDraw函数。
例如,最大的一个画布其实就是一个CContainer对象,其中包含有界面中的文本框、人物头像等元素,也包含有一些类似牌堆的CContainer(CCardContainer)对象,画布并不直接管理牌元素,而是CContainer来对其进行管理。可以参考四人帮设计模式中的组合模式。
每次定时器时间到后,会触发画布的onDraw消息,由顶层的CContainer传递给下层的CGraph,层层递进。
消息传递
软件采用MFC为框架,主画布是MFC中静态框(CStatic)的子类
class CSurface : publicCStatic, public CContainer
{
DECLARE_DYNAMIC(CSurface)
/* 省略无关代码 */
private:
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnMouseMove(UINT nFlags,CPoint point);
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnRButtonDown(UINT nFlags,CPoint point);
afx_msg void OnLButtonUp(UINT nFlags,CPoint point);
afx_msg void OnRButtonUp(UINT nFlags, CPointpoint);
afx_msg void OnLButtonDblClk(UINT nFlags,CPoint point);
};
那么,CSurface就可以通过MFC中静态框的机制来截获定时器消息和鼠标消息。一个CSurface的对象,就可以作最底层画布,同时也是鼠标消息的入口。
按照上面所说的,整个画面是个层次结构,container中可以包含其他的container,每个container重载鼠标消息时,不对具体的消息做处理,而是根据坐标位置,将消息传递给下一级的元素。从而实现消息的传递。
在onTimer函数中,调用draw函数,即从surface的draw开始,一级一级往下调用,每个元素如果是可见的,都会将自己画在画布的相应位置上。
一些元素
在界面中,要根据界面的消息来响应的话,主要响应重绘和鼠标两个消息,下面以两种控件来分别说明这两种消息的响应方法:
CAnimationCtrl
CAnimationCtrl是动画控件,所有的动画是由不同的连续图片组成的,每当定时器时间到的时候,就需要显示下一幅图片。下图是组成某个动画的六张连续图片。将它们连续循环播放,就会是一个动画效果。
所要做的工作,就是在初始化的时候指定图片文件,并且指明动态图帧的个数。这样,底层会将图片切为六份,每次时间到后,在刷新时就显示下一帧图片。
图 3. 动画的静态图片
CButtonCtrl
CButtonCtrl是按键,在显示方面,如果鼠标移动到按键上,就要换Foucus的图片,如果鼠标按下,就要换Press的图片,所以,需要响应鼠标移动、按下,松开的消息,并照此更改显示的图片。
除了要显示图片外,button还有一个功能,要给外界提供一个注册回调的接口,在鼠标单击时,就能够调用该回调函数,以完成功能。一般的回调函数是静态函数,如果是某个类对象需要注册的话,需要另外写一个静态函数,然后将自己的指针传入,以便在回调函数中使用。
而如果使用类函数指针的话,就可以不要方便些。
图 4. 消息机制类关系图
如上图所示,首先要有一个基类CMsgHandle,如果要用到按键的机制,则必须继承于它,并新建一个CButtonCtrl的对象,然后将回调函数和自己的指针注册到对象中。
voidCButtonCtrl::registerRButtonDownHandler( MSGCALLBK fun, void* usr )
{
m_pLButtonClickHandler = fun;
m_pLButtonClickUsr = usr;
}
这样,当鼠标点击了以后,就可以直接回调到类的成员函数中。
voidCButtonCtrl::onLButtonClicked( int xPos, int yPos )
{
if ( m_pLButtonClickHandler &&m_pLButtonClickUsr)
{
CMsgHandler* pHandler = (CMsgHandler*)m_pLButtonClickUsr;
( pHandler->*m_pLButtonClickHandler)();
}
return;
}
元素的移动
这个框架特别流出了一个可以支持元素在界面上移动的接口。例如,出牌时,牌是渐进地移动到界面中间,而不是直接瞬间移动。
图 5. 实现元素移动的类图
如上图所示,在CGraph中,有一个CMovement对象的指针,每次在重绘之前,会先让CMovement对象来调整当前元素的位置:
void CGraph::draw(LPDIRECTDRAWSURFACE7 surface )
{
if ( m_pMovement )
{
if ( !m_pMovement->moveStep( m_rc ))
{
delete m_pMovement;
m_pMovement = NULL;
}
}
if ( m_visible )
{
onDraw( surface );
}
}
在本程序中用到了CLinearMovement(即线性移动,设置了起点、终点和运动时间),每次重绘前,调用其moveStep函数,就会根据当前时间来计算下一步要将位置设置到哪里。于是,牌就有了线性移动的效果。
如果需要其他效果,将CGraph中的m_pMovement设置为其他的类型即可,比如,要让一个物体自由落体下降,那么就可以新建一个CMovement的子类,每次重绘时,根据当前的速度来计算下一个时刻将下落到哪个位置,以及更新当前的速度。参考代码+编译后的程序:http://download.csdn.net/detail/hustxyj/7024091