寫這篇博客之前,糾正之前代碼中一處寫的不好的地方:
Vector2.h中Rotation應寫爲Rotate。
下面我們進行今天的主題:黃金礦工
如何利用之前我們實現的一些東西來做黃金礦工呢?
其實不算很難,注意一下幾個地方即可。
1.在沒有任何操作的情況下,抓手應該是來回180°旋轉,要注意到臨界點時,角度反轉,最好是用一個變量控制當前角度值,一個變量控制角度遞增量(正負值控制旋轉方向)
2.按下抓取按鈕時,抓手停止搖擺,長度勻速遞增,遞增過程中判斷抓手是否與物體相撞(這裏其實有一些優化的地方,這裏沒有深入做),也要同時判斷是否與屏幕邊界相碰
3.與物體相碰時,物體立即“上鉤”—物體中心位置與抓手相同,然後抓手返回
4.無論是否抓取到物體,抓手返回過程中判斷是否已經縮短至最初時的狀態,然後繼續搖擺,再自動隨機產生其他物體
遊戲效果如下:
主要的遊戲代碼如下:
#include <iostream>
#include <windows.h>
#include <vector>
#include "vector2.h"
#include "matrix3.h"
#pragma comment(lib, "Msimg32.lib")
//定義一些窗口常量
#define _CLIENT_PW 640 //屏幕像素寬
#define _CLIENT_PH 480 //屏幕像素高
#define _FRAME_PER_SECOND 40 //屏幕每秒刷新幀數
#define _FRAME_SLEEPTIME (1000/_FRAME_PER_SECOND) //每幀休息時間
#define _PI 3.1415 //pai
//全局窗口句柄
HWND g_hWnd = nullptr;
//窗口活動
BOOL g_Active = FALSE;
//函數聲明
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void GameInit();
void GameRun();
void GameEnd();
//程序入口點
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//填充窗口類別結構體
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = nullptr;
wc.lpszClassName = __TEXT("My3D");
//註冊窗口類別結構體
RegisterClass(&wc);
//根據客戶區矩形計算窗口矩形
int screen_pw = GetSystemMetrics(SM_CXSCREEN);
int screen_ph = GetSystemMetrics(SM_CYSCREEN);
RECT r =
{
(screen_pw - _CLIENT_PW) / 2,
(screen_ph - _CLIENT_PH) / 2,
r.left + _CLIENT_PW,
r.top + _CLIENT_PH
};
//校準窗口矩形
AdjustWindowRect(&r, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);
//創建窗口
g_hWnd = CreateWindow(
wc.lpszClassName,
__TEXT("My3D"),
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
r.left, r.top, r.right - r.left, r.bottom - r.top, //窗口左上角位置和寬高
HWND_DESKTOP,
NULL,
wc.hInstance,
NULL);
//更新窗口
UpdateWindow(g_hWnd);
//顯示窗口
ShowWindow(g_hWnd, nCmdShow);
//遊戲初始化
GameInit();
//消息循環
MSG msg = {};
while (WM_QUIT != msg.message)
{
//下面的代碼表示,一旦有消息就調用消息處理函數來處理消息
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//當沒有消息且窗口激活那麼就執行遊戲循環
else if (TRUE == g_Active)
{
//遊戲運行
GameRun();
}
//當沒有消息且窗口未激活就等待消息
else
WaitMessage();
}
//遊戲結束
GameEnd();
return (int)msg.wParam;
}
//消息回調函數
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_ACTIVATEAPP:
{
g_Active = static_cast<BOOL>(wParam);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(1);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//主顯示設備
HDC g_MainDC = nullptr;
//後備顯示設備
HDC g_BackDC = nullptr;
float Arm_CurrentAngle; //角度變化值
float Add_Angle; //角度遞增量
float Previou_Add_Angle;//記錄按下時刻角度遞增量
float Arm_CurrentL; //遊戲運行中當前機械臂長度
float Add_L; //機械臂的長度增量
float Arm_PreviousL; //機械臂的初始長度
//機械臂的位置
Vector2 Arm_Start;
Vector2 Arm_End;
//小球的位置及半徑
Vector2 Ball;
float Ball_R;
bool isReturn; //機械臂是否需要返回
bool isDown; //機械臂是否開始抓取
bool isGrab; //機械臂是否抓到物品
bool isExist; //物品是否已經被抓回且不存在
void GameInit()
{
//得到主顯示設備
g_MainDC = GetDC(g_hWnd);
//創建後備顯示設備
g_BackDC = CreateCompatibleDC(g_MainDC);
//創建和主顯示設備兼容的位圖
HBITMAP hbmp = CreateCompatibleBitmap(g_MainDC, _CLIENT_PW, _CLIENT_PH);
//將兼容位圖選入兼容設備,刪除老的位圖
DeleteObject(SelectObject(g_BackDC, hbmp));
//初始化各項數據
Arm_CurrentL = Arm_PreviousL = 100.0f;
Add_L = 5.0f;
Add_Angle = 0.02f;
Previou_Add_Angle = Add_Angle;
Arm_Start.Set(0.0f, 0.0f);
Arm_End.Set(-Arm_PreviousL, 0.0f);
Ball.Set(0.0f, _CLIENT_PH / 2);
Ball_R = 0.0f;
isReturn = false;
isDown = false;
isGrab = false;
isExist = false;
}
// 設備 起點x,y 終點x,y
void DrawLine(HDC hdc, float b_x1, float b_y1, float e_x2, float e_y2)
{
MoveToEx(hdc, b_x1, b_y1, nullptr);
LineTo(hdc, e_x2, e_y2);
}
void GameRun()
{
//得到從操作系統啓動一瞬間到目前的毫秒數
ULONGLONG begin_time = GetTickCount64();
//清屏
BitBlt(g_BackDC, 0, 0, _CLIENT_PW, _CLIENT_PH, nullptr, 0, 0, WHITENESS);
//創建變換矩陣:先縮放,再旋轉,最後平移
Matrix3 m[4];
m[0].Scale(Arm_CurrentL / Arm_PreviousL, 1.0f);
m[1].Rotate(Arm_CurrentAngle += Add_Angle);
m[2].Translate(_CLIENT_PW / 2, 0);
m[3] = m[0] * m[1] * m[2];
//創建模型接受變換後的數值:原始數據不能隨意修改
Vector2 temp[2];
temp[0] = Arm_Start * m[3];
temp[1] = Arm_End * m[3];
//繪製機械臂
DrawLine(g_BackDC, temp[0].x, temp[0].y, temp[1].x, temp[1].y);
//繪製圓
if (isExist)
Ellipse(g_BackDC, Ball.x - Ball_R, Ball.y - Ball_R, Ball.x + Ball_R, Ball.y + Ball_R);
//隨機產生圓:位置半徑都隨機
if (!isExist)
{
Ball.Set(rand() % _CLIENT_PW, rand() % (_CLIENT_PH / 2) + _CLIENT_PH / 2);
Ball_R = static_cast<float>(rand() % 30 + 25);
isExist = true;
}
//按下空格
if (GetAsyncKeyState(VK_SPACE) & 1 && !isDown)
{
isDown = true;
Previou_Add_Angle = Add_Angle;
Add_Angle = 0.0f;
}
//按下
if (isDown)
{
Arm_CurrentL += Add_L;
}
//抓到
if (static_cast<float>((Ball.x - temp[1].x) * (Ball.x - temp[1].x)) + static_cast<float>((Ball.y - temp[1].y) * (Ball.y - temp[1].y)) <= Ball_R * Ball_R && !isGrab)
{
isGrab = true;
isReturn = true;
Add_L = -Add_L;
}
//按下且抓到了
if (isDown && isGrab && isReturn)
{
Ball = temp[1];
}
//越界修正
if ((temp[1].x < 0 || temp[1].x > _CLIENT_PW || temp[1].y > _CLIENT_PH) && !isReturn)
{
Add_L = -Add_L;
Arm_CurrentL += Add_L;
isReturn = true;
}
//還原標誌
if (Arm_CurrentL < Arm_PreviousL)
{
Arm_CurrentL = Arm_PreviousL;
Add_Angle = Previou_Add_Angle;
Add_L = -Add_L;
isReturn = false;
if (isGrab)
isExist = false;
isDown = false;
isGrab = false;
}
//角度越界修正
if (temp[1].y < 0)
{
Add_Angle = -Add_Angle;
}
//後備顯示設備顏色傳輸給主顯示設備
BitBlt(
g_MainDC,
0, 0, _CLIENT_PW, _CLIENT_PH,
g_BackDC,
0, 0,
SRCCOPY);
//得到本次遊戲循環總的消耗時間
ULONGLONG frame_time = GetTickCount() - begin_time;
//進行本次遊戲循環休息
if (_FRAME_SLEEPTIME > frame_time)
Sleep(_FRAME_SLEEPTIME - frame_time);
else
Sleep(1); //必須休息一下
}
void GameEnd()
{
//刪除後備設備,釋放主設備
DeleteDC(g_BackDC);
ReleaseDC(g_hWnd, g_MainDC);
}