在某些情况下,全局变量/子集在游戏开发中有用吗?[关闭]


11

我知道拥有全局变量或单例类会创建很难测试/管理的案例,而我在代码中使用这些模式的工作已陷入困境,但常常需要交付。

那么在游戏开发中是否存在全局变量或单例实际上有用的情况?

Answers:


30

这些东西总是有用的。到底是最漂亮还是最安全的解决方案是另一回事,但我认为游戏开发涉及一定程度的实用主义。


非常接近我的口头禅。
奥拉维尔Waage

15

绝对不是穷举列表,但这里有:

缺点

  • 终身管理。Singleton和Globals可能希望在初始化关键系统(例如堆)之前启动,具体取决于您如何设置它们。如果您想拆除它们(例如,在进行泄漏跟踪时很有用),则必须注意拆除顺序,或者开始接触诸如phoenix singletons之类的东西。(请参阅静态初始化顺序失败。)
  • 访问控制。渲染应该只具有对游戏数据的const访问,而更新却不是const吗?使用单例和全局变量可能更难执行。
  • 状态。正如Kaj所指出的那样,在非共享内存体系结构中,很难对功能进行分解和转换的单例完全起作用。对于其他类型的NUMA系统(访问非本地内存),它也可能会影响性能。单例通常表示集中状态,因此是通过纯度使转换变得容易的对立面。

赞成或反对

  • 并发性。在并发环境中,单例可能会很麻烦(您必须考虑数据争用/可重入问题)或很幸运(更容易集中化并进行锁定和资源管理的推理)。诸如线程本地存储之类的东西的巧妙使用可以在某种程度上缓解潜在的问题,但这通常不是一个容易的问题。
  • Codegen(取决于编译器,体系结构等):对于朴素的首次使用时创建单例实现,您可能正在为每次访问评估一个额外的条件分支。(这可能加起来。)函数中使用的多个全局变量可能会使文字池膨胀。一种“所有全局结构”的方法可以节省文字池中的空间:结构的基址只有一个入口,然后在装入指令中编码偏移量。最后,如果不惜一切代价避免使用全局变量和单例,通常需要至少使用一些额外的内存(寄存器,堆栈或堆)来传递指针,引用甚至副本。

优点

  • 简单性。如果您知道上面列出的缺点对您的影响不大(例如,您正在为单个CPU手持式平台在单线程环境中工作),则可以避免使用某些体系结构(例如前面提到的参数传递)。对于经验不足的编码人员,单例和全局变量可能更易于理解(尽管错误使用它们可能更容易)。
  • 终身管理(再次)。如果您使用“全局结构”或除“首次请求创建”以外的某种机制,则可以轻松地读取并且可以对初始化和销毁​​顺序进行细粒度的控制。您可以在某种程度上将其自动化或手动进行管理(根据全局变量/单例的数量及其相互依赖关系来进行管理可能是有利的也可能是不利的)。

我们在掌上电脑游戏中经常使用“全局单例结构”。PC和游戏机名称往往较少依赖它们。我们将更多地转向事件驱动/消息架构。话虽这么说,PC /控制台标题仍然经常使用中央TextureManager。由于它通常包装单个共享资源(纹理内存),因此对我们来说很有意义。

如果您保持API相对整洁,则在需要时将其重构为单例模式可能不是很困难。


很棒的清单。就个人而言,由于共享(可能易变)状态带来的问题,我在单例和全局变量中仅发现负值。我认为“ codegen”问题不是问题;您要么需要将指针传递通过寄存器,要么需要执行一些操作序列来获取它,我认为它会显着改变代码与数据大小,但总和将大致相同。
dash-tom-bang 2010年

是的,关于代码生成,我们真正看到的唯一显着变化是从单个全局变量到全局表:它将文字池减少了,因此.text节的大小缩小了百分之几。在小型系统上,这是一件非常好的事情。=)对于字面量,不必过多地访问dcache的性能好处只是一个不错的好处(尽管在大多数情况下很小)。移动到全局表的另一个优点是能够真正轻松地将其移动到驻留在更快内存中的部分的能力……
leander 2010年

4

它们可能非常有用,尤其是在原型设计或实验实现期间,但是总的来说,我们更喜欢为管理器之类的结构传递引用,这样您至少可以对从何处访问它们进行控制。全局变量和单例变量的最大问题(我认为)是它们对多线程的不友好,并且使用它们的代码更难移植到非共享内存(例如SPU)。


2

我会说单例设计本身根本没有用。全局变量肯定是有用的,但是我宁愿看到它们隐藏在编写良好的界面后面,以便您不知道它们的存在。使用单例,您可以清楚地知道它们的存在。

