问题标题: 从零开始,制作C++ 3D游戏 (一)

2
1
已解决
王牌工作室官方
王牌工作室官方
新手光能
新手光能

前言

我在酷丁堂上课时,到不在酷丁堂上课用小号看问答,无数大佬前仆后继钟情于3D,然而,他们之中的大部分没有成功

而我为什么把这篇文章发出来呢?我不是猜想,不是征集,我已经有了完整的思路!

现在,我们开始吧。

一,检测玩家与墙的距离

检测玩家与墙的距离,就可以知道墙该画多大

先从最简单的开始,墙是一条线段,玩家是一个点,那么就是计算线段和射线的交点。

这是本章的核心部分

用到的变量解释(用图像思维)

  • p:射线的起点,比如玩家坐标

  • r:射线的方向,比如你看的方向(可以是 (cos(angle), sin(angle))

  • q:线段的起点(墙的一端)

  • s:线段的方向向量(墙的另一端 - 墙的起点)

这些变量就像是: 你站在 p,拿着激光枪沿着 r 开火 墙从 q 指向 q + s

四个变量都是向量

然后就可以解决几个问题

  • 有没有交点?

    • 计算射线方向 r 和线段方向 s 的“旋转差” → 用 叉积 cross(r, s)

    • 如果为 0,说明两条线是平行的(永远不会碰上)

  • 交点的位置在哪?

    • 找出两个方向的交叉点,计算出 t:射线走了多少距离才碰到墙

    • 再计算出 u:线段上的位置(0 到 1 表示在线段范围内)

  • 交点合法吗?

    • 射线:t >= 0 表示交点在你前方,不是在你后背

    • 线段:0 <= u <= 1 表示交点就在墙上,而不是墙延伸出去的地方

现在我们开始写代码

假设你站在 (0, 0),朝着 45° 方向发出一束射线,也就是向量 (1, 1)。 你要检测它是否碰到一条墙,墙的两个端点是:

  • A = (2, 0)

  • B = (0, 2)

这个墙是从右下到左上的斜线段。

定义变量

Vec2 p = {0, 0};      // 射线起点
Vec2 r = {1, 1};      // 射线方向

Vec2 q = {2, 0};      // 墙的起点
Vec2 s = {-2, 2};     // 墙的方向 = B - A = (0 - 2, 2 - 0)

计算是否平行

rxs = cross(r, s) = 1 * 2 - 1 * (-2) = 2 + 2 = 4

不等于0,说明不平行

接下来计算t和u

qp = q - p = (2 - 0, 0 - 0) = (2, 0)
t = cross(qp, s) / rxs = (2 * 2 - 0 * -2) / 4 = 4 / 4 = 1
u = cross(qp, r) / rxs = (2 * 1 - 0 * 1) / 4 = 2 / 4 = 0.5

判断结果

  • t = 1 👉 交点在射线前方 1 个单位处

  • u = 0.5 👉 交点在线段中间部分

所以,射线与线段确实相交

计算交点坐标

intersection.x = p.x + t * r.x = 0 + 1 * 1 = 1
intersection.y = p.y + t * r.y = 0 + 1 * 1 = 1

所以交点位于(1,1)

#include <SDL.h>
#include <cmath>
#include <iostream>

struct Vec2 {
    float x, y;
};

struct Line {
    float x1, y1, x2, y2;
};

// 叉积
float cross(const Vec2& a, const Vec2& b) {
    return a.x * b.y - a.y * b.x;
}

// 射线与线段求交
bool intersectRayLine(const Vec2& rayStart, const Vec2& rayDir, const Line& seg, Vec2& out) {
    Vec2 p = rayStart;
    Vec2 r = rayDir;
    Vec2 q = { seg.x1, seg.y1 };
    Vec2 s = { seg.x2 - seg.x1, seg.y2 - seg.y1 };

    float rxs = cross(r, s);
    if (rxs == 0.0f) return false; // 平行或重合

    Vec2 qp = { q.x - p.x, q.y - p.y };
    float t = cross(qp, s) / rxs;
    float u = cross(qp, r) / rxs;

    if (t >= 0.0f && u >= 0.0f && u <= 1.0f) {
        out = { p.x + t * r.x, p.y + t * r.y };
        return true;
    }
    return false;
}

int main(int argc, char* argv[]) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* win = SDL_CreateWindow("test",
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        800, 600, SDL_WINDOW_SHOWN);
    SDL_Renderer* renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);

    // 定义射线起点和方向
    Vec2 rayStart = { 400, 300 };
    Vec2 rayDir = { 1, 0.5 }; // 射线方向

    // 定义墙体线段
    Line wall = { 200, 200, 600, 450 };
    Vec2 intersection;
    bool hit = intersectRayLine(rayStart, rayDir, wall, intersection);

    bool running = true;
    SDL_Event e;

    while (running) {
        while (SDL_PollEvent(&e)) {
            if (e.type == SDL_QUIT) running = false;
        }

        SDL_SetRenderDrawColor(renderer, 20, 20, 20, 255);
        SDL_RenderClear(renderer);

        // 渲染墙体
        SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255);
        SDL_RenderDrawLine(renderer, wall.x1, wall.y1, wall.x2, wall.y2);

        // 渲染射线(长一点)
        Vec2 rayEnd = { rayStart.x + rayDir.x * 1000, rayStart.y + rayDir.y * 1000 };
        SDL_SetRenderDrawColor(renderer, 0, 180, 255, 255);
        SDL_RenderDrawLine(renderer, rayStart.x, rayStart.y, rayEnd.x, rayEnd.y);

        // 如果有交点,渲染交点
        if (hit) {
            SDL_SetRenderDrawColor(renderer, 255, 50, 50, 255);
            SDL_Rect point = { (int)intersection.x - 3, (int)intersection.y - 3, 6, 6 };
            SDL_RenderFillRect(renderer, &point);
        }

        SDL_RenderPresent(renderer);
        SDL_Delay(16);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 0;
}

功能说明

  • 玩家在屏幕中央 (400, 300)

  • 发出一束射线,方向为 (1, 0.5)(右上)

  • 墙体是一条线段,从 (200, 200)(600, 450)

  • 用颜色高亮交点


0
0
肖付强
肖付强
高级天翼
高级天翼

大佬,牛逼。我记得你以前,发过类似的帖子,当时你提出的是相对来说3D,自己不动,参照物动。

0
0
0
石峻帆
石峻帆
新手光能
新手光能

但是头文件......

我说怎么这么奇怪

头文件怎么办

我想看效果

0
0
0
0
李子墨
李子墨
新手天翼
新手天翼

%%%

大佬!!!

(所以你是……谁)

0
石峻帆
石峻帆
新手光能
新手光能

我酷丁平台登不上去啊

我要回答