从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++ 在游戏开发领域创造更多的精彩!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/894118.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Block总结】DynamicFilter,动态滤波器降低计算复杂度,替换传统的MHSA|即插即用

论文信息 标题: FFT-based Dynamic Token Mixer for Vision 论文链接: https://arxiv.org/pdf/2303.03932 关键词: 深度学习、计算机视觉、对象检测、分割 GitHub链接: https://github.com/okojoalg/dfformer 创新点 本论文提出了一种新的标记混合器&#xff08;token mix…

(done) MIT6.S081 2023 学习笔记 (Day6: LAB5 COW Fork)

网页&#xff1a;https://pdos.csail.mit.edu/6.S081/2023/labs/cow.html 任务1&#xff1a;Implement copy-on-write fork(hard) (完成) 现实中的问题如下&#xff1a; xv6中的fork()系统调用会将父进程的用户空间内存全部复制到子进程中。如果父进程很大&#xff0c;复制过程…

鸢尾花书01---基本介绍和Jupyterlab的上手

文章目录 1.致谢和推荐2.py和.ipynb区别3.Jupyterlab的上手3.1入口3.2页面展示3.3相关键介绍3.4代码的运行3.5重命名3.6latex和markdown说明 1.致谢和推荐 这个系列是关于一套书籍&#xff0c;结合了python和数学&#xff0c;机器学习等等相关的理论&#xff0c;总结的7本书籍…

【愚公系列】《循序渐进Vue.js 3.x前端开发实践》033-响应式编程的原理及在Vue中的应用

标题详情作者简介愚公搬代码头衔华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xff0c;阿里云签约作者&#xff0c;腾讯云优秀博主&…

【javaweb项目idea版】蛋糕商城(可复用成其他商城项目)

该项目虽然是蛋糕商城项目&#xff0c;但是可以复用成其他商城项目或者购物车项目 想要源码的uu可点赞后私聊 技术栈 主要为&#xff1a;javawebservletmvcc3p0idea运行 功能模块 主要分为用户模块和后台管理员模块 具有商城购物的完整功能 基础模块 登录注册个人信息编辑…

为什么LabVIEW适合软硬件结合的项目?

LabVIEW是一种基于图形化编程的开发平台&#xff0c;广泛应用于软硬件结合的项目中。其强大的硬件接口支持、实时数据采集能力、并行处理能力和直观的用户界面&#xff0c;使得它成为工业控制、仪器仪表、自动化测试等领域中软硬件系统集成的理想选择。LabVIEW的设计哲学强调模…

Fort Firewall:全方位守护网络安全

Fort Firewall是一款专为 Windows 操作系统设计的开源防火墙工具&#xff0c;旨在为用户提供全面的网络安全保护。它基于 Windows 过滤平台&#xff08;WFP&#xff09;&#xff0c;能够与系统无缝集成&#xff0c;确保高效的网络流量管理和安全防护。该软件支持实时监控网络流…

【PyTorch】6.张量形状操作:在深度学习的 “魔方” 里,玩转张量形状

目录 1. reshape 函数的用法 2. transpose 和 permute 函数的使用 4. squeeze 和 unsqueeze 函数的用法 5. 小节 个人主页&#xff1a;Icomi 专栏地址&#xff1a;PyTorch入门 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&am…

[STM32 - 野火] - - - 固件库学习笔记 - - -十三.高级定时器

一、高级定时器简介 高级定时器的简介在前面一章已经介绍过&#xff0c;可以点击下面链接了解&#xff0c;在这里进行一些补充。 [STM32 - 野火] - - - 固件库学习笔记 - - -十二.基本定时器 1.1 功能简介 1、高级定时器可以向上/向下/两边计数&#xff0c;还独有一个重复计…

Cyber Security 101-Build Your Cyber Security Career-Security Principles(安全原则)

了解安全三元组以及常见的安全模型和原则。 任务1&#xff1a;介绍 安全已成为一个流行词;每家公司都想声称其产品或服务是安全的。但事实真的如此吗&#xff1f; 在我们开始讨论不同的安全原则之前&#xff0c;了解我们正在保护资产的对手至关重要。您是否试图阻止蹒跚学步…

