Лучший способ организовать объекты в игре?
Скажем, я создаю игру OpenGL на С++, у которой будет много объектов (врагов, персонажей игроков, предметов и т.д.). Мне интересно, как их организовать, поскольку они будут созданы и уничтожены в реальном времени на основе времени, позиции игрока/действий и т.д.
Вот что я думал до сих пор:
Я могу иметь глобальный массив для хранения указателей на эти объекты. Текстуры/контекст для этих объектов загружаются в их конструкторы. Эти объекты будут иметь разные типы, поэтому я могу указать указатели, чтобы получить их в массиве, но позже я хочу иметь функцию renderObjects(), которая будет использовать цикл для вызова функции ObjectN.render() для каждого существующего объекта.
Я думаю, что я пробовал это раньше, но я не знал, для какого типа инициализировать массив, поэтому я выбрал произвольный тип объекта, а затем применил все, что не было такого типа. Если я помню, это не сработало, потому что компилятор не хотел, чтобы я разыменовал указатели, если он уже не знал их тип, даже если заданная функция-член имеет одно и то же имя: (* Object5).render() < - не работает?
Есть ли лучший способ? Как в коммерческих играх, как HL2 справиться с этим? Я предполагаю, что должен быть какой-то модуль и т.д., Который отслеживает все объекты.
Ответы
Ответ 1
Я не уверен, что полностью понимаю вопрос, но я думаю, что вы хотите создать коллекцию полиморфных объектов. При доступе к полиморфному объекту вы всегда должны ссылаться на него указателем.
Вот пример. Сначала вам нужно создать базовый класс для вывода ваших объектов из:
class BaseObject
{
public:
virtual void Render() = 0;
};
Затем создайте массив указателей. Я использую набор STL, потому что это упрощает добавление и удаление элементов в случайном порядке:
#include <set>
typedef std::set<BaseObject *> GAMEOBJECTS;
GAMEOBJECTS g_gameObjects;
Чтобы добавить объект, создайте производный класс и создайте его:
class Enemy : public BaseObject
{
public:
Enemy() { }
virtual void Render()
{
// Rendering code goes here...
}
};
g_gameObjects.insert(new Enemy());
Затем для доступа к объектам просто перебирайте их:
for(GAMEOBJECTS::iterator it = g_gameObjects.begin();
it != g_gameObjects.end();
it++)
{
(*it)->Render();
}
Чтобы создать разные типы объектов, просто выведите больше классов из класса BaseObject. Не забывайте удалять объекты, когда вы удаляете их из коллекции.
Ответ 2
Для моего скоро будущего персонального игрового проекта я использую систему сущностей на основе компонентов.
Вы можете узнать больше об этом, выполнив поиск "разработка игр на основе компонентов". Известная статья Развивайте свою иерархию из блога программирования Cowboy.
В моей системе сущности - это только идентификаторы - unsigned long, немного похожие в реляционную базу данных.
Все данные и логика, связанные с моими объектами, записываются в Компоненты. У меня есть системы, которые связывают идентификаторы объектов с их соответствующими компонентами. Что-то вроде этого:
typedef unsigned long EntityId;
class Component {
Component(EntityId id) : owner(id) {}
EntityId owner;
};
template <typename C> class System {
std::map<EntityId, C * > components;
};
Затем для каждого типа функциональности я пишу специальный компонент. Все объекты не имеют одинаковых компонентов.
Например, у вас может быть объект статического рока, в котором есть WorldPositionComponent и ShapeComponent, а также движущийся противник с теми же компонентами и VelocityComponent.
Вот пример:
class WorldPositionComponent : public Component {
float x, y, z;
WorldPositionComponent(EntityId id) : Component(id) {}
};
class RenderComponent : public Component {
WorldPositionComponent * position;
3DModel * model;
RenderComponent(EntityId id, System<WorldPositionComponent> & wpSys)
: Component(id), position(wpSys.components[owner]) {}
void render() {
model->draw(position);
}
};
class Game {
System<WorldPositionComponent> wpSys;
System<RenderComponent> rSys;
void init() {
EntityId visibleObject = 1;
// Watch out for memory leaks.
wpSys.components[visibleObject] = new WorldPositionComponent(visibleObject);
rSys.components[visibleObject] = new RenderComponent(visibleObject, wpSys);
EntityId invisibleObject = 2;
wpSys.components[invisibleObject] = new WorldPositionComponent(invisibleObject);
// No RenderComponent for invisibleObject.
}
void gameLoop() {
std::map<EntityId, RenderComponent *>::iterator it;
for (it = rSys.components.iterator(); it != rSys.components.end(); ++it) {
(*it).second->render();
}
}
};
Здесь у вас есть 2 компонента, WorldPosition и Render. Класс Game содержит 2 системы. Компонент Render имеет доступ к позиции объекта. Если у объекта нет компонента WorldPosition, вы можете выбрать значения по умолчанию или игнорировать объект.
Метод Game:: gameLoop() будет визуализировать visibleObject. Нет ненужной обработки необработанных компонентов.
Вы также можете разделить мой класс игры на два или три, чтобы отделить системы отображения и ввода от логики. Что-то вроде Model, View и Controller.
Я считаю аккуратным определять свою логику игры с точки зрения компонентов и иметь сущности, которые имеют только те функции, которые им нужны, - не более пустые render() или бесполезные проверки обнаружения столкновений.
Ответ 3
То, как я подходил к нему, - это иметь слой отображения, который ничего не знает о самом игровом мире. его единственная задача - получить упорядоченный список объектов для рисования на экране, которые соответствуют единому формату для графического объекта. так, например, если это 2D-игра, ваш дисплейный слой получит список изображений вместе с их коэффициентом масштабирования, непрозрачностью, поворотным, флип-текстурой и исходной текстурой и любыми другими атрибутами, которые может иметь экранный объект. Представление может также отвечать за получение высокоуровневых взаимодействий мыши с этими отображаемыми объектами и отправку их где-то подходящим. Но важно, чтобы слой представления ничего не знал о том, что он отображает. Только то, что это какой-то квадрат с площадью поверхности и некоторыми атрибутами.
Затем следующий слой вниз - это программа, задачей которой является просто упорядочить список этих объектов. Это полезно, если каждый объект в списке имеет уникальный идентификатор, так как он позволяет использовать определенные стратегии оптимизации на уровне представления. Создание списка объектов отображения является гораздо менее сложной задачей, чем пытаться выяснить для каждого типа персонажа, как он физически себя выводит.
Сортировка Z достаточно проста. Ваш код создания объекта отображения просто должен генерировать список в том порядке, в котором вы хотите, и вы можете использовать все, что вам нужно, чтобы добраться туда.
В нашей программе отображения списка объектов каждый символ, prop и NPC имеет две части: помощник базы данных ресурсов и экземпляр символа. Помощник базы данных представляет для каждого символа простой интерфейс, из которого каждый персонаж может вытащить любое изображение/статистику/анимацию/компоновку и т.д., Которое понадобится персонажу. Вероятно, вы захотите создать довольно однородный интерфейс для извлечения данных, но он немного изменится с объекта на объект. Дереву или скале не требуется столько материала, сколько полностью анимированный NPC, например.
Затем вам нужен способ генерации экземпляра для каждого типа объекта. Вы можете реализовать эту дихотомию, используя свой язык, построенный в системах класса/экземпляра, или в зависимости от ваших потребностей, возможно, вам придется немного поработать над этим. например, наличие каждой базы данных ресурсов является экземпляром класса базы данных ресурсов, а каждый экземпляр символа является экземпляром класса "символ". Это избавляет вас от написания фрагмента кода для каждого маленького объекта в системе. Таким образом вам нужно всего лишь написать код для широких категорий объектов и изменить только некоторые вещи, например, какую строку базы данных можно получить из.
Затем, не забудьте иметь внутренний объект, представляющий вашу камеру. Затем выполните задание вашей камеры, чтобы запросить каждого персонажа о том, где они находятся по отношению к камере. В основном это происходит вокруг каждого экземпляра символа и запрашивает его экранный объект. "Как ты выглядишь, и где ты?"
Каждый экземпляр персонажа, в свою очередь, имеет свою небольшую ресурсную базу данных, которая запрашивает запрос. Поэтому каждый экземпляр персонажа имеет доступ ко всей информации, необходимой ему, чтобы сообщить камере, что ему нужно знать.
Это дает вам набор экземпляров символов в мире, который более или менее не обращает внимания на то, как они должны отображаться на физическом экране, и более или менее забывая о том, как получить изображение данные с жесткого диска. Это хорошо - это дает вам как можно более чистый сланец для своего рода платонически "чистого" мира персонажей, в котором вы можете реализовать свою логику игры, не беспокоясь о вещах, подобных падению с края экрана. Подумайте, какой интерфейс вам нужен, если бы вы включили язык сценариев в свой игровой движок. Как можно проще? Как обоснованно в смоделированном мире, насколько это возможно, не беспокоясь о незначительных деталях технической реализации? Эта стратегия позволяет вам делать.
Кроме того, разделение проблем позволяет вам менять уровень отображения с любой технологией, которая вам нравится: Open GL, DirectX, рендеринг программного обеспечения, Adobe Flash, Nintendo DS, независимо от того, что вам не нужно слишком много шумихи с другими слоями.
Кроме того, вы можете поменять слой базы данных, чтобы сделать что-то вроде reskin всех символов. Или в зависимости от того, как вы его построили, замените в совершенно новой игре новым контентом, который повторно использует основную часть взаимодействия/столкновения символов обнаружение/поиск пути, которые вы написали в среднем слое.
Ответ 4
Вы должны сделать суперкласс всех ваших объектов, которые имеют общий метод render(). объявите этот метод виртуальным, и каждый подкласс реализует его по-своему.
Ответ 5
Есть ли лучший способ? Как в коммерческих играх, как HL2 справиться с этим? Я предполагаю, что должен быть какой-то модуль и т.д., Который отслеживает все объекты.
В коммерческих 3D-играх используется вариант Scene Graph. Иерархия объектов, подобная описанию одного Адама, помещается в структуру дерева. Чтобы визуализировать объекты или манипулировать ими, вы просто ходите по дереву.
В нескольких книгах обсуждается это, и лучшее, что я нашел, - это дизайн и архитектура 3D-игр, как Дэвидом Эберли.