繁体   English   中英

设计用于存储多人游戏的各种要求和统计数据的表格

[英]Designing tables for storing various requirements and stats for multiplayer game

原始问题:

你好,

我正在创建一个非常简单的爱好项目 - 基于浏览器的多人游戏。 我坚持设计用于存储任务/技能要求信息的表格。

现在,我按照以下方式设计了表格:

  • 用户 (有关用户的基本信息)
  • 统计 (各种统计)
  • table user_stats (用stats连接每个用户)

另一个例子:

  • 怪物 (关于npc敌人的基本信息)
  • table monster_stats (用统计数据连接怪物,使用上面相同的统计表)

那些是简单的案例。 我必须承认,我在设计不同事物的要求时遇到困难,例如任务。 示例任务A可能只有最低字符等级要求(并且易于实现) - 但另一个,任务B有许多其他要求(完成任务,获得技能,拥有特定项目等) - 什么是好方法设计用于存储此类信息的表格?

以类似的方式 - 什么是存储技能要求信息的有效方式? (特定角色等级,最低等级)。

我将非常感谢有关创建数据库驱动游戏的任何帮助或信息。

编辑:

谢谢你的答案,但我想收到更多。 由于我在设计一个相当复杂的数据库布局时遇到了一些问题,我正在为这个问题开始最大的赏金。

我希望收到文章/代码片段的链接/与设计用于存储游戏数据的数据库的最佳实践相关的任何内容(这种信息的一个很好的例子可以在buildingbrowsergames.com上获得 )。

我将不胜感激任何帮助。

我会编辑这个以尽可能多地添加其他相关问题,尽管我希望OP能够解决我上面的评论。 我作为一个专业的在线游戏开发者,多年来作为一个业余爱好者在线游戏开发者,多年来一直在说它的价值。

在线游戏意味着某种持久性,这意味着您有两种类型的数据 - 一种是由您设计的,另一种是由玩家在游戏过程中创建的。 您很可能将两者都存储在数据库中。 确保您有不同的表格,并通过通常的数据库规范化规则正确地交叉引用它们。 (例如,如果你的玩家制作了一把大刀,你就不会创建一个包含剑的所有属性的整个新行。你可以在player_items表中创建一个带有每个实例属性的新行,并参考中的大刀行包含每个itemtype属性的item_types表。)如果您发现一行数据中包含您设计的某些内容以及播放期间播放器正在更改的某些内容,则需要将其标准化为两个表。

这实际上是典型的类/实例分离问题,并且适用于此类游戏中的许多事情:地精实例不需要存储它对于妖精(例如绿皮肤)的所有细节,只有相关的事情到那个实例(例如,位置,当前的健康状况)。 有时,构造行为有一个微妙之处,在这种情况下,需要根据类数据创建数据。 (例如,根据地精类型的最大生命值设置地精实例的起始生命值。)我的建议是将这些硬编码到代码中,以创建实例并为其插入行。 这些信息很少变化,因为在实践中很少有这样的值。 (耗尽资源的初始分数,如健康,耐力,法力......这就是它。)

尝试找到一致的术语,将实例数据与类型数据分开 - 这将使您以后修补实时游戏并尽量不通过编辑错误的表来破坏玩家的辛苦工作时的生活更轻松。 这也使得缓存变得更加容易 - 您通常可以缓存您的类/类型数据而不受惩罚,因为只有当您(设计人员)将新数据推送到那里时它才会发生变化。 您可以通过memcached运行它,或者如果您的游戏有一个连续的过程(即不是PHP / ASP / CGI /等),可以考虑在启动时加载它们。

请记住,一旦您上线,从设计端数据中删除任何内容都会有风险,因为播放器生成的数据可能会引用它。 在部署到实时服务器之前,在本地彻底测试所有内容,因为一旦它在那里,就很难将其删除。 考虑如何能够以安全的方式将这些数据行标记为已删除 - 可能是布尔“实时”列,如果设置为false,则表示它不会显示在典型查询中。 想想如果你禁用他们所获得的物品对玩家的影响(如果这些是他们付出的物品,那就加倍了)。

