首页 » 技术分享 » 网络台球游戏的网络编程(vc++)

网络台球游戏的网络编程(vc++)

 

网络台球游戏的网络编程

转载请注明出处

本文章的源代码下载地址,请单击此链接

网络台球是一款对战式台球游戏,程序界面如图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 类,一个是会话SocketCClientSocket,另一个是服务器监听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++),转载请注明来源!

0