当前位置: 首页 > news >正文

从0到1:C++ 开启游戏开发奇幻之旅(二)

目录

游戏开发核心组件设计

游戏循环

游戏对象管理

碰撞检测

人工智能(AI) 与物理引擎

人工智能

物理引擎

性能优化技巧

内存管理优化

多线程处理

实战案例:开发一个简单的 2D 射击游戏

项目结构设计

代码实现

总结与展望


游戏开发核心组件设计

游戏循环

游戏循环是游戏运行的核心机制,它就像是游戏的 “心脏”,不断地跳动,驱动着游戏世界的运转。在游戏循环中,程序会不断地重复执行一系列的操作,包括处理用户输入、更新游戏状态、进行物理模拟和渲染画面等。这些操作的不断循环,使得游戏能够实时响应用户的操作,呈现出动态的游戏画面,为玩家带来沉浸式的游戏体验。

以一个简单的 2D 游戏为例,假设我们使用 SDL 库来创建游戏窗口和进行基本的图形绘制。下面是一个简单的游戏循环代码示例:

#include <SDL2/SDL.h>
#include <iostream>const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;int main(int argc, char* argv[]) {// 初始化SDLif (SDL_Init(SDL_INIT_VIDEO) < 0) {std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;return 1;}// 创建窗口SDL_Window* window = SDL_CreateWindow("My Game", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);if (window == NULL) {std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;SDL_Quit();return 1;}// 创建渲染器SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);if (renderer == NULL) {std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() << std::endl;SDL_DestroyWindow(window);SDL_Quit();return 1;}bool running = true;SDL_Event event;// 游戏循环while (running) {// 处理事件while (SDL_PollEvent(&event)!= 0) {if (event.type == SDL_QUIT) {running = false;}}// 清空屏幕SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);SDL_RenderClear(renderer);// 绘制内容(这里可以添加游戏对象的绘制代码)// 更新屏幕SDL_RenderPresent(renderer);}// 清理资源SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();return 0;
}

在这个代码示例中,while (running) 就是游戏循环的开始。在循环内部,首先通过 SDL_PollEvent 函数来处理用户输入事件,当用户点击关闭窗口时,running 变量被设置为 false,游戏循环结束。然后,使用 SDL_SetRenderDrawColor 和 SDL_RenderClear 函数清空屏幕,接着可以在这部分添加绘制游戏对象的代码,最后通过 SDL_RenderPresent 函数将绘制的内容显示在屏幕上。通过这样不断地循环,游戏就能够持续运行并响应用户的操作。

游戏对象管理

在游戏开发中,游戏对象管理是一个至关重要的环节,它涉及到如何有效地组织和管理游戏中的各种元素,如角色、敌人、道具等。使用面向对象编程思想可以将这些游戏元素抽象为类,每个类封装了对象的属性和行为,通过创建类的实例来表示具体的游戏对象。

以一个简单的角色扮演游戏为例,我们可以创建一个 Character 类来表示角色,这个类包含了角色的生命值、攻击力、防御力等属性,以及移动、攻击、防御等行为。然后,使用 std::vector 容器来存储多个角色对象,这样可以方便地对角色进行管理和操作。下面是一个简单的代码示例:

#include <iostream>
#include <vector>class Character {
public:int health;int attackPower;int defense;Character(int h, int ap, int d) : health(h), attackPower(ap), defense(d) {}void move(int x, int y) {std::cout << "Character moves to (" << x << ", " << y << ")" << std::endl;}void attack(Character& target) {int damage = attackPower - target.defense;if (damage > 0) {target.health -= damage;std::cout << "Character attacks target, dealing " << damage << " damage. Target's health is now " << target.health << std::endl;} else {std::cout << "Character's attack is blocked by target's defense." << std::endl;}}void defend() {std::cout << "Character defends, increasing defense temporarily." << std::endl;// 这里可以添加增加防御的具体逻辑}
};int main() {// 创建角色对象Character player(100, 20, 10);Character enemy(80, 15, 8);// 使用vector存储角色std::vector<Character> characters;characters.push_back(player);characters.push_back(enemy);// 角色操作示例characters[0].move(5, 10);characters[0].attack(characters[1]);return 0;
}

在这个示例中,Character 类封装了角色的属性和行为。main 函数中创建了两个角色对象 player 和 enemy,并将它们存储在 characters 向量中。通过向量,我们可以方便地访问和操作这些角色对象,如调用 move 方法让角色移动,调用 attack 方法让角色攻击其他角色。这种面向对象的设计方式使得游戏对象的管理更加灵活和可扩展,当需要添加新的角色类型或行为时,只需要在 Character 类中进行扩展或创建新的子类即可。

碰撞检测

碰撞检测是游戏开发中不可或缺的一部分,它用于判断游戏中的物体是否发生碰撞,这对于游戏的交互性和真实性至关重要。在 2D 游戏中,矩形碰撞检测是一种常见且简单有效的碰撞检测算法,它通过比较两个矩形的位置和大小来判断它们是否相交。

