Difference between revisions of "HOWTO-Engines"

Jump to navigation Jump to search
8,908 bytes removed ,  13:08, 23 November 2022
Removed references to quux and the quoted example. Automated way is to go
(→‎Manual Steps: mark as obsolete)
(Removed references to quux and the quoted example. Automated way is to go)
Line 30: Line 30:
</syntaxhighlight>
</syntaxhighlight>


== Manual Steps ==
== General Conventions ==
<font color=red>'''OBSOLETE, DO NOT USE'''</font>
 
The remainder of this page contains the original instructions for creating an engine from scratch that predate the create_engine tool. They may still be of some use to read if you want to better understand the individual files that make up a ScummVM engine. For this, it will step you through creating a sample "quux" engine.
 
# Add a new directory <tt>engines/quux/</tt>
# Add <tt>engines/quux/configure.engine</tt> (looking at configure.engine files of existing engines should make it clear what you have to do, Here is more description: [[configure.engine options]] ).
# 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/detection.cpp</tt> and <tt>engines/quux/detection.h</tt>; this will contain your game detection entries (see [[Advanced Detector]]).
# Add <tt>engines/quux/metaengine.cpp</tt> and <tt>engines/quux/metaengine.h</tt>; this 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!
 
Important note: Use a C++ namespace for all your work, e.g. "namespace Quux" in this case.


=== File name conventions ===
=== File name conventions ===
Line 87: Line 73:


=== Subclassing Engine ===
=== Subclassing Engine ===
The example code below gives a simple example of an engine. It contains a few important points
The generated by the create_engine code gives a simple example of an engine. It contains a few important points:
* It initializes the screen at a given resolution
* 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 creates a debugger class and registers it with the engine framework. Currently, it's only a skeleton defined in <engine>.h. For a full game, you'd implement it in its own debugger.cpp and .h files
* It has a simple event loop
* It has a simple event loop
* It also demonstrates how to read and write savegames
* It also demonstrates how to read and write savegames
Line 132: Line 118:
   uint32
   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.
Do not use the following functions:
 
  sprintf -> snprintf
  strcpy -> strncpy or Common::strlcpy
 
Additionally ScummVM offers a 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 as demonstrated in the generated <engine>.cpp file.


