通过版本控制,将游戏引擎与类似游戏中的游戏代码分开


15

我有一个完成的游戏,想要在其他版本中拒绝。这些游戏将是类似的游戏,或多或少具有相同的设计,但并非总是如此,基本上情况可能会发生变化,有时会有所变化,有时会有所变化。

我希望将核心代码与游戏分开进行版本控制,以便如果说我修复了游戏A中发现的错误,则该修复将出现在游戏B中。

我正在尝试找到最好的方法来管理它。我最初的想法是这样的:

  • 创建一个engine模块/文件夹/所有内容,其中包含可以概括的所有内容,并且与游戏的其余部分100%独立。这将包括一些代码,但也包括游戏之间共享的通用资产。
  • 将此引擎放在自己的git存储库中,该存储库将作为一个git submodule

我苦苦挣扎的部分是如何管理其余代码。假设您有自己的菜单场景,此代码是特定于游戏的,但大多数代码也往往是通用的,可以在其他游戏中重用。我不能将其放在中engine,但是为每个游戏重新编码都效率很低。

也许使用某种git分支变量可以有效地解决这一问题,但我不认为这是最好的方法。

任何人都有一些想法,经验要分享或与此有关的任何事情?


您的引擎使用哪种语言?一些语言具有专用的软件包管理器,它们可能比使用git子模块更有意义。例如,NodeJS具有npm(可以将Git存储库作为源)。
Dan Pantry 2015年

您是关于如何最好地配置管理通用代码或如何配置管理“半通用”代码或如何设计代码,如何设计代码或什么的问题吗?
Dunk 2015年

在每种编程语言环境中,这可能有所不同,但是,您不仅可以考虑使用控制版本软件,而且还应该了解如何从游戏代码(如程序包,文件夹和API)中拆分游戏引擎,以及以后的内容。 ,应用控件版本。
umlcat

如何在一个分支中拥有一个文件夹的干净历史记录:重构您的引擎,以使单独的(将来的)存储库位于单独的文件夹中,这就是您的最后一次提交。然后创建一个新分支,删除该文件夹之外的所有内容,然后提交。然后转到存储库的第一次提交,并将其与新分支合并。现在,您的分支只有该文件夹:将其拉入其他项目和/或与现有项目合并回去。如果您的代码已经分开,那么这对于在分支中分离引擎很有帮助。我不需要git模块。
Barry Staes

Answers:


13

创建一个引擎模块/文件夹/任何东西,其中包含可以概括的所有内容,并且与游戏的其余部分100%独立。这将包括一些代码,但也包括游戏之间共享的通用资产。

将此引擎放在其自己的git存储库中,它将作为git子模块包含在游戏中

那正是我所做的,并且效果很好。我有一个应用程序框架和一个渲染库,它们每个都被当作我项目的子模块。我发现SourceTree在涉及子模块时很有用,因为它可以很好地管理子模块,并且不会让您忘记任何事情,例如,如果您更新了项目A中的引擎子模块,它将通知您提取项目B中的更改。

有了经验,就会知道引擎中应该包含什么代码,而每个项目中应该包含什么代码。我建议,如果您甚至不确定,请暂时将其保存在每个项目中。随着时间的流逝,您将在各个项目中看到什么保持不变,然后可以逐步将其纳入引擎代码中。换句话说:复制代码,直到接近100%的时间,直到确定每个项目都不会离散地更改代码,然后再进行泛化。

关于源代码管理和二进制文件的说明

请记住,如果您希望二进制资产经常更改,则可能不希望将它们放在基于文本的源代码控制中,例如git。只是说......二进制文件有更好的解决方案。您现在可以执行的最简单的操作来帮助保持“引擎源”存储库的清洁和高性能,即拥有一个单独的“引擎二进制”存储库,该存储库仅包含二进制文件,您也将该二进制文件作为子模块包含在项目中。这样,您就可以减轻对“引擎源”存储库造成的性能损害,该存储库一直在变化,因此需要快速迭代:提交,推送,拉取等。git等源代码管理系统在文本增量上运行,一旦您引入了二进制文件,就从文本角度引入了大量的增量-最终会花费您大量的开发时间。GitLab附件。Google是您的朋友。


它们实际上并不经常更改,但是我对此感兴趣。我对二进制版本控制一无所知。有什么解决方案?
Malharhak 2015年

@Malharhak编辑以回答您的评论。
工程师

@Malharak这是有关此主题的很多信息
工程师

1
+1可以使项目尽可能长时间处于项目中。通用代码赋予更高的复杂性。除非绝对需要,否则应避免使用。
Gusdor

1
@Malharhak不,特别是因为您的目标只是保留“副本”,直到您注意到上述代码是不可变的,并且可以认为是通用代码。古斯多(Gusdor)重申了这一点-提请注意-可以通过过早地将事情分解出来而轻易浪费时间,然后尝试找到使该代码具有足够的通用性以保持通用性,但又具有足够的适应性以适合各种项目的方法...最终大量的参数和开关,变成了丑陋的混乱,但这仍然不是您所需要的,因为最终无论如何您都要为每个新项目进行更改。不要太早淘汰了。有耐心。
工程师

6

在某个时候,引擎必须专门研究游戏知识。我将在这里切线。

在RTS中获取资源。一场比赛可以有CreditsCrystal另一个MetalPotatoes

您应该正确使用OO概念并最大程度地利用。代码重用。显然,Resource这里存在一个概念。

