如何为许多独特的武器/法术/能力构建代码


22

我是一个没有经验的程序员,使用Python(类似于Pogue,到目前为止还没有PyGame,因为我仍然只关注文本),因此在FTL的背景下创建了“类似Roguelike”的游戏。

我的游戏将包含大量产生独特能力的武器(入门者约为50种)。我正在努力了解如何以既强大(就允许武器产生根本不同的效果)又具有可扩展性(以便以后可以通过将它们放入文件夹等方式添加更多武器)的方式来构造目标代码。 )。

我的第一个本能是拥有BasicWeapon类,并从该类继承不同的武器。但是,这对我来说似乎是个问题:要么我必须使BasicWeapon类变得如此准系统以至于它基本上是无用的(所有武器共有的唯一功能就是名称和类型(手枪,斧头等)),或者我必须预测每个我将想出独特的效果并将其编码到BasicWeapon中。

后者显然是不可能的,但是前者仍然可以工作。但是,这给我留下了一个问题:我应该将单个武器的代码放在哪里?

我是否要创建plasmarifle.py,rocketlauncher.py,swarmofbees.py等,然后将它们全部放入游戏可以导入它们的文件夹中?

还是有一种方法可以拥有一个数据库样式的文件(也许像Excel电子表格一样简单),而该文件却以某种方式包含每种武器的唯一代码-无需求助于eval / exec?

关于后一种解决方案(数据库),我认为我正在努力解决的基本问题是,尽管我理解保持代码和数据之间的分离是可取的,但我觉得武器模糊了“代码”之间的界限和“数据”一点;它们代表了可以在游戏中找到的各种各样类似的事物,从某种意义上说,它们就像数据一样,但是它们中的大多数将至少需要一些不与其他任何项目共享的唯一代码,从某种意义上说,它们自然就是码。

我在此站点的其他地方找到的部分解决方案建议给BasicWeapon类提供一堆空方法-on_round_start(),on_attack(),on_move()等-然后为每种武器覆盖这些方法。在战斗周期的相关阶段,游戏将为每个角色的武器调用适当的方法,并且只有定义了方法的对象才会实际执行某项操作。这会有所帮助,但仍然无法告诉我每个武器的代码和/或数据应该放在哪里。

有没有其他语言或工具可以用作半数据,半代码嵌合体?我是否完全掌握了良好的编程习惯?

我对OOP的理解充其量只是个粗略的知识,所以我希望对那些不太计算机科学的回答表示赞赏。

编辑:沃恩·希茨(Vaughan Hilts)在下面的帖子中明确指出,我本质上讲的是数据驱动编程。我的问题的实质是:我如何以一种数据可以包含脚本的方式实施数据驱动的设计,从而使新武器能够在不更改主程序代码的情况下做新事情?



@ Byte56相关;但是我认为这是OP想要避免的事情。我认为他们正在尝试寻找一种更数据驱动的方法。如果我错了纠正我。
旺市(Vaughan)击中了2013年

我同意他们正在尝试寻找一种更加面向数据的方法。具体而言,我喜欢Josh的问题的答案:gamedev.stackexchange.com/a/17286/7191
MichaelHouse

啊,对此感到抱歉。:)我有阅读“可接受答案”的习惯。
旺市(Vaughan)击中了2013年

Answers:


17

几乎可以肯定,您需要一种数据驱动的方法,除非您的游戏将完全出乎我们的意料和/或程序生成到核心。

本质上,这涉及以您选择的标记语言或文件格式存储有关武器的信息。XML和JSON都是很好的可读性选择,如果您只是想快速入门,可以使用它使编辑变得相当简单,而无需复杂的编辑器。(并且Python也可以很容易地解析XML!)您将设置所有相关的属性,例如'power','defense','cost'和'stats'。数据的结构方式将由您决定。

如果武器需要添加状态效果,请为其指定一个状态效果节点,然后通过另一个数据驱动对象指定状态效果的效果。这将使您的代码减少对特定游戏的依赖,并使对游戏的编辑和测试变得微不足道。不必一直重新编译也是一个好处。

补充阅读资料如下:


2
有点像基于组件的系统,其中通过脚本读取组件。就像这样:gamedev.stackexchange.com/questions/33453/...
MichaelHouse

2
并且,在使用脚本时,将脚本作为该数据的一部分,以便新武器无需更改主要代码即可完成新工作。
Patrick Hughes