假设我们有两个矩形,分别用左上角坐标和宽高来表示。下面是一个简单的矩形碰撞检测代码示例:

#include <iostream>struct Rectangle {int x;int y;int width;int height;
};bool checkCollision(const Rectangle& rect1, const Rectangle& rect2) {return (rect1.x < rect2.x + rect2.width &&rect1.x + rect1.width > rect2.x &&rect1.y < rect2.y + rect2.height &&rect1.y + rect1.height > rect2.y);
}int main() {Rectangle rect1 = {10, 10, 50, 50};Rectangle rect2 = {30, 30, 50, 50};if (checkCollision(rect1, rect2)) {std::cout << "Rectangles are colliding!" << std::endl;} else {std::cout << "Rectangles are not colliding." << std::endl;}return 0;
}

在这个示例中,Rectangle 结构体表示一个矩形,包含左上角坐标 x、y 和宽高 width、height。checkCollision 函数通过比较两个矩形的坐标和宽高来判断它们是否相交。如果满足相交条件,则返回 true,表示两个矩形发生了碰撞;否则返回 false。在 main 函数中,创建了两个矩形 rect1 和 rect2,并调用 checkCollision 函数来检测它们是否碰撞,最后输出检测结果。这种简单的矩形碰撞检测算法在许多 2D 游戏中都有广泛的应用,如平台游戏中角色与障碍物的碰撞检测、射击游戏中子弹与敌人的碰撞检测等。

人工智能(AI) 与物理引擎

人工智能

在游戏的虚拟世界中,人工智能(AI)扮演着举足轻重的角色,它赋予了游戏中的非玩家角色(NPC)以智慧和自主行为能力,极大地提升了游戏的趣味性和挑战性。以《塞尔达传说:旷野之息》为例,游戏中的敌人 AI 设计非常出色,它们能够根据林克的位置、行为和周围环境做出智能决策。当林克靠近时,敌人会进入警戒状态,主动寻找掩护,并且会根据林克的攻击方式进行躲避或反击。在战斗中,敌人还会相互配合,有的负责吸引林克的注意力,有的则从侧翼或背后发动攻击,这种智能的协作使得战斗更加具有策略性和挑战性,让玩家充分感受到了与 “聪明” 敌人战斗的乐趣。

在游戏开发中,实现简单的 AI 寻路和决策是让游戏更加生动和有趣的重要手段。下面以 A寻路算法为例,展示如何实现敌人的简单寻路功能。A寻路算法是一种启发式搜索算法,它结合了 Dijkstra 算法的广度优先搜索和最佳优先搜索的优点,通过评估函数来选择最优路径,能够在复杂的地图环境中快速找到从起点到终点的最短路径。

首先,我们需要定义地图的数据结构,假设地图是一个二维数组,0 表示可通行区域,1 表示障碍物:

#include <vector>// 定义地图
std::vector<std::vector<int>> map = {{0, 0, 0, 0},{0, 1, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0}
};

接下来,定义节点类,用于表示地图上的每个位置,每个节点包含坐标、父节点指针、G 值(从起点到当前节点的实际代价)和 H 值(从当前节点到目标节点的估计代价):

struct Node {int x, y;Node* parent;int g, h;Node(int _x, int _y) : x(_x), y(_y), parent(nullptr), g(0), h(0) {}// 计算F值,F = G + Hint f() const {return g + h;}
};

然后,实现 A * 寻路算法的核心逻辑。在这个函数中,我们使用两个容器,openList用于存储待探索的节点,closedList用于存储已经探索过的节点。通过不断从openList中取出 F 值最小的节点进行扩展,直到找到目标节点或者openList为空:

