世界属于将思考付诸实践的人

游戏中基于消息的异步非阻塞WinSock模型

类归于: 游戏开发 — colin @ 6:44 下午 2005年05月28日

在游戏中,服务器端或者客户端发送数据都是主动的,而接收数据则是在消息被动驱动下主动接收,这样就实现了非阻塞。我对服务器端以及客户端的WinSock都进行了简单的封装,毕竟TCP的验证和重发机制让我比较放心,在封装的时候只是设计了方便的调用方法,而在数据通信机制上没有任何的干涉。 服务器端的初始化如下:

BOOL CServer::InitAndListen(HWND hwnd,UINT port)
{
 m_uPort=port;
 m_hWnd=hwnd;

 if(m_hSocket != NULL)
    {
  closesocket(m_hSocket);
  m_hSocket = NULL;
 }

 m_hSocket = socket(AF_INET, SOCK_STREAM,0);
 ASSERT(m_hSocket != NULL);
 ServerInit();

 m_addr.sin_family = AF_INET;
 m_addr.sin_addr.S_un.S_addr = INADDR_ANY;
 m_addr.sin_port = htons(m_uPort);

 int ret = 0;
 int error = 0;
 ret = bind(m_hSocket, (LPSOCKADDR)&m_addr, sizeof(m_addr));

 if(ret == SOCKET_ERROR)
 {
  AfxMessageBox(”端口绑定失败,您已经在其他程序中使用了该端口!”);
  return FALSE;
 }
 
 ret = listen(m_hSocket, 5);
 if(ret == SOCKET_ERROR)
 {
  AfxMessageBox(”服务器监听失败!”);
  return FALSE;
 }

 return TRUE;
}

服务器的作用就是在某个端口上一直监听,当收到某个客户端的数据后,经过一些处理,最后反馈给包括发送数据的客户端和其他的所有客户端。所以在以上初始化成功后,要对服务器端的Socket进行异步模式设置:

 if(WSAAsyncSelect(m_hSocket, m_hWnd, SER_MESSAGE, FD_ACCEPT|FD_READ|FD_WRITE|FD_CLOSE)>0) AfxMessageBox(”error select”);

其中SER_MESSAGE自定义消息,定义在头文件里:

 #define SER_MESSAGE WM_USER + 100

另外,还要定义该消息的处理函数:

afx_msg LRESULT OnServerMessage(WPARAM wParam, LPARAM lParam);

ON_MESSAGE(SER_MESSAGE,OnServerMessage)

这样一来,当客户端给服务器端发送数据的时候,服务器端便会立即执行该函数,所以非阻塞的优势便在于此,使得服务器端不必一直等待数据。 当服务器端被消息触发开始执行消息处理函数的时候,如何知道发来的消息是什么类型以及是哪个游戏客户端发来的呢?利用WPARAM wParam, LPARAM lParam这两个参数就可以了。

switch(lParam)
{
      case FD_ACCEPT:
            socket= accept(server.m_hSocket,NULL,NULL);

      case FD_READ:
            //………………..
            //读取数据内容,根据数据格式执行各种逻辑
}

到现在为止,服务器端的框架就出来了,客户端跟服务器很相似,唯一不同的是,客户端不需要监听,但是却要连接服务器,连接成功后,也需要异步模式设置,然后是自定义消息和消息处理函数。 在这之后,就是如何定义一种合理的数据格式,在服务器端和客户端之进行互相传送。这个数据格式的设计原则我觉得应该是尽量的短小精悍,但是也不吝啬长度,因为如果通信数据短了,那么客户端的负担就重了,与增加的负担相比,增加几个字节的通信数据也无伤大雅,关键是一个平衡度的把握。我觉得应该把客户端不容易计算或者计算需要很多辅助条件的数据直接通过通信传输获得,这样也可以很好的协调各个客户端的数据同步。尤其是在扑克游戏中,每个玩家都可以看到对方的剩余牌数,这个数据如果在每个客户端自主计算的话,确实也不是什么难事,但是如果在获得服务器数据的同时,附加每个玩家的牌数,就可以更加方便的得到数据,而且不会因为某个客户端的计算错误,导致几个玩家看到的对方牌数不一样。