=== True RGB color support ===
=== True RGB color support ===
If you need to support more than 256 colors in your game engine, please refer to [[API-Truecolor|the truecolor API reference page]] for specifications and initialization protocol.
If you need to support more than 256 colors in your game engine, please refer to [[API-Truecolor|the truecolor API reference page]] for specifications and initialization protocol.
== Example ==
The copy of this code could be found at https://github.com/scummvm/scummvm/tree/quux/engines/quux
=== Example: engines/quux/quux.h ===
<syntaxhighlight lang="cpp">
#ifndef QUUX_H
#define QUUX_H
#include "common/random.h"
#include "common/serializer.h"
#include "engines/engine.h"
#include "gui/debugger.h"
namespace Quux {
class Console;
// our engine debug channels
enum {
kQuuxDebugExample = 1 << 0,
kQuuxDebugExample2 = 1 << 1
// next new channel must be 1 << 2 (4)
// the current limitation is 32 debug channels (1 << 31 is the last one)
};
class QuuxEngine : public Engine {
private:
// We need random numbers
Common::RandomSource *_rnd;
public:
QuuxEngine(OSystem *syst);
~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;
void syncGameStream(Common::Serializer &s);
};
// Example console class
class Console : public GUI::Debugger {
public:
Console(QuuxEngine *vm) {
}
virtual ~Console(void) {
}
};
} // End of namespace Quux
#endif
</syntaxhighlight>
=== Example: engines/quux/quux.cpp ===
<syntaxhighlight lang="cpp">
#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/debug-channels.h"
#include "common/error.h"
#include "common/events.h"
#include "common/file.h"
#include "common/fs.h"
#include "common/system.h"
#include "engines/util.h"
#include "quux/quux.h"
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 run().
// Do not initialize graphics here
// Do not initialize audio devices here
// However this is the place to specify all default directories
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "sound");
// Don't forget to register your random source
_rnd = new Common::RandomSource("quux");
debug("QuuxEngine::QuuxEngine");
}
QuuxEngine::~QuuxEngine() {
debug("QuuxEngine::~QuuxEngine");
// Dispose your resources here
delete _rnd;
}
Common::Error QuuxEngine::run() {
// Initialize graphics using following:
initGraphics(320, 200);
// You could use backend transactions directly as an alternative,
// but it isn't recommended, until you want to handle the error values
// from OSystem::endGFXTransaction yourself.
// This is just an example template:
//_system->beginGFXTransaction();
// // This setup the graphics mode according to users seetings
// initCommonGFX(false);
//
// // Specify dimensions of game graphics window.
// // In this example: 320x200
// _system->initSize(320, 200);
//FIXME: You really want to handle
//OSystem::kTransactionSizeChangeFailed here
//_system->endGFXTransaction();
// Create debugger console. It requires GFX to be initialized
Console *console = new Console(this);
setDebugger(console);
// Additional setup.
debug("QuuxEngine::init");
// Your main even loop should be (invoked from) here.
debug("QuuxEngine::go: Hello, World!");
// 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");
// 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;
}
void QuuxEngine::syncGameStream(Common::Serializer &s) {
// Use methods of Serializer to save/load fields
int dummy = 0;
s.syncAsUint16LE(dummy);
}
} // End of namespace Quux
</syntaxhighlight>
=== Example: engines/quux/detection.cpp ===
<syntaxhighlight lang="cpp">
#include "base/plugins.h"
#include "engines/advancedDetector.h"
#include "quux/quux.h"
// Here is the right place to set up the engine specific debug channels.
// The list must be terminated by the DEBUG_CHANNEL_END macro
static const DebugChannelDef debugFlagList[] = {
{ Quux::kQuuxDebugExample, "example", "this is just an example for a engine specific debug channel" },
{ Quux::kQuuxDebugExample2, "example2", "also an example" },
DEBUG_CHANNEL_END
};
namespace Quux {
static const PlainGameDescriptor quuxGames[] = {
{ "quux", "Quux the Example Module" },
{ "quuxcd", "Quux the Example Module (CD version)" },
{ 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
};
} // End of namespace Quux
class QuuxMetaEngineDetection : public AdvancedMetaEngineDetection {
public:
QuuxMetaEngineDetection() : AdvancedMetaEngineDetection(Quux::gameDescriptions, sizeof(ADGameDescription), Quux::quuxGames) {
}
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.";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
REGISTER_PLUGIN_STATIC(QUUX_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, QuuxMetaEngineDetection);
</syntaxhighlight>
=== Example: engines/quux/metaengine.cpp ===
<syntaxhighlight lang="cpp">
#include "quux/quux.h"
#include "engines/advancedDetector.h"
class QuuxMetaEngine : public AdvancedMetaEngine {
public:
const char *getName() const override {
return "quux";
}
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
};
Common::Error QuuxMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
*engine = new Quux::QuuxEngine(syst);
return Common::kNoError;
}
#if PLUGIN_ENABLED_DYNAMIC(QUUX)
REGISTER_PLUGIN_DYNAMIC(QUUX, PLUGIN_TYPE_ENGINE, QuuxMetaEngine);
#else
REGISTER_PLUGIN_STATIC(QUUX, PLUGIN_TYPE_ENGINE, QuuxMetaEngine);
#endif
</syntaxhighlight>
=== Example: engines/quux/module.mk ===
<syntaxhighlight lang="make">
MODULE := engines/quux
MODULE_OBJS := \
metaengine.o \
quux.o
MODULE_DIRS += \
engines/quux
# This module can be built as a plugin
ifeq ($(ENABLE_QUUX), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o
</syntaxhighlight>
=== Example: engines/quux/configure.engine ===
<syntaxhighlight lang="bash">
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
add_engine quux "Quux" no
</syntaxhighlight>

Navigation menu