#include <queue>
#include <cmath>
#include <algorithm>// 比较函数,用于优先队列按F值从小到大排序
struct CompareNode {bool operator()(const Node* a, const Node* b) const {return a->f() > b->f();}
};// A*寻路算法
std::vector<Node> aStarSearch(int startX, int startY, int endX, int endY) {std::priority_queue<Node*, std::vector<Node*>, CompareNode> openList;std::vector<std::vector<bool>> closedList(map.size(), std::vector<bool>(map[0].size(), false));Node* startNode = new Node(startX, startY);Node* endNode = new Node(endX, endY);openList.push(startNode);while (!openList.empty()) {Node* currentNode = openList.top();openList.pop();if (currentNode->x == endNode->x && currentNode->y == endNode->y) {// 找到路径,回溯生成路径std::vector<Node> path;while (currentNode!= nullptr) {path.push_back(*currentNode);currentNode = currentNode->parent;}std::reverse(path.begin(), path.end());delete startNode;delete endNode;return path;}closedList[currentNode->x][currentNode->y] = true;// 探索相邻节点for (int i = -1; i <= 1; ++i) {for (int j = -1; j <= 1; ++j) {if (i == 0 && j == 0) continue;int newX = currentNode->x + i;int newY = currentNode->y + j;if (newX >= 0 && newX < map.size() && newY >= 0 && newY < map[0].size() &&map[newX][newY] == 0 &&!closedList[newX][newY]) {Node* neighbor = new Node(newX, newY);neighbor->parent = currentNode;neighbor->g = currentNode->g + 1;neighbor->h = std::abs(newX - endNode->x) + std::abs(newY - endNode->y);bool inOpenList = false;for (auto& node : openList) {if (node->x == neighbor->x && node->y == neighbor->y) {if (neighbor->f() < node->f()) {node->parent = neighbor->parent;node->g = neighbor->g;node->h = neighbor->h;}inOpenList = true;break;}}if (!inOpenList) {openList.push(neighbor);}}}}}delete startNode;delete endNode;return {};
}

在上述代码中,aStarSearch函数接受起点和终点的坐标作为参数,返回从起点到终点的路径节点列表。在函数内部,通过优先队列openList来管理待探索的节点,优先队列会根据节点的 F 值自动排序,每次取出 F 值最小的节点进行扩展。在扩展节点时,检查相邻节点是否可通行且未被探索过,如果是,则计算其 G 值和 H 值,并将其加入openList中。如果找到了目标节点,则通过回溯父节点的方式生成路径。

物理引擎

物理引擎在游戏开发中扮演着不可或缺的角色,它为游戏世界注入了真实的物理规律,让游戏中的物体行为更加贴近现实,极大地增强了游戏的沉浸感和交互性。以《绝地求生》为例,游戏中的物理引擎精确地模拟了各种武器的后坐力、子弹的飞行轨迹、车辆的行驶和碰撞等物理效果。玩家在射击时,能够明显感受到武器后坐力对射击精度的影响,需要通过压枪等操作来控制射击;在驾驶车辆时,车辆的加速、减速、转弯以及碰撞后的变形和损坏都表现得非常真实,让玩家仿佛置身于真实的战场之中。

Box2D 是一款流行的 2D 物理引擎,它提供了丰富的功能,如刚体模拟、碰撞检测、关节约束等,能够帮助开发者轻松实现各种复杂的物理效果。下面以一个简单的示例展示如何使用 Box2D 创建一个包含重力和碰撞效果的场景。

首先,需要包含 Box2D 的头文件并初始化 Box2D 世界:

#include <Box2D/Box2D.h>
#include <iostream>int main() {// 创建Box2D世界,设置重力为(0, -10),表示向下的重力加速度为10b2Vec2 gravity(0.0f, -10.0f);b2World world(gravity);

然后,创建地面刚体,地面是一个静态刚体,不会受到重力影响,用于支撑其他物体:

    // 创建地面刚体b2BodyDef groundBodyDef;groundBodyDef.position.Set(0.0f, -10.0f);b2Body* groundBody = world.CreateBody(&groundBodyDef);b2PolygonShape groundBox;groundBox.SetAsBox(50.0f, 10.0f);groundBody->CreateFixture(&groundBox, 0.0f);

接着,创建一个动态刚体,它会受到重力影响并与地面发生碰撞:

    // 创建动态刚体b2BodyDef bodyDef;bodyDef.type = b2_dynamicBody;bodyDef.position.Set(0.0f, 4.0f);b2Body* body = world.CreateBody(&bodyDef);b2PolygonShape dynamicBox;dynamicBox.SetAsBox(1.0f, 1.0f);b2FixtureDef fixtureDef;fixtureDef.shape = &dynamicBox;fixtureDef.density = 1.0f;fixtureDef.friction = 0.3f;body->CreateFixture(&fixtureDef);

最后,通过循环模拟物理世界的变化,每帧更新刚体的位置和状态:

    // 模拟运动float timeStep = 1.0f / 60.0f;int32 velocityIterations = 6;int32 positionIterations = 2;for (int32_t i = 0; i < 60; ++i) {world.Step(timeStep, velocityIterations, positionIterations);b2Vec2 position = body->GetPosition();float angle = body->GetAngle();std::cout << "位置: (" << position.x << ", " << position.y << ") 角度: " << angle << std::endl;}return 0;
}

在上述代码中,首先创建了一个 Box2D 世界,并设置了重力方向和大小。然后创建了地面刚体和一个动态刚体,地面刚体通过b2PolygonShape定义为一个矩形,动态刚体同样是一个矩形,并且设置了密度和摩擦系数。在模拟循环中,通过world.Step函数按照固定的时间步长更新物理世界,每次更新后获取动态刚体的位置和角度并输出。这样,就实现了一个简单的包含重力和碰撞效果的物理场景,动态刚体在重力作用下下落并与地面发生碰撞,其位置和角度会随着时间不断变化。

性能优化技巧

内存管理优化

在 C++ 游戏开发中,内存管理是性能优化的关键环节。内存泄漏和内存碎片问题如同隐藏在游戏中的 “定时炸弹”,会随着游戏的运行逐渐消耗系统资源,导致游戏性能下降,甚至出现崩溃的情况。因此,掌握有效的内存管理优化技巧至关重要。

避免内存泄漏的关键在于确保每一次内存分配都有对应的释放操作。在使用new分配内存后,一定要记得使用delete释放内存;对于数组,要使用delete[]。然而,手动管理内存容易出错,特别是在复杂的游戏逻辑中,很容易遗漏释放操作。C++11 引入的智能指针(如std::shared_ptr、std::unique_ptr和std::weak_ptr)为我们提供了一种更加安全和便捷的内存管理方式。std::unique_ptr拥有对对象的唯一所有权,当它离开作用域时,会自动释放所指向的对象,这就像是给对象找了一个专属的 “管家”,时刻关注着对象的生命周期,一旦 “管家” 离开,对象也就被妥善处理了。std::shared_ptr则允许多个指针共享对一个对象的所有权,通过引用计数来管理对象的生命周期,当引用计数为 0 时,对象自动被释放,这就好比多个 “管家” 共同照顾一个对象,只有当所有 “管家” 都不再需要这个对象时,它才会被释放。std::weak_ptr是一种弱引用,它不增加对象的引用计数,主要用于解决std::shared_ptr的循环引用问题,就像是一个 “旁观者”,可以观察对象的存在,但不会影响对象的生命周期。

以一个简单的游戏角色类为例:

#include <memory>
#include <iostream>class Character {
public:int health;int attackPower;Character() : health(100), attackPower(20) {std::cout << "Character created" << std::endl;}~Character() {std::cout << "Character destroyed" << std::endl;}
};int main() {// 使用std::unique_ptr管理Character对象std::unique_ptr<Character> character1 = std::make_unique<Character>();// 使用std::shared_ptr管理Character对象std::shared_ptr<Character> character2 = std::make_shared<Character>();// 演示std::weak_ptr的使用std::weak_ptr<Character> weakCharacter = character2;if (auto locked = weakCharacter.lock()) {std::cout << "Weak pointer can access the character, health: " << locked->health << std::endl;}// character1和character2离开作用域,自动释放内存return 0;
}

