高级教程1
高级教程1:资源和资源管理器
本文的第一部分将详细介绍资源加载,卸载,加载和销毁的细节过程。在下一部分,我们将创建一个新的资源类型,和对应的管理器。
请使用本论坛主题讨论问题,建议等
目录 1资源的生命周期 1.2从一开始起资源的创造, 1.3卸载和销毁资源 |
OGRE的API文档的说明了资源的基本生命周期,但略有一些概念混淆。 我们希望,会更清晰一点。
以下条款将被用来区分可在不同阶段加载一个资源:
未知(Unknown) :OGRE不能识别资源的存在。 它的文件名存储在一个资源组(ResourceGroup),但OGRE不知道如何处理它。
声明(Declared) :资源被标记已创建,直接或间接的对其他一些行为产生影响。 OGRE知道它的资源的类型,以及如何与它做在时机成熟时创建它。
创建(Created) :OGRE创造了空的资源实例,并添加到相关的资源管理器(ResourceManager)中。
加载 (Loaded):创建的实例已被全部加载,资源的所有数据现在驻留在内存中。 这实际上通常是在访问该资源的文件阶段出现。 你不想在创建阶段访问文件。
资源直到他们被加载才占用空间,,所以不存在增加全部资源造成资源管理器损坏的问题,然后在你的应用程序所需的时候加载/卸载它们。
1. OGRE 自带的资源管理器在根节点处创建。
2. 第一件事需要做的是指定资源的位置。 这是通过调用ResourceGroupManager::addResourceLocation来实现的。这个函数处理以下几件事情:
1. 如果没有这样做了,创建指定的资源组。
2. 创建指定类型新的文档实例
3. 创建一个新资源位置,对它增加文档,然后添加资源位置(ResourceLocation)到资源组(ResourceGroup)。
4. 最后一步是获取所有的文档文件列表,并将其添加在资源组(ResourceGroup)名单。 经过这一步完成后,资源的处于未知(Unknown)阶段。
3. 下一步是手动声明资源。 在这个时候没有资源被声明,虽然当资源管理器(ResourceManager)解析脚本的时候,他们不少人将被宣布启动时的许多资源将会被声明。如果你想手动声明资源,调用Reso urceGroupManager。::declareResource函数。 在这一点上,任何已被手动声明资源处在声明阶段。 其余的都是未知阶段 。
4. 接下来,ResourceGroups初始化。 这是通过ResourceGroupManager::initialiseResourceGroup或ResourceGroupManager::initialiseAllResourceGroups实现的,后者为所有的ResourceGroups调用前一个函数。 它这样子实现
§ 解析资源组(resourceGroup) 中的所用脚本。 脚本的定义是通过从脚本加载器(ScriptLoader)继承的资源管理器实现的。 这可能会导致一些资源需要被声明。
§ 建立所有的资源声明。
§ 有关的资源管理器(ResourceManager)创建一个新的资源实例,并将其添加到自己所在的组。 所有资源都存储在资源管理器(ResourceManagers)中。
§ 资源也是插入到“命令装货清单”中。 这使资源在一个特定的顺序加载,如果你想一次加载整个资源组(ResourceGroup)。 资源的加载顺序由(资源管理器)ResourceManager指定。
§ 在这一点上,任何声明的资源目前正处于创建阶段。
5. 最后,我们完成了初始化。 没有资源被加载,但很好,因为我们没有使用资源。 最后一步-从创建到加载过渡-可以来了解以下方式:
§ 使用资源。例如,需要特定的网格的实体被创建。 显然,一旦资源以这种方式加载的,如果另一个实体需要的时候,它不会再加载了。 如果资源目前正处于未知状态,它将会创建和全部加载。
§ ResourceGroupManager:: loadResourceGroup -被称为任何创建资源被加载。
§ 有关的资源管理器(ResourceManager)的加载方法被调用。 这可以用来装载尚未创建的资源,因为如有必要,它会自动为您创建他们的第一次加载。
§ 通过获取指向它的指针,资源是被直接加载,并调用它的加载方法。当然如果资源是处于创建阶段,你只能这样做,。
§ 在这一点上, 加载资源准备马上使用。
注意 :如果您已经创建任何自定义资源管理器(ResourceManagers),您必须手动声明资源或其中一些可能无法找到的资源在初始化他们之前。
在您的应用程序,这通常归结为以下事件序列:
1. 创建根对象。
2. 反复调用ResourceGroupManager::addResourceLocation,直到添加完所有资源的位置。
3. 创建任何自定义的您所使用的资源管理器(ResourceManager)对象的并使用ResourceGroupManager::_registerResourceManager注册他们。 您还应该通过调用ResourceGroupManager::_registerScriptLoader函数注册所有的脚本加载器(ScriptLoader)对象。
4. 手动声明您的任何资源与ResourceGroupManager::declareResource函数所需要的任何资源。
5. 为资源组调用适当的初始化函数。 要么使用ResourceGroupManager::initialiseResourceGroup去初始化一个组,或调用ResourceGroupManager::initialiseAllResourceGroups去立即初始化所用的东西。
资源管理器(ResourceManager)::从加载到创建资源卸载还原资源。
要完全删除资源,调用的ResourceManager::remove。 这将资源从以前的那个阶段一路回到未知阶段。 你可以通过调用ResourceManager::getByName获取一个指向资源的指针和如果你想的话,卸载或者手动删除它。
当资源管理器(ResourceManager)被销毁的时候任何现有的资源都被被删除。
重载资源是非常有用的编辑功能。 从本质上讲,资源先被卸载,然后加载。 它从加载到创建,然后再回到加载阶段。 资源必须在加载阶段时重新加载。
ResourceManager:: reloadAll 重新加载所有的同一类型的资源。
资源可以单独重新加载通过使用 Resource::reload
现在我们知道如何OGRE的资源系统如何工作,创造新的资源类型,其实非常简单。 您的应用程序几乎肯定会使用额外的资源作为声音文件,XML或只是纯文本。 在这个例子中,我们将创建一个简单的文本文件加载器。 该代码是整齐分割,易于扩展到任何类型的文件-唯一需要改变是Resource::Load方法,以及我们加载的数据去访问文件资源的公共接口。
有两个限制性条件是:脚本资源和手动资源装载器。 这个例子都没有使用两者,但它们将得到解释。
第一个将被创建的是TextFile.h文件。 这将声明我们的资源,文本文件,以及创建这个共享指针的执行情况。下面是它的源代码:
#ifndef __TEXTFILE_H__
#define __TEXTFILE_H__
#include <OgreResourceManager.h>
class TextFile : public Ogre::Resource
{
Ogre::String mString;
protected:
//一定要执行这些来自Ogre::Resource的接口
void loadImpl();
void unloadImpl();
size_t calculateSize() const;
public:
TextFile(Ogre::ResourceManager *creator, const Ogre::String &name,
Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual = false, Ogre::ManualResourceLoader *loader = 0);
virtual ~TextFile();
void setString(const Ogre::String &str);
const Ogre::String &getString() const;
};
class TextFilePtr : public Ogre::SharedPtr<TextFile>
{
public:
TextFilePtr() : Ogre::SharedPtr<TextFile>() {}
explicit TextFilePtr(TextFile *rep) : Ogre::SharedPtr<TextFile>(rep) {}
TextFilePtr(const TextFilePtr &r) : Ogre::SharedPtr<TextFile>(r) {}
TextFilePtr(const Ogre::ResourcePtr &r) : Ogre::SharedPtr<TextFile>()
{
// lock & copy other mutex pointer
OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
pRep = static_cast<TextFile*>(r.getPointer());
pUseCount = r.useCountPointer();
if (pUseCount)
{
++(*pUseCount);
}
}
/// Operator used to convert a ResourcePtr to a TextFilePtr
TextFilePtr& operator=(const Ogre::ResourcePtr& r)
{
if (pRep == static_cast<TextFile*>(r.getPointer()))
return *this;
release();
// lock & copy other mutex pointer
OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
pRep = static_cast<TextFile*>(r.getPointer());
pUseCount = r.useCountPointer();
if (pUseCount)
{
++(*pUseCount);
}
return *this;
}
};
#endif
这里是对应的。cpp文件。 我们正在使用一个简单的字符串来存储的数据,因此,它不需要特殊的初始化。 如果您使用更复杂的对象,必须适当地初始化。
#include "TextFile.h"
#include "TextFileSerializer.h"
TextFile::TextFile(Ogre::ResourceManager* creator, const Ogre::String &name,
Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual,
Ogre::ManualResourceLoader *loader) :
Ogre::Resource(creator, name, handle, group, isManual, loader)
{
/* If you were storing a pointer to an object, then you would set that pointer to NULL here.
*/
/* For consistency with StringInterface, but we don't add any parameters here
That's because the Resource implementation of StringInterface is to
list all the options that need to be set before loading, of which
we have none as such. Full details can be set through scripts.
*/
createParamDictionary("TextFile");
}
TextFile::~TextFile()
{
unload();
}
// farm out to TextFileSerializer
void TextFile::loadImpl()
{
TextFileSerializer serializer;
Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource(mName, mGroup, true, this);
serializer.importTextFile(stream, this);
}
void TextFile::unloadImpl()
{
/* If you were storing a pointer to an object, then you would check the pointer here,
and if it is not NULL, you would destruct the object and set its pointer to NULL again.
*/
mString.clear();
}
size_t TextFile::calculateSize() const
{
return mString.length();
}
void TextFile::setString(const Ogre::String &str)
{
mString = str;
}
const Ogre::String &TextFile::getString() const
{
return mString;
}
你会发现提及的“TextFileSerializer”包括在内。 这是一个事实上未被加载的帮助类。 尤其是对资源这个简单,它并非是必不可少的,但它允许我们使对象连续,而不必在对象周围环绕资源,这是我们应该想做的事情。 该串行器基类包含了许多有用的实用功能。 我们不会使用它们,但无论如何,它的子类将会继承它。
TextFileSerializer.h:
#ifndef __TEXTSERIALIZER_H__
#define __TEXTSERIALIZER_H__
#include <OgreSerializer.h>
class TextFile; // forward declaration
class TextFileSerializer : public Ogre::Serializer
{
public:
TextFileSerializer();
virtual ~TextFileSerializer();
void exportTextFile(const TextFile *pText, const Ogre::String &fileName);
void importTextFile(Ogre::DataStreamPtr &stream, TextFile *pDest);
};
#endif
TextFileSerializer.cpp:
#include "TextFileSerializer.h"
#include "TextFile.h"
TextFileSerializer::TextFileSerializer()
{
}
TextFileSerializer::~TextFileSerializer()
{
}
void TextFileSerializer::exportTextFile(const TextFile *pText, const Ogre::String &fileName)
{
std::ofstream outFile;
outFile.open(fileName.c_str(), std::ios::out);
outFile << pText->getString();
outFile.close();
}
void TextFileSerializer::importTextFile(Ogre::DataStreamPtr &stream, TextFile *pDest)
{
pDest->setString(stream->getAsString());
}
最后一堂课,我们需要编写当然是TextFileManager。
TextFileManager.h:
#ifndef __TEXTFILEMANAGER_H__
#define __TEXTFILEMANAGER_H__
#include <OgreResourceManager.h>
#include "TextFile.h"
class TextFileManager : public Ogre::ResourceManager, public Ogre::Singleton<TextFileManager>
{
protected:
// must implement this from ResourceManager's interface
Ogre::Resource *createImpl(const Ogre::String &name, Ogre::ResourceHandle handle,
const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader,
const Ogre::NameValuePairList *createParams);
public:
TextFileManager();
virtual ~TextFileManager();
virtual TextFilePtr load(const Ogre::String &name, const Ogre::String &group);
static TextFileManager &getSingleton();
static TextFileManager *getSingletonPtr();
};
#endif
最后是TextFileManager.cpp
#include "TextFileManager.h"
template<> TextFileManager *Ogre::Singleton<TextFileManager>::ms_Singleton = 0;
TextFileManager *TextFileManager::getSingletonPtr()
{
return ms_Singleton;
}
TextFileManager &TextFileManager::getSingleton()
{
assert(ms_Singleton);
return(*ms_Singleton);
}
TextFileManager::TextFileManager()
{
mResourceType = "TextFile";
// low, because it will likely reference other resources
mLoadOrder = 30.0f;
// this is how we register the ResourceManager with OGRE
Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this);
}
TextFileManager::~TextFileManager()
{
// and this is how we unregister it
Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType);
}
TextFilePtr TextFileManager::load(const Ogre::String &name, const Ogre::String &group)
{
TextFilePtr textf = getByName(name);
if (textf.isNull())
textf = create(name, group);
textf->load();
return textf;
}
Ogre::Resource *TextFileManager::createImpl(const Ogre::String &name, Ogre::ResourceHandle handle,
const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader,
const Ogre::NameValuePairList *createParams)
{
return new TextFile(this, name, handle, group, isManual, loader);
}
脚本加载器( ScriptLoader )
一些资源,比如说材料是从脚本加载的,在这些情况下,则ResourceManager将来自ScriptLoader。 然后它将设立了'脚本格式' -该文件类型的脚本,它认为(*.material, *.compositor, etc)脚本在构造器中式文件类型。 在这一点它将调用ResourceGroupManager:: _registerScriptLoader注册为加载ResourceManager脚本本身。 后来,当ResourceGroupManager::initialiseResourceGroup被调用时,任何注册的脚本文件将被解析。
如果您想编写一个加载脚本的ResourceManager,您将需要从ScriptLoader写起,执行parseScript方法。ResourceGroupManager::initialiseResourceGroup方法时parseScript将会被调用,这是你应该声明任何您想要创建的资源。
当用ResourceGroupManager::declareResource声明资源的时候,它可能获得一个可选ManualResourceLoader。 ManualResourceLoaders可用于不是从文件中加载资源-他们可能以编程方式创建的资源。 下面是一个简单的例子,使用TextFile:
/ /不要把这部分内容添加到项目里边
class ManualTextFileLoader : public Ogre::ManualResourceLoader
{
public:
ManualTextFileLoader() {}
virtual ~ManualTextFileLoader() {}
void loadResource(Ogre::Resource *resource)
{
TextFile *tf = static_cast<TextFile *>(resource);
tf->setString("manually loaded");
}
};
TextFile文件被声明如下:
Ogre::ResourceGroupManager::getSingleton ().declareResource("hello.txt", "TextFile", "General", mtfl);
用法
若要使用新的资源管理器,在Root::initialise或任何ResourceGroups已初始化之前,我们创建了一个实例。
TextFileManager *tfm = new TextFileManager();
我们撤销它,当我们关闭当然也就是在我们销毁Ogre::Root对象之前。OGRE没有破坏它自身,这是你的职责:
delete Ogre::ResourceGroupManager::getSingleton()._getResourceManager("TextFile");
我们最需要做的是看一个我们已经创建的例子 。新建一个名为“hello.txt”的文件,并将其放置在一个media目录下OGRE可以找到它。 由于确实吗没有存放一个自定义脚本的位置,我建议把该文件放在"media/materials/scripts"目录下。 以下内容添加到该文本文件:
现在创建一个名为main.cpp的文件,并添加下面的代码给它。 一定要通读createScene的功能,我们可以做这样一些:
#include <ExampleApplication.h>
#include "TextFileManager.h"
#include "TextFileSerializer.h"
class TutorialApplication : public ExampleApplication
{
private:
TextFileManager *mTFM;
public:
TutorialApplication()
: mTFM(0)
{
}
~TutorialApplication()
{
delete mTFM;
}
void setupResources()
{
mTFM = new TextFileManager();
// hello.txt will be created when initialiseResourceGroup is called
ResourceGroupManager::getSingleton().declareResource("hello.txt", "TextFile");
ExampleApplication::setupResources();
}
void createScene(void)
{
// Load the file, get the data
TextFilePtr textfile = mTFM->load("hello.txt", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
String str = textfile->getString();
// Reload the file
textfile->reload();
// export the file
TextFileSerializer serializer;
serializer.exportTextFile(static_cast<TextFile *>(textfile.getPointer()), "hello.out.txt");
// unload/remove the file
mTFM->unload("hello.txt");
mTFM->remove("hello.txt");
}
};
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
int main(int argc, char **argv)
#endif
{
// Create application object
TutorialApplication app;
try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occurred: %s/n",
e.getFullDescription().c_str());
#endif
}
return 0;
}
如果您通过此功能与调试步骤,你会看到调用的getString,确实将返回hello.txt的内容。 虽然这未必是在其当前状态最有用的东西,你可以很容易地扩展这个创建您自己的资源和资源装载机。
OGRE---旺
译于2009--11--29晨