3D游戏开发---大系列了---第一篇
2026-02-20 15:56:53
发布于:湖南
V1.0问世(可惜只能移动。。。)
代码:
#include <windows.h>
#include <cmath>
const int WIDTH = 1280;
const int HEIGHT = 720;
const float PI = 3.1415926535f;
const float RAD = PI / 180.0f;
const float NEAR_CLIP = 0.5f;
const float FAR_CLIP = 1000.0f;
const float FOV = 60.0f;
HWND g_hWnd = nullptr;
HDC g_hMemDC = nullptr;
HBITMAP g_hMemBitmap = nullptr;
HBITMAP g_hOldBitmap = nullptr;
bool g_bRunning = true;
bool g_bWindowActive = false;
HPEN g_hGridPen = nullptr;
HPEN g_hXAxisPen = nullptr;
HPEN g_hZAxisPen = nullptr;
struct Vec3 {
float x, y, z;
Vec3() : x(0), y(0), z(0) {}
Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
};
inline Vec3 add(Vec3 a, Vec3 b) {
return Vec3(a.x + b.x, a.y + b.y, a.z + b.z);
}
inline Vec3 sub(Vec3 a, Vec3 b) {
return Vec3(a.x - b.x, a.y - b.y, a.z - b.z);
}
inline Vec3 mul(Vec3 a, float s) {
return Vec3(a.x * s, a.y * s, a.z * s);
}
inline float dot(Vec3 a, Vec3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
inline float len(Vec3 a) {
return sqrt(dot(a, a));
}
inline Vec3 norm(Vec3 a) {
float l = len(a);
return l > 0.001f ? mul(a, 1.0f / l) : a;
}
inline Vec3 cross(Vec3 a, Vec3 b) {
return Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}
struct Camera {
Vec3 pos;
float baseYaw;
float basePitch;
float moveSpeed;
float sprintMult;
float mouseSens;
float velY;
float gravity;
float jumpForce;
bool isGrounded;
float bobTimer;
float bobWalkFrequency;
float bobSprintFrequency;
float bobHorizontalPosAmp;
float bobYawViewAmp;
float bobSprintMultiplier;
bool isMoving;
Camera() {
pos = Vec3(0, 2.0f, -15.0f);
baseYaw = 0.0f;
basePitch = 0.0f;
moveSpeed = 6.0f;
sprintMult = 2.5f;
mouseSens = 0.15f;
velY = 0.0f;
gravity = -18.0f;
jumpForce = 8.0f;
isGrounded = true;
bobTimer = 0.0f;
bobWalkFrequency = 10.0f;
bobSprintFrequency = 16.0f;
bobHorizontalPosAmp = 0.12f;
bobYawViewAmp = 0.25f;
bobSprintMultiplier = 2.0f;
isMoving = false;
}
Vec3 getForward() {
float yawBob = sin(bobTimer * 0.5f) * bobYawViewAmp;
if (isMoving && (GetAsyncKeyState('R') & 0x8000)) {
yawBob *= bobSprintMultiplier;
}
float finalYaw = (baseYaw + yawBob) * RAD;
float finalPitch = basePitch * RAD;
return norm(Vec3(
sin(finalYaw) * cos(finalPitch),
sin(finalPitch),
cos(finalYaw) * cos(finalPitch)
));
}
Vec3 getRight() {
Vec3 forward = getForward();
Vec3 worldUp = Vec3(0, 1, 0);
return norm(cross(worldUp, forward));
}
Vec3 getUp() {
Vec3 forward = getForward();
Vec3 right = getRight();
return norm(cross(forward, right));
}
void processInput(float deltaTime) {
if (!g_bWindowActive || !IsWindow(g_hWnd)) return;
float speed = moveSpeed;
if (GetAsyncKeyState('R') & 0x8000) speed *= sprintMult;
float vel = speed * deltaTime;
float baseYawRad = baseYaw * RAD;
float basePitchRad = basePitch * RAD;
Vec3 baseForward = norm(Vec3(
sin(baseYawRad) * cos(basePitchRad),
sin(basePitchRad),
cos(baseYawRad) * cos(basePitchRad)
));
Vec3 baseRight = norm(cross(Vec3(0, 1, 0), baseForward));
Vec3 moveForward = norm(Vec3(baseForward.x, 0, baseForward.z));
Vec3 moveRight = norm(Vec3(baseRight.x, 0, baseRight.z));
bool wPressed = (GetAsyncKeyState('W') & 0x8000) != 0;
bool sPressed = (GetAsyncKeyState('S') & 0x8000) != 0;
bool aPressed = (GetAsyncKeyState('A') & 0x8000) != 0;
bool dPressed = (GetAsyncKeyState('D') & 0x8000) != 0;
isMoving = (wPressed || sPressed || aPressed || dPressed) && isGrounded;
if (wPressed) pos = add(pos, mul(moveForward, vel));
if (sPressed) pos = sub(pos, mul(moveForward, vel));
if (aPressed) pos = sub(pos, mul(moveRight, vel));
if (dPressed) pos = add(pos, mul(moveRight, vel));
if ((GetAsyncKeyState(VK_SPACE) & 0x8000) && isGrounded) {
velY = jumpForce;
isGrounded = false;
}
velY += gravity * deltaTime;
pos.y += velY * deltaTime;
if (pos.y <= 2.0f) {
pos.y = 2.0f;
velY = 0.0f;
isGrounded = true;
}
float currentFrequency = isMoving ? bobWalkFrequency : 0.0f;
if (isMoving && (GetAsyncKeyState('R') & 0x8000)) {
currentFrequency = bobSprintFrequency;
}
if (isMoving) {
bobTimer += deltaTime * currentFrequency;
if (bobTimer > 2 * PI) bobTimer -= 2 * PI;
} else {
if (bobTimer > 0.0f) {
bobTimer = (bobTimer - deltaTime * bobWalkFrequency * 2.0f) > 0.0f ?
(bobTimer - deltaTime * bobWalkFrequency * 2.0f) : 0.0f;
} else {
bobTimer = 0.0f;
}
}
}
void processMouse(float xOffset, float yOffset) {
if (!g_bWindowActive || !IsWindow(g_hWnd)) return;
baseYaw += xOffset * mouseSens;
basePitch += yOffset * mouseSens;
if (basePitch > 89.0f) basePitch = 89.0f;
if (basePitch < -89.0f) basePitch = -89.0f;
}
};
Camera g_cam;
bool g_firstMouse = true;
float g_lastMouseX = WIDTH / 2.0f;
float g_lastMouseY = HEIGHT / 2.0f;
bool g_ignoreMouse = false;
enum ClipPlane {
PLANE_NEAR, PLANE_FAR, PLANE_LEFT, PLANE_RIGHT, PLANE_TOP, PLANE_BOTTOM
};
float pointToPlaneDistance(Vec3 point, ClipPlane plane) {
Vec3 forward = g_cam.getForward();
Vec3 right = g_cam.getRight();
Vec3 up = g_cam.getUp();
Vec3 camPos = g_cam.pos;
float currentHorizontalAmp = g_cam.isMoving ? g_cam.bobHorizontalPosAmp : 0.0f;
if (g_cam.isMoving && (GetAsyncKeyState('R') & 0x8000)) {
currentHorizontalAmp *= g_cam.bobSprintMultiplier;
}
float horizontalBob = sin(g_cam.bobTimer * 0.5f) * currentHorizontalAmp;
camPos = add(camPos, mul(right, horizontalBob));
Vec3 delta = sub(point, camPos);
float tanHalfFov = tan(FOV * RAD / 2.0f);
float aspect = (float)WIDTH / HEIGHT;
switch (plane) {
case PLANE_NEAR:
return dot(delta, forward) - NEAR_CLIP;
case PLANE_FAR:
return FAR_CLIP - dot(delta, forward);
case PLANE_LEFT:
return dot(delta, right) + tanHalfFov * aspect * dot(delta, forward);
case PLANE_RIGHT:
return tanHalfFov * aspect * dot(delta, forward) - dot(delta, right);
case PLANE_TOP:
return tanHalfFov * dot(delta, forward) - dot(delta, up);
case PLANE_BOTTOM:
return dot(delta, up) + tanHalfFov * dot(delta, forward);
default:
return 0.0f;
}
}
Vec3 lineIntersectPlane(Vec3 p1, Vec3 p2, ClipPlane plane) {
float d1 = pointToPlaneDistance(p1, plane);
float d2 = pointToPlaneDistance(p2, plane);
float t = d1 / (d1 - d2);
return add(p1, mul(sub(p2, p1), t));
}
bool clipLineToFrustum(Vec3& p1, Vec3& p2) {
for (int i = 0; i < 6; i++) {
ClipPlane plane = (ClipPlane)i;
float d1 = pointToPlaneDistance(p1, plane);
float d2 = pointToPlaneDistance(p2, plane);
if (d1 < 0 && d2 < 0) return false;
if (d1 >= 0 && d2 >= 0) continue;
if (d1 < 0) p1 = lineIntersectPlane(p1, p2, plane);
else p2 = lineIntersectPlane(p1, p2, plane);
}
return true;
}
bool projectPoint(Vec3 point, POINT* out) {
Vec3 forward = g_cam.getForward();
Vec3 right = g_cam.getRight();
Vec3 up = g_cam.getUp();
Vec3 camPos = g_cam.pos;
float currentHorizontalAmp = g_cam.isMoving ? g_cam.bobHorizontalPosAmp : 0.0f;
if (g_cam.isMoving && (GetAsyncKeyState('R') & 0x8000)) {
currentHorizontalAmp *= g_cam.bobSprintMultiplier;
}
float horizontalBob = sin(g_cam.bobTimer * 0.5f) * currentHorizontalAmp;
camPos = add(camPos, mul(right, horizontalBob));
Vec3 delta = sub(point, camPos);
float z = dot(delta, forward);
if (z < NEAR_CLIP || z > FAR_CLIP) return false;
float x = dot(delta, right);
float y = dot(delta, up);
float tanHalfFov = tan(FOV * RAD / 2.0f);
float aspect = (float)WIDTH / HEIGHT;
float ndcX = x / (z * tanHalfFov * aspect);
float ndcY = y / (z * tanHalfFov);
if (ndcX < -10.0f || ndcX > 10.0f || ndcY < -10.0f || ndcY > 10.0f) return false;
out->x = (int)((ndcX + 1.0f) * 0.5f * WIDTH);
out->y = (int)((1.0f - ndcY) * 0.5f * HEIGHT);
return true;
}
void drawLine(Vec3 p1, Vec3 p2, HPEN pen) {
if (!clipLineToFrustum(p1, p2)) return;
POINT sp1, sp2;
if (!projectPoint(p1, &sp1) || !projectPoint(p2, &sp2)) return;
HPEN oldPen = (HPEN)SelectObject(g_hMemDC, pen);
MoveToEx(g_hMemDC, sp1.x, sp1.y, nullptr);
LineTo(g_hMemDC, sp2.x, sp2.y);
SelectObject(g_hMemDC, oldPen);
}
void render() {
if (!g_hMemDC || !IsWindow(g_hWnd)) return;
RECT rect = {0, 0, WIDTH, HEIGHT};
FillRect(g_hMemDC, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH));
const int gridHalf = 20;
const float cellSize = 2.0f;
float start = -gridHalf * cellSize;
float end = gridHalf * cellSize;
for (int i = -gridHalf; i <= gridHalf; i++) {
float x = i * cellSize;
Vec3 p1 = Vec3(x, 0, start);
Vec3 p2 = Vec3(x, 0, end);
HPEN usePen = (fabs(x) < 0.01f) ? g_hXAxisPen : g_hGridPen;
drawLine(p1, p2, usePen);
}
for (int i = -gridHalf; i <= gridHalf; i++) {
float z = i * cellSize;
Vec3 p1 = Vec3(start, 0, z);
Vec3 p2 = Vec3(end, 0, z);
HPEN usePen = (fabs(z) < 0.01f) ? g_hZAxisPen : g_hGridPen;
drawLine(p1, p2, usePen);
}
const char* title = "3D第一人称移动 | WASD移动 R疾跑 空格跳跃 ESC退出";
SetWindowText(g_hWnd, title);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE: {
HDC hdc = GetDC(hWnd);
g_hMemDC = CreateCompatibleDC(hdc);
g_hMemBitmap = CreateCompatibleBitmap(hdc, WIDTH, HEIGHT);
if (g_hMemDC && g_hMemBitmap) {
g_hOldBitmap = (HBITMAP)SelectObject(g_hMemDC, g_hMemBitmap);
}
g_hGridPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
g_hXAxisPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
g_hZAxisPen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
ReleaseDC(hWnd, hdc);
break;
}
case WM_ERASEBKGND:
return 1;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
render();
if (g_hMemDC) {
BitBlt(hdc, 0, 0, WIDTH, HEIGHT, g_hMemDC, 0, 0, SRCCOPY);
}
EndPaint(hWnd, &ps);
break;
}
case WM_MOUSEMOVE: {
if (g_ignoreMouse || !g_bWindowActive) {
g_ignoreMouse = false;
return 0;
}
int x = LOWORD(lParam);
int y = HIWORD(lParam);
if (g_firstMouse) {
g_lastMouseX = (float)x;
g_lastMouseY = (float)y;
g_firstMouse = false;
}
float xOffset = (float)x - g_lastMouseX;
float yOffset = g_lastMouseY - (float)y;
g_lastMouseX = (float)x;
g_lastMouseY = (float)y;
g_cam.processMouse(xOffset, yOffset);
RECT clientRect;
GetClientRect(hWnd, &clientRect);
ClientToScreen(hWnd, (LPPOINT)&clientRect);
ClientToScreen(hWnd, (LPPOINT)&clientRect + 1);
int centerX = (clientRect.left + clientRect.right) / 2;
int centerY = (clientRect.top + clientRect.bottom) / 2;
g_ignoreMouse = true;
SetCursorPos(centerX, centerY);
g_lastMouseX = (float)(centerX - clientRect.left);
g_lastMouseY = (float)(centerY - clientRect.top);
break;
}
case WM_ACTIVATE: {
if (wParam == WA_INACTIVE || IsIconic(hWnd)) {
g_bWindowActive = false;
ShowCursor(TRUE);
ClipCursor(nullptr);
} else {
g_bWindowActive = true;
ShowCursor(FALSE);
g_firstMouse = true;
RECT rect;
GetClientRect(hWnd, &rect);
ClientToScreen(hWnd, (LPPOINT)&rect);
ClientToScreen(hWnd, (LPPOINT)&rect + 1);
ClipCursor(&rect);
}
break;
}
case WM_KEYDOWN: {
if (wParam == VK_ESCAPE) {
DestroyWindow(hWnd);
}
break;
}
case WM_DESTROY:
g_bRunning = false;
g_bWindowActive = false;
ClipCursor(nullptr);
ShowCursor(TRUE);
if (g_hGridPen) {
DeleteObject(g_hGridPen);
g_hGridPen = nullptr;
}
if (g_hXAxisPen) {
DeleteObject(g_hXAxisPen);
g_hXAxisPen = nullptr;
}
if (g_hZAxisPen) {
DeleteObject(g_hZAxisPen);
g_hZAxisPen = nullptr;
}
if (g_hOldBitmap && g_hMemDC) {
SelectObject(g_hMemDC, g_hOldBitmap);
g_hOldBitmap = nullptr;
}
if (g_hMemBitmap) {
DeleteObject(g_hMemBitmap);
g_hMemBitmap = nullptr;
}
if (g_hMemDC) {
DeleteDC(g_hMemDC);
g_hMemDC = nullptr;
}
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmd, int show) {
(void)hPrev;
(void)cmd;
WNDCLASSEX wc = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.hInstance = hInst;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = "3DMovementClass";
RegisterClassEx(&wc);
RECT winRect = {0, 0, WIDTH, HEIGHT};
AdjustWindowRect(&winRect, WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME, FALSE);
g_hWnd = CreateWindowEx(
0, "3DMovementClass", "3D第一人称移动",
WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME,
CW_USEDEFAULT, CW_USEDEFAULT,
winRect.right - winRect.left,
winRect.bottom - winRect.top,
NULL, NULL, hInst, NULL
);
if (!g_hWnd) return 0;
ShowWindow(g_hWnd, show);
UpdateWindow(g_hWnd);
SetForegroundWindow(g_hWnd);
LARGE_INTEGER freq, lastTime, currTime;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&lastTime);
const float frameTime = 1.0f / 60.0f;
float accumulator = 0.0f;
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while (g_bRunning) {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
g_bRunning = false;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (!IsWindow(g_hWnd)) break;
QueryPerformanceCounter(&currTime);
float deltaTime = (float)(currTime.QuadPart - lastTime.QuadPart) / (float)freq.QuadPart;
lastTime = currTime;
if (deltaTime > 0.25f) deltaTime = 0.25f;
accumulator += deltaTime;
while (accumulator >= frameTime) {
g_cam.processInput(frameTime);
accumulator -= frameTime;
}
InvalidateRect(g_hWnd, NULL, FALSE);
Sleep(1);
}
UnregisterClass("3DMovementClass", hInst);
return (int)msg.wParam;
}
制作不易,点个赞呗!
这里空空如也




















有帮助,赞一个