在这个示例中,character1使用std::unique_ptr管理,character2使用std::shared_ptr管理,它们在离开作用域时,所指向的Character对象会自动被销毁,避免了内存泄漏。同时,通过std::weak_ptr演示了弱引用的使用,它可以在不增加对象引用计数的情况下访问对象。

内存碎片是另一个需要关注的问题。当频繁地分配和释放内存时,容易产生内存碎片,导致内存利用率降低,影响游戏性能。对象池技术是一种有效的解决方法。对象池预先分配一定数量的对象,当游戏需要时,直接从对象池中获取对象,而不是每次都进行新的内存分配;当对象不再使用时,将其放回对象池,而不是立即释放内存。这就像是一个 “对象仓库”,里面存放着预先准备好的对象,游戏需要时随时可以取用,用完后再归还,避免了频繁地创建和销毁对象带来的内存开销。

下面是一个简单的对象池实现示例:

#include <queue>
#include <mutex>
#include <memory>template<typename T>
class ObjectPool {
public:std::shared_ptr<T> acquire() {std::lock_guard<std::mutex> lock(mutex_);if (!pool_.empty()) {auto obj = std::move(pool_.front());pool_.pop();return obj;}return std::make_shared<T>();}void release(std::shared_ptr<T> obj) {std::lock_guard<std::mutex> lock(mutex_);pool_.push(std::move(obj));}private:std::queue<std::shared_ptr<T>> pool_;std::mutex mutex_;
};

在这个对象池类中,acquire方法用于从对象池中获取对象,如果对象池不为空,则直接从池中取出一个对象返回;否则,创建一个新的对象返回。release方法用于将对象放回对象池,以便后续重复使用。通过这种方式,可以有效地减少内存碎片的产生,提高内存利用率。

多线程处理

随着硬件技术的不断发展,多核处理器已经成为主流,充分利用多核处理器的性能是提升游戏性能的重要途径。C++ 的多线程库为我们提供了强大的工具,使我们能够将游戏中的不同任务分配到不同的线程中执行,实现并行处理,从而提高游戏的整体性能。

在游戏开发中,一个常见的应用场景是将渲染和逻辑更新放在不同的线程中。渲染线程负责处理图形渲染,将游戏中的各种元素绘制到屏幕上,它需要实时地响应用户的操作和游戏状态的变化,以保证画面的流畅性;逻辑更新线程则负责处理游戏的逻辑,如角色的移动、碰撞检测、AI 决策等,它需要根据游戏规则和用户输入来更新游戏状态。将这两个任务放在不同的线程中,可以避免它们相互干扰,提高游戏的性能和响应速度。