如果不知道你想如何设计你的游戏,真正的制作方面就无法真正回答。 数据库设计必须遵循游戏设计。 但我会经历一个微不足道的想法。 也许你会希望能够创建一个基本对象,然后用符文或水晶或其他东西来增强它。 为此,您只需要项目实例和扩充实例之间的一对多关系。 (请记住,您也可能有项目类型和扩充类型表。)每次扩充都可以指定项目的属性(例如,持久性,在战斗中完成的最大伤害,重量)和修饰符(通常作为乘数,例如1.1到增加10%的奖金)。 你可以在这里这里看到我对如何实现这些修改效果的解释 - 同样的原则适用于临时技能和法术效果,适用于永久物品修改。

对于数据库驱动游戏中的角色统计,我通常建议坚持每个统计数据的一个列(整数或浮点数)的天真方法。 稍后添加列并不是一项困难的操作,因为您将要经常阅读这些值,您可能不希望一直在对它们执行连接。 但是,如果你确实需要灵活性,那么你的方法很好。 这非常类似于我在下面建议的技能水平表:许多游戏数据可以用这种方式建模 - 将一个事物的类或实例映射到其他事物的类或实例,通常用一些额外的数据来描述映射(在这种情况下,统计的价值)。

一旦你设置了这些基本连接 - 实际上是以一种可能不方便你的代码的方式分离类/实例数据而导致的任何其他复杂查询 - 考虑创建一个视图或存储过程来执行它们场景,以便您的应用程序代码不再需要担心它。

当然,其他良好的数据库实践适用 - 当您需要确保多个动作以原子方式发生时使用事务(例如交易),将索引放在您最常搜索的字段上,在静默期间使用VACUUM / OPTIMIZE TABLE /以保持性能提升等

(这一点下面的原始答案。)

说实话,我不会将任务需求信息存储在关系数据库中,而是存储在某种脚本中。 最终,你对“需求”的想法采取了几种不同的形式,可以利用不同种类的数据(例如,等级,等级,完成的先前任务,物品拥有)和操作员(等级可能是最小值或最大值,某些任务可能需要一个项目,而其他人可能需要一个项目,等等)更不用说连词和分离的组合(一些任务要求满足所有要求,而其他任务可能只需要满足几个中的一个)。 这种事情在命令式语言中更容易指定。 这并不是说你没有DB中的任务表,只是你没有尝试将有时任意的需求编码到模式中。 我有一个requirements_script_id列来引用外部脚本。 我想你可以将实际的脚本作为文本字段放入DB中,如果它也适合的话。

虽然技能要求适合于数据库,但考虑到学习技能的典型游戏系统,当您在某个级别中升级时,技能要求非常微不足道:

table skill_levels
{
    int skill_id FOREIGN KEY;
    int class_id FOREIGN KEY;
    int min_level;
}

myPotentialSkillList = SELECT * FROM skill_levels INNER JOIN
    skill ON skill_levels.skill_id = skill.id
    WHERE class_id = my_skill
    ORDER BY skill_levels.min_level ASC;

需要技能树吗? 添加列prerequisite_skill_id 等等。

更新:

从评论来看,看起来很多人都有XML问题。 我知道现在抨击它很酷,它确实有问题,但在这种情况下我认为它有效。 我选择它的另一个原因是有大量的库可以解析它,这样可以让生活更轻松。

另一个关键概念是信息真的是非关系型的 所以是的,您可以将数据存储在一堆具有大量连接的不同表中的任何特定示例中,但这很痛苦。 但如果我不断给你一个不同的例子,我打赌你必须无限修改你的设计广告。 我不认为添加表和修改复杂的SQL语句非常有趣。 因此,@ scheibk的评论已被投票,这有点令人沮丧。

原帖:

我认为在数据库中存储任务信息时可能遇到的问题是它不是真正的关系(也就是说,它并不真正适合表格)。 这可能就是您在设计数据表时遇到问题的原因。

