我是一个业余爱好者C++
和DirectX
程序员,因此我所拥有的大部分知识来自旧的游戏开发书籍,其中代码设计只是为了获得一些东西并作为演示运行即使是最简单的程序,也让我有很多设计考虑因素。在开发这样的程序的过程中,我最近学到了RAII
,所以我决定给这个设计模式一个镜头,因为根据我的理解,一个对象在构造时应该是可用的和有效的,这大大简化了对象的方式。被程序使用。以前,我一直在使用create()
&我的一些对象中的destroy()
模式导致大多数成员函数中的大量验证检查。
在程序的架构中,我只有很少的图形对象是DirectX
资源的包装器,其中一个是Texture
对象。例如,如果我想渲染tile map,我可以拥有Tile
个对象,这些对象是用指向AnimatedImage
对象的指针构造的,这些对象是用Texture
个对象的指针构造的。
使用DirectX
的问题在于图形设备有时会丢失,例如程序执行期间视频卡的驱动程序更新。发生这些事件时,必须释放现有图形资源并重新获取以继续正常渲染,包括Texture
对象的销毁和重建。这使得使用RAII
设计模式看起来可能不是最佳选择。我需要重新创建Texture
个对象,重新创建AnimatedImage
个对象,然后重新创建Tile
个对象。这似乎是一个极端麻烦,因为重新创建的一些对象将包含的不仅仅是图像数据。
因此,如果我们从一些示例代码开始(不完全,但它有用):
// Construct graphics objects into some structure we will pass around.
graphics.pDevice = new GraphicsDevice(windowHandle, screenWitdth, screenHeight);
graphics.pTexture1 = new Texture(graphics.pDevice, width1, height1, pPixelData1);
graphics.pTexture2 = new Texture(graphics.pDevice, width2, height2, pPixelData2);
graphics.pSpriteBuffer = new SpriteBuffer(graphics.pDevice, maxSprites);
在为瓷砖地图构建对象的程序中的其他地方:
// Construct some in-game animations.
images.pGrass = new AnimatedImage(graphics.pTexture1, coordinates1[4], duration1);
images.pWater = new AnimatedImage(graphics.pTexture2, coordinates2[4], duration2);
// Construct objects to display the animation and contain physical attributes.
thisMap.pMeadowTile = new Tile(images.pGrass, TILE_ATTRIBUTE_SOFT);
thisMap.pPondTile = new Tile(images.pWater, TILE_ATTRIBUTE_SWIMMABLE);
然后在渲染例程中:
while (gameState.isRunning())
{
graphics.pSpriteBuffer->clear();
thisMap.bufferSprites(graphics.pSpriteBuffer);
graphics.pSpriteBuffer->draw();
if (graphics.pDevice->present() == RESULT_COULD_NOT_COMPLETE)
{
// Uh oh! The device has been lost!
// We need to release and recreate all graphics objects or we cannot render.
// Let's destruct the sprite buffer, textures, and graphics device.
// But wait, our animations were constructed with textures, the pointers are
// no longer valid and must be destructed.
// Come to think of it, the tiles were created with animations, so they too
// must be destructed, which is a hassle since their physical attributes
// really are unrelated to the graphics.
// Oh no, what other objects have graphical dependencies must we consider?
}
}
我在这里缺少一些设计概念,或者这是RAII
工作的那种情况之一,但是如果存在大量对象依赖对象,那么成本会不必要地大?是否有任何已知的设计模式特别适合这种情况?
以下是我想到的一些方法:
使用recreate()
方法装备图形对象。优点是任何指向纹理的对象都可以保留该指针而不会被破坏。缺点是如果重新获取失败,我将留下一个僵尸对象,它不比create()
& destroy()
模式。
使用所有图形对象的注册表添加一个间接级别,该注册表将返回索引到Texture
指针或指向Texture
指针的指针,以便依赖于图形的现有对象不需要被摧毁。优点和缺点与上述相同,但间接引起额外开销的附加缺点。
存储程序的当前状态并向后展开,直到重新获取图形对象,然后以它所处的状态重建程序。没有我能想到的真正优势,但似乎最多{{1适当的。缺点是在不太常见的场景中实现这一点的复杂性。
完全隔离对象的所有可视表示与其物理表示。优点是实际上只需要重新创建必要的对象,这可以使程序的其余部分保持有效状态。缺点是物理和视觉对象仍然需要以某种方式相互了解,这可能会导致一些膨胀的对象管理代码。
中止程序执行。优点是,这很简单,并且很少花费在经常发生的事情上。缺点是使用该程序的任何人都会感到沮丧。
答案 0 :(得分:1)
对于2000年代末期来说这是一个很好的问题(至少对于桌面图形而言)。在2015年,你最好忘记DirectX 9,它的设备丢失,做DirectX 11(甚至是即将推出的DirectX 12)。
如果您仍然希望坚持使用已弃用的API(或者如果您在移动设备上同时使用类似OpenGL ES的东西,其中上下文丢失是常见事件),那么有一种方法可以很好地工作(等等)。基本上它是你的混合
为图形对象配备recreate()方法
和
使用所有图形对象的注册表添加间接级别
这是:
使用Factory pattern强制用户重构您的代码:使用函数分配新资源(包裹new
,std::make_shared
或其中使用的任何内容)
auto resource = device->createResource(param0, param1);
让工厂以某种方式记住资源
std::vector<IResourcePtr> resources;
ResourcePtr Device::createResource(T param0, U param1)
{
auto resource = std::make_shared<Resource>(this, param0, param1);
resources.push_back(resource);
return resource;
}
让资源记住它的参数(如果需要,可以在运行时更改它们,但也应该保存。对于大型或昂贵的参数对象,使用Proxy pattern)
Resource::Resource(IDevice* device, T param0, U param1)
: m_device(device)
, m_param0(param0)
, m_param1(param1)
{
create(); // private
}
关于设备丢失事件,释放所有对象,而不是重新创建它们
while (rendering)
{
device->fixIfLost();
...
}
void Device::fixIfLost()
{
if(isLost())
{
for(auto&& resource: resources)
resource->reload();
}
}
void Resource::reload()
{
release(); // private
create(); // private
}
您可以在此基础上构建更复杂,更智能的系统。
缺点是如果重新获取失败,我会离开 与僵尸对象
并非特定于设备丢失事件。在放弃对用户的控制之前,处理资源会立即失败,这与您在第一次创建资源时失败(通过抛出异常(因此用户可以处理它)或使用占位符资源或关闭申请,或其他任何事情 - 你要决定)
完全隔离对象的所有可视表示 物理表征。
必须有。除非你正在建立一个俄罗斯方块,否则甚至不是要讨论的问题。使用MVC或现代资料,例如ECS。切勿将Mesh
保存在Player
内,将ParticleEmitter
保存在Fireball
内。从来没有让他们彼此了解。
存储程序的当前状态并展开,直到 已重新获取图形对象,然后在中重建程序 说它在
这非常有用。你所描述的是“保存游戏”/“加载游戏”机制。它还可以用于实现“重放”功能和游戏引擎电影。注意(至于添加到第2点),您的保存数据将永远不会包含可视化表示(除非您需要多千兆字节的保存文件)。
不要overengineer。大多数游戏都没有打扰。他们以与用户“保存游戏”相同的方式处理设备丢失 - &gt; “退出” - &gt; “加载游戏”,适当地设计启动设施。
另一种单独使用或与工厂结合使用的方法是Lazy initialization:让您的资源验证它本身是否有效以及设备丢失。
void Resource::apply()
{
if((!isValid()) || (!device->isValid()))
{
}
// apply resource here
}
每次访问资源时都会增加一些开销,但实现这种方法非常简单,以确保资源在需要时启动