下面是一个简单的示例,展示如何使用 C++ 的多线程库将渲染和逻辑更新放在不同的线程中:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>std::mutex mtx;
std::condition_variable cv;
bool running = true;// 模拟逻辑更新函数
void logicUpdate() {while (running) {std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟逻辑更新的耗时{std::unique_lock<std::mutex> lock(mtx);std::cout << "Logic updated" << std::endl;}cv.notify_one(); // 通知渲染线程更新}
}// 模拟渲染函数
void render() {while (running) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock); // 等待逻辑更新完成的通知std::cout << "Rendered" << std::endl;}
}int main() {std::thread logicThread(logicUpdate);std::thread renderThread(render);// 主线程等待一段时间后结束程序std::this_thread::sleep_for(std::chrono::seconds(5));{std::unique_lock<std::mutex> lock(mtx);running = false;}cv.notify_all(); // 通知所有线程结束logicThread.join();renderThread.join();return 0;
}

在这个示例中,logicUpdate函数模拟逻辑更新,它每隔 100 毫秒执行一次逻辑更新操作,并通过条件变量cv通知渲染线程。render函数模拟渲染,它在接收到逻辑更新完成的通知后,执行渲染操作。主线程创建了逻辑更新线程和渲染线程,并在 5 秒后结束程序,同时通知所有线程结束。通过这种方式,实现了渲染和逻辑更新的并行处理,提高了游戏的性能。

在多线程编程中,数据共享和同步是需要特别注意的问题。当多个线程同时访问和修改共享数据时,可能会导致数据竞争和不一致的问题。为了避免这些问题,我们可以使用互斥锁(std::mutex)、条件变量(std::condition_variable)、原子操作(std::atomic)等同步机制来保证数据的一致性和线程安全。互斥锁就像是一把 “锁”,当一个线程获取到锁后,其他线程就无法再获取该锁,直到该线程释放锁,这样就保证了同一时间只有一个线程可以访问共享数据。条件变量则用于线程之间的通信,一个线程可以等待某个条件满足,当另一个线程满足该条件时,通过条件变量通知等待的线程。原子操作则是一种不可分割的操作,它可以保证在多线程环境下的操作是原子性的,不会被其他线程打断。

以一个简单的计数器为例,展示如何使用互斥锁来保护共享数据:

#include <iostream>
#include <thread>
#include <mutex>std::mutex counterMutex;
int counter = 0;// 线程函数,用于增加计数器
void incrementCounter() {for (int i = 0; i < 1000; ++i) {std::lock_guard<std::mutex> lock(counterMutex);counter++;}
}int main() {std::thread thread1(incrementCounter);std::thread thread2(incrementCounter);thread1.join();thread2.join();std::cout << "Final counter value: " << counter << std::endl;return 0;
}

在这个示例中,counter是一个共享的计数器,incrementCounter函数用于增加计数器的值。为了保证线程安全,使用std::lock_guard<std::mutex>来自动管理互斥锁的生命周期,在进入函数时自动获取锁,在离开函数时自动释放锁,这样就避免了多个线程同时修改counter导致的数据不一致问题。

实战案例:开发一个简单的 2D 射击游戏

项目结构设计

为了开发一个简单的 2D 射击游戏,我们需要精心设计项目的整体结构,合理规划各个类的职责和功能。其中,玩家类、敌人类和子弹类是游戏的核心组成部分,它们相互协作,共同构建起游戏的基本逻辑。

玩家类(Player)负责管理玩家的各种行为和状态。它包含了玩家的位置信息,通过x和y坐标来确定玩家在游戏屏幕中的位置;速度信息speed决定了玩家移动的快慢;生命值health则表示玩家的生存状态,当生命值降为 0 时,玩家游戏失败。此外,玩家还具备移动和射击的能力。移动函数move根据传入的方向参数,更新玩家的位置坐标,实现玩家在游戏中的移动操作;射击函数shoot则负责创建子弹对象,并将其加入到游戏的子弹管理系统中,开启一场激烈的射击战斗。

敌人类(Enemy)模拟了游戏中的敌人行为。它同样拥有位置、速度和生命值等属性,这些属性决定了敌人在游戏中的行动和生存状态。敌人的 AI(人工智能)是其核心部分,通过ai函数实现。在这个简单的实现中,敌人的 AI 表现为追踪玩家,它会根据玩家的位置不断调整自己的移动方向,试图接近玩家并对玩家造成威胁,增加游戏的挑战性。

子弹类(Bullet)用于管理游戏中的子弹。它包含子弹的位置、速度和方向等属性。子弹的位置决定了它在游戏屏幕中的显示位置,速度影响子弹的飞行速度,方向则决定了子弹的飞行轨迹。update函数是子弹类的关键函数,它根据子弹的速度和方向,不断更新子弹的位置,模拟子弹的飞行过程。同时,子弹还需要与其他游戏对象(如敌人和玩家)进行碰撞检测,当检测到碰撞时,根据碰撞的对象进行相应的处理,如对敌人造成伤害或导致玩家游戏失败。

代码实现

下面是关键功能的代码实现,这些代码展示了如何通过 C++ 实现玩家移动、射击,敌人 AI 以及子弹碰撞检测等功能。