另一方面,如果您将任务信息直接放入代码中,这意味着您必须编辑代码并在每次添加任务时重新编译。 瘸。

所以,如果我是你,我可能会考虑将我的任务信息存储在XML文件或类似的东西中。 我知道这是几乎任何事情的通用解决方案,但在这种情况下,这对我来说是正确的。 XML实际上用于存储非关系和/或分层数据,就像您需要为您的任务存储的东西一样。

简介:您可以提出自己的架构,创建XML文件,然后以某种方式在运行时加载它(甚至将XML存储在数据库中)。

示例XML:

<quests>
    <quest name="Return Ring to Mordor">
        <characterReqs>
            <level>60</level>
            <finishedQuests>
                <quest name="Get Double Cheeseburger" />
                <quest name="Go to Vegas for the Weekend" />
            </finishedQuests>
            <skills>
                <skill name="nunchuks" />
                <skill name="plundering" />
            </skills>
            <items>
                <item name="genie's lamp" />
                <item name="noise cancelling headphones for robin williams' voice />
            </items>
        </characterReqs>
        <steps>
            <step number="1">Get to Mordor</step>
            <step number="2">Throw Ring into Lava</step>
            <step number="3">...</step>
            <step number="4">Profit</step>
        </steps>
    </quest>
</quests>

听起来你已经为通用面向对象设计 (OOD)原则做好了准备。 我将故意忽略上下文(游戏,MMO等),因为这与你如何进行设计过程无关。 我给你的链接没有什么比解释哪些条款最有助于自己查找,IMO; 我会把那些用粗体。

在OOD中,数据库模式直接来自您的系统设计 ,而不是相反。 您的设计将告诉您基本对象类是什么以及哪些属性可以存在于同一个表中(与对象的关系1:1 )与制作映射表的对象(具有1:nn:m关系的任何关系) - 例如,一个用户有多个统计数据,因此它是1:n)。 事实上,如果你正确地完成了OOD,你将无法做出关于最终数据库布局的决定。

进行任何OO映射的“正确”方法是作为称为“数据库规范化”的多步骤过程学习的。 其基本原理正如我所描述的那样:找到对象关系的“arity” (1:1,1:n,...)并为1:n和n:m制作映射表 对于1:n,你最终会得到两个表,“base”表和一个“base_subobjects”表(例如你的“users”和“user_stats”就是一个很好的例子),带有“外键” (基数的Id) object)作为子对象映射表中的一列。 对于n:m,最终得到三个表:“base”,“subobjects”和“base_subobjects_map”,其中地图有一列用于基本Id,另一列用于子对象Id。 在您的N个任务示例中,这可能是必要的,每个任务都有M个要求(因此需求条件可以在任务之间共享)。

这是你需要知道的85%。 其余的是如何处理继承,我建议你跳过,除非你是自虐。 现在,在你开始编写内容之前,先弄清楚你希望它如何工作,剩下的就是蛋糕了。

@Shea Daniel的答案中的主题是在正确的轨道上:任务的规范是非关系的 ,还包括逻辑数据

使用XML或Lua是示例,但更一般的想法是开发自己的特定域的语言来编码任务。 这里有一些关于这个概念的文章,与游戏设计有关:

您可以将给定任务的代码块存储到数据库的TEXT字段中,但是使用SQL查询其中的特定部分您将没有太大的灵活性。 例如,考虑到角色目前拥有的技能,哪些任务对他开放? 如果任务先决条件在TEXT字段中的DSL中编码,则在SQL中查询将不容易。

您可以尝试以关系方式对各个先决条件进行编码,但很快就会失控。 关系和面向对象只是不能很好地融合在一起。 您可以尝试以这种方式建模:

Chars <--- CharAttributes --> AllAttributes <-- QuestPrereqs --> Quests

然后执行LEFT JOIN查找角色属性中没有先决条件的任何任务。 这是伪代码:

SELECT quest_id
FROM QuestPrereqs
 JOIN AllAttributes
 LEFT JOIN CharAttributes
