网络台球游戏的网络编程
转载请注明出处
网络台球是一款对战式台球游戏,程序界面如图4.13所示。
图4.13网络台球
在游戏开始的时候,程序会弹出一个类型选择
框,如果4.14 所示,玩家可以选择作为服务器、客
户机或者是单人练习赛。
图4.14游戏类型选择
网络台球的绘制部分采用OpenGL 技术。
OpenGL 技术将会在第5章中介绍,这里只介绍台球
碰撞算法和网络实现部分。
先来看看网络台球的碰撞算法。
4.8.1 刚体运动与碰撞
当玩家击球后,系统调用HitBall 函数。HitBall 函数定义如下:
void CBilliardsPlayView::HitBall()
{
if (!m_bAnimate)
{
m_nEnter = 100;
CMainFrame *pFrame = (CMainFrame*)AfxGetMainWnd();
CBilliardsPlayView*pView=(CBilliardsPlayView*)(pFrame->
m_wndSplitter).GetPane(0,1);
CBilliardsDoc *pDoc = (CBilliardsDoc*)pView->GetDocument();
if (pDoc->m_bPlayer!=IDLE)
{
m_nEnter = -1;
if (m_bCanHit)
pDoc->SendVelocity(m_fVelocity[0]);
}
m_bAnimate = TRUE;
SetTimer(ANIMATE_TIMER,5,NULL);
}
}
在HitBall 函数里,需要设置一个所有球都已经静止的标志m_bAnimate。如果不是单人练习赛,
则要将这个击球动作发送到网络的另一端。最后要设置一个动画定时器。
定时器消息响应函数如下:
void CBilliardsPlayView::OnTimer(UINT nIDEvent)
{
CMainFrame *pFrame = (CMainFrame*)AfxGetMainWnd();
CBilliardsPlayView*pView=(CBilliardsPlayView*)(pFrame->m_wndSplitter).
GetPane(0,1);
if (nIDEvent == ANIMATE_TIMER){
DrawScene();
pView->GetDlgItem(IDC_BUTTON_SHOOT)->EnableWindow(m_bCanHit&&!m_bAnimate);
}
CView::OnTimer(nIDEvent);
}
在定时器消息响应函数中调用了函数DrawScene,结构如下:
Void DrawScene()
图4.14 游戏类型选择
第4 章网络游戏开发241
{
…………//省略
if (m_bAnimate)
{
if (Calculate())
{
KillTimer(ANIMATE_TIMER);
m_bAnimate = FALSE;
…………//省略
…………
}
}
…………//省略
}
不难看出Calculate 函数就是计算球函数。当Calculate函数返回1时,表明当前桌面的球都静止
了,所以需要删除定时器,重置球静止标志;当Calculate函数返回0时,说明还有球在运动。
Calculate 函数中使用的bool数组m_bVisible是球落袋的标志。Calculate函数定义如下:
int CBilliardsPlayView::Calculate()
{
CMainFrame *pView=(CMainFrame*)(AfxGetMainWnd());
float x1,z1,x2,z2,x3,z3;
float tmpf,minf;
BOOL mark[NUM_BALL][NUM_BALL];
int i,j;
int k;
x1 = TABLE_LENGTH - BAR_WIDTH;
z1 = TABLE_WIDTH - BAR_WIDTH;
x2 = 0.0;
z2 = TABLE_WIDTH;
for (i=0;i<NUM_BALL;i++)
if (m_bVisible[i]) //排除已落袋的球
{
x3 = fabs(m_fPosition[i][0]);
z3 = fabs(m_fPosition[i][2]);
//球进洞了
if(((x1-x3)*(x1-x3)+(z1-z3)*(z1-z3)<2.0*BALL_RADIUS*BALL_RADIUS)||
((x2-x3)*(x2-x3)+(z2-z3)*(z2-z3)<2.0*BALL_RADIUS*BALL_RADIUS))
{
m_bVisible[i] = FALSE;
if(pView->m_bSoundGoal)
sndPlaySound("res\\goal.wav",SND_ASYNC);
if (m_nEnter<i)
m_nEnter = i;
}
}
242 Visual C++游戏开发技术与实例
for (i=0;i<NUM_BALL;i++)
for (j=0;j<NUM_BALL;j++)
mark[i][j]=FALSE; //mark 用来标记不要重复计算
for (i=0;i<NUM_BALL;i++)
if (m_bVisible[i])
{
if ((m_fPosition[i][0]>TABLE_LENGTH-BAR_WIDTH-BALL_RADIUS)
&&(fabs(m_fPosition[i][2])<TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS))
{
//右壁
m_fPosition[i][0]=TABLE_LENGTH-BAR_WIDTH-BALL_RADIUS;
hitx=INFINITY;
hitz=INFINITY;
if (m_fVelocity[i][0]>0.0)
{
m_fVelocity[i][0] = -DECR*m_fVelocity[i][0];
if(pView->m_bSoundCush)
sndPlaySound("res\\cush.wav",SND_ASYNC);
}
}
else
if((m_fPosition[i][0]<-(TABLE_LENGTH-BAR_WIDTH-BALL_RADIUS))
&&(fabs(m_fPosition[i][2])<TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS))
{
//左壁
m_fPosition[i][0]=-(TABLE_LENGTH-BAR_WIDTH-BALL_RADIUS);
hitx=INFINITY;
hitz=INFINITY;
if (m_fVelocity[i][0]<0.0)
{
m_fVelocity[i][0] = -DECR*m_fVelocity[i][0];
if(pView->m_bSoundCush)
sndPlaySound("res\\cush.wav",SND_ASYNC);
}
}
else
if((m_fPosition[i][2]>TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS)
&&(fabs(m_fPosition[i][0])>1.2*BALL_RADIUS))
{
//上壁
m_fPosition[i][2]=TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS;
hitx=INFINITY;
hitz=INFINITY;
if (m_fVelocity[i][2]>0.0)
{
m_fVelocity[i][2] = -DECR*m_fVelocity[i][2];
if(pView->m_bSoundCush)
sndPlaySound("res\\cush.wav",SND_ASYNC);
}
}
else
第4 章网络游戏开发243
if((m_fPosition[i][2]<-(TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS))
&&(fabs(m_fPosition[i][0])>1.2*BALL_RADIUS))
{
//下壁
m_fPosition[i][2]=-(TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS);
hitx=INFINITY;
hitz=INFINITY;
if (m_fVelocity[i][2]<0.0)
{
m_fVelocity[i][2] = -DECR*m_fVelocity[i][2];
if(pView->m_bSoundCush)
sndPlaySound("res\\cush.wav",SND_ASYNC);
}
}
else //不碰壁
{
minf = 8*BALL_RADIUS*BALL_RADIUS;
//找出所有球心和当前球心的最小距离
for (j=0;j<NUM_BALL;j++)
if(m_bVisible[j]&&(i!=j)&&!mark[i][j])
{
tmpf = (m_fPosition[i][0]-m_fPosition[j][0])*
(m_fPosition[i][0]-m_fPosition[j][0])+
(m_fPosition[i][2]-m_fPosition[j][2])*
(m_fPosition[i][2]-m_fPosition[j][2]);
//两球心距离的平方和
if (tmpf<minf)
{
minf=tmpf;
k=j;
}
}
//如果两球发生碰撞
if (minf<=4*BALL_RADIUS*BALL_RADIUS)
{
float line[3];
float t1,t2;
float vec1[3],vec2[3],vec3[3],vec4[3];
//x 坐标差
line[0]=m_fPosition[k][0]-m_fPosition[i][0];
line[1]=0.0f;
//z 坐标差
line[2]=m_fPosition[k][2]-m_fPosition[i][2];
//长度
t1 = sqrt(DOTPROD3(line,line));
if (t1>0.0)
244 Visual C++游戏开发技术与实例
VEC3_V_OP_S(line,line,/,t1);
//line[i]=line[i]/t1,乘单位向量
//i 号球的速度乘单位向量
t1=DOTPROD3(m_fVelocity[i],line);
//k 号球的速度乘单位向量
t2=DOTPROD3(m_fVelocity[k],line);
//恢复原line 的长度,放入vec1
VEC3_V_OP_S(vec1,line,*,t1);
//i 号球的x,z 方向的分速度-vec1x,z 方向分速度
VEC3_V_OP_V(vec1,m_fVelocity[i],-,vec1);
//k 号球的速度
VEC3_V_OP_S(vec2,line,*,t2);
VEC3_V_OP_V(vec2,m_fVelocity[k],-,vec2);
if (t1-t2>0.0)
{
VEC3_V_OP_S(vec3,line,*,t2);
VEC3_V_OP_V(m_fVelocity[i],vec1,+,vec3);
VEC3_V_OP_S(vec4,line,*,t1);
VEC3_V_OP_V(m_fVelocity[k],vec2,+,vec4);
mark[i][k]=mark[k][i]=TRUE;
if(pView->m_bSoundBall)
sndPlaySound("res\\ball2ball.wav",SND_ASYNC);
}
hitx=INFINITY;
hitz=INFINITY;
}
}
if (fabs(m_fPosition[i][0])>TABLE_LENGTH||
fabs(m_fPosition[i][2])>TABLE_WIDTH)
{
m_bVisible[i]=FALSE;
if(pView->m_bSoundGoal)
sndPlaySound("res\\goal.wav",SND_ASYNC);
if (m_nEnter<i)
m_nEnter = i;
}
}
for (i=0;i<NUM_BALL;i++)
if (m_bVisible[i])
{
float t1,s1;
float vec1[3];
t1 =sqrt(DOTPROD3(m_fVelocity[i],m_fVelocity[i]));
if (t1>0.0f)
第4 章网络游戏开发245
{
VEC3_V_OP_S(vec1,m_fVelocity[i],/,t1);
s1 = t1+ACCELERATION/2.0;
t1 = t1+ACCELERATION;
//速度小于一定值就停下来
if (t1<-0.1*ACCELERATION)
{
s1=0.0f;
t1=0.0f;
}
VEC3_V_OP_S(m_fVelocity[i],vec1,*,t1);
VEC3_V_OP_S(vec1,vec1,*,s1);
if(i==0)
{
if(DOTPROD3(vec1,vec1)>((m_fPosition[i][0]-hitx)*
(m_fPosition[i][0]-hitx)+(m_fPosition[i][2]-hitz)*
(m_fPosition[i][2]-hitz))){
m_fPosition[i][0]=hitx;
m_fPosition[i][2]=hitz;
hitx=INFINITY;
hitz=INFINITY;
}
else
VEC3_V_OP_V(m_fPosition[i],m_fPosition[i],+,vec1);
}
else
VEC3_V_OP_V(m_fPosition[i],m_fPosition[i],+,vec1);
x3 = fabs(m_fPosition[i][0]);
z3 = fabs(m_fPosition[i][2]);
if(((x1-x3)*(x1-x3)+(z1-z3)*(z1-z3)<BALL_RADIUS*
BALL_RADIUS)||((x2-x3)*(x2-x3)+(z2-z3)*(z2-z3)
<BALL_RADIUS*BALL_RADIUS));
//以下防止球越界过多
else
if((m_fPosition[i][0]>TABLE_LENGTH-BAR_WIDTH-BALL_RADIUS)
&&(fabs(m_fPosition[i][2])<TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS))
{
m_fPosition[i][0]=TABLE_LENGTH-BAR_WIDTH-BALL_RADIUS+0.01;
}
else
if((m_fPosition[i][0]<-(TABLE_LENGTH-BAR_WIDTHBALL_
RADIUS))&&(fabs(m_fPosition[i][2])<TABLE_WIDTH-BAR_WI
DTH-BALL_RADIUS))
{
m_fPosition[i][0]=-(TABLE_LENGTH-BAR_WIDTH-BALL_RADIUS)-0.01;
hitx=INFINITY;
hitz=INFINITY;
}
else
246 Visual C++游戏开发技术与实例
if((m_fPosition[i][2]>TABLE_WIDTH-BAR_WIDTHBALL_
RADIUS)&&(fabs(m_fPosition[i][0])>1.2*BALL_RADIUS))
{
m_fPosition[i][2]=TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS+0.01;
hitx=INFINITY;
hitz=INFINITY;
}
else
if((m_fPosition[i][2]<-(TABLE_WIDTH-BAR_WIDTHBALL_
RADIUS))&&(fabs(m_fPosition[i][0])>1.2*BALL_RADIUS))
{
m_fPosition[i][2]=-(TABLE_WIDTH-BAR_WIDTH-BALL_RADIUS)-0.01;
hitx=INFINITY;
hitz=INFINITY;
}
for (j=0;j<NUM_BALL;j++)
if (m_bVisible[j]&&(i!=j))
{
tmpf = (m_fPosition[i][0]-m_fPosition[j][0])*
(m_fPosition[i][0]-m_fPosition[j][0])+(m_fPosition[i][2]-m_fPosition[j][2])*(m_fPositio
n[i][2]-m_fPosition[j][2]);
if (tmpf<4*BALL_RADIUS*BALL_RADIUS)
break;
}
if (j<NUM_BALL) //出现两球距离小于直径的情况
{
VEC3_V_OP_S(vec1,vec1,/,2.0);
VEC3_V_OP_V(m_fPosition[i],m_fPosition[i],-,vec1);
}
}
}
for (i=0;i<NUM_BALL;i++)
if (m_bVisible[i])
if((m_fVelocity[i][0]!=0.0)||(m_fVelocity[i][2]!=0.0))
break;
if (i<NUM_BALL)
return 0;
return 1;
}
在Calculate 函数中,要对所有没有落袋的球进行检测,计算两两球心之间的距离,从而判断是否
发生碰撞。如果发生碰撞则根据动力学原理,重新计算它们的运行速度和方向。另外,如果球和球桌
边缘发生碰撞,则需要计算发生碰撞后球的新速度和运动方向。
4.8.2 网络部分的代码实现
网络台球的网络部分是用Socket 实现的。在CBilliardsApp 类的InitInstance函数中,首先要初始
化Socket,即调用AfxSocketInit函数。
这里构建了两个Socket 类,一个是会话Socket类CClientSocket,另一个是服务器监听Socket类
CListenSocket。
第4 章网络游戏开发247
CClientSocket 类继承自CSocket。它负责通信数据的传输,所以需要重载OnReceive以获得网络
数据消息。这和前面介绍的CSocket 使用方法并无两样。CClientSocket 的实现代码如下:
CClientSocket::CClientSocket(CBilliardsDoc* pDoc)
{
m_pClientDoc=pDoc;
}
CClientSocket::~CClientSocket()
{
}
//接受数据的消息响应函数
void CClientSocket::OnReceive(int nErrorCode)
{
CString strTemp;
if(nErrorCode==0){
char *pBuf=new char[100];
int nReceived;
if((nReceived=Receive(pBuf,50))==SOCKET_ERROR){
AfxMessageBox("无法接收数据",MB_OK);
}
pBuf[nReceived]=’\0’;
if(pBuf[0]==’2’)
m_pClientDoc->ReceiveVelocity(pBuf);
else
{
strTemp=pBuf;
m_pClientDoc->ReceiveMSG(strTemp);
}
}
}
OnReceive 首先判断消息类型,如果是击球消息(控制码是2),则调用文档类的ReceiveVelocity
函数。ReceiveVelocity 函数将接收到的矢量位置在本地做模拟碰撞计算,碰撞计算是在视图类中的
HitBall 中完成的。如果是控制消息,则调用文档类的ReceiveMSG函数,用于处理控制消息。
ReceiveVelocity 函数定义如下。
void CBilliardsDoc::ReceiveVelocity(char *pBuf)
{
Velocity *v=new Velocity;
v=(Velocity *)pBuf;
CBilliardsPlayView* pPlayView;
pPlayView=(CBilliardsPlayView *)((CMainFrame*)AfxGetMainWnd())->m_wndSplitter.
GetPane(0,0);
pPlayView->m_fVelocity[0][0]=v->fvalue0;
pPlayView->m_fVelocity[0][1]=v->fvalue1;
pPlayView->m_fVelocity[0][2]=v->fvalue2;
while (pPlayView->m_bAnimate);
pPlayView->HitBall(); //调用视图类的碰撞算法
248 Visual C++游戏开发技术与实例
}
ReceiveMSG 函数定义如下。
BOOL CBilliardsDoc::ReceiveMSG(CString strTemp)
{
CBilliardsFormView* pFormView;
CMainFrame *pView=(CMainFrame*)(AfxGetMainWnd());
CPropertyItem *pItem=NULL;
POSITION pos;
for(pos=GetFirstViewPosition();pos!=NULL;)
{
CView* pView = GetNextView(pos);
pFormView= DYNAMIC_DOWNCAST(CBilliardsFormView,pView);
if (pFormView != NULL)
break;
}
switch(strTemp.GetAt(0)){
case ’0’: //Login 消息
strTemp.Delete(0);
if(m_bPlayer==SERVER){
//如果是服务器,则表示有客户上站消息
pFormView->GetDlgItem(IDC_STATIC_CLIENT)->SetWindowText(strTemp);
pFormView->GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(TRUE);
m_strClient=strTemp;
sndPlaySound("res\\notify.wav",SND_ASYNC);
}
else{//如果是客户机,则表示成功连接服务器
pFormView->GetDlgItem(IDC_STATIC_SERVER)->SetWindowText(strTemp);
pFormView->GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(TRUE);
m_strServer=strTemp;
}
break;
case ’1’: //聊天消息
strTemp.Delete(0);
if(m_bPlayer==SERVER){
pItem=new CPropertyItem(m_strClient,strTemp,PIT_EDIT,"");
pFormView->m_ctlListMSG.AddPropItem(pItem);
if(pView->m_bSoundMsg)
sndPlaySound("res\\ringin.wav",SND_ASYNC);
}
else{
pItem=newCPropertyItem(m_strServer,strTemp,PIT_EDIT,"");
pFormView->m_ctlListMSG.AddPropItem(pItem);
if(pView->m_bSoundMsg)
sndPlaySound("res\\ringin.wav",SND_ASYNC);
}
pItem=NULL;
break;
第4 章网络游戏开发249
case ’3’: //Logout 消息
if(m_bPlayer==SERVER){
AfxMessageBox("客户端已离开游戏",MB_OK);
this->m_pClientSocket->ShutDown();
this->m_pClientSocket->Close();
pFormView->GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(FALSE);
}
else{
AfxMessageBox("服务器端已离开游戏",MB_OK);
this->m_pClientSocket->ShutDown();
this->m_pClientSocket->Close();
((CMainFrame*)AfxGetMainWnd())->m_wndToolBar.GetToolBarCtrl()
.HideButton(ID_NEWGAME,FALSE);
((CMainFrame *)AfxGetMainWnd())->m_wndToolBar.GetToolBarCtrl()
.HideButton(ID_JOINGAME,FALSE);
((CMainFrame*)AfxGetMainWnd())->m_wndToolBar.GetToolBarCtrl()
.HideButton(ID_STOPGAME);
((CMainFrame*)AfxGetMainWnd())->GetMenu()->GetSubMenu(0)
->EnableMenuItem(ID_NEWGAME,MF_ENABLED);
((CMainFrame*)AfxGetMainWnd())->GetMenu()->GetSubMenu(0)
->EnableMenuItem(ID_JOINGAME,MF_ENABLED);
((CMainFrame*)AfxGetMainWnd())->GetMenu()->GetSubMenu(0)
->EnableMenuItem(ID_STOPGAME,MF_GRAYED);
pFormView->GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(FALSE);
}
break;
default:
break;
}
return 1;
}
CListenSocket 也继承自CSocket,它是服务器端的连接监听Socket,所以需要重载OnAccept以获
得网络连接消息。这也和前面介绍的CSocket使用方法差别不大。
CListenSocket::CListenSocket(CBilliardsDoc* pDoc)
{
m_pListenDoc=pDoc;
}
CListenSocket::~CListenSocket()
{
}
//接收连接的消息响应函数
void CListenSocket::OnAccept(int nErrorCode)
{
//当有连接到来的时候,调用文档类的AcceptConnection 函数处理连接
if(nErrorCode==0)
m_pListenDoc->AcceptConnection();
}
250 Visual C++游戏开发技术与实例
OnAccept 函数使用了文档类函数AcceptConnection处理连接。AcceptConnection定义如下:
BOOL CBilliardsDoc::AcceptConnection()
{
if(!m_pListenSocket->Accept(*m_pClientSocket)){
AfxMessageBox("接受连接失败!");
return 0;
}
//向客户端发送成功登陆消息
CString strTemp;
strTemp="0"+m_strServer;
m_pClientSocket->Send(strTemp,strTemp.GetLength());
return 1;
}
在CBilliardsDoc 中,函数SendVelocity是用来发送击球位置的,击球消息的控制码是2。
void CBilliardsDoc::SendVelocity(floatm_fVelocity[3])
{
Velocity v;
v.type=’2’;
v.fvalue0=m_fVelocity[0];
v.fvalue1=m_fVelocity[1];
v.fvalue2=m_fVelocity[2];
m_pClientSocket->Send(&v,sizeof(v));
}
转载自原文链接, 如需删除请联系管理员。
原文链接:网络台球游戏的网络编程(vc++),转载请注明来源!