//This has info that's needed to draw images in the shader
[Material]
.char[3] diffuse, specular, ambient; //rgb that will be used in the shader
.char* textureData; //the rgba data from an image
.char* normalData; //the normal map from an image
.int textureID; //the textureID that will be filled when it's loaded in the GPU
You would be better off just using a texture pointer directly, or better still a shared pointer to a texture. Textures may have ID’s (and they very well should, to prevent redundant texture loads), but using that ID rather than a direct pointer wastes precious cycles, as you then need to find that texture via binary searches, hash tables, etc.
Additionally, colors should be floats, not char’s, and
especially not
signed char’s. They should also have 4 components, as that is what you will get inside the shader anyway. As far as the GPU is concerned, float[3] and float[4] consume the same space.
//A mesh is the data to draw an object in screen
[Mesh]
.float* vertexes;
.float* normals;
.float* uvs;
.int* vertexIndexes;
.Material material; //A mesh can only have a single material
.MeshType meshType; //this will be an enum that will specify which shader to use, if it's glass, water, or something that need some specific shader effect
.int vboID; //the ID that will be filled when it's loaded in the GPU
Separate the mesh from the low-level components that are used to render it.
A low-level class such as “CVertexBuffer” should have the VBO ID and the vertices, normals, UV’s, etc.
Another low-level class such as “CIndexBuffer” obviously contains the index data.
A mesh is a higher-level class that brings these low-level classes together. The mesh itself should never touch the vertex buffer’s internal data except on loading, and if you do want to mess with the vertex data it should be done through an interface provided by CVertexBuffer.
CVertexBuffer should also not be hard-coded to have normals and UV’s. The only thing a vertex buffer is guaranteed to have is vertex data. I would give you a pass since this is your first time, but think ahead to when you later want to add tangents and bitangents. This is fairly relevant since you already mentioned having normal-map textures. But not every object will have normal maps, so you clearly need to think carefully about a more dynamic vertex-buffer system.
And again, all of that dynamic mess will be entirely handled by the CVertexBuffer class. Its whole purpose is to make it easy for the rest of your engine to use vertex buffers. That is
not job of the mesh, so
don’t mix responsibilities.
//This resource object will contain all data about a single object, such as a "House"
[ResourceObject]
.string name; //a name to identify it, not sure if it'll be needed
.Mesh* meshes; //the resource object can have multiple meshes, such as a "door", "window", "walls", etc
You are somewhat on the right track now, but I wouldn’t call it a “ResourceObject”. That is a very sweeping term that could encompass any form of resource, including models, terrain, textures, BGM, etc.
What you have just described is only a model. A model is a collection of meshes, which is exactly what you have described here.
This hints to the idea that you believe every 3D thing you see on the screen is a mesh or a collection of meshes. They aren’t. Terrain, for example, will use a heightmap of some sort with an LOD method of your choice (I recommend GeoClipmaps), which makes its rendering pipeline almost entirely different from how simple things such as doors and walls are drawn. For that matter, interiors of buildings are also drawn entirely differently so that they can take advantage of PVS’s etc.
I am not saying you need to get so complex with your own engine, especially on your first go, but I want to make sure you understand that in reality, very little of what you see on the screen of any modern 3D game is really just a basic mesh. Those are usually just characters, ammunition/weapons, and various other props. Other things, such as terrain, vegetation, buildings (especially interiors), skyboxes, etc., are all made efficient by utilizing rendering pipelines unique to themselves.
//This is the class that will be responsible for loading and unloading objects from disk in the RAM
[ResourceEngine]
.list<ResourceObject> listObjects; //a list of all objects that are currently loaded
.list<Material> listMaterials; //a list of all materials that are currently loaded
.LoadObject(int count, ...); //load X resources in the list, will use "string" to identify them
.LoadMaterial(int count, ...); //same as above, but for materials
.UnloadObject(int objectID); //unload an object from the list
.UnloadMaterial(int materialID); //unload a material from the list
I wouldn’t bring all of these things together into one mega-class. Again:
Single Responsibility Principle.
Expanding on the concept I introduced on how diverse the types of objects in your game world ultimately could be, imaging trying to load and construct all of those objects in one place.
It will quickly become a major mess.
Firstly you need to familiarize yourself with Single Responsibility Principle.
In this case, let’s look at the potential diversity of the types of things that could go into your game world and pick separation points.
A model is so different from terrain that models and terrain could each be their own entirely separate modules (or libraries).
Of course they would both build on top of a common foundation (the graphics module/library, where CVertexBuffer and CShader live).
So let’s say that models are entirely their own module. In my engine it builds as its own .lib file, but in keeping things simple for your first time, let’s just say it’s a folder in your source tree. Anything under that folder has one job and one job only: Models.
When you break it down like this it starts to make sense that models should actually just load themselves. Models know what models are. They know what their binary format is. There is no reason to push the responsibility of the loading process onto any other module or class.
If you are worried that the model module is stepping out of its bounds by having direct access to disk, don’t give it direct access to disk. Give it a “
stream”.
Now your model module/library is still just doing its job. It doesn’t know anything about hard drives, networks, or otherwise, yet via the abstract interface provided by streams it can actually load its own model data through both of them.
Because of the raw number of resource types, a “ResourceEngine” doesn’t really make sense, but it could find a nest in your design if you do it right. Just make sure you understand that a “resource engine” also has only one responsibility, and it is
not the actual loading process for each resource. If anything, it only provides a high-level interface for loading, but passes off the actual loading process to each object it creates.
For example, if you ask it for a model, it will create a new model, prepare a file stream for the model, and let the model use that stream to load itself. It then destroys the model and returns NULL if loading failed, or it returns a shared pointer to that model.
Don’t use ID’s for everything, as, as I already mentioned, it wastes precious cycles.
//This is the class responsible for loading the objects in the GPU and manage shaders
[GraphicEngine]
.list<Shader> listShaders; //a list of shaders, not sure if this will be needed
.LoadObject(ResourceObject* object); //load an object in the GPU, will fill the "vboID"
.LoadMaterial(Material* material); //load an material in the GPU, will fill the "textureID"
.Unload(int vboID); //unload a VBO
.Unload(int textureID); //unload a texture
.CompileShaders(string shaderCodes); //this will compile the shader, and fill the list and specify an ID to it, which an object will be able to call it if needed
The graphics engine should provide low-level classes such as CVertexBuffer, CTexture2D, CTextureCube, CIndexBuffer, CShader, etc.
And when I say “provide” I don’t mean that you request these things from the graphics engine, I mean that the graphics module is where these things all live and can be taken by any class at any time. That’s how the model and terrain modules can be totally separate from each other while still sharing a common graphics module.
How high-level a graphics engine can and should be is partly a matter of opinion, but everyone will agree that it needs to be flexible and be equipped to handle the very wide variety of things that it will be used to draw.
That is why it is best to keep the features it provides as low-level as possible.
So a graphics module should basically just be a collection of the low-level parts needed to perform any abstract rendering process. You don’t even need (but you can if you want) to have an interface for activating textures and vertex buffers. CVertexBuffer and CTexture2D can handle these tasks themselves.
The main problem you have here is that the graphic module knows what a “ResourceObject” (a model) is when it should be entirely the other way around.
Because of how different the pipelines are for rendering a standard model vs. GeoClipmap terrain, trying to make a monolithic graphics module that knows about both of these and tries to render them properly all by itself is not only a mess but a violation of Single Responsibility Principle.
Things will go much more smoothly if the graphics module simply provides a set of low-level classes and a small interface for setting render states etc., and then let the models and terrain use these classes directly and set states directly before rendering themselves, by themselves.
Of note, that does not mean you are completely off base by thinking about sending the graphics module a class/structure to let the graphics module do some work of its own, setting states, activating textures, setting vertex buffers, etc.
But you went too high-level with it.
The graphics module can receive a structure with state settings (blending mode, culling mode, etc.) and pointers to textures to activate, but that is a mid-level feature and only acts as a helper function.
In other words, it is not doing anything special that models and terrain could not do themselves manually. Setting blending modes and textures could still be done manually, and even if you let the graphics module do it for you, it’s still just using its own already-exposed low-level API for it.
.GetCurrentTime(); //returns the float of the currentTime
It most certainly does not.I already wrote a sub-chapter about this for my upcoming book, but I don’t have it with me right now.
I will post it here when I get home.
//This class will keep track of the game's ACTIONs and playerInput
[InputEngine]
.keyboard[256]; //For each key in the keyboard, has a bool to check if it's pressed or not
.mouse[8]; //Same for mouse, and mousewheel
//I'm thinking in moving these two to OS, since mobiles have different controls and it'll depend on the target platform the game will be
.list<Actions> listActions; //A list of all actions in the game, loaded from a resourceFile as strings such as "PLAYER_MOVERIGHT"
.list<Actions> playerInput; //A list of all actions input by the player, with timestamps
Handling input is much more complex than this, but I don’t think you would be able to handle its full intricacies on your first attempt.
This is one of those cases where I can only say, “You’re doing it wrong,” but will let you learn from personal experience. You will need that experience to understand why this is wrong even if I told you, and, besides, it would require a major restructure of your engine.
But I mention this is wrong not to just point and laugh, but to give you a head’s up on one potential place where you could do a lot better next time. If I said nothing at all you might be completely clueless on this part for a few iterations of your engine.
//This is an actual on-screen object, it uses a resourceObject data and there can be multiple of them on screen
[GameObject]
.ResourceObject* resourceObject; //the data of this object
.float positionX, positionY, positionZ;
.float directionX, directionY, directionZ;
.float speedX, speedY, speedZ;
//other variables needed depending on the game
For your first time, I am not going to nit-pick here, but my recommendations would be:
#1: Use vectors, not arrays of floats. Adding velocities and positions becomes a lot easier when you use overloaded CVector operators, and it just makes your code easier to maintain (especially in avoiding copy-paste errors and accidentally increasing X, Y, and Y, instead of X, Y, and Z).
#2: You have a chance to practice Single Responsibility Principle here. Every type of entity in your game, from the characters to the guns to the camera itself, will have a position, scale, and rotation. Sounds as though it is a good responsibility for a COrientation class. Another thing I have written for the book, which I can post when I get home, is just such a class.
//This is the main class where most of the code will be
Then you have not distributed enough of your code to other classes/modules.
To be frank, in my engine, this is probably the smallest class among the major non-helper type classes.
[GameEngine]
.SceneGraph sceneGraph; //basically a list of all GameObjects currently on the level
Just to point out: A scene graph is not a list, it is a hierarchy. It won’t be a scene graph unless you have a parent/child relationship in place (and I suggest you do; it really helps when you want that sword to stay in the guy’s hand).
But don’t misunderstand. In addition to that hierarchical relationship you do still need a flat list of objects for easy traversal.
.Initialize(); //empty function that's called on startup, will be filled differently for each game
.Update(); //empty function that's called every frame
.Draw(); //function that gets a list of objects that "can be seen" from the sceneGraph, and draws them
General Game/Engine StructurePassing Data Between Game States And my GameEngine's Update() would have something like this:
void GameEngine::Update()
{
switch(gameState)
{
case GameState::Startup_Loading:
{
}
case GameState::Startup:
{
}
case GameState::MainGame_Loading:
{
}
case GameState::MainGame:
{
}
}
}
Once again:
General Game/Engine StructureL. Spiro