win32_弹弹球游戏

效果:


实现代码:

// 弹弹球.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.没有增加游戏趣味性,可以将本游戏改为台球游戏;

原文地址:https://www.cnblogs.com/ncgds/p/6733839.html