另一个问题是,某个玩家执行出牌操作后,只是给服务器端发送数据告诉服务器自己要出什么牌,然后服务器将出牌的一些数据,包括出牌面值、出牌的方式、出牌的格式和出牌的玩家socket,告诉所有的玩家,也包括出牌人,这时候,包括出牌人在内的所有玩家收到服务器发来的出牌数据后,更新自己的桌面,显示出牌画面。这是一种方式,另一种方式是,自己出牌后,首先更新自己的桌面,显示出牌,同时发送给服务器,服务器只要将出牌数据发送给其余的玩家。我没有采用后者,因为后者相当于将自己特殊化了,同样的代码要给自己用一次,其他玩家再用一次。而事实上对于服务器端来说每个玩家都是平等的,在客户端必然存在一种方式可以让每个玩家都执行相同的操作,只是界面上给玩家的感觉是有一方属于自己。

还有对于出牌的数据设计,这是个最重要的问题,如何通过简单的字符串描述出牌的信息,从而让对方玩家能够不需要过多的分析就可以进行计算。还有出牌的验证,这些都是客户端的任务。这些会在单独的文章中介绍。 再次感谢试玩游戏和支持我的朋友们!

Share/Save/Bookmark

北京,2008年再见!

类归于: 摄影&游记 — colin @ 9:33 下午 2005年05月18日

5月11日踏上了开往北京的T42,随后的一个星期时间里,在同学的陪伴下逛了几个传说中的地方。这是我第一次来到北京,和想像中的北京相比,感触很多。

12日早晨6点28分到了北京,火车没有晚点。出了火车站,坐在公交车上看着沿途的街道和建筑,感觉北京比想像中的陈旧那么一些。之后的七天,几乎每天都在不停的坐车,不停的走路……

第一天:北大、天安门、王府井
第二天:颐和园、清华、天安门夜景
第三天:石景山游乐场
第四天:八达岭长城
第五天:同学聚会
第六天:十刹海
第七天:休息、T231

七天的行程就不细写了,也许是对每个地方都期望过高,以至于看到实物已经不那么激动了呵呵。最后竟然发现北京最宏伟的建筑是北京西站。

北京实在是大,坐公交车真是让人难以忘怀,几乎每天都在公交车上度过两三个小时,而且几乎没有座位。北京公交车上确实比较温馨,尊老爱幼的风气很浓厚,大学生经常主动给老年人让坐。但是也存在个别乘务员和乘客发生争吵的情况。还有有些公交车乘务员的北京腔报站真是听不懂,比粤语还难懂!

北京的交通总的来说不错,几条环城路修建的比较完善,几乎都是非常宽的高速,而且每隔不远都会有天桥方便行人通过,这点做得非常好,在环城路上几乎看不到行人穿行马路。

北京的高层建筑不多,但是有很多半仿古建筑非常漂亮,我见过的太平洋、北京图书馆、长安街上很多建筑都很漂亮。但是对于大多数住宅楼,都感觉很陈旧,看上去有很久的历史了,也应了“老北京”这个词。

北京的公厕都是免费的,而且比较卫生,做到这点可真是不容易,可见北京的城市环保工作确实不错。

北京出租车大部分都是现代,而且感觉司机比较礼貌,服务态度也很好。

北京的旅游景点感觉还需要很好的开发,比如八达岭长城的周边环境就不太好,同样是世界奇迹,兵马俑的开发感觉就不错。

北京的“发票”、“办证”真是多如牛毛,很多高校门口都聚集了这些无法分子,地面、天桥、广告牌上到处都能看到那些办证的电话,真是应该治理一下了。

北京的立交桥真是百闻不如一见,传说中让交警都痛不欲生的西直门立交桥真让人眼花缭乱,到北京后的前三天,每天都要到那里坐地铁,但是要让我说清楚立交桥的结构,还是不行,附图如下:

2005521221418337.jpg

……

最后当我离开北京的那一刻,我喜欢上了这个城市,我对北京的感觉在一个星期里戏剧性的变化着。来到北京的时候,我都不知道北京西站的样子,跟着同学坐着公交车就出了车站,可能是走了其他的路,在离开北京的时候,我看到了北京西站的全貌,真是太壮观了。说实话那天还不太想离开北京,这几天感觉时间过得很慢,每天都安排得很充实,临走前一天的一场大雨,好像在为我们送别。北京同学的热情,更让我觉得首都北京的亲切。

没有去的地方真多,故宫、电视台、圆明园、天坛、世纪坛、北海公园……以后还有机会的,2008年再见!

北京,2008年希望你更加美好,再见了!

 2005521224719740.jpg

20055212248406351.jpg

20055212249219081.jpg

2005521225035737.jpg

2005521225133421.jpg

2005521225252390.jpg

Share/Save/Bookmark

《空袭日本》3D游戏开发日记[三] 多层纹理渲染

类归于: 游戏开发 — colin @ 6:54 下午 2005年05月02日

这篇开发日记距离上一篇已经有段时间了,因为这段时间主要是在学习即将要用到的一些技术,所以开发几乎暂停,并且利用这段时间在思考已经设计好的结构是否合理。

一个关键的技术,多层纹理混合渲染(Muti Texture),在3D游戏中经常会用到,比如一些FPS游戏中的墙壁上可以看到一些动态的光影效果,甚至感觉覆盖一层雾气,那些都是使用了多层纹理,其实最多三层纹理就可以搞定,而目前D3D支持最多8层纹理,我们几乎不需要那么多,目前最炫的3D游戏也就是最多3-4层纹理。所以合理巧妙的使用多层纹理可以起到事半功倍的效果,这种效果在使用光照的情况下未必可以表现的出,而且光照的计算量要大得多。

多层纹理的混合需要想象力,因为D3D提供了很多的混合运算方式,怎么把每个层面的纹理颜色或者alpha通道进行何种运算混合,就看你的想象力了!

以下是一个两层纹理渲染得示例(摘自GameRes):

定义FVF和顶点格式
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX2)

struct CUSTOMVERTEX
{
    D3DXVECTOR3 position; // The position
    D3DCOLOR color; // The color
    FLOAT tu, tv; // The texture coordinates
    FLOAT tu2, tv2;
};

设置TextureStageState:
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 );

m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_ADD);//MODULATE2X );
m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT );

动态改变第二层贴图坐标

CUSTOMVERTEX* pVertices;

if( FAILED( g_pVB->Lock( 0, 0, (BYTE**)&pVertices, 0 ) ) )
    return E_FAIL;

for( DWORD i=0; i<100; i++ )
{
    FLOAT theta = (2*D3DX_PI*i)/(100-1);

    // pVertices[2*i+0].position = D3DXVECTOR3( sinf(theta),-1.0f, cosf(theta) );
    // pVertices[2*i+0].color = 0xffffffff;
    // pVertices[2*i+0].tu = ((FLOAT)i)/(100-1);
    // pVertices[2*i+0].tv = 1.0f;
    pVertices[2*i+0].tu = fTexTimeKey/40+((FLOAT)i)/(100-1);
    // pVertices[2*i+0].tv2 = 1.0f;

    // pVertices[2*i+1].position = D3DXVECTOR3( sinf(theta), 1.0f, cosf(theta) );
    // pVertices[2*i+1].color = 0xff808080;
    // pVertices[2*i+1].color = 0xffffffff;
    // pVertices[2*i+1].tu = ((FLOAT)i)/(100-1);
    // pVertices[2*i+1].tv = 0.0f;
    pVertices[2*i+1].tu = fTexTimeKey/40+((FLOAT)i)/(100-1);
    // pVertices[2*i+1].tv2 = 0.0f;

}
g_pVB->Unlock();

Share/Save/Bookmark