GROUP BY quest_id
HAVING COUNT(AllAttributes) = COUNT(CharAttributes);

但问题在于,现在你必须对角色的每个方面进行建模,这可能是一个先决条件(统计,技能,等级,所有权,完成任务),作为适合这种结构的某种抽象“ 属性 ”。

这解决了跟踪任务先决条件的问题,但它给你留下了另一个问题:角色以非关系方式建模,本质上是一个实体 - 属性 - 值体系结构,它打破了一堆关系规则并使其他类型的查询难以置信难。

与数据库的设计没有直接关系,但几周之后,类似的问题被问到关于RPG的类图示例

我相信你可以找到有用的东西:)

关于你的基本结构,你可以(根据你的游戏的性质)考虑推动玩家角色和非玩家角色之间的表达收敛,这样任何一个自然运行相同的代码都不必担心关于区别。 这将建议,而不是具有usermonster表,具有表示PC和NPC共有的所有内容的character表,然后是用于PC和/或用户帐户特有的信息的user表。 user表将具有character_id外键,并且您可以通过存在与其对应的user行这一事实来告知玩家字符行。

为了代表像你这样的模型中的任务,我会这样做的方式如下:

quest_model
===============
id
name ['Quest for the Holy Grail', 'You Killed My Father', etc.]
etc.

quest_model_req_type
===============
id
name ['Minimum Level', 'Skill', 'Equipment', etc.]
etc.

quest_model_req
===============
id
quest_id
quest_model_req_type_id
value [10 (for Minimum Level), 'Horseback Riding' (for Skill), etc.]

quest
===============
id
quest_model_id
user_id
status
etc.

所以quest_model是任务结构的核心定义; 每个quest_model可以具有0..n关联的quest_model_req行,这些行是特定于该任务模型的要求。 每个quest_model_req都与quest_model_req相关联, quest_model_req_type定义了一般要求类型:达到最低级别,拥有技能,拥有一件装备,等等。 quest_model_req还有一个value ,用于配置此特定任务的要求; 例如,最低级别类型要求的value可能为20,这意味着您必须至少达到20级。

因此, quest表是玩家正在进行或已经进行的任务的个别实例 questquest_modeluser相关联(或者可能是character ,如果你想让NPC能够完成任务!),并且status指示任务的进度在哪里,以及其他跟踪结果有用。

这是一个简单的结构,当然,必须建立以满足特定游戏的需求,但它应该说明我建议的方向。

哦,而且由于其他人全身心地投入了他们的资质,我的是我已经成为一个业余的,面向公众的项目的业余爱好者游戏开发者已经16年了。

我会非常小心你在数据库中存储的内容,特别是对于MMORPG。 请记住,这些东西被设计为成千上万的用户,并且游戏代码必须过快地执行并通过网络发送大量数据,不仅是家庭连接上的播放器,还有服务器之间的服务器。后端。 您还必须最终扩展,数据库和扩展不是我认为特别好的两件事,特别是当您开始分片到不同的区域然后将实例服务器添加到您的分片等等。 你最终得到了很多服务器与数据库交谈并传递大量数据,其中一些根本与游戏无关(进入SQL服务器的SQL文本是无用的网络流量,你应该减少)。

这里有一个建议:限制你的SQL数据库只存储随着玩家玩游戏而改变的东西。 怪物和怪物统计数据不会改变。 项目和项目统计信息不会更改。 任务目标不会改变。 不要将这些东西存储在SQL数据库中,而是将它们存储在某个代码中。