因此,我们认为资源具有以下优势:

  1. 主循环中的一个钩子,用于自己增加/减少
  2. 一种获取当前金额的方法(返回int
  3. 一种任意减法/加法的方法(玩家转移资源,购买...)

注意,这个a的概念Resource可能表示游戏中的击杀或得分!它不是很强大。

现在让我们考虑一个游戏。我们可以通过交易几美分并在输出中添加小数点来获得某种货币。我们不能做的是“瞬时”资源。就像说“电网发电”

假设您InstantResource使用类似的方法添加了一个类。您现在(开始)使用资源污染引擎。


问题

让我们再次以RTS为例。假设玩家向Crystal其他玩家捐赠了一些东西。您想要做类似的事情:

if(transfer.target == engine.getPlayerId()) {
    engine.hud.addIncoming("You got "+transfer.quantity+" of "+
        engine.resourceDictionary.getNameOf(transfer.resourceId)+
        " from "+engine.getPlayer(transfer.source).name);
}
engine.getPlayer(transfer.target).getResourceById(transfer.resourceId).add(transfer.quantity)
engine.getPlayer(transfer.source).getResourceById(transfer.resourceId).add(-transfer.quantity)

但是,这确实很混乱。这是通用的,但是很混乱。尽管已经加上了resourceDictionary,这意味着现在您的资源必须有名称!而且是每位玩家,因此您不再拥有团队资源。

这是“太多”的抽象(我不会承认这是一个出色的例子),相反,您应该达到一个点,即您接受游戏拥有玩家和水晶,然后就可以拥有(例如)

engine.getPlayer(transfer.target).crystal().receiveDonation(transfer)
engine.getPlayer(transfer.source).crystal().sendDonation(transfer)

使用类Player和类CurrentPlayerwhere CurrentPlayercrystal对象时,对象将自动在HUD上显示用于转移/发送捐赠的东西。

这会污染晶体,捐赠晶体,HUD上当前玩家的信息以及所有这些污染引擎。它既更快又更容易读取/写入/维护(这很重要,因为它并不明显更快)。


结束语

资源案例并不出色。希望您仍然明白这一点。如果有什么可以证明“资源不属于引擎”的话,那么具体游戏需要什么以及适用于所有资源概念的东西就完全不同了。您通常会发现3(或4)个“图层”

  1. “核心”-这是引擎的教科书定义,它是带有事件挂钩的场景图,它处理着色器和网络数据包以及播放器的抽象概念
  2. “ GameCore”-这对游戏类型来说是通用的,但并非对所有游戏都通用-例如RTS中的资源或FPS中的弹药。游戏逻辑开始渗入此处。这就是我们较早的资源概念所在的地方。我们添加了对大多数RTS资源有意义的这些内容。
  3. “ GameLogic”非常适合制作的实际游戏。您会发现名称为creatureship或的变量squad。使用继承你会得到跨越所有3层类(例如Crystal Resource GameLoopEventListener说)
  4. “资产”对任何其他游戏都没有用。以半衰期2中的Combine AI脚本为例,它们不会在具有相同引擎的RTS中使用。

用旧引擎制作新游戏

这很常见。阶段1是淘汰第3层和第4层(如果游戏是完全不同的类型,则淘汰第2层)假设我们是从旧的RTS制作RTS。我们仍然有资源,只是没有晶体和东西-因此第2层和第1层的基类仍然有意义,可以丢弃第3层和第4层引用的所有晶体。我们照做。但是,我们可能会对其进行检查以作为我们想要做的参考。


第1层的污染

这可能发生。抽象和性能是敌人。例如,UE4提供了许多优化的组合案例(因此,如果您希望X和Y有人编写了可以非常快地同时执行X和Y的代码-它知道这两者都在做),因此结果确实很大。这还不错,但是很费时间。第1层将决定诸如“如何将数据传递到着色器”以及如何对事物进行动画处理之类的事情。为您的项目做最好的选择永远是件好事。只是尝试为未来做计划,重用代码是您的朋友,继承有意义的代码。


分类层

最后(我保证)不要太怕分层。引擎是固定功能管道的古老术语,引擎在图形上几乎以相同的方式工作(因此有很多共同点),可编程管道将其抛在脑后,因此“第1层”受到污染开发人员想要达到的任何效果。AI是引擎的显着特征(由于无数种方法),现在它是AI和图形。

您的代码不应归档在这些层中。甚至著名的虚幻引擎都有许多不同的版本,每个版本都针对不同的游戏。只有很少的文件(可能不是类似的数据结构)会保持不变。这可以!如果您要制作另一个新游戏,则需要30分钟以上的时间。关键是要计划,知道要复制和粘贴哪些位以及剩下什么。


1

我个人对如何处理混合了通用内容和特定内容的个人建议是使其动态化。我将以您的菜单屏幕为例。如果我误解了您的要求,请告诉我您想知道的内容,我将调整我的答案。

菜单场景中总是(几乎)存在3种东西:背景,游戏徽标和菜单本身。这些东西通常根据游戏而有所不同。为此内容可以做的是在引擎中创建一个MenuScreenGenerator,它带有3个对象参数:BackGround,Logo和Menu。这3个部分的基本结构也是您引擎的一部分,但是您的引擎实际上并没有说出这些部分是如何生成的,只是说您应该给它们提供什么参数。

然后,在实际的游戏代码中,为BackGround,徽标和菜单创建对象,然后将其传递给MenuScreenGenerator。同样,您的游戏本身不处理菜单的生成方式,这是针对引擎的。您的游戏只需告诉引擎它的外观和位置。

从本质上讲,您的引擎应该是游戏可以告诉显示内容的API。如果做得正确,您的引擎应该进行艰苦的工作,您的游戏本身应该只告诉引擎要使用什么资产,要采取什么行动以及情况如何。

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.