#include <iostream>
#include <vector>
#include <cmath>// 定义一个简单的向量类,用于表示位置和方向
class Vector2 {
public:float x;float y;Vector2(float _x = 0, float _y = 0) : x(_x), y(_y) {}// 向量加法Vector2 operator+(const Vector2& other) const {return Vector2(x + other.x, y + other.y);}// 向量减法Vector2 operator-(const Vector2& other) const {return Vector2(x - other.x, y - other.y);}// 向量数乘Vector2 operator*(float scalar) const {return Vector2(x * scalar, y * scalar);}// 计算向量的长度float length() const {return std::sqrt(x * x + y * y);}// 归一化向量Vector2 normalize() const {float len = length();if (len > 0) {return Vector2(x / len, y / len);}return *this;}
};// 玩家类
class Player {
public:Vector2 position;float speed;int health;Player() : position(Vector2(400, 300)), speed(5), health(100) {}// 玩家移动函数void move(int direction) {// 0: 上, 1: 下, 2: 左, 3: 右switch (direction) {case 0:position.y -= speed;break;case 1:position.y += speed;break;case 2:position.x -= speed;break;case 3:position.x += speed;break;}}// 玩家射击函数void shoot(std::vector<Vector2>& bullets) {// 假设子弹从玩家位置出发,向上飞行bullets.push_back(position + Vector2(0, -1));}
};// 敌人类
class Enemy {
public:Vector2 position;float speed;int health;Enemy() : position(Vector2(200, 200)), speed(3), health(50) {}// 敌人AI函数,简单的追踪玩家void ai(const Player& player) {Vector2 direction = player.position - position;direction = direction.normalize();position = position + direction * speed;}
};// 子弹类
class Bullet {
public:Vector2 position;Vector2 velocity;Bullet(const Vector2& pos, const Vector2& vel) : position(pos), velocity(vel) {}// 子弹更新函数void update() {position = position + velocity;}
};// 碰撞检测函数,检测子弹与敌人是否碰撞
bool checkCollision(const Bullet& bullet, const Enemy& enemy) {// 简单的距离检测,假设子弹和敌人都是一个点Vector2 diff = bullet.position - enemy.position;float distance = diff.length();return distance < 10; // 假设碰撞半径为10
}int main() {Player player;Enemy enemy;std::vector<Vector2> bullets;// 游戏循环示例for (int i = 0; i < 100; ++i) {// 处理玩家输入,这里简单模拟玩家按方向键移动和射击player.move(3); // 向右移动player.shoot(bullets);// 更新敌人AIenemy.ai(player);// 更新子弹状态for (auto& bullet : bullets) {Bullet b(bullet, Vector2(0, -5)); // 假设子弹速度为(0, -5)b.update();bullet = b.position;// 检测子弹与敌人的碰撞if (checkCollision(b, enemy)) {enemy.health -= 10;// 这里可以添加更多碰撞后的处理逻辑,比如移除子弹std::cout << "Enemy hit! Remaining health: " << enemy.health << std::endl;}}// 简单输出游戏状态std::cout << "Player position: (" << player.position.x << ", " << player.position.y << ")" << std::endl;std::cout << "Enemy position: (" << enemy.position.x << ", " << enemy.position.y << ")" << std::endl;std::cout << "Bullets: ";for (const auto& bullet : bullets) {std::cout << "(" << bullet.x << ", " << bullet.y << ") ";}std::cout << std::endl;// 简单的结束条件,敌人生命值为0if (enemy.health <= 0) {std::cout << "You win!" << std::endl;break;}}return 0;
}

在这段代码中,Player类的move函数根据传入的方向参数更新玩家的位置,shoot函数将子弹的初始位置添加到bullets向量中。Enemy类的ai函数通过计算玩家与敌人的位置差,归一化后得到移动方向,从而实现敌人追踪玩家的功能。Bullet类的update函数根据子弹的速度更新其位置。checkCollision函数通过计算子弹与敌人的距离来判断是否发生碰撞。在main函数中,模拟了游戏循环,在每次循环中处理玩家输入、更新敌人 AI、更新子弹状态并进行碰撞检测,同时输出游戏状态,当敌人生命值为 0 时,游戏胜利。

总结与展望

C++ 凭借其卓越的性能、精细的内存管理和强大的跨平台能力,在游戏开发领域占据着举足轻重的地位。从搭建开发环境到掌握面向对象编程、内存管理、STL 等基础知识,再到设计游戏循环、对象管理、碰撞检测等核心组件,以及应用 AI 和物理引擎,优化游戏性能,每一个环节都凝聚着 C++ 的独特魅力和强大功能。通过开发简单的 2D 射击游戏,我们更加深入地理解了 C++ 在游戏开发中的实际应用和重要性。

