在简单的冒险游戏中实现行为


11

我最近一直在通过编写一个简单的基于文本的冒险游戏来娱乐自己,而我一直停留在看似非常简单的设计问题上。

简要概述一下:游戏分为Room对象。每个对象都有该房间Room中的Entity对象的列表。每个对象Entity都有一个事件状态,它是一个简单的string-> boolean映射,以及一个动作列表,它是一个string-> function映射。

用户输入采用形式[action] [entity]。在Room使用实体名称返回合适的Entity对象,然后使用操作名称来找到正确的功能,并执行它。

为了生成房间描述,每个Room对象显示其自己的描述字符串,然后附加每个的描述字符串Entity。该Entity描述可基于其状态(“门被打开”,“门关闭”,“门被锁定”,等等)。

问题出在这里:使用这种方法,我需要快速实现的描述和动作函数的数量就一发不可收拾。我的起居室仅在5个实体之间就具有约20种功能。

我可以将所有动作组合为一个函数,并通过if-else /进行切换,但是每个实体仍然是两个函数。我还可Entity以为常见/通用对象(例如门和钥匙)创建特定的子类,但是到目前为止,这还不多。

编辑1:根据要求,这些动作函数的伪代码示例。

string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
    if thisEntity["is_searched"] then
        return "There was nothing more in the bushes."
    else
        thisEntity["is_searched"] := true
        currentRoom.setEntity("dungeonDoorKey")
        return "You found a key in the bushes."
    end if

string dungeonDoorKeyUse(currentRoom, thisEntity, player)
    if getEntity("outsideDungeonDoor")["is_locked"] then
        getEntity("outsideDungeonDoor")["is_locked"] := false
        return "You unlocked the door."
    else
        return "The door is already unlocked."
    end if

描述函数的行为几乎相同,检查状态并返回适当的字符串。

编辑2:修改了我的问题措辞。假设可能有大量游戏对象与其他对象没有共同的行为(对特定动作的基于状态的响应)。与为每个特定于实体的操作编写自定义函数相比,有没有一种方法可以以更简洁,更可维护的方式定义这些独特行为?


1
我认为您需要解释这些“动作功能”的作用,也许还要发布一些代码,因为我不确定您在说什么。
2012年

添加了代码。
埃里克(Eric)

Answers:


5

而不是为名词和动词的每种组合都提供单独的功能,您应该建立一种体系结构,在该体系结构中,游戏中的所有对象都实现一个公共接口。

我最想知道的一种方法是定义一个实体对象,游戏中所有特定的对象都将扩展。每个实体将具有一个表(您的语言用于关联数组的任何数据结构),该表将不同的动作与不同的结果相关联。如果您的语言支持一等函数,则表中的操作可能是字符串(例如“ open”),而关联的结果甚至可能是对象中的私有函数。

类似地,对象的状态存储在对象的各个字段中。因此,例如,您可以在Bush中放置一个东西数组,然后与“搜索”关联的函数将对该数组起作用,返回找到的对象或字符串“灌木丛中没有其他东西”。

同时,一个公共方法是类似Entity.actOn(String action)的东西,然后在该方法中将传入的动作与该对象的动作表进行比较;如果该操作在表中,则返回结果。

现在,每个对象所需的所有不同功能都将包含在该对象中,从而可以轻松在其他房间中重复该对象(例如,在每个有门的房间中实例化Door对象)

最后,用XML或JSON或其他方式定义所有房间,这样您就可以拥有很多独特的房间,而无需为每个房间编写单独的代码。游戏开始时加载此数据文件,并分析数据以实例化填充游戏的对象。就像是:

<rooms>
  <room id="room1">
    <description>Outside the dungeon you see some bushes and a heavy door over the entrance.</description>
    <entities>
      <bush>
        <description>The bushes are thick and leafy.</description>
        <contains>
          <key />
        </contains>
      </bush>
      <door connection="room2" isLocked="true">
        <description>It's an oak door with stout iron clasps.</description>
      </door>
    </entities>
  </room>

  <room id="room2">
    etc.

补充:啊哈,我刚刚读了FxIII的答案,而结尾处的这一点跳到了我身上:

(no things like <item triggerFlamesOnPicking="true"> that you will use just once)

