Difference between revisions of "HOWTO-Engines"

Jump to navigation Jump to search
6,490 bytes removed ,  03:24, 9 June 2023
m
Mention new optional engine skeleton style
(→‎Make-based Systems: fixed command line)
m (Mention new optional engine skeleton style)
 
(10 intermediate revisions by 3 users not shown)
Line 15: Line 15:


=== Make-based Systems ===
=== Make-based Systems ===
Run `make devtools/create_engine`, then run the `devtools/create_engine/create_engine <engine name>`, where `engine name` is the desired engine name.
Run <tt>make devtools/create_engine</tt>, then run the <tt>devtools/create_engine/create_engine <engine name></tt>, where <tt>engine name</tt> is the desired engine name.


=== Visual Studio ===
=== Visual Studio ===
Line 22: Line 22:


=== Common Instructions ===
=== Common Instructions ===
There are two styles of engine skeletons supported. The first, if you just specify an engine name, creates a bare bones engine with a sample event loop. The second kind is selected if you also specify -events as an extra parameter after the engine name. In this case, it creates a more sophisticated engine skeleton that has a single centralized event loop, and dispatches events to views, which can be easily switched between. This has benefits of avoiding duplicating event loops in multiple different places, each handling event processing and quit checks. Each view simply has to implement a drawing method and override different event handler methods to handle whichever events it needs.


Once you've got the skeleton engine compiling, your next step should be to update the placeholder detection entry in detection_tables.h. The easiest way is to choose a file in your game's folder that you think is unique, and change the filename in the detection entry to it. If you then run ScummVM and try to add the game, it should prompt you that an unrecognised md5 for the game has been found, and give you the option to copy it to clipboard. You can do so, and then paste it to extract the md5 and filesize for the file, which can be used to update the detection entry. With this done you should be able to add your game, and run the skeleton engine.
In either case, once you've got the skeleton engine compiling, your next step should be to update the placeholder detection entry in detection_tables.h. The easiest way is to choose a file in your game's folder that you think is unique, and change the filename in the detection entry to it. If you then run ScummVM and try to add the game, it should prompt you that an unrecognised md5 for the game has been found, and give you the option to copy it to clipboard. You can do so, and then paste it to extract the md5 and filesize for the file, which can be used to update the detection entry. With this done you should be able to add your game, and run the skeleton engine.


== Manual Steps ==
As a second optional step, by default everytime your game starts, you'll get an unsupported game warning dialog appear. If you want to suppress it, locate and open your scummvm.ini file, and find the section added for your game. Then add the following line to it:
The remainder of this page primarily focuses on if you're trying to create an engine manually from scratch, such as if you don't have access to Visual Studio. For this, we'll assume your engine is called "quux".
<syntaxhighlight lang="bash">
 
enable_unsupported_game_warning=false
# Add a new directory <tt>engines/quux/</tt>
</syntaxhighlight>
# 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.
== General Conventions ==


=== File name conventions ===
=== 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 the 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:
Over 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 the backend code, or to simply add new functionality. It is therefore expected 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:


{| border="1" cellpadding="2" width=100%
{| border="1" cellpadding="2" width=100%
Line 54: Line 48:
|saveload.cpp || Code related to savegames
|saveload.cpp || Code related to savegames
|-
|-
|debug.cpp, debugger.cpp || (console) debugger
|console.cpp || (console) debugger
|-
|-
|gfx.cpp (alt: graphics.cpp) || Graphics code
|gfx.cpp (alt: graphics.cpp) || Graphics code
Line 80: Line 74:


=== 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 125: Line 119:
   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)
== Extended Saves ==
: 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
To easily support ScummVM-specific metadata in the saves, we implemented the ExtendedSaves interface.
// Do not initialize audio devices here


// However this is the place to specify all default directories
This basically makes your saves look like this:
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "sound");


// Don't forget to register your random source
  &lt;DATA>    Engine-specific save data
_rnd = new Common::RandomSource("quux");
  <METAINFO> ScummVM metainfo, including play time, screenshot, save name etc
  <OFFSET>  4-bytes offset to the the ScummVM metainfo from the end of the save


debug("QuuxEngine::QuuxEngine");
Thus, all generic methods like listSaves() are retrieving this information.
}


QuuxEngine::~QuuxEngine() {
In order to benefit from it, do the following:
debug("QuuxEngine::~QuuxEngine");


// Dispose your resources here
# In MetaEngine::hasFeature(), add kSavesUseExtendedFormat, kSimpleSaveNames, kSupportsListSaves, kSupportsDeleteSave, kSavesSupportMetaInfo, kSavesSupportThumbnail, kSavesSupportCreationDate, and kSavesSupportPlayTime. Also kSupportsLoadingDuringStartup if you intend to support loading savegames directly from the launcher.
delete _rnd;
# Overload loadGameStream() and saveGameStream() where you parse/save only your engine-specific data
}
# ...
# PROFIT!


Common::Error QuuxEngine::run() {
== General Tips ==
// Initialize graphics using following:
initGraphics(320, 200);


// You could use backend transactions directly as an alternative,
* During the initial porting process, it might be preferable to link to C++ STL. In such cases, the `FORBIDDEN_SYMBOL_ALLOW_ALL` define can be used. However, you'll eventually need to get rid of this define.
// 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>
265

edits

Navigation menu