展望未来,随着硬件技术的不断发展和玩家对游戏体验要求的日益提高,C++ 在游戏开发中的应用前景将更加广阔。人工智能、虚拟现实、云游戏等新兴技术的崛起,将为 C++ 游戏开发带来新的机遇和挑战。在人工智能方面,C++ 将继续发挥其高性能的优势,与机器学习、深度学习等技术深度融合,实现更加智能的游戏角色和更加复杂的游戏玩法。在虚拟现实领域,C++ 将助力打造更加沉浸式的游戏体验,通过对硬件资源的精细控制和高效的图形渲染,为玩家呈现出更加逼真的虚拟世界。云游戏的发展也将依赖于 C++ 的高性能和稳定性,实现云端渲染和流媒体传输的优化,让玩家能够随时随地畅玩高品质的游戏。

对于广大游戏开发者来说,持续学习和掌握 C++ 的最新技术和应用,不断提升自己的编程能力和创新思维,将是在未来游戏开发领域取得成功的关键。无论是追求极致性能的 3A 大作,还是充满创意的独立游戏,C++ 都将是开发者们实现梦想的有力工具。让我们一起期待 C++ 在游戏开发领域创造更多的精彩!

相关文章:

从0到1:C++ 开启游戏开发奇幻之旅(二)

目录 游戏开发核心组件设计 游戏循环 游戏对象管理 碰撞检测 人工智能&#xff08;AI&#xff09; 与物理引擎 人工智能 物理引擎 性能优化技巧 内存管理优化 多线程处理 实战案例&#xff1a;开发一个简单的 2D 射击游戏 项目结构设计 代码实现 总结与展望 游戏…...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.18 逻辑运算引擎:数组条件判断的智能法则

1.18 逻辑运算引擎&#xff1a;数组条件判断的智能法则 1.18.1 目录 #mermaid-svg-QAFjJvNdJ5P4IVbV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-QAFjJvNdJ5P4IVbV .error-icon{fill:#552222;}#mermaid-svg-QAF…...

EasyExcel写入和读取多个sheet

最近在工作中&#xff0c;作者频频接触到Excel处理&#xff0c;因此也对EasyExcel进行了一定的研究和学习&#xff0c;也曾困扰过如何处理多个sheet&#xff0c;因此此处分享给大家&#xff0c;希望能有所帮助 目录 1.依赖 2. Excel类 3.处理Excel读取和写入多个sheet 4. 执…...

LLM架构与优化:从理论到实践的关键技术

标题&#xff1a;“LLM架构与优化&#xff1a;从理论到实践的关键技术” 文章信息摘要&#xff1a; 文章探讨了大型语言模型&#xff08;LLM&#xff09;开发与应用中的关键技术&#xff0c;包括Transformer架构、注意力机制、采样技术、Tokenization等基础理论&#xff0c;以…...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.22 形状操控者:转置与轴交换的奥秘

1.22 形状操控者&#xff1a;转置与轴交换的奥秘 目录 #mermaid-svg-Qb3eoIWrPbPGRVAf {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Qb3eoIWrPbPGRVAf .error-icon{fill:#552222;}#mermaid-svg-Qb3eoIWrPbPGRVAf…...

NLP模型大对比:Transformer >Seq2Seq > LSTM > RNN > n-gram

结论 Transformer 大于 传统的Seq2Seq 大于 LSTM 大于 RNN 大于 传统的n-gram n-gram VS Transformer 我们可以用一个 图书馆查询 的类比来解释它们的差异&#xff1a; 一、核心差异对比 维度n-gram 模型Transformer工作方式固定窗口的"近视观察员"全局关联的&q…...

DeepSeek部署教程(基于Ollama)

虽说在过年&#xff0c;但不能忘了学习。这几天科技圈最火的莫过于deepseek&#xff0c;我抽空也学习一下deepseek的部署过程&#xff0c;主要还是因为官方服务已经彻底瘫了[手动狗头]。 1、下载Ollama并安装 https://github.com/ollama/ollama/releases/latest/download/Oll…...

Java基础面试题总结(题目来源JavaGuide)

问题1&#xff1a;Java 中有哪 8 种基本数据类型&#xff1f;它们的默认值和占用的空间大小知道不&#xff1f; 说说这 8 种基本数据类型对 应的包装类型。 在 Java 中&#xff0c;有 8 种基本数据类型&#xff08;Primitive Types&#xff09;&#xff1a; 基本数据类型关键…...

WPS mathtype间距太大、显示不全、公式一键改格式/大小

1、间距太大 用mathtype后行距变大的原因 mathtype行距变大到底怎么解决-MathType中文网 段落设置固定值 2、显示不全 设置格式&#xff1a; 打开MathType编辑器点击菜单栏中的"格式(Format)"选择"间距(Spacing)"在弹出的对话框中调整"分数间距(F…...

宇宙大爆炸是什么意思

根据宇宙大爆炸学说&#xff0c;宇宙间的一切都在彼此远离&#xff0c;而且距离越远&#xff0c;远离的速度越快。我们只能在地球上观察这种现象&#xff0c;而我们观察到的速度符合如下公式&#xff0c;其中 为哈勃常数&#xff0c; 为距离&#xff0c; 为速度&#xff08;…...