python:斐索实验(Fizeau experiment)

斐索实验&#xff08;Fizeau experiment&#xff09;是在1851年由法国物理学家阿曼德斐索&#xff08;Armand Fizeau&#xff09;进行的一项重要实验&#xff0c;旨在测量光在移动介质中的传播速度。这项实验的结果对当时的物理理论产生了深远的影响&#xff0c;并且在后来的相…

青少年CTF练习平台 贪吃蛇

题目 CtrlU快捷键查看页面源代码 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>贪吃蛇游戏</title><style>#gameCanvas {border: 1px solid black;}</style> </head>…

芯片AI深度实战:基础篇之Ollama

有这么多大模型&#xff0c;怎么本地用&#xff1f; Ollama可以解决这一问题。不依赖GPU&#xff0c;也不需要编程。就可以在CPU上运行自己的大模型。 软件甚至不用安装&#xff0c;直接在ollama官网下载可执行文件即可。 现在最流行的deepseek-r1也可以使用。当然还有我认为最…

本地部署deepseek模型步骤

文章目录 0.deepseek简介1.安装ollama软件2.配置合适的deepseek模型3.安装chatbox可视化 0.deepseek简介 DeepSeek 是一家专注于人工智能技术研发的公司&#xff0c;致力于打造高性能、低成本的 AI 模型&#xff0c;其目标是让 AI 技术更加普惠&#xff0c;让更多人能够用上强…

DeepSeek R1中提到“知识蒸馏”到底是什么

在 DeepSeek-R1 中&#xff0c;知识蒸馏&#xff08;Knowledge Distillation&#xff09;是实现模型高效压缩与性能优化的核心技术之一。在DeepSeek的论文中&#xff0c;使用 DeepSeek-R1&#xff08;教师模型&#xff09;生成 800K 高质量训练样本&#xff0c;涵盖数学、编程、…

关联传播和 Python 和 Scikit-learn 实现

文章目录 一、说明二、什么是 Affinity Propagation。2.1 先说Affinity 传播的工作原理2.2 更多细节2.3 传播两种类型的消息2.4 计算责任和可用性的分数2.4.1 责任2.4.2 可用性分解2.4.3 更新分数&#xff1a;集群是如何形成的2.4.4 估计集群本身的数量。 三、亲和力传播的一些…

通过配置代理解决跨域问题(Vue+SpringBoot项目为例)

跨域问题&#xff1a; 是由浏览器的同源策略引起的&#xff0c;同源策略是一种安全策略&#xff0c;用于防止一个网站访问其他网站的数据。 同源是指协议、域名和端口号都相同。 跨域问题常常出现在前端项目中&#xff0c;当浏览器中的前端代码尝试从不同的域名、端口或协议…

(1)Linux高级命令简介

Linux高级命令简介 在安装好linux环境以后第一件事情就是去学习一些linux的基本指令&#xff0c;我在这里用的是CentOS7作演示。 首先在VirtualBox上装好Linux以后&#xff0c;启动我们的linux&#xff0c;输入账号密码以后学习第一个指令 简介 Linux高级命令简介ip addrtou…

TOGAF之架构标准规范-信息系统架构 | 数据架构

TOGAF是工业级的企业架构标准规范&#xff0c;信息系统架构阶段是由数据架构阶段以及应用架构阶段构成&#xff0c;本文主要描述信息系统架构阶段中的数据架构阶段。 如上所示&#xff0c;信息系统架构&#xff08;Information Systems Architectures&#xff09;在TOGAF标准规…

Windows 程序设计7:文件的创建、打开与关闭

文章目录 前言一、文件的创建与打开CreateFile1. 创建新的空白文件2. 打开已存在文件3. 打开一个文件时&#xff0c;如果文件存在则打开&#xff0c;如果文件不存在则新创建文件4.打开一个文件&#xff0c;如果文件存在则打开文件并清空内容&#xff0c;文件不存在则 新创建文件…