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

  • 相关阅读:
    O052、Create Volume 操作 (Part III)
    O051、Create Volume 操作 (Part II)
    O050、Create Volume 操作 (Part I)
    O049、准备 LVM Volume Provider
    O048、掌握 cinder-scheduler 调度逻辑
    O047、 Cinder 组件详解
    O046、掌握Cinder 的设计思想
    O045、理解 Cinder 架构
    O044、一张图秒懂 Nova 16种操作
    O043、计算节点宕机了怎么办
  • 原文地址:https://www.cnblogs.com/ncgds/p/6733839.html
Copyright © 2020-2023  润新知