这样做意味着每个生活的服务器都将始终知道所有这些信息,而无需查询数据库。 现在,你根本不存储任务,你只是存储玩家的成就,并且游戏以编程方式确定完成的任务的影响。 您不会在服务器之间浪费数据传输信息,因为您只发送事件ID或类似的东西(您可以通过仅使用足够的位来表示所有事件ID来优化您传递的数据,这将减少网络上的数据流量似乎微不足道,但在大型网络应用程序中没有什么是微不足道的。

为怪物统计和项目统计做同样的事情。 这些东西在游戏过程中不会改变,因此根本不需要将它们保存在数据库中,因此这些信息永远不需要通过网络传输。 你存储的唯一东西是物品或怪物杀戮的ID或任何非确定性的东西(即它可以在游戏过程中以你无法预测的方式改变)。 您可以拥有专用的项目服务器或怪物统计服务器或类似的东西,如果您最终拥有大量占用太多内存的东西,那么您可以将它们添加到您的分片中,然后只传递特定任务所需的数据或实例服务器的区域,处理该事件以进一步减少空间,但请记住,这将增加您传递网络以清空新实例服务器所需的数据量,因此这是一个权衡。 只要你意识到这种权衡的后果,你就可以运用良好的判断力来决定你想做什么。 另一种可能性是将实例服务器限制为特定的任务/区域/事件/任何事情,并且仅为其负责的事物配备足够的信息,但这更复杂并且可能限制您的扩展,因为资源分配将变为静态而不是动态(如果你有50个服务器的每个任务,突然每个人都在同一个任务,你将有49个空闲服务器和一个真正淹没的服务器)。 再次,这是一个权衡,所以一定要了解它,并为您的应用程序做出正确的选择。

一旦确定游戏中的确切信息是非确定性的,那么您可以围绕该信息设计数据库。 这变得有点容易:玩家有统计数据,玩家有物品,玩家有技能,玩家有成就等等,所有这些都很容易制作出来。 您不需要描述技能,成就,项目等,甚至是它们的效果或名称等任何内容,因为服务器可以在运行时从这些内容的ID中为您确定所有内容而无需数据库查询。

现在,很多这可能听起来有点矫枉过正。 毕竟,一个好的数据库可以非常快速地进行查询。 但是,即使在数据中心,您的带宽也非常宝贵,因此您需要将其使用限制为仅发送绝对必要的内容,并且只有在绝对有必要发送数据时才发送该数据。

现在,为了代表代码中的任务,我会考虑规范模式( http://en.wikipedia.org/wiki/Specification_pattern )。 这将允许您根据所需的事件轻松构建任务目标,以确保满足完成该任务的规范。 然后,您可以在构建游戏时使用LUA(或其他东西)来定义任务,这样您就不必进行大量的代码更改并重建整个该死的东西来制作它,这样您就必须杀死11个怪物而不是10个怪物。在特定的任务中获得1000个真理的剑。 如何真正做到这一点,我认为超出了这个答案的范围,并开始触及我对游戏编程知识的边缘,所以如果你选择走这条路线,也许这里的其他人可以帮助你。

另外,我知道我在这个答案中使用了很多术语,请询问是否有任何你不熟悉的内容,我可以解释一下。

编辑:没有注意到你对可制作物品的补充。 我将假设这些是玩家可以在游戏中专门创建的东西,比如自定义项目。 如果玩家可以不断更改这些项目,那么您可以在运行时组合他们所制作的属性,但是您需要将每个属性的ID存储在DB中的某个位置。 如果您可以添加有限数量的东西(如暗黑破坏神II中的宝石),那么您可以通过向表中添加该列数来消除连接。 如果可以制作有限数量的项目,并且有限数量的方法可以将不同的东西连接在一起成为新项目,那么当组合某些项目时,您不需要存储组合属性; 它只是你已经在某个时候定义的一个新项目。 然后,他们只有那个项目而不是它的组件。 如果你澄清了游戏的行为,我可以添加额外的建议,如果这样做有用的话。

我会从面向对象的角度来看待这个问题,而不是以数据为中心的观点。 看起来你可能有很多(可能是复杂的)对象 - 我建议先将它们建模(与它们的关系),然后依靠ORM来保持持久性。

当您遇到以数据为中心的问题时,数据库就是您的朋友。 到目前为止你所做的似乎是完全正确的。

另一方面,您提到的其他问题似乎是以行为为中心的。 在这种情况下,面向对象的分析和解决方案将更好地工作。

例如:使用specificQuest子类创建任务类。 每个孩子都应该实现一个bool HasRequirements(Player player)方法。

另一种选择是某种规则引擎(Drools,例如,如果您使用的是Java)。

如果我正在为这种情况设计数据库,我可能会这样做:

Quest
    [quest properties like name and description]
    reqItemsID
    reqSkillsID
    reqPlayerTypesID
RequiredItems
    ID
    item
RequiredSkills
    ID
    skill
RequiredPlayerTypes
    ID
    type

在这里,ID到各个表的映射,然后你检索该ID下的所有条目,以获得所需项目,技能,你有什么。 如果允许动态创建项目,则应该映射到包含所有可能项目的另一个表。

要记住的另一件事是规范化。 这里有一篇很长的文章但我已经将前三个级别或多或少压缩到以下几个级别:

  • 第一范式表示没有数据库条目,其中特定字段中包含多个项目
  • 第二范式表示如果您有复合主键,则所有其他字段完全依赖于整个键,而不仅仅是每个表中的部分键
  • 第三个法线是没有任何非关键字段依赖于任何表中的其他非关键字段

[免责声明:我对SQL数据库的经验很少,并且对这个领域不熟悉。 我希望我能帮到你。]

我做了类似的事情,我的一般解决方案是使用大量的元数据。 我松散地使用这个术语来表示任何时候我需要新数据来做出给定的决定(允许任务,允许使用项目等)我会创建一个新属性。 这基本上只是一个具有任意数量的值和描述的表。 然后每个字符都有这些类型属性的列表。

例如:杀戮,等级,访问地区等列表

这对你的开发过程有两个作用:

1)每次游戏中都有一个事件,你需要有一个很大的旧开关块来检查所有这些属性类型,看看是否有需要更新的东西

2)每次需要一些数据时,在添加新属性表之前检查所有属性表。

