簡單渲染流水管線C++代碼實現(三)---實戰之《黃金礦工》

寫這篇博客之前,糾正之前代碼中一處寫的不好的地方:
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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章