@Vaughan Hilts:谢谢,数据驱动似乎正是我直觉上所需要的。由于我仍然需要答案,因此我将问题开放一段时间,但可能会选择它作为最佳答案。
henrebotha 2013年

@帕特里克·休斯(Patrick Hughes): 正是我想要的!我怎么做?您能给我看一个简单的例子或教程吗?
henrebotha

1
首先,您的引擎中需要一个脚本引擎,许多人选择了LUA,它可以访问诸如效果和统计之类的游戏系统。然后,由于您已经从数据描述中重新创建对象,因此可以在激活新对象时嵌入引擎调用的脚本。在过去的MUD中,这被称为“ proc”(Process的缩写)。困难的部分是使引擎中的游戏功能具有足够的灵活性,以从外部调用并具有足够的功能。
Patrick Hughes

6

(很抱歉,我提交的是答案而不是评论,但我还没有回复。)

沃恩的答案很好,但我想加两分钱。

您想要使用XML或JSON并在运行时对其进行解析的主要原因之一是无需重新编译代码即可进行更改和试验新值。我认为Python是经过解释的,而且可读性很强,您可以将原始数据保存在带有字典的文件中,并将所有内容组织起来:

weapons = {
           'megaLazer' : {
                          'name' : "Mega Lazer XPTO"
                          'damage' : 100
                       },
           'ultraCannon' : {
                          'name' : "Ultra Awesome Cannon",
                          'damage' : 200
                       }
          }

这样,您只需导入文件/模块并将其用作普通字典即可。

如果要添加脚本,则可以利用Python和1st class函数的动态特性。您可以执行以下操作:

def special_shot():
    ...

weapons = { 'megalazer' : { ......
                            shoot_gun = special_shot
                          }
          }

尽管我认为这将不利于数据驱动的设计。要成为100%DDD,您需要具有信息(数据),以指定特定武器将使用的功能和代码。这样,您就不会破坏DDD,因为您不会将数据与功能混在一起。


谢谢。只是看到一个简单的代码示例就可以使其点击。
henrebotha

1
+1是一个不错的答案,也可以让您有足够的代表发表评论。;)欢迎。
2013年

4

数据驱动设计

我向 最近将代码审查

经过一些建议和改进后,结果得到了一个简单的代码,该代码将使在基于字典(或JSON)的武器创建方面具有一定的相对灵活性。数据在运行时进行解释,并且简单的验证由Weapon类本身完成,而无需依赖整个脚本解释器。

尽管Python是一种解释型语言(无需重新编译即可编辑源文件和数据文件),但数据驱动设计听起来像在您所介绍的情况下正确的做法。这个问题将详细介绍该概念及其优缺点。还有一个不错的在康奈尔大学上介绍

与其他语言(例如C ++)相比,它可能会使用脚本语言(例如LUA)来处理数据x引擎交互和脚本编写,以及某种数据格式(例如XML)来存储数据,Python实际上可以做到它全部依靠自己(既考虑标准dict又考虑标准weakref,后者专门用于资源加载和缓存)。

但是,独立开发人员可能不会像本文中建议的那样将数据驱动方法发挥到极致:

我需要多少数据驱动设计?我认为游戏引擎不应包含一行特定于游戏的代码。不是一个。没有硬编码的武器类型。没有硬编码的HUD布局。没有硬编码的单位AI。娜达 压缩。齐尔奇

也许,使用Python,可以同时从面向对象和数据驱动的方法中受益,以提高生产力和可扩展性。

简单的样品处理

在讨论代码审查的特定情况下,词典将存储“静态属性”和要解释的逻辑-如果武器具有任何条件行为。

在下面的示例中,剑应具有“ antipaladin”类的角色手中的某些能力和属性,并且没有任何效果,其他角色使用时其属性较低):