我发现这对于有机增长的游戏来说是一个很好的快速发展策略(不是提前在纸上完全计划) - 但是一个很大的限制是你的过去/当前内容(等级/事件等)不兼容具有未来属性 - 即该地图不会为您提供区域徽章,因为编码时没有区域徽章。 这当然要求您在将新属性添加到系统时更新过去的内容。

只需要一些小点供您考虑:

1)总是尽量让你的“获得任务”要求变得简单......并且“完成任务”要求变得复杂。

第1部分可以通过“尝试按层次顺序进行任务”来完成:
例如:

QuestA :(杀死恶魔)(任务请求:Lvl1)
QuestA.1:在森林中保存“unkown”以获取一些信息..(quest req:QuestA)
QuestA.2:制作水晶之剑......等等。(任务请求:QuestA.1 ==完成)
QuestA.3:......等..(任务请求:QuestA.2 ==完成)
QuestA.4:......等..(任务请求:QuestA.3 ==完成)
等等...
QuestB(找到丢失的坟墓)(任务请求:(QuestA.statues ==完成))
QuestC(去恶魔大卖场)(任务请求:(QuestA.statues == Done && player.level == 10)
等等....

这样做可以节省大量数据字段/表格关节。

其他想法:
如果您使用上述系统,您可以在名为“enableQuests”的任务表中添加额外的奖励字段,并添加需要启用的任务名称。
逻辑上..你有一个“启用”字段分配给每个任务..

2)您的制作问题的一个小解决方案,创建制作配方,包含存储在其中的待制作项目制作要求的项目..所以当玩家试图制作一个项目时......他需要先购买食谱。然后尝试制作..这样的项目描述的一个简单例子是:
ItemName:“传说中的死剑”
Craftevel req。 :75
所需物品:
Item_1:死者之刃
Item_2:被诅咒的印章
item_3:死者的圣宝石
等等...

当他按下“工艺”动作时,你可以解析它并与他的库存/工艺盒进行比较......

所以你的Crafting DB只有1个字段(如果你想要添加制作LvL req,则为2个,尽管它已经包含在配方中。

其他想法:
这些项目,可以在表格中以xml格式存储..这样可以更容易解析...

3)类似的XML系统可以应用于你的任务系统..来实现任务结束要求..

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM