ITKeyword,专注技术干货聚合推荐

注册 | 登录

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

u010019717 分享于 2013-04-03

推荐:VC++之网络编程五 聊天编程实例(UDP)

转自http://blog.csdn.net/thinkscape/article/details/4037483 Server: #include <Winsock2.h>#include <stdio.h>void main(){ //mide delete word WORD wV

2019阿里云全部产品优惠券(新购或升级都可以使用,强烈推荐)
领取地址https://promotion.aliyun.com/ntms/yunparter/invite.html

网络台球游戏的网络编程

转载请注明出处

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

网络台球是一款对战式台球游戏,程序界面如图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;

推荐:VC++消息钩子编程

一、消息钩子的概念 1、基本概念    Windows应用程序是基于消息驱动的,任何线程只要注册窗口类都会有一个消息队列用于接收用户输入的消息和系统消息。为了拦截

}

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++之网络编程五 聊天编程实例

 server: //NetSrv.cpp #include <Winsock2.h> #include <stdio.h> void main(){ //mide delete word WORD wVersionRequested; WSADATA wsaData; int err; wVers

网络台球游戏的网络编程 转载请注明出处 本文章的源代码下载地址,请单击此链接 网络台球是一款对战式台球游戏,程序界面如图4.13所示。                       图4.13网络台球 在游戏开始的时候

相关阅读排行


用户评论

游客

相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。