WEAPONS = {
    "bastard's sting": {
        # magic enhancement, weight, value, dmg, and other attributes would go here.
        "magic": 2,

        # Those lists would contain the name of effects the weapon provides by default.
        # They are empty because, in this example, the effects are only available in a
        # specific condition.    
        "on_turn_actions": [],
        "on_hit_actions": [],
        "on_equip": [
            {
                "type": "check",
                "condition": {
                    'object': 'owner',
                    'attribute': 'char_class',
                    'value': "antipaladin"
                },
                True: [
                    {
                        "type": "action",
                        "action": "add_to",
                        "args": {
                            "category": "on_hit",
                            "actions": ["unholy"]
                        }
                    },
                    {
                        "type": "action",
                        "action": "add_to",
                        "args": {
                            "category": "on_turn",
                            "actions": ["unholy aurea"]
                        }
                    },
                    {
                        "type": "action",
                        "action": "set_attribute",
                        "args": {
                            "field": "magic",
                            "value": 5
                        }
                    }
                ],
                False: [
                    {
                        "type": "action",
                        "action": "set_attribute",
                        "args": {
                            "field": "magic",
                            "value": 2
                        }
                    }
                ]
            }
        ],
        "on_unequip": [
            {
                "type": "action",
                "action": "remove_from",
                "args": {
                    "category": "on_hit",
                    "actions": ["unholy"]
                },
            },
            {
                "type": "action",
                "action": "remove_from",
                "args": {
                    "category": "on_turn",
                    "actions": ["unholy aurea"]
                },
            },
            {
                "type": "action",
                "action": "set_attribute",
                "args": ["magic", 2]
            }
        ]
    }
}

为了进行测试,我创建了简单类PlayerWeapon类:第一个类持有/装备武器(因此调用其有条件的on_equip设置),第二类作为单个类,它将根据作为Weapon初始化期间的参数。它们不能反映正确的游戏类设计,但仍可用于测试数据:

class Player:
    """Represent the player character."""

    inventory = []

    def __init__(self, char_class):
        """For this example, we just store the class on the instance."""
        self.char_class = char_class

    def pick_up(self, item):
        """Pick an object, put in inventory, set its owner."""
        self.inventory.append(item)
        item.owner = self


class Weapon:
    """A type of item that can be equipped/used to attack."""

    equipped = False
    action_lists = {
        "on_hit": "on_hit_actions",
        "on_turn": "on_turn_actions",
    }

    def __init__(self, template):
        """Set the parameters based on a template."""
        self.__dict__.update(WEAPONS[template])

    def toggle_equip(self):
        """Set item status and call its equip/unequip functions."""
        if self.equipped:
            self.equipped = False
            actions = self.on_unequip
        else:
            self.equipped = True
            actions = self.on_equip

        for action in actions:
            if action['type'] == "check":
                self.check(action)
            elif action['type'] == "action":
                self.action(action)

    def check(self, dic):
        """Check a condition and call an action according to it."""
        obj = getattr(self, dic['condition']['object'])
        compared_att = getattr(obj, dic['condition']['attribute'])
        value = dic['condition']['value']
        result = compared_att == value

        self.action(*dic[result])

    def action(self, *dicts):
        """Perform action with args, both specified on dicts."""
        for dic in dicts:
            act = getattr(self, dic['action'])
            args = dic['args']
            if isinstance(args, list):
                act(*args)
            elif isinstance(args, dict):
                act(**args)

    def set_attribute(self, field, value):
        """Set the specified field with the given value."""
        setattr(self, field, value)

    def add_to(self, category, actions):
        """Add one or more actions to the category's list."""
        action_list = getattr(self, self.action_lists[category])

        for action in actions:
            if action not in action_list:
                action_list.append(action)

    def remove_from(self, category, actions):
        """Remove one or more actions from the category's list."""
        action_list = getattr(self, self.action_lists[category])

        for action in actions:
            if action in action_list:
                action_list.remove(action)

我希望通过将来的改进,有一天能使我拥有一个动态的制作系统,可以处理武器组件而不是整个武器。

测试

  1. 角色A拿起武器,装备它(我们打印其状态),然后放下;
  2. 角色B选择相同的武器装备(我们再次打印其统计数据以显示它们的不同之处)。

像这样:

def test():
    """A simple test.

    Item features should be printed differently for each player.
    """
    weapon = Weapon("bastard's sting")
    player1 = Player("bard")
    player1.pick_up(weapon)
    weapon.toggle_equip()
    print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
        weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))
    weapon.toggle_equip()

    player2 = Player("antipaladin")
    player2.pick_up(weapon)
    weapon.toggle_equip()
    print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
        weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))

if __name__ == '__main__':
    test()

它应该打印:

对于吟游诗人

增强效果:2,命中效果:[],其他效果:[]

对于抗帕拉丁

增强效果:5,命中效果:['unholy'],其他效果:['unholy aurea']

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.