272
edits
(→Steps: Added link to configure.engine options) |
Dreammaster (talk | contribs) (Fleshing out Quux example with an advanced meta engine detection and savegames) |
||
Line 18: | Line 18: | ||
# Add <tt>engines/quux/module.mk</tt> (Again, just check out what is done for the existing engines). | # Add <tt>engines/quux/module.mk</tt> (Again, just check out what is done for the existing engines). | ||
# Add <tt>engines/quux/quux.h</tt> and <tt>engines/quux/quux.cpp</tt>; this will contain your Engine subclass (or at least parts of it). | # Add <tt>engines/quux/quux.h</tt> and <tt>engines/quux/quux.cpp</tt>; this will contain your Engine subclass (or at least parts of it). | ||
# Add <tt>engines/quux/detection.cpp</tt>; It will contain the plugin interface code (more on that in the next section). | # Add <tt>engines/quux/detection.cpp</tt> and <tt>engines/quux/detection.h</tt>; It will contain the plugin interface code (more on that in the next section). | ||
That's it. The difficult part is of course writing the Engine subclass. More on that in the next section! | That's it. The difficult part is of course writing the Engine subclass. More on that in the next section! | ||
Line 50: | Line 50: | ||
Additionally, the files saved by each engine should be consistent. | Additionally, the files saved by each engine should be consistent. | ||
* '''Saves''': | * '''Saves''': If you use the new loadGameStream & saveGameStream methods, these will automatically be created in the form of <targetid>.### (where ### is the slot id). If you want saves to be consistent across all game variants, you should instead provide your own implementation of loadGameState and saveGameState, and create savefiles using the format <gameid>.###. | ||
* '''Other files''': These should be named <targetid>-<filename> or <gameid>-<filename>. Again the latter when the files can be shared | * '''Other files''': These should be named <targetid>-<filename> or <gameid>-<filename>. Again the latter when the files can be shared across all variants of the game (an example for these type of files would be when a minigame saves a high score record). | ||
Here, only use the <gameid> scheme if you are '''absolutely''' sure that such files can be shared across '''all''' (that is every game platform, every game language, every game patch version, every game release, etc.) versions. If you are not sure whether this is the case, then stick to the <targetid> based scheme. | Here, only use the <gameid> scheme if you are '''absolutely''' sure that such files can be shared across '''all''' (that is every game platform, every game language, every game patch version, every game release, etc.) versions. If you are not sure whether this is the case, then stick to the <targetid> based scheme. | ||
=== Subclassing | === Subclassing AdvancedMetaEngine === | ||
Let's implement the plugin interface:<br> | Let's implement the plugin interface:<br> | ||
You'll have to create a custom | You'll have to create a custom AdvancedMetaEngine subclass. This provides the information and functionality related to the engine that can be used by the launcher without loading and running the game engine, which includes detecting games, listing savegames and instancing the engine. | ||
The following example illustrates this | The following example illustrates this. It contains the necessary fundamentals of the details of the games and the code to create the engine. Finally, in either case, you will then have to specify your MetaEngine class to the REGISTER_PLUGIN_* macros. | ||
Finally, in either case, you will then have to specify your MetaEngine class to the REGISTER_PLUGIN_* macros. | |||
=== Subclassing Engine === | === Subclassing Engine === | ||
The example code below gives a simple example of an engine. It contains a few important points | |||
* It initializes the screen at a given resolution | |||
* It creates a debugger class and registers it with the engine framework. Currently it's only a skeleton defined in quux.h. For a full game, you'd implement it in it's own debugger.cpp and .h files | |||
* It has a simple event loop | |||
Miscellaneous important: If you end up using the ScummVM GUI manually in your game (g_gui and stuff) you have always to call g_gui.handleScreenChanged() if you received a OSystem::EVENT_SCREEN_CHANGED event, else it could be that your gui looks strange or even crashes ScummVM. | |||
For opening files in your engine, see the [[HOWTO-Open Files|how to open files]] page. | For opening files in your engine, see the [[HOWTO-Open Files|how to open files]] page. | ||
Line 118: | Line 118: | ||
#ifndef QUUX_H | #ifndef QUUX_H | ||
#define QUUX_H | #define QUUX_H | ||
#include "common/random.h" | #include "common/random.h" | ||
#include "common/serializer.h" | |||
#include "engines/engine.h" | #include "engines/engine.h" | ||
#include "gui/debugger.h" | #include "gui/debugger.h" | ||
namespace Quux { | namespace Quux { | ||
class Console; | class Console; | ||
// our engine debug channels | // our engine debug channels | ||
enum { | enum { | ||
Line 134: | Line 135: | ||
// the current limitation is 32 debug channels (1 << 31 is the last one) | // the current limitation is 32 debug channels (1 << 31 is the last one) | ||
}; | }; | ||
class QuuxEngine : public Engine { | class QuuxEngine : public Engine { | ||
private: | |||
// We need random numbers | |||
Common::RandomSource *_rnd; | |||
public: | public: | ||
QuuxEngine(OSystem *syst); | QuuxEngine(OSystem *syst); | ||
~QuuxEngine(); | ~QuuxEngine(); | ||
Common::Error run() override; | |||
bool hasFeature(EngineFeature f) const override; | |||
bool canLoadGameStateCurrently() override { return true; } | |||
bool canSaveGameStateCurrently() override { return true; } | |||
Common::Error loadGameStream(Common::SeekableReadStream *stream) override; | |||
Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override; | |||
Common:: | void syncGameStream(Common::Serializer &s); | ||
}; | }; | ||
// Example console class | // Example console class | ||
class Console : public GUI::Debugger { | class Console : public GUI::Debugger { | ||
public: | public: | ||
Console(QuuxEngine *vm) {} | Console(QuuxEngine *vm) { | ||
virtual ~Console(void) {} | } | ||
virtual ~Console(void) { | |||
} | |||
}; | }; | ||
} // End of namespace Quux | } // End of namespace Quux | ||
#endif | #endif | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 164: | Line 170: | ||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
#include "common/scummsys.h" | #include "common/scummsys.h" | ||
#include "common/config-manager.h" | #include "common/config-manager.h" | ||
#include "common/debug.h" | #include "common/debug.h" | ||
Line 172: | Line 178: | ||
#include "common/file.h" | #include "common/file.h" | ||
#include "common/fs.h" | #include "common/fs.h" | ||
#include "engines/util.h" | #include "engines/util.h" | ||
#include "quux/quux.h" | #include "quux/quux.h" | ||
namespace Quux { | namespace Quux { | ||
QuuxEngine::QuuxEngine(OSystem *syst) | QuuxEngine::QuuxEngine(OSystem *syst) | ||
: Engine(syst), _console(nullptr) { | |||
// Put your engine in a sane state, but do nothing big yet; | // Put your engine in a sane state, but do nothing big yet; | ||
// in particular, do not load data from files; rather, if you | // in particular, do not load data from files; rather, if you | ||
// need to do such things, do them from run(). | // need to do such things, do them from run(). | ||
// Do not initialize graphics here | // Do not initialize graphics here | ||
// Do not initialize audio devices here | // Do not initialize audio devices here | ||
// However this is the place to specify all default directories | // However this is the place to specify all default directories | ||
const Common::FSNode gameDataDir(ConfMan.get("path")); | const Common::FSNode gameDataDir(ConfMan.get("path")); | ||
SearchMan.addSubDirectoryMatching(gameDataDir, "sound"); | SearchMan.addSubDirectoryMatching(gameDataDir, "sound"); | ||
// Here is the right place to set up the engine specific debug channels | // Here is the right place to set up the engine specific debug channels | ||
DebugMan.addDebugChannel(kQuuxDebugExample, "example", "this is just an example for a engine specific debug channel"); | DebugMan.addDebugChannel(kQuuxDebugExample, "example", "this is just an example for a engine specific debug channel"); | ||
DebugMan.addDebugChannel(kQuuxDebugExample2, "example2", "also an example"); | DebugMan.addDebugChannel(kQuuxDebugExample2, "example2", "also an example"); | ||
// Don't forget to register your random source | // Don't forget to register your random source | ||
_rnd = new Common::RandomSource("quux"); | _rnd = new Common::RandomSource("quux"); | ||
debug("QuuxEngine::QuuxEngine"); | debug("QuuxEngine::QuuxEngine"); | ||
} | } | ||
QuuxEngine::~QuuxEngine() { | QuuxEngine::~QuuxEngine() { | ||
debug("QuuxEngine::~QuuxEngine"); | debug("QuuxEngine::~QuuxEngine"); | ||
Line 207: | Line 213: | ||
// Dispose your resources here | // Dispose your resources here | ||
delete _rnd; | delete _rnd; | ||
// Remove all of our debug levels here | // Remove all of our debug levels here | ||
DebugMan.clearAllDebugChannels(); | DebugMan.clearAllDebugChannels(); | ||
} | } | ||
Common::Error QuuxEngine::run() { | Common::Error QuuxEngine::run() { | ||
// Initialize graphics using following: | // Initialize graphics using following: | ||
initGraphics(320, 200, false); | initGraphics(320, 200, false); | ||
// You could use backend transactions directly as an alternative, | // You could use backend transactions directly as an alternative, | ||
// but it isn't recommended, until you want to handle the error values | // but it isn't recommended, until you want to handle the error values | ||
Line 230: | Line 236: | ||
//OSystem::kTransactionSizeChangeFailed here | //OSystem::kTransactionSizeChangeFailed here | ||
//_system->endGFXTransaction(); | //_system->endGFXTransaction(); | ||
// Create debugger console. It requires GFX to be initialized | // Create debugger console. It requires GFX to be initialized | ||
Console *console = new Console(this); | |||
setDebugger(console); | |||
// Additional setup. | // Additional setup. | ||
debug("QuuxEngine::init"); | debug("QuuxEngine::init"); | ||
// Your main even loop should be (invoked from) here. | // Your main even loop should be (invoked from) here. | ||
debug("QuuxEngine::go: Hello, World!"); | debug("QuuxEngine::go: Hello, World!"); | ||
// This test will show up if -d1 and --debugflags=example are specified on the commandline | // This test will show up if -d1 and --debugflags=example are specified on the commandline | ||
debugC(1, kQuuxDebugExample, "Example debug call"); | debugC(1, kQuuxDebugExample, "Example debug call"); | ||
// This test will show up if --debugflags=example or --debugflags=example2 or both of them and -d3 are specified on the commandline | // This test will show up if --debugflags=example or --debugflags=example2 or both of them and -d3 are specified on the commandline | ||
debugC(3, kQuuxDebugExample | kQuuxDebugExample2, "Example debug call two"); | debugC(3, kQuuxDebugExample | kQuuxDebugExample2, "Example debug call two"); | ||
// Simple main event loop | |||
Common::Event evt; | |||
while (!shouldQuit()) { | |||
g_system->getEventManager()->pollEvent(evt); | |||
g_system->delayMillis(10); | |||
} | |||
return Common::kNoError; | |||
} | |||
bool QuuxEngine::hasFeature(EngineFeature f) const { | |||
return | |||
(f == kSupportsReturnToLauncher) || | |||
(f == kSupportsLoadingDuringRuntime) || | |||
(f == kSupportsSavingDuringRuntime); | |||
} | |||
Common::Error QuuxEngine::loadGameStream(Common::SeekableReadStream *stream) { | |||
Common::Serializer s(stream, nullptr); | |||
syncGameStream(s); | |||
return Common::kNoError; | |||
} | |||
Common::Error QuuxEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) { | |||
Common::Serializer s(nullptr, stream); | |||
syncGameStream(s); | |||
return Common::kNoError; | return Common::kNoError; | ||
} | } | ||
void QuuxEngine::syncGameStream(Common::Serializer &s) { | |||
// Use methods of Serializer to save/load fields | |||
int dummy = 0; | |||
s.syncAsUint16LE(dummy); | |||
} | |||
} // End of namespace Quux | } // End of namespace Quux | ||
</syntaxhighlight> | |||
=== Example: engines/quux/detection.h === | |||
The following example implements a custom meta engine | |||
<syntaxhighlight lang="cpp"> | |||
#ifndef QUUX_DETECTION_H | |||
#define QUUX_DETECTION_H | |||
#include "engines/advancedDetector.h" | |||
class QuuxMetaEngine : public AdvancedMetaEngine { | |||
public: | |||
QuuxMetaEngine(); | |||
const char *getEngineId() const override { | |||
return "quux"; | |||
} | |||
const char *getName() const override { | |||
return "Quux"; | |||
} | |||
const char *getOriginalCopyright() const override { | |||
return "Copyright (C) Quux Entertainment Ltd."; | |||
} | |||
bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override; | |||
}; | |||
#endif | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Example: engines/quux/detection.cpp === | === Example: engines/quux/detection.cpp === | ||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
#include "quux/detection.h" | |||
#include "quux/quux.h" | #include "quux/quux.h" | ||
#include "engines/game.h" | |||
#include " | |||
namespace Quux { | |||
static const PlainGameDescriptor quuxGames[] = { | |||
static const PlainGameDescriptor | |||
{ "quux", "Quux the Example Module" }, | { "quux", "Quux the Example Module" }, | ||
{ "quuxcd", "Quux the Example Module (CD version)" }, | { "quuxcd", "Quux the Example Module (CD version)" }, | ||
{ 0, 0 } | { 0, 0 } | ||
}; | }; | ||
static const ADGameDescription gameDescriptions[] = { | |||
{ | |||
"quux", | |||
0, | |||
AD_ENTRY1s("quux.txt", 0, 0), | |||
Common::EN_ANY, | |||
Common::kPlatformDOS, | |||
ADGF_NO_FLAGS, | |||
GUIO1(GUIO_NOMIDI) | |||
}, | |||
AD_TABLE_END_MARKER | |||
Common:: | |||
}; | }; | ||
} // End of namespace Quux | |||
QuuxMetaEngine::QuuxMetaEngine() : AdvancedMetaEngine( | |||
Quux::gameDescriptions, sizeof(ADGameDescription), Quux::quuxGames) { | |||
} | |||
bool QuuxMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { | |||
*engine = new Quux::QuuxEngine(syst); | |||
return true; | |||
} | |||
#if PLUGIN_ENABLED_DYNAMIC(QUUX) | #if PLUGIN_ENABLED_DYNAMIC(QUUX) | ||
REGISTER_PLUGIN_DYNAMIC(QUUX, PLUGIN_TYPE_ENGINE, QuuxMetaEngine); | |||
#else | #else | ||
REGISTER_PLUGIN_STATIC(QUUX, PLUGIN_TYPE_ENGINE, QuuxMetaEngine); | |||
#endif | #endif | ||
</syntaxhighlight> | </syntaxhighlight> |
edits