效果:
实现代码:
// 弹弹球.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "resource.h" #include <time.h> #define MAX_LOADSTRING 100 // Global Variables: HINSTANCE hInst; // current instance TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // The title bar text #define BALLS_NUM 64 //最多小球的数量 #define MAX_V 4 //小球的最大速度 int ballsX[BALLS_NUM],ballsY[BALLS_NUM]; //数组保存每个小球的位置 int ballsVX[BALLS_NUM],ballsVY[BALLS_NUM]; //数组保存每个小球的速度 COLORREF ballsC[BALLS_NUM]; //数组保存每个小球的颜色 int nBalls = 0; //当前小球的数量 int radius = 20; //小球的半径 int timeStep = 50; // 定时器的时间间隔 int wndWidth = 0; //窗口尺寸 int wndHeight = 0; //Foward declarations of functions included in this code module: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); //自定义的函数 //在窗口中心随机生成一个小球的函数 //@n 小球的数量 int GenerateBall( int * n) { if( *n >= BALLS_NUM) //小球数量过多就不生成 return 0; ballsX[*n] = wndWidth/2; //中心位置 ballsY[*n] = wndHeight/2; ballsVX[*n] = MAX_V - 2*(rand()%(MAX_V+1)); //随机速度 ballsVY[*n] = MAX_V - 2*(rand()%(MAX_V+1)); ballsC[*n] = RGB(rand()%256,rand()%256,rand()%256); //随机颜色 (*n)++; return 1; } //绘制小球函数 void DrawBalls(HDC hdc,int n ,int r, int X[], int Y[],COLORREF C[]) { HBRUSH brush; for(int i = 0;i < n; i++) { brush = CreateSolidBrush(C[i]); //使用当前颜色的笔刷绘制小球 SelectObject(hdc, brush); Ellipse(hdc, X[i]-r, Y[i]-r, X[i]+r, Y[i]+r); DeleteObject(brush); } } //当两个小球碰撞后的反应,计算碰撞后的速度 int Response(int v1[2],int v2[2],int u[2]) { if(u[0]*u[0]+u[1]*u[1] == 0) //二者重叠的话暂时不碰撞 return 0; //保存连线上和垂直的速度分量 int tmp,v11[2],v12[2],v21[2],v22[2]; v11[0] =( v1[0]*u[0] + v1[1] * u[1])*u[0]/(u[0] *u[0]+u[1] * u[1]); v11[1] =( v1[0]*u[0] + v1[1] * u[1])*u[1]/(u[0] *u[0]+u[1] * u[1]); v12[0] = v1[0] - v11[0]; v12[1] = v1[1] - v11[1]; v21[0] =( v2[0]*u[0] + v2[1] * u[1])*u[0]/(u[0] *u[0]+u[1] * u[1]); v21[1] =( v2[0]*u[0] + v2[1] * u[1])*u[1]/(u[0] *u[0]+u[1] * u[1]); v22[0] = v2[0] - v21[0]; v22[1] = v2[1] - v21[1]; tmp = v11[0]; v11[0] = v21[0]; v21[0] = tmp; tmp = v11[1]; v11[1] = v21[1]; v21[1] = tmp; v1[0] = v11[0] + v12[0]; v1[1] = v11[1] + v12[1]; v2[0] = v21[0] + v22[0]; v2[1] = v21[1] + v22[1]; return 1; } //每一帧,按照小球所处的碰撞状态修改小球的速率,并更新小球的位置 void UpdateBalls(int n, int r, int X[], int Y[], int VX[], int VY[],int elapseTime) { if(n > BALLS_NUM) return ; for(int i =0; i < n; i++) { for(int j =i+1;j < n; j++) { int v1xy[2], v2xy[2],u[2];//两个小球的初速度和碰撞方向向量 //1 小球是否发生相互碰撞 //目前的碰撞检测和反应知识简单的计算方法,会出现以下问题 //a,小球之间可能出现粘黏 //b,当小球速度过快市,可能错过检测碰撞 int dist2 = (X[i] -X[j])*(X[i] -X[j])+(Y[i] - Y[j])*(Y[i] - Y[j]); if(dist2 <= 4*r*r) { u[0] = X[j] - X[i]; u[1] = Y[j] - Y[i]; v1xy[0] = VX[i]; v1xy[1] = VY[i]; v2xy[0] = VX[j]; v2xy[1] = VY[j]; //如果小球相互碰撞,则修改小球的速度 if(Response(v1xy, v2xy, u)) { VX[i] = v1xy[0]; //碰撞后两个小球的速度的改变 VY[i] = v1xy[1]; VX[j] = v2xy[0]; VY[j] = v2xy[1]; } } } } //2处理小球和屏幕边界的碰撞 for( i = 0; i < n; i++) { if((X[i] - r) <= 0) //左边界 VX[i] = abs(VX[i]); else if((X[i] + r) >= wndWidth) //右边界 VX[i] = -abs(VX[i]); if((Y[i] + r) >= wndHeight) //下边界 VY[i] = -abs(VY[i]); else if((Y[i] - r) <= 0)//上边界 VY[i] = abs(VY[i]); } //按照速度更新小球的位置,以便产生动画效果 for( i = 0; i<n; i++) { X[i] += VX[i] * elapseTime; Y[i] += VY[i] * elapseTime; } } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // TODO: Place code here. srand(time((NULL))); // 随机种子 GenerateBall(&nBalls); //随机生成一个小球 MSG msg; HACCEL hAccelTable; // Initialize global strings LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_MY, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_MY); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMMENTS: // // This function and its usage is only necessary if you want this code // to be compatible with Win32 systems prior to the 'RegisterClassEx' // function that was added to Windows 95. It is important to call this function // so that the application will get 'well formed' small icons associated // with it. // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_MY); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = (LPCSTR)IDC_MY; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL); return RegisterClassEx(&wcex); } // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } // // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; TCHAR szHello[MAX_LOADSTRING]; LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING); switch (message) { case WM_CREATE://程序启动后触发的构造消息 //开始设置同一个ID为1的定时器,每timestep毫秒触发一个定时器消息 SetTimer(hWnd,1,timeStep,NULL); break; case WM_TIMER://定时器响应消息 if(wParam == 1)//如果是感兴趣的定时器,则更新游戏 { UpdateBalls(nBalls, radius, ballsX, ballsY,ballsVX , ballsVY,timeStep / 10); //让窗口变为无效,从而触发重绘消息 InvalidateRect(hWnd, NULL, TRUE); } break; case WM_SIZE://获取窗口尺寸 wndWidth = LOWORD(lParam); wndHeight = HIWORD(lParam); break; case WM_KEYDOWN: if(wParam == ' ') //按下空格 { GenerateBall(&nBalls);//生成一个小球 InvalidateRect(hWnd,NULL,TRUE);//触发绘制消息 } break; case WM_ERASEBKGND: break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: { hdc = BeginPaint(hWnd, &ps); // TODO: Add any drawing code here... //以下步骤是为了避免产生屏幕闪烁,而将画面首先绘制到内存中,然后一次性拷贝到屏幕上 //创建内存HDC HDC memHDC = CreateCompatibleDC(hdc); //获取客户区大小 RECT rectClient; GetClientRect(hWnd, &rectClient); //创建位图 HBITMAP bmpBuff = CreateCompatibleBitmap(hdc,wndWidth,wndHeight); HBITMAP pOldBMP = (HBITMAP)SelectObject(memHDC, bmpBuff); //设置白色背景 PatBlt(memHDC,0,0,wndWidth,wndHeight,WHITENESS); //绘制到后备缓存 DrawBalls(memHDC, nBalls, radius, ballsX, ballsY, ballsC); //拷贝内存HDC内容到实际HDC BOOL tt = BitBlt(hdc, rectClient.left, rectClient.top, wndWidth, wndHeight, memHDC, rectClient.left, rectClient.top,SRCCOPY ); //内存回收 SelectObject(memHDC, pOldBMP); DeleteObject(bmpBuff); DeleteDC(memHDC); EndPaint(hWnd, &ps); break; } case WM_DESTROY: KillTimer(hWnd, 1);//程序退出时,删除定时器 PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // Mesage handler for about box. LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; }
存在的问题:
1.碰撞检测问题,当两个小球位置接近时,碰撞判断可能不断发生,表现为小球“粘连”,请读者对碰撞检测和碰撞反应进行修正;
2.没有增加游戏趣味性,可以将本游戏改为台球游戏;