How/Who create the GameObjects?

Started by
40 comments, last by Khatharr 11 years, 5 months ago
[source lang="cpp"]class Game{

list < GameObjects * > m_gos;

void Update(float delta){

for(list::it goIt ..)
(*goIt)->Update(delta);
}

...
};[/source]


Pseudo code..

Id like to make the Class Game the sole responsible for the creation and deletion of the gameobjects.
I always do this with a template function:
[source lang="cpp"]template< class derivedGO>
derivedGO* Create(){

derivedGO *p = new derivedGO();
m_gos.add(p);
return p;

}[/source]
And this is the only way to give the Game class the objects (so if they got created externally, they will not be part of the game).
The only (really annoying imo) problem is that derivedGO must provide a compatible constructor, this sucks, cause I always, then, have to create a Init(params) function that is always called just after calling create..

Is this poor design?
Advertisement
I can't really comment on the design itself, but if you are using C++11, you can forward the arguments (without knowing how many arguments or what types) to the constructor like this:

template<typename derivedGO, typename Arg>
derivedGO* Create(Arg &&arg){

derivedGO *p = new derivedGO(std::forward<Arg>(arg));
m_gos.add(p);
return p;
}


([size=2]'Game' sounds like it's a monolith class, though)
It is not particularly good design. You are trying to do too much for the users. There comes a point when you give users so little control that your engine/framework/whatever simply becomes useless.

Your game class is doing way too much. It is a mega-class.
Break it down into smaller classes each with a specific task.
For example, objects don’t exist inside the game, they exist inside scenes.
So for starters, get all the objects out of your Game class and put them into a Scene class.

On my first few engine attempts I also thought there should be at most only one scene active at a time.
This caused me to create hacky solutions when trying to render one set of objects with one camera and another set of objects with another camera.
The correct solution is not to limit how many scenes you can have.
So a scene can have any number of objects inside it. The Game class can have any number of scenes inside it.


Back to my first point, why do objects have to be created only through the Game (or Scene) class?
You probably think you are doing your users a favor and making their lives easier by doing some of the heavy lifting for them.
You’re not.
There is no logical reason why objects in the scene must strictly be created by the scene manager.
Of course, creating objects via the scene manager should be one option, but not the only one.


m_dmipInstance = m_sgpScene->CreateDrawableModelInstance( "Cayman.lsm" );
m_dmipInstance->Orientation().SetPos( CVector3( 0.0f, -m_dmipInstance->Aabb().m_vMin.y, 0.0f ) );
m_dmipInstance->SetCellShading( true );

CDrawableModelInstancePtr pdmipTemp = m_sgpScene->CreateDrawableModelInstance( "Ground.lsm" );
pdmipTemp->SetCastsShadow( false );

This is just one way to create models. You can also do this:
CDrawableModelInstancePtr dmipCustomLoad;
dmipCustomLoad.New();
dmipCustomLoad->LoadFile( "BMW X6" );
dmipCustomLoad->SetCookTorranceShading( true );
m_sgpScene->AddDrawableModelInstance( dmipCustomLoad );


dmipCustomLoad.New(); // Notice how this is a smart pointer.
// Custom setting of vertices, normals, etc. here.
m_sgpScene->AddDrawableModelInstance( dmipCustomLoad );



I am speaking from experience when I say that loading models strictly through the scene manager is a pain in the ass.
If you think you have to do it that way, you have a problem with your overall design/architecture that you need to fix.


If your goal is to make the engine easy to use, employ smart pointers and use them for the objects in your scene. And don’t be picky about how models end up in the scene.



L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

All pseudo code here, but you should reconsider your approach to the general design on the game engine. I apologize in advance, i started typing all this out and got bored by the end, if i skipped something and you would like some explanation, just ask. But it should all be self-explanatory.

You have a game, which contains a stack of scenes. Only the top most scene is updated, but any scene can be rendered. This makes it so if you have an in game menu, you can pause the game (by no longer updating the game scene) and render it as well as the menu on top. Makes it pretty easy. The scene has game objects, which are basically just glue objects containing components (Transforms, character controllers, ui's etc). Each component does something specific, with variables you can set at any time. You can retrieve game objects from the scene, and components from game objects. All objects are created by Game, as it is the factory. When game starts, it creates the main menu scene, and you just take it from there. This may seem a bit fuzzy or abstract, but the implementation is simple, consider the following:

Some class definitions
[source lang="cpp"]class Component() {
enum Type { RENDERER, ENEMY, PLAYER, UI, ANIMATION, SCRIPT }
Type m_eComponentType;

virtual void Awake() = 0; // Called when component is created
virtual void Destroy() = 0; // Called when component is destroyed

virtual void Start() = 0; // Called when component is activated
virtual void Stop() = 0; // Called when component is deactivated

virtual void Update(float dt) = 0; // I'm not going to explain
}

class Renderer : public Component {
int m_nTextureHandle; // All sorts of component specific members
int m_nBufferId; // Look, we even have a buffer!

void Awake(); // Overload
void Destroy(); // Overload
void Start(); // Overload
void Stop(); // Overload
void Update(float dt); // Overload

void Render(); // Again, component specific!
}

class Enemy : public Component { // Two components, we're on fire!
int m_nHealth; // Enemy specific junk
AStar* m_pPathFinder; // Specific junk

void Awake(); // Override
void Destroy(); // Override
void Start(); // Override
void Stop(); // Override
void Update(float dt); // Override
}

class Player : public Component { // See above
void Awake();
void Destroy();
void Start();
void Stop();
void Update(float dt);
}

class UI : public Component { // See above
void Awake();
void Destroy();
void Start();
void Stop();
void Update(float dt);
}

class GameObject() { // Now for the fun bit
std::vector<Component*> m_vComponents; // A game object has components
std::string m_strGameObjectName; // So we can retrieve game objects later
bool m_bIsActive; // Does not always need to be active

void GameObjectWasCreated(); // Created
void GameObjectDidBecomeActive(); // Became active
void GameObjectDidBecomeInactive(); // Became inactive
void GameObjectWasDestroyed(); // Destroyed

void AddComponent(Component*);
void RemoveComponent(Component*);
Component* GetComponent(Component::Type);

void Update(float dt);
void Render();
}

class Scene() {
std::vector<GameObject*> m_vGameObjectList; // You can be more creative
bool m_bRenderWhenNotActive;

void SceneWasCreated();
void SceneDidBecomeActive();
void SceneDidBecomeInactive();
void SceneWasDestroyed();

void AddGameObject(GameObject*);
void RemoveGameObject(GameObject*);
GameObject* GetGameObject(std::string);

void Update(float dt);
void Render();
}

class Game {
std::vector<Scene*> m_vSceneStack;

bool Initialize(); // Initializes the game (Keyboard, mouse, managers, etc)
void Shutdown(); // Destroys the game (blows away scene stack, game objects etc, shuts down managers)

Scene* CreateNewScene(); // Creates a scene object
GameObject* CreateGameObject(); // Creates a game object
Component* CreateComponent(Component::Type); // Creates a component

void DestroyScene(Scene*); // Delete the scene
void DestroyGameObject(GameObject*); // Delete the game object
void DestroyComponent(Component*); // Delete the component

void Update(float dt); // Updates the top most object
void Render();
}[/source]

A little bit of implementation
[source lang="cpp"]void Renderer::Awake() { m_eComponentType = RENDERER; m_bIsActive = true; } // Do other stuff to set up your buffers and textures

void Renderer::Destroy() { } // Release your buffers and textures here

void Renderer::Start() { } // Don't care

void Renderer::Stop() { } // Don't care

void Renderer::Update(float dt) { } // Don't care

void Renderer::Render() { BindBuffer(m_nBufferId); BindTexture(m_nTextureId); RenderBatch(); }

void Enemy::Awake() { m_eComponentType = ENEMY; m_bIsActive = true; } // Do other enemy stuff, like ai initialization and whatnot

void Enemy::Destroy() { } // Shutdown

void Enemy::Start() { if (m_pAStar == 0) m_pAStar = AStar::Factory::NewPathFinder(); } // I don't know, just arbitrary shit

void Enemy::Stop() { if (m_pAStart != 0) AStar::Factory::Destroy(m_pAStar); m_pAStar = 0; } // Make sure you shut shit down

void Enemy::Update(float dt) { m_pAStar->Find("Player"); AttackPlayerWhenPossible(); }

// Be creative, make your player and ui components and junk!
// For extra credit, make a script component, being able to script your engine will make life easy.

class GameObject() {
std::vector<Component*> m_vComponents;
std::string m_strGameObjectName;
bool m_bIsActive;

void GameObject::GameObjectWasCreated() { m_bIsActive = true; } // Depending on your game, go crazy

void GameObject::GameObjectDidBecomeActive() { // Let all the active components know, they are active again!
for (int i = 0, size = m_vComponents.size(); i < size; ++i ) {
if (m_vComponents.m_bIsActive)
m_vComponents.Start();
}
}

void GameObject::GameObjectDidBecomeInactive() { // Active components, go away!
for (int i = 0, size = m_vComponents.size(); i < size; ++i ) {
if (m_vComponents.m_bIsActive)
m_vComponents.Stop();
}
}

void GameObject::GameObjectWasDestroyed() { DestroyAllComponents(); } // No more game object, no more components

void GameObject::AddComponent(Component* p) {
// Make sure no other component of this type exists (be creative)
m_vComponents.push_back(p);
p->Awake();
p->Start();
}

void GameObject::RemoveComponent(Component* p) {
// Make sure p is in component list
// Get the index of the component
p->Stop();
p->Destroy();
m_vComponents.erase(m_vComponents.begin() + componentIndex);
}

Component* GameObject::GetComponent(Component::Type t) {
for (int i = 0; i < m_vComponents.size(); i < size; ++i)
if (m_vComponents.m_eComponentType == t) // Remember, add component is only allowing one of each type
return m_vComponents;
return 0;
}

void GameObject::Update(float dt) { // Propogate update to all components
for (int i = 0; i < m_vComponents.size(); i < size; ++i)
if (m_vComponents.m_bIsActive)
m_vComponents.Update(dt);
}

void GameObject::Render() { // Only render render components
for (int i = 0; i < m_vComponents.size(); i < size; ++i)
if (m_vComponents.m_eComponentType == RENDERER && m_vComponents.m_bIsActive)
((RendererComponent*)m_vComponents)->Render();
}

void Scene::SceneWasCreated() { } // Do junk

void Scene::SceneDidBecomeActive() {
for (int i = 0, size = m_vGameObjectList.size(); i < size; ++i)
if (m_vGameObjectList->m_bIsActive) // Only active objects need to wake up
m_vGameObjectList->GameObjectDidBecomeActive();
}

void Scene::SceneDidBecomeInactive() {
for (int i = 0, size = m_vGameObjectList.size(); i < size; ++i)
if (m_vGameObjectList->m_bIsActive) // Only active objects need to go to sleep
m_vGameObjectList->GameObjectDidBecomeActive();
}

void Scene::SceneWasDestroyed() { } // Do the opposite of create

void Scene::AddGameObject(GameObject* p) {
// Should not have two addresses of the same object in the list
m_vGameObjectList.push_back(p);
p->GameObjectWasCreated(); // Game object created in scene
p->GameObjectDidBecomeActive(); // Game object is now active
}

void Scene::RemoveGameObject(GameObject* p) {
// Make sure that p is in game object list
p->GameObjectDidBecomeInactive(); // Game object no longer active
p->GameObjectWasDestroyed(); // Game object is no longer in scene
}

GameObject* Scene::GetGameObject(std::string name) {
// Retrieve first instance of game object (or null) based on name string
}

void Scene::Update(float dt) {
for (int i = 0, size = m_vGameObjectList.size(); i < size; ++i)
if (m_vGameObjectList->m_bIsActive) // Only propogate to active game objects
m_vGameObjectList->GameObjectDidBecomeActive();
}

void Scene::Render() {
for (int i = 0, size = m_vGameObjectList.size(); i < size; ++i)
if (m_vGameObjectList->m_bIsActive && m_vGameObjectList->GetComponent(RENDERER) != 0)
m_vGameObjectList->Render(); // Don't need the above check, but figured i'd demonstrate
}


bool Game::Initialize() {
Scene* mainMenu = CreateNewScene();
mainMenu->m_bRenderWhenNotActive = true;

GameObject* mainMenuUIObject = CreateGameObject();
mainMenuUIObject->m_strGameObjectName = "MainMenuUI";

Component* mainMenuUIComponent = CreateComponent(UI);
// Set all sorts of textures and junk for the main menu ui

mainMenu->AddGameObject(mainMenuUIObject);
mainMenuUIObject->AddComponent(mainMenuUIComponent);

// You can make other scenes here, or inside of main menu, the point is
// each new scene will be pushed on top of the scene stack.

// How the stack may be used:
// Push main menu, push audio options, set volume, pop audio options
// Press play, pushes the game, now your playing
// Exit to menu pops the game, now your back to the main menu
}

void Game::Shutdown() {
// Tedious cleanup
}

Scene* Game::CreateNewScene() {
return new Scene(); // You have a factory, be more creative
}

GameObject* Game::CreateGameObject() {
return new GameObject(); // Same as above
}

Component* Game::CreateComponent(Component::Type t) {
switch (t) {
// Return appropriate component subclass
}
}

void Game::DestroyScene(Scene* s) {
delete s; // I'm getting bored of typing all this
}

void Game::DestroyGameObject(GameObject* p) {
delete p;
}

void Game::DestroyComponent(Component* p) {
delete p;
}

void Game::Update(float dt) {
// Only update the top most scene
m_vSceneStack[m_vSceneStack.size() - 1]->Update(dt);
}

void Game::Render() {
// Render all renderable scenes
// So, if you have the following stack
// main menu > game > options
// and game and options are both rendering, and options are opaque
// It just looks like game is paused under options.
for (int i = 0; i < m_vSceneStack.size() - 1; ++i)
if (m_vSceneStack->m_bRenderWhenNotActive)
m_vSceneStack->Render();
m_vSceneStack[m_vSceneStack.size() - 1]->Render();
}
[/source]

The limitations of this system are obvious, but you could easily make script components, and add a lot of power to your engine.

Hope this helps!

Edit, didn't see L.Spyro's response when i wrote this. Consider this the long version.

For example, objects don’t exist inside the game, they exist inside scenes.


What about game objects that need to persist across scenes? Like puzzle logic state that spans levels? Or the main player character + all of his stats/score/whatever?
Data that needs to persist across scenes/states goes on the Game class. That is specifically its job if nothing else.
But that does not mean the 3D or 2D rendering data for your main character etc. That just means your current level, current HP, etc. The bare minimum that could be accessed by any part of the game at any time.

[EDIT]
Note that I failed to mention that all that data that belongs just to one game or another should be part of your “MyGame” class which inherits from “Game”.
Game itself is general across all games and should obviously not be the place for that kind of data.
[/EDIT]


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

To represent different scenes:

enum { SCENE_LOGO, SCENE_TITLE, SCENE_OPTIONS, SCENE_PLAY, SCENE_GAME_OVER };
int c_scene=SCENE_LOGO; // current scene

What about game objects that need to persist across scenes? Like puzzle logic state that spans levels? Or the main player character + all of his stats/score/whatever?


Segregate your shared data, so that you can better identify the character of it--what it means and where it comes from. Then, you don't need to expose it globally in your Game class, you can just pass it between scenes that need it (as parameters.) Yes, it's more work, but it's also more explicit, and ultimately much safer.


Is this poor design?


Yes, for a variety of reasons. What Servant of the Lord said is true, and will work well if you want to carry on with your design, but the design is flawed. Think to yourself, will a virtual Update method always be enough to fully achieve the purpose of a particular GameObject? The answer is no. Which means that you will unavoidably have to pluck GameObjects out of wherever they polymorphically reside, and cast them to their proper types. This is folly. How can you be sure of the types, except by experimentation with dynamic_cast or divine knowledge? Don't store heterogeneous types homogeneously. You might think that they're homogeneous types, because they derive from the same base class, but they're nearly useless if they're never casted.

It can seem messier to deal only with concrete types in this instance, but it's really only being more honest about what is actually happening. Furthermore, there should be a strict separation between GAME code and ENGINE code. This looks like Game code to me, so it doesn't really matter if it's directly oriented toward your game, and concretely references your derived GameObjects. If you've got too many derived GameObjects for this to be feasible, that's merely an indicator that something else is wrong.

Between Scylla and Charybdis: First Look <-- The game I'm working on

Object-Oriented Programming Sucks <-- The kind of thing I say

nox_pp: I agree that OOP sucks :) C++ is for newbies who know nothing about real programming, but I must admit that uglybdavis presented some smart code. I admire that.

typedef struct {
void *p;
int x, y, w, h,
bpp, key, alpha;
} IMAGE;

int load_image(IMAGE *i, char *file);
void move_image(IMAGE *i, int x, int y);
void draw_image(IMAGE *i);

My ASM programming site: http://sungod777.zxq.net/

To represent different scenes:

enum { SCENE_LOGO, SCENE_TITLE, SCENE_OPTIONS, SCENE_PLAY, SCENE_GAME_OVER };
int c_scene=SCENE_LOGO; // current scene

And then what? A switch case to handle the logic between different scenes based on just that integer?
General Game/Engine Structure



nox_pp: I agree that OOP sucks smile.png C++ is for newbies who know nothing about real programming, but I must admit that uglybdavis presented some smart code. I admire that.

typedef struct {
void *p;
int x, y, w, h,
bpp, key, alpha;
} IMAGE;

int load_image(IMAGE *i, char *file);
void move_image(IMAGE *i, int x, int y);
void draw_image(IMAGE *i);

I don’t get the point of this post.
No one asked for opinions on object-oriented programming nor do IMAGE structures (with C code trying to mimic objects with them) have anything to do with the topic.
If you are trying to imply that the original poster should abandon C++ and code in the style you proposed (because C++ is for newbies?), I would point out that your proposed style leaves much to be desired.

  1. const-correctness. Use “const char *”, not “char *”. Does draw_image() modify the image pointer? If so, why? If not, why is the pointer not const?
  2. Type-appropriateness. When will an image have a negative width or height? Don’t use signed types for things that are unsigned. You chose “int” out of laziness.
  3. Why do you have to move the image in a separate step from drawing it? This is superfluous. We aren’t working with lines where the previous position of the line might have a desirable side effect on the current draw operation.
  4. You basically just manually implemented objects in C. The class is “IMAGE” and it has 3 methods:

    1. bool load_image( const char * file )
    2. void move_image( int x, int y )
    3. void draw_image()
      Why would you not just use C++ instead? What exactly is the benefit from doing it the long “non-newbie” way? It is more verbose, less obvious that load_image() was actually supposed to return bool, and you don’t get the extra benefits of virtual functions, templates, etc.


I just don’t get the point.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

This topic is closed to new replies.

Advertisement