MotionLCM 部署笔记

目录 依赖项 humanml3d&#xff1a; sentence-t5-large 下载数据&#xff1a; 报错&#xff1a;No module named sentence_transformers 继续报错&#xff1a;from transformers.integrations import CodeCarbonCallback 解决方法&#xff1a; GitHub - Dai-Wenxun/Moti…...

VLLM性能调优

1. 抢占 显存不够的时候&#xff0c;某些request会被抢占。其KV cache被清除&#xff0c;腾退给其他request&#xff0c;下次调度到它&#xff0c;重新计算KV cache。 报这条消息&#xff0c;说明已被抢占&#xff1a; WARNING 05-09 00:49:33 scheduler.py:1057 Sequence gr…...

ESP32-S3模组上跑通esp32-camera(39)

接前一篇文章:ESP32-S3模组上跑通esp32-camera(38) 一、OV5640初始化 2. 相机初始化及图像传感器配置 上一回继续对reset函数的后一段代码进行解析。为了便于理解和回顾,再次贴出reset函数源码,在components\esp32-camera\sensors\ov5640.c中,如下: static int reset…...

Linux《基础指令》

在之前的Linux《Linux简介与环境的搭建》当中我们已经初步了解了Linux的由来和如何搭建Linux环境&#xff0c;那么接下来在本篇当中我们就要来学习Linux的基础指令。在此我们的学习是包括两个部分&#xff0c;即指令和关于Linux的基础知识&#xff1b;因此本篇指令和基础知识的…...

9.进程间通信

9.进程间通信 **1. 进程间通信&#xff08;IPC&#xff09;概述****2. 无名管道&#xff08;Pipe&#xff09;****3. 有名管道&#xff08;FIFO&#xff09;****4. 信号通信&#xff08;Signal&#xff09;****5. 练习与作业****6. 信号的应用****7. 总结** 1. 进程间通信&…...

Windows中本地组策略编辑器gpedit.msc打不开/微软远程桌面无法复制粘贴

目录 背景 解决gpedit.msc打不开 解决复制粘贴 剪贴板的问题 启用远程桌面剪贴板与驱动器 重启RDP剪贴板监视程序 以上都不行&#xff1f;可能是操作被Win11系统阻止 最后 背景 远程桌面无法复制粘贴&#xff0c;需要查看下主机策略组设置&#xff0c;结果按WinR输入…...

供应链系统设计-供应链中台系统设计(十二)- 清结算中心设计篇(一)

概述 在之前的文章中&#xff0c;我们通过之前的两篇文章中&#xff0c;如下所示&#xff1a; 供应链系统设计-供应链中台系统设计&#xff08;十&#xff09;- 清结算中心概念片篇 供应链系统设计-供应链中台系统设计&#xff08;十一&#xff09;- 清结算中心概念片篇 说…...

Vue.js 单页应用(SPA)开发教程:从零开始构建你的第一个项目

单页应用&#xff08;SPA&#xff0c;Single Page Application&#xff09;是现代前端开发的主流模式。Vue.js 是一个非常适合构建 SPA 的框架&#xff0c;它通过 Vue Router 实现页面导航&#xff0c;通过组件化开发和状态管理实现复杂的交互功能。本篇教程将带你了解 SPA 的基…...

Linux C openssl aes-128-cbc demo

openssl 各版本下载 https://openssl-library.org/source/old/index.html#include <stdio.h> #include <string.h> #include <openssl/aes.h> #include <openssl/rand.h> #include <openssl/evp.h>#define AES_KEY_BITS 128 #define GCM_IV_SIZ…...

你了解哪些Java限流算法?

大家好&#xff0c;我是锋哥。今天分享关于【你了解哪些Java限流算法?】面试题。希望对大家有帮助&#xff1b; 你了解哪些Java限流算法? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Java 中常用的限流算法主要有以下几种&#xff0c;它们广泛应用于处理流量控…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

Webpack性能优化:构建速度与体积优化策略

一、构建速度优化 1、​​升级Webpack和Node.js​​ ​​优化效果​​&#xff1a;Webpack 4比Webpack 3构建时间降低60%-98%。​​原因​​&#xff1a; V8引擎优化&#xff08;for of替代forEach、Map/Set替代Object&#xff09;。默认使用更快的md4哈希算法。AST直接从Loa…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文通过代码驱动的方式&#xff0c;系统讲解PyTorch核心概念和实战技巧&#xff0c;涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...

消息队列系统设计与实践全解析

文章目录 &#x1f680; 消息队列系统设计与实践全解析&#x1f50d; 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡&#x1f4a1; 权衡决策框架 1.3 运维复杂度评估&#x1f527; 运维成本降低策略 &#x1f3d7;️ 二、典型架构设计2.1 分布式事务最终一致…...