Difference between revisions of "HOWTO-Engines"
(→Example: engines/quux/quux.h: update / compilation fixes) |
(→Example: engines/quux/quux.cpp: quux.cpp was badly outdated and broken -- fixed it) |
||
Line 143: | Line 143: | ||
<pre> | <pre> | ||
#include "common/scummsys.h" | #include "common/scummsys.h" | ||
#include "common/events.h" // for getEventManager() | #include "common/events.h" // for getEventManager() | ||
#include "common/config-manager.h" | |||
#include "common/file.h" | |||
#include "common/fs.h" | |||
#include "base/game.h" | |||
#include "base/ | |||
#include "base/plugins.h" | #include "base/plugins.h" | ||
Line 152: | Line 155: | ||
static const | static const PlainGameDescriptor quux_setting[] = { | ||
{ "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 } | ||
}; | }; | ||
GameList | GameList Engine_QUUX_gameIDList() { | ||
GameList games; | GameList games; | ||
const | const PlainGameDescriptor *g = quux_setting; | ||
while (g->gameid) { | while (g->gameid) { | ||
games.push_back(*g); | games.push_back(*g); | ||
Line 169: | Line 172: | ||
} | } | ||
GameDescriptor Engine_QUUX_findGameID(const char *gameid) { | |||
const PlainGameDescriptor *g = quux_setting; | |||
while (g->gameid) { | |||
if (0 == scumm_stricmp(gameid, g->gameid)) | |||
break; | |||
g++; | |||
} | |||
return GameDescriptor(g->gameid, g->description); | |||
} | |||
GameList Engine_QUUX_detectGames(const FSList &fslist) { | |||
GameList detectedGames; | |||
// Iterate over all files in the given directory | // Iterate over all files in the given directory | ||
for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { | for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { | ||
if (!file->isDirectory()) { | if (!file->isDirectory()) { | ||
const char *gameName = file-> | const char *gameName = file->getName().c_str(); | ||
if (0 == scumm_stricmp("README", gameName)) { | if (0 == scumm_stricmp("README", gameName)) { | ||
Line 187: | Line 200: | ||
} | } | ||
PluginError Engine_QUUX_create(OSystem *syst, Engine **engine) { | |||
// At this point you may want to perform | assert(syst); | ||
assert(engine); | |||
// Scan the target directory for files (error out if it does not exist) | |||
FSList fslist; | |||
FilesystemNode dir(ConfMan.get("path")); | |||
if (!dir.getChildren(fslist, FilesystemNode::kListAll)) { | |||
return kInvalidPathError; | |||
} | |||
// Invoke the detector | |||
Common::String gameid = ConfMan.get("gameid"); | |||
GameList detectedGames = Engine_QUUX_detectGames(fslist); | |||
for (uint i = 0; i < detectedGames.size(); i++) { | |||
if (detectedGames[i].gameid() == gameid) { | |||
// At this point you may want to perform additional sanity checks. | |||
*engine = new Quux::QuuxEngine(syst); | |||
return kNoError; | |||
} | |||
} | |||
// Failed to find any game data | |||
return kNoGameDataFoundError; | |||
} | } | ||
REGISTER_PLUGIN(QUUX, "Quux the Example Module"); | REGISTER_PLUGIN(QUUX, "Quux the Example Module", "Copyright (C) Quux Entertainment Ltd."); | ||
Line 208: | Line 243: | ||
// However this is the place to specify all default directories | // However this is the place to specify all default directories | ||
File::addDefaultDirectory(_gameDataPath + "sound/"); | Common::File::addDefaultDirectory(_gameDataPath + "sound/"); | ||
// Here is the right place to set up the engine specific debug levels | // Here is the right place to set up the engine specific debug levels | ||
Line 228: | Line 263: | ||
} | } | ||
int QuuxEngine::init( | int QuuxEngine::init() { | ||
// Initialize graphics using following template | // Initialize graphics using following template | ||
// You have to use transactions | // You have to use transactions | ||
Line 234: | Line 269: | ||
// This is handled by base Engine class and processes command | // This is handled by base Engine class and processes command | ||
// line parameters | // line parameters | ||
initCommonGFX(true); | |||
// Specify dimensions of game graphics window | // Specify dimensions of game graphics window. | ||
_system->initSize( | // In this example: 320x200 | ||
_system->initSize(320, 200); | |||
_system->endGFXTransaction(); | _system->endGFXTransaction(); | ||
Line 260: | Line 296: | ||
return 0; | return 0; | ||
} | } | ||
Revision as of 13:59, 22 January 2008
Introduction
This page is meant as a mini-HOWTO which roughly outlines the steps needed to add a new engine to ScummVM. It does not tell you how to create an engine for a given game; rather it is meant to tell a developer how to properly "hook" into ScummVM.
I will assume that you are at least roughly familiar with ScummVM, and have a fresh checkout of our Subversion repository. Note that it's strongly adviced to base your work on the current development version of ScummVM, and not on a release version. This will ease integration of your work.
Overview
Essentially, you will have to implement a subclass of the Engine class. Our Doxygen documentation is your friend and should hopefully explain enough about this, see here: http://scummvm.org/docs/doxygen/html/classEngine.php .
You also must hook yourself into the regular ScummVM main build system. Actually, some ports use custom build system, but their maintainers will usually add your new engine once it has been added to ScummVM.
Finally, you need to make ScummVM aware of your new engine by updating a couple source files (see below).
Steps
In the following I assume your engine is called "quux".
- Add a new directory engines/quux/
- Add engines/quux/module.mk (take a look at module.mk files of 2-3 existing engines to understand the content).
- Add engines/quux/quux.h and engines/quux/quux.cpp; this will contain your Engine subclass (or at least parts of it). It will also contain the plugin interface code (more on that in the next section).
- Modify engines/engines.mk by adding your engine. It should be clear what to do by looking at what is done for the other engines there.
- Modify configure by adding a new add_engine line. Again, just check out what is done for the existing engines.
- Modify base/plugins.cpp; in particular, you have to add your engine to the list in PluginManager::loadPlugins.
That's it. The difficult part is of course writing the Engine subclass. More on that in the next section!
Important note: Use a C++ namespace for all your work, e.g. "namespace Quux" in this case.
File name conventions
Since of the course of its existence, many people will have to deal with the source code of a given engine (be it to fix bugs in it, modify it to still compile after changes made to tbe backend code, or to simply add new functionality), it is useful to adhere to various conventions used throughout all engines. Besides source code conventions (see Code Formatting Conventions), this affects filenames. We suggest you use the following names for specific parts of your engine:
ENGINENAME.h | Contains your (primary) Engine subclass. |
ENGINENAME.cpp | Contains at least the constructor and destructor of your (primary) Engine subclass, as well as the implementations of the mandatory (i.e. pure virtual) Engine methods. |
detection.cpp | Code related to game detection. Also contains the implementation of the plugin interface, as described in base/plugins.h .
|
saveload.cpp | Code related to savegames |
debug.cpp, debugger.cpp | (console) debugger |
gfx.cpp (alt: graphics.cpp) | Graphics code |
sound.cpp | Sound code |
music.cpp | Music code |
inter.cpp, logic.cpp, script.cpp | Game logic, resp. script/bytecode interpreter |
Subclassing Engine
TODO: We should probably give some sample code, maybe even provide a full (empty) Engine demo class. Maybe even provide a real mini engine project somewhere on our site which demonstrates using events, drawing, etc. ? Not sure whether this would be worth the effort, though.
TODO: At the very least, describe the plugin interface: I.e. which functions *must* be implemented, and what they are supposed to do. Once again, sample code would be nice.
Important: If you're using the ScummVM GUI (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.
Infrastructure services
Header file common/scummsys.h provides services needed by virtually any source file:
- defines platform endianness
- defines portable types
- defines common constants and macros
Moreover, it deals with providing suitable building environment for different platforms:
- provides common names for non-standard library functions
- disables bogus and/or annoying warnings
- provides a lean environment to build win32 executables/libraries
TODO: give descriptions for other commonly used header files.
Common portability issues
There are wrapper around number of non-portable functions. These are:
max() -> MAX() min() -> MIN() rand() -> use Common::RandomSource class stricmp() -> scumm_stricmp() strnicmp() -> scumm_strnicmp() strrev() -> scumm_strrev()
Also we have predefined common integer types. Please, use them instead of rolling your own:
byte int8 uint8 int16 uint16 int32 uint32
Additionally ScummVM offers way of recording all events and then playing them back on request. That could be used for "demoplay" mode. But to ensure that it will work for your engine, you have to register your RandomSource class instance. See example engine below.
Example: engines/quux/quux.h
#ifndef QUUX_H #define QUUX_H #include "engines/engine.h" #include "gui/debugger.h" namespace Quux { class Console; // our engine debug levels enum { kQuuxDebugExample = 1 << 0, kQuuxDebugExample2 = 1 << 1 // next new level must be 1 << 2 (4) // the current limitation is 32 debug levels (1 << 31 is the last one) }; class QuuxEngine : public Engine { public: QuuxEngine(OSystem *syst); ~QuuxEngine(); virtual int init(); virtual int go(); private: Console *_console; // We need random numbers Common::RandomSource _rnd; }; // Example console class class Console : public GUI::Debugger { public: Console(QuuxEngine *vm); virtual ~Console(void); }; } // End of namespace Quux #endif
Example: engines/quux/quux.cpp
#include "common/scummsys.h" #include "common/events.h" // for getEventManager() #include "common/config-manager.h" #include "common/file.h" #include "common/fs.h" #include "base/game.h" #include "base/plugins.h" #include "quux/quux.h" static const PlainGameDescriptor quux_setting[] = { { "quux", "Quux the Example Module" }, { "quuxcd", "Quux the Example Module (CD version)" }, { 0, 0 } }; GameList Engine_QUUX_gameIDList() { GameList games; const PlainGameDescriptor *g = quux_setting; while (g->gameid) { games.push_back(*g); g++; } return games; } GameDescriptor Engine_QUUX_findGameID(const char *gameid) { const PlainGameDescriptor *g = quux_setting; while (g->gameid) { if (0 == scumm_stricmp(gameid, g->gameid)) break; g++; } return GameDescriptor(g->gameid, g->description); } GameList Engine_QUUX_detectGames(const FSList &fslist) { GameList detectedGames; // Iterate over all files in the given directory for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { const char *gameName = file->getName().c_str(); if (0 == scumm_stricmp("README", gameName)) { // You could check the contents of the file now if you need to. detectedGames.push_back(quux_setting[0]); break; } } } return detectedGames; } PluginError Engine_QUUX_create(OSystem *syst, Engine **engine) { assert(syst); assert(engine); // Scan the target directory for files (error out if it does not exist) FSList fslist; FilesystemNode dir(ConfMan.get("path")); if (!dir.getChildren(fslist, FilesystemNode::kListAll)) { return kInvalidPathError; } // Invoke the detector Common::String gameid = ConfMan.get("gameid"); GameList detectedGames = Engine_QUUX_detectGames(fslist); for (uint i = 0; i < detectedGames.size(); i++) { if (detectedGames[i].gameid() == gameid) { // At this point you may want to perform additional sanity checks. *engine = new Quux::QuuxEngine(syst); return kNoError; } } // Failed to find any game data return kNoGameDataFoundError; } REGISTER_PLUGIN(QUUX, "Quux the Example Module", "Copyright (C) Quux Entertainment Ltd."); namespace Quux { QuuxEngine::QuuxEngine(OSystem *syst) : Engine(syst) { // Put your engine in a sane state, but do nothing big yet; // in particular, do not load data from files; rather, if you // need to do such things, do them from init(). // Do not initialize graphics here // However this is the place to specify all default directories Common::File::addDefaultDirectory(_gameDataPath + "sound/"); // Here is the right place to set up the engine specific debug levels Common::addSpecialDebugLevel(kQuuxDebugExample, "example", "this is just an example for a engine specific debug level"); Common::addSpecialDebugLevel(kQuuxDebugExample2, "example2", "also an example"); // Don't forget to register your random source syst->getEventManager()->registerRandomSource(_rnd, "quux"); printf("QuuxEngine::QuuxEngine\n"); } QuuxEngine::~QuuxEngine() { // Dispose your resources here printf("QuuxEngine::~QuuxEngine\n"); // Remove all of our debug levels here Common::clearAllSpecialDebugLevels(); } int QuuxEngine::init() { // Initialize graphics using following template // You have to use transactions _system->beginGFXTransaction(); // This is handled by base Engine class and processes command // line parameters initCommonGFX(true); // Specify dimensions of game graphics window. // In this example: 320x200 _system->initSize(320, 200); _system->endGFXTransaction(); // Create debugger console. It requires GFX to be initialized _console = new Console(this); // Additional setup. printf("QuuxEngine::init\n"); return 0; } int QuuxEngine::go() { // Your main even loop should be (invoked from) here. printf("QuuxEngine::go: Hello, World!\n"); // This test will show up if -d1 and --debugflags=example are specified on the commandline 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 debugC(3, kQuuxDebugExample | kQuuxDebugExample2, "Example debug call two"); return 0; } } // End of namespace Quux
Example: engines/quux/module.mk
MODULE := engines/quux MODULE_OBJS := \ quux.o MODULE_DIRS += \ engines/quux # This module can be built as a plugin ifdef BUILD_PLUGINS PLUGIN := 1 endif # Include common rules include $(srcdir)/common.rules