我经常将全局变量用于需要整个引擎访问的事物。我的性能工具是一个很好的例子,我在整个引擎上都使用它来进行调用。调用很简单;ProbeRegister(),ProbeHit()和ProbeScoped()。他们的实际访问更加棘手,并且在某些情况下使用全局变量。


2

全局变量和单例实现不佳的主要问题是模糊的构造和解构错误。

因此,如果您使用没有这些问题或非常了解指针问题的原语。这样就可以安全地使用它们。

全局变量具有与goto相同的位置,因此不应一概而论,而应谨慎使用。

Google C ++样式指南》对此有很好的解释。


我不同意那是主要的问题。“不使用全局变量”比存在构造函数要追溯到更远。我想说他们有两个主要问题。首先,它们使有关程序的推理复杂化。如果我有一个访问全局函数,则该函数的行为不仅取决于其自身的参数,还取决于访问该全局函数的所有其他函数。还有很多要考虑的。其次,即使您可以全力以赴(您不能),它们仍然会导致更复杂的并发问题。

@Joe的关键词是“依赖关系”。访问全局变量(或单例或任何其他共享状态)的函数对所有这些事物都具有隐式依赖性。如果所有代码的依赖项都是显式的,则对代码进行推理就容易得多,这是在参数列表中记录了完整的依赖项列表时所得到的。
dash-tom-bang 2010年

1

全局变量在快速原型化在函数调用之间需要某种状态的系统时非常有用。一旦确定系统可以正常工作,就将状态移到一个类中,然后将功能放入该类的方法中。

单身人士对于给自己和他人造成问题很有用。引入的全局状态越多,代码正确性,维护性,可扩展性,并发性等问题就会越多。请不要这样做。


1

我倾向于建议使用带有自定义生存期管理的某种DI / IoC容器,而不要使用单例(即使您使用“单实例”生存期管理器)。至少那么很容易换出实现以方便测试。


对于不认识的人,DI是“依赖注入”,而IoC是“控制反转”。链接:martinfowler.com/articles/injection.html (我以前读过这些书,但从未见过首字母缩写,所以花了我一些时间。)但是+1却提到了这种出色的转换。
leander 2010年


0

单例是在早期原型中存储共享状态的好方法。

它们不是灵丹妙药,并且存在一些问题,但是对于某些UI /逻辑状态,它们是非常有用的模式。

例如,在iOS中,您使用单例来获取[UIApplication sharedApplication],在cocos2d中,您可以使用它来获取对某些对象的引用,例如[CCNotifications sharedManager],就我个人而言,我通常从[Game sharedGame]单例开始存储许多不同组件之间共享的状态。


0

哇,这对我来说很有趣,因为我个人从未遇到过单例模式的问题。我当前的项目是Nintendo DS的C ++游戏引擎,并且我将许多硬件访问实用程序(即VRAM Bank,Wifi和两个图形引擎)实现为单例实例,因为它们旨在包装全局C基础库中的函数。


那么我的问题是,为什么要完全使用单例呢?我看到人们喜欢用仅包含静态函数的单例或类来包装API,但是为什么还要烦恼该类呢?对我来说,仅具有函数调用(按需要包装)似乎更容易,但是在它们内部访问“全局”状态。例如Log :: GetInstance()-> LogError(...)就像LogError(...)一样,它在内部访问任何全局状态而无需客户端代码知道它。
dash-tom-bang 2010年

0

仅当您拥有一个只有一个控制器但由多个模块处理的项目时。

例如鼠标界面。或操纵杆界面。或音乐播放器。或声音播放器。或屏幕。或保存文件管理器。


-1

全球人要快得多!因此,它非常适合性能密集型应用程序,例如游戏。

单例是更好的全局,IMO,因此是正确的工具。

谨慎使用!


什么快得多?如果全局变量是指针,它们将位于某个随机存储器页面中;如果它们是一个值,则它们将位于一些固定但可能较远的存储器页面中。编译器不能对它们使用任何有用的窥孔优化。从我能想到的任何角度看,全局变量都很

比getter和setter和传递引用更快。但不是很多。它确实有助于减小尺寸,因此在某些系统中会有所帮助。当我说“太多”时,我可能会夸大其词,但是我非常怀疑有人说您不应该使用某些东西。您只需要使用常识即可。毕竟,单例仅是静态类成员,而它们是全局变量。
jacmoe 2010年

但是要解决与全局变量的任何同步问题,您都需要为它们设置getter / setter,因此这并不重要。全局状态的问题(以及它的罕见好处)是它是全局状态,而不是接口速度或(有效)语法方面的任何问题。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.