As you'd expect, I've arrived at a question I can't answer and I can only guess.
Runtime polymorphism is the goal, using the virtual mechanism, but the result I'm getting is as if I called the method while suppressing it; like I called the base class method.
I can only guess then that I am somehow doing that, the call is from an object that is a base class, although it was constructed as a derived class. So, constructed as a derived, stored as a base.
I do store this as a static base class variable within a cpp and interfaced with multiple extern functions to access elsewhere. (perhaps, that's the gotcha?)
GameScene.h:
class GameScene {
public:
GameScene() { SceneInit(); }
~GameScene() { }
virtual void LevelInit() { } // gcc complains w/o {}
void SceneInit();
virtual void UpdateLevel( const float& timeDelta ) { } // gcc complains unless {} added
void UpdateScene( const float& timeDelta );
};
extern void NewScene( const GameScene& level );
extern void UpdateScene( const float& timeDelta );
class TestLevel : public GameScene {
public:
TestLevel() { SceneInit(); }
// implementation here in header
void LevelInit() override
{
// level-specific initialization that is apparent at runtime
}
void UpdateLevel( const float& timeDelta ) override
{
// level-specific checks and performance
// but, to test, I simply log "This is the test level"
}
};
class TutorialLevel : public GameScene {
public:
TutorialLevel() { SceneInit(); }
// implementation here in header
void LevelInit() override
{
// level-specific initialization that is apparent at runtime
}
void UpdateLevel( const float& timeDelta )
{
// level-specific checks and performance
// debug log "This is the tutorial level"
}
};
GameScene.cpp:
#include "GameScene.h"
static GameScene currentScene; // I am now wondering if this pattern is the problem (by explicitly storing this as the base class)
extern void NewScene( const GameScene& level )
{
currentScene = level;
}
extern void UpdateScene( const float& timeDelta )
{
currentScene.UpdateScene( timeDelta );
}
GameScene::SceneInit()
{
// general init
LevelInit(); // this _properly_ calls the subclass overridden version
// init completion
}
GameScene::UpdateScene( const float& timeDelta )
{
// general updating and game handling
UpdateLevel( timeDelta ); // this was _meant_ to call the overridden version, it does not
}
EntryPoint.cpp:
#include "GameScene.h"
int main()
{
//NewScene( TestLevel() );
NewScene( TutorialLevel() );
float deltaTime;
while (gameLoop)
{
deltaTime = SecondsSinceLastFrame(); // pseudo
UpdateScene( deltaTime );
}
}
So, I was following a pattern that worked with SceneInit() calling LevelInit(), which is overridden in the derived classes. If I use either derived class constructor in NewScene(), I get those LevelInit() results at runtime. I thought this would be safe to use this pattern with UpdateScene().
What I see is UpdateScene() called the GameScene::UpdateLevel(), even though it is clearly overridden in the subclasses, just like LevelInit() is.
My (wild) guess is, I am calling UpdateLevel() as if I had explicitly cast it as GameScene. :\
And maybe, this is because I store currentScene as GameScene? (but then, doesn't that defeat the purpose of having polymorphism?)
I am missing something about either storing currentScene, or calling UpdateLevel(). I've tried calling like this:
GameScene *s = currentScene;
s->UpdateLevel();
after reading that, as a pointer, the virtual mechanism should be finding the most-derived version of the method. (but alas...)
I wish I could have found an example here or elsewhere, but my searches pointed out problems with virtual in constructors/deconstructors, or just not using the 'virtual' keyword, etc.
currentScene
is a GameScene
. You've declared it as such. It will never be anything but a GameScene
. A GameScene
is not a TestLevel
or a TutorialLevel
; it is a GameScene
. When you assign a TestLevel
or TutorialLevel
object to currentScene
you are slicing it.
Polymorphism in C++ only works with pointers and references. A pointer or reference to a GameScene
can refer to a TestLevel
or TutorialLevel
object, but a GameScene
object cannot be a TestLevel
or TutorialLevel
.
What this boils down to is that currentScene
needs to be a (smart) pointer to a GameScene
instead of a GameScene
. It can then point to a dynamically-allocated object of any class derived from GameScene
. For example:
static std::unique_ptr<GameScene> currentScene;
extern void NewScene( std::unique_ptr<GameScene> level )
{
currentScene = std::move(level);
}
int main()
{
NewScene( std::make_unique<TutorialLevel>() );
// ...
}