尽管我不同意触发火焰陷阱的情况只会发生一次(我可以看到该火焰陷阱可用于许多不同的对象),但我认为我终于明白了您对对用户输入做出独特反应的实体的含义。我可能会解决一些问题,例如通过使用组件体系结构构建我的所有实体,使地牢中的一扇门有火球陷阱(在其他地方详细说明)。

这样,每个Door实体都被构造成一堆组件,并且我可以灵活地在不同实体之间混合和匹配组件。例如,大多数门的配置如下

<entity name="door">
  <description>It's an oak door with stout iron clasps.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room2" />
  </components>
</entity>

但一扇带火球陷阱的门是

<entity name="door">
  <description>There are strange runes etched into the wood.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room7" />
    <fireballTrap />
  </components>
</entity>

然后我唯一需要为该门编写的唯一代码是FireballTrap组件。它将使用与所有其他门相同的Lock和Portal组件,如果我后来决定在宝箱上使用FireballTrap或将FireballTrap组件添加到该宝箱这样的简单操作。

无论是在编译代码中定义所有组件还是在单独的脚本语言中定义所有组件,这在我看来都不是很大的区别(无论您是要在某处编写代码),但重要的是可以显着减少您需要编写的独特代码数量。哎呀,如果您不关心关卡设计师/修改者的灵活性(毕竟您是自己编写游戏的话),甚至可以使所有实体都从Entity继承并在构造函数中添加组件,而不是在配置文件或脚本中添加组件。随你:

Door extends Entity {
  public Door() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
  }
}

TrappedDoor extends Entity {
  public TrappedDoor() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
    addComponent(new FireballTrap());
  }
}

1
适用于常见的可重复项。但是,那些对用户输入做出独特响应的实体呢?创建Entity仅用于单个对象的子类会将代码分组在一起,但不会减少我必须编写的代码量。还是在这方面不可避免的陷阱?
埃里克(Eric)

1
我讲了你给的例子。我看不懂你的想法;您想要什么对象和输入?
2012年

编辑了我的帖子,以更好地解释我的意图。如果我正确理解了您的示例,则看起来每个实体标签都对应于的某个子类,Entity并且属性定义了其初始状态。我猜该实体的子标签充当与该标签关联的任何操作的参数,对吗?
埃里克(Eric)

是的,就是这个主意。
2012年

我应该知道,组件将成为解决方案的一部分。谢谢您的帮助。
埃里克

1

您解决的尺寸问题是很正常的,几乎是不可避免的。您想找到一种既简洁灵活的表示实体的方法。

一个“容器”(跳动的答案中的灌木丛)是一种简单的方法,但是您会发现它不够灵活

我不建议你去尝试找到一个通用的接口,然后使用配置文件指定的行为,因为你总是有不愉快的感觉一个岩石之间(标准和枯燥的实体,容易描述)和硬地(独特的奇妙实体,但实施时间太长)。

我的建议是使用一种解释性语言来编码行为。

想想灌木丛的例子:它是一个容器,但是我们的灌木丛中需要有特定的物品;容器对象可能具有:

  • 讲故事的人添加项目的方法,
  • 引擎显示其中包含的项目的方法,
  • 玩家选择物品的方法。

这些物品之一有一根绳子,它触发了一种装置,进而点燃了燃烧灌木丛的火焰……(你看,我能读懂你的想法,所以我知道你喜欢的东西)。

您可以使用脚本来描述此灌木丛,而不是使用配置文件来描述此灌木丛每次有人从容器中选取项目时,将相关的额外代码放在从主程序执行钩子中。

现在,您有许多体系结构选择:您可以使用代码语言或脚本语言(如容器,类似门的东西)将行为工具定义为基类。这些事情的目的是让您轻松描述实体,以汇总简单行为使用脚本语言上的绑定对其进行配置

脚本应该可以访问所有实体:您可以将标识符与每个实体相关联,并将其放在以脚本语言脚本扩展形式导出的容器中。

使用脚本策略可以使您的配置保持简单(没有类似的事情<item triggerFlamesOnPicking="true">,您将只使用一次),同时可以表达一些奇怪的行为(有趣的行为),并添加一些代码行

简而言之:脚本作为可以运行代码的配置文件。

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.