Лично я рекомендую не использовать функцию draw самого класса Object. Я даже рекомендую хранить расположение / координаты Объектов вне самого Объекта.
Этот метод draw () будет иметь дело с API-интерфейсом низкоуровневого рендеринга OpenGL, OpenGL ES, Direct3D, вашим слоем обертывания для этих API или API движков. Возможно, вам придется переключаться между ними (например, если вы хотите поддержать OpenGL + OpenGL ES + Direct3D.
Этот GameObject должен просто содержать основную информацию о его внешнем виде, такую как Mesh или, возможно, более крупный пакет, включая входы шейдеров, состояние анимации и так далее.
Также вам понадобится гибкий графический конвейер. Что произойдет, если вы хотите упорядочить объекты по их расстоянию до камеры. Или их тип материала. Что произойдет, если вы хотите нарисовать «выбранный» объект другим цветом. Как насчет того, чтобы вместо того, чтобы фактически выполнять рендеринг так же, как вы вызываете функцию рисования объекта, вместо этого он помещает ее в список командных действий для выполнения рендеринга (может потребоваться для многопоточности). Вы можете делать такие вещи с другой системой, но это PITA.
Я рекомендую вместо непосредственного рисования связывать все объекты, которые вы хотите, с другой структурой данных. Эта привязка действительно должна иметь ссылку на местоположение объектов и информацию рендеринга.
Ваши уровни / куски / области / карты / концентраторы / весь мир / все, что получает пространственный индекс, содержит объекты и возвращает их на основе координатных запросов и может быть простым списком или чем-то вроде Octree. Это также может быть обертка к чему-то, реализованному сторонним физическим движком как физическая сцена. Он позволяет вам выполнять такие действия, как «Запрос всех объектов, находящихся в поле зрения камеры, с некоторой дополнительной областью вокруг них», или для более простых игр, в которых вы можете просто визуализировать все, захватывая весь список.
Пространственные индексы не должны содержать фактическую информацию о позиционировании. Они работают, сохраняя объекты в древовидных структурах относительно расположения других объектов. Они могут быть своего рода кэшем с потерями, позволяющим быстро искать объект в зависимости от его положения. Нет необходимости дублировать ваши действительные координаты X, Y, Z. Сказав, что вы могли бы, если вы хотите сохранить
На самом деле ваши игровые объекты даже не должны содержать собственную информацию о местоположении. Например, объект, который не был помещен в уровень, не должен иметь координаты x, y, z, что не имеет смысла. Вы можете указать это в специальном указателе. Если вам нужно искать координаты объекта на основе его фактической привязки, тогда вы захотите иметь привязку между объектом и графом сцены (графы сцены предназначены для возврата объектов на основе координат, но медленно возвращают координаты на основе объектов) ,
Когда вы добавляете объект на уровень. Это сделает следующее:
1) Создайте структуру местоположения:
class Location {
float x, y, z; // Or a special Coordinates class, or a vec3 or whatever.
SpacialIndex& spacialIndex; // Note this could be the area/level/map/whatever here
};
Это также может быть ссылка на объект в сторонних физических движках. Или это могут быть координаты смещения со ссылкой на другое местоположение (для камеры слежения или прикрепленного объекта или примера). С полиморфизмом это может зависеть от того, статический или динамический объект. Сохраняя ссылку на пространственный индекс здесь, когда координаты обновляются, пространственный индекс также может быть.
Если вы беспокоитесь о динамическом распределении памяти, используйте пул памяти.
2) Связывание / связь между вашим объектом, его местоположением и графом сцены.
typedef std::pair<Object, Location> SpacialBinding.
3) Привязка добавляется к пространственному индексу внутри уровня в соответствующей точке.
Когда вы готовитесь к визуализации.
1) Получить камеру (это будет просто другой объект, за исключением того, что его местоположение будет отслеживать персонажа игроков, а ваш рендерер будет иметь специальную ссылку на него, фактически это все, что ему действительно нужно).
2) Получить SpacialBinding камеры.
3) Получить пространственный индекс из привязки.
4) Запрос объектов, которые (возможно) видны для камеры.
5А) Вам необходимо обработать визуальную информацию. Текстуры загружаются в GPU и так далее. Лучше всего это сделать заранее (например, при загрузке уровня), но, возможно, это можно сделать во время выполнения (для открытого мира вы можете загружать вещи, когда вы приближаетесь к чанку, но все же следует делать это заранее).
5B) При необходимости создайте кэшированное дерево рендеринга, если вы хотите выполнить глубину / сортировку материала или отслеживать близлежащие объекты, которые могут быть видны позже. В противном случае вы можете просто запросить пространственный индекс каждый раз, когда он будет зависеть от ваших требований к игре / производительности.
Вашему рендереру, вероятно, понадобится объект RenderBinding, который свяжет объект с координатами
class RenderBinding {
Object& object;
RenderInformation& renderInfo;
Location& location // This could just be a coordinates class.
}
Затем при рендеринге просто запустите список.
Я использовал ссылки выше, но это могут быть умные указатели, необработанные указатели, дескрипторы объектов и так далее.
РЕДАКТИРОВАТЬ:
class Game {
weak_ptr<Camera> camera;
Level level1;
void init() {
Camera camera(75.0_deg, 1.025_ratio, 1000_meters);
auto template_player = loadObject("Player.json")
auto player = level1.addObject(move(player), Position(1.0, 2.0, 3.0));
level1.addObject(move(camera), getRelativePosition(player));
auto template_bad_guy = loadObject("BadGuy.json")
level1.addObject(template_bad_guy, {10, 10, 20});
level1.addObject(template_bad_guy, {10, 30, 20});
level1.addObject(move(template_bad_guy), {50, 30, 20});
}
void render() {
camera->getFrustrum();
auto level = camera->getLocation()->getLevel();
auto object = level.getVisible(camera);
for(object : objects) {
render(objects);
}
}
void render(Object& object) {
auto ri = object.getRenderInfo();
renderVBO(ri.getVBO());
}
Object loadObject(string file) {
Object object;
// Load file from disk and set the properties
// Upload mesh data, textures to GPU. Load shaders whatever.
object.setHitPoints(// values from file);
object.setRenderInfo(// data from 3D api);
}
}
class Level {
Octree octree;
vector<ObjectPtr> objects;
// NOTE: If your level is mesh based there might also be a BSP here. Or a hightmap for an openworld
// There could also be a physics scene here.
ObjectPtr addObject(Object&& object, Position& pos) {
Location location(pos, level, object);
objects.emplace_back(object);
object->setLocation(location)
return octree.addObject(location);
}
vector<Object> getVisible(Camera& camera) {
auto f = camera.getFtrustrum();
return octree.getObjectsInFrustrum(f);
}
void updatePosition(LocationPtr l) {
octree->updatePosition(l);
}
}
class Octree {
OctreeNode root_node;
ObjectPtr add(Location&& object) {
return root_node.add(location);
}
vector<ObjectPtr> getObjectsInRadius(const vec3& position, const float& radius) { // pass to root_node };
vector<ObjectPtr> getObjectsinFrustrum(const FrustrumShape frustrum;) {//...}
void updatePosition(LocationPtr* l) {
// Walk up from l.octree_node until you reach the new place
// Check if objects are colliding
// l.object.CollidedWith(other)
}
}
class Object {
Location location;
RenderInfo render_info;
Properties object_props;
Position getPosition() { return getLocation().position; }
Location getLocation() { return location; }
void collidedWith(ObjectPtr other) {
// if other.isPickup() && object.needs(other.pickupType()) pick it up, play sound whatever
}
}
class Location {
Position position;
LevelPtr level;
ObjectPtr object;
OctreeNote octree_node;
setPosition(Position position) {
position = position;
level.updatePosition(this);
}
}
class Position {
vec3 coordinates;
vec3 rotation;
}
class RenderInfo {
AnimationState anim;
}
class RenderInfo_OpenGL : public RenderInfo {
GLuint vbo_object;
GLuint texture_object;
GLuint shader_object;
}
class Camera: public Object {
Degrees fov;
Ratio aspect;
Meters draw_distance;
Frustrum getFrustrum() {
// Use above to make a skewed frustum box
}
}
Что касается того, чтобы вещи «знали» друг о друге. Это обнаружение столкновений. Это будет реализовано в Octree, вероятно. Вам нужно будет предоставить некоторый обратный вызов в вашем главном объекте. Этот материал лучше всего обрабатывается соответствующим физическим движком, таким как Bullet. В этом случае просто замените Octree на PhysicsScene, а Position - ссылкой на что-то вроде CollisionMesh.getPosition ().