我个人建议将draw函数保持在Object类本身之外。我什至建议将对象的位置/坐标保持在对象本身之外。
该draw()方法将处理OpenGL,OpenGL ES,Direct3D,这些API上的包装层或引擎API的低级渲染API。可能是您必须在那时之间进行交换(例如,如果要支持OpenGL + OpenGL ES + Direct3D。
该GameObject应该只包含有关其视觉外观的基本信息,例如Mesh或更大的捆绑包,包括着色器输入,动画状态等。
另外,您将需要灵活的图形管道。如果要根据对象到摄像机的距离来订购对象,该怎么办。或它们的材料类型。如果您想用不同的颜色绘制“选定的”对象,会发生什么。如果不是像在对象上调用draw函数那样实际渲染那么慢,而是将其放入渲染的操作命令列表中(线程可能需要),该怎么办呢?您可以使用其他系统来做这种事情,但这是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)查询(可能)摄像机可见的对象。
5A)您需要处理视觉信息。纹理已上传到GPU等。最好提前完成(例如在级别加载时),但最好在运行时完成(对于开放世界,您可以在接近块时加载内容,但仍应提前完成)。
5B)(可选)构建一个缓存的渲染树,如果您想对材质进行深度/材质排序或跟踪附近的对象,则以后可能会看到它们。否则,您每次可以根据游戏/性能要求查询空间索引。
您的渲染器可能需要一个RenderBinding对象,该对象将在Object,
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)处理。在那种情况下,只需将PhysicsScene和Position替换为Octree,并使用CollisionMesh.getPosition()之类的链接。