【问题标题】:Coding interactions in a text adventure文本冒险中的编码交互
【发布时间】:2011-04-20 05:10:44
【问题描述】:

编辑:如果您懒得阅读这个庞大的问题,我会在底部放置一个摘要。

我目前正在为我将在 C# 中进行的文本冒险开发一种“框架”,作为编码练习。在此框架中,可能的操作由“交互”类定义。

潜在的“可操作”对象是库存物品(棍子、枪、剑)、环境物品(墙、门、窗)和角色(人、动物)。其中每一个都有一个属性,即交互列表。目前,交互基本上是一个“动作/响应”名称值对。当您键入“粉碎窗口”时,它会查看播放器可用并匹配主题的所有可能的可操作项目(在本例中为“窗口”)。然后计算出该操作是“粉碎”,并在窗口(环境项)上的交互列表中查找以获取对粉碎操作的响应,然后将其写入控制台。

这一切都完成了,但这是我卡住的地方:

一个动作具有任意数量的潜在后果,这些后果因每个潜在的交互作用而异。它们是:

- 通过查找交互来返回描述操作结果的响应,可能与第二个主题

任一 - 动作的主题(物品栏物品、环境物品或角色)改变了它的描述 例如。 “punch wall”可以改变墙壁的描述来描述墙上的凹痕 - 动作的主题被另一个项目替换 例如。 “粉碎瓶子”导致“瓶子”变为“破瓶子”或“杀死约翰”导致角色约翰被环境物品“约翰的尸体”取代。

- 返回描述先前更改的响应 例如。 “瓶子的碎片散落在地板上。”

- 一个区域的描述被改变了。 例如。 “粉碎灯泡”导致房间的描述变为描述漆黑的房间

- 从物品栏或环境中添加/移除物品 例如。 “捡瓶子”。现在,您的库存中有一个瓶子,并且该瓶子已从环境中移除。

- 可用于移动的方向和它们通向的区域已更改 例如。 “用钥匙开锁”可以让你向东移动到另一个房间

- 玩家被移动到一个新区域 例如。 “向北走”带你到另一个区域。

我需要以某种通用方式确定特定交互应该调用这些后果中的哪一个,并调用它们。一个动作可能会产生许多这样的后果,或者只是一个。

例如,如果项目是一个瓶子:

给瓶子装满水”会首先返回一个响应,描述你已经给瓶子装满了水。然后它将用“bottle of water”项目替换“bottle”项目。 这是两个后果,返回响应和替换项目。

假设你当时要做“往窗户扔瓶水”。这更复杂。它首先会返回一个描述所发生事件的响应,瓶子和窗户都会破碎,水会到处流。该瓶子将从玩家的库存中移除。 接下来,将“bottle of water”替换为“broken bottle”,将“Window”替换为“Broken window”。区域描述也会改变以反映这一点。 那是五个后果,返回响应,从库存中删除一个项目,替换两个项目和更新当前区域的描述。

如您所见,我需要一种通用方式,能够在每个“交互”的基础上定义该操作的后果,并适当地更新其他对象,例如项目、播放器(用于库存)和区域.

如果不清楚,我很抱歉,如果有人有任何问题,我会尽力澄清。

编辑: 有没有办法让我在交互上定义一个方法,我可以将许多方法传递给调用(及其参数)?返回的初始响应将是默认的强制结果,如果指定,则可能会有额外的结果。

例如,在上面的示例中,对于第一次交互“加水”,我会告诉它返回一个响应(“您已将瓶子装满水”),并调用一个 ReplaceItem 方法,该方法将用“bottle of water”替换“bottle”主题。

对于第二次交互,我会告诉它返回一个响应(“瓶子在空中飞驰而入......”),在动作的主题上调用 RemoveFromInventory,在瓶子上调用 UpdateStatus(“瓶子被打碎了") 和窗户 ("窗户被砸了") 并调用 UpdateAreaDescription 更改当前区域的描述 ("你站在一个只有一扇窗户的房间里,玻璃被砸得粉碎")。

这听起来可行吗?为了所有不同的可能交互,我试图使其尽可能通用。

编辑 2:进一步澄清,并尝试总结问题:

在我的游戏中,有可操作的对象(瓶子、墙、约翰)。每个 Actionable 对象都有一个列表 描述玩家如何与它们交互的交互对象。目前,一个交互有一个 “Name”属性(“throw”、“hit”、“break”)并返回一个 Response(“You throw the”)。

我要解决的问题是,交互还需要做许多其他事情,各不相同 通过每个特定的交互。让我们以玻璃瓶为例。

“扔玻璃瓶”
- 返回响应(“你扔了玻璃瓶。”)
- “瓶子”从玩家的物品栏中移除。
- 替换为新的以反映更改。 (“瓶子”替换为“破瓶子”)。
- 返回第二个响应(“玻璃瓶碎片散落在地板上”)。

“向窗户扔玻璃瓶”
- 返回响应(“您将玻璃瓶扔到窗户上。”)
- 对象“瓶子”从玩家的物品栏中移除。
- 将对象替换为新对象以反映更改。 (“瓶子”替换为“破瓶子”)。
- 第二个可选对象被替换为新对象以反映更改。 (“窗口”替换为“破窗”)。
- 当前区域的“描述”属性已更新。 (“你站在一个房间里,只有一扇破窗户。”)。

创建交互时,如何改变它们执行的其他操作,例如状态更改 主题,或更改当前区域的描述?

如果您需要更多上述操作示例,请告诉我,我会再做一些。

【问题讨论】:

    标签: c# .net text adventure


    【解决方案1】:

    好的,伙计们,这就是我的处理方式。这是我能想到的最通用的方式,我认为它适合我想要实现的目标。

    我在 Interaction 类中添加了一个“Invoke()”方法,一个名为 IActionResult 的新接口定义了一个“Initiate()”方法,并为每个可能的结果添加了许多不同的 ActionResult 类型。我还在交互中添加了一个 ActionResults 列表。 Invoke 方法将简单地遍历所有 IActionResult 对象并调用 Initiate() 方法。

    当您在一个项目上定义一个交互时,您将传入该交互的动词列表,然后根据该交互的结果添加多个 ActionResult 对象。

    我还添加了一个 GlobalActionReference,它会在每次执行操作时更新,并且 ActionResult 可以适当地访问它需要通过它更新的对象。

    非常感谢您提出的所有建议,如果我不清楚我的问题或我的 cmets(甚至这个答案),我很抱歉。感谢您的帮助。

    【讨论】:

      【解决方案2】:

      交互可以定义为“动词 + {过滤器列表} + {响应列表}”

      对于您的“用水填充瓶子”示例,交互将是:

      • 动词:Fill({"fill", "pour"})
      • 过滤器列表:Have(player, "bottle")Have(currentRoom, "water tap")
      • 回复列表:Print("You filled the bottle with water")Remove(player, "bottle")Add(player, "bottle of water")
      • 或者,响应列表可以是:SetAttribute(player.findInventory("bottle"), "fill", "water")

      那么如果你需要“向窗户扔瓶水”:

      • 动词:Throw({"throw", "smash"})
      • 过滤器列表:Have(player, "bottle of water")Have(currentRoom, "windows")
      • 响应列表:Print("The bottle smashed with the windows, and both of them are broken")Remove(player, "bottle of water")Add(curentRoom, "broken bottle")Remove(currentRoom, "window")Add(currentRoom, "broken window")SetAttribute(currentRoom, "description", "There is water on the floor")

      进入房间后,框架将查询房间中的所有对象以获取有效动词的列表,并枚举它们。当玩家输入命令时,框架会搜索与该命令匹配的动词;然后它会检查过滤器列表,如果它们都为真,则遍历响应列表以按顺序执行它们。

      Responses 将是一个函数对象,它实现了具有一些构造函数的 IResponse 接口和一个 IResponse.do() 方法。 Filters 将是实现 IFilter 接口的函数对象,同样带有一些构造函数,并且 IFilter.check() 方法返回一个布尔值。您甚至可以使用 And()、Or() 和 Not() 过滤器来进行更复杂的查询。

      你可以通过一些方便的方法使事情更具可读性,一个 Player 可以有 Player.have(Actionable) 方便方法,所以你可以编写 player.have("bottle of water"),它不返回瓶子对象本身,而是一个 IFilter 对象,它会在调用 .check() 方法时检查玩家是否有“瓶水”。基本上,让对象变得懒惰。

      【讨论】:

      • 这不是我做的方式,但是,是的,那行得通。如果我要回去那样做,我不得不重新开始,但谢谢你的建议。
      【解决方案3】:

      您的问题似乎是管理事件的传播。微软使用观察者模式/事件来处理这个问题(为了减少色彩的目的)。

      我认为结合Gamma等的“设计模式”一书中的观察者和中介者设计模式对你很有帮助。这本书有一个示例 ChangeManager 类可能会有所帮助,但我附上了一些其他的链接,它们应该可以很好地为您服务。

      我的一个实现建议是使用一个静态或单例类作为中介,并将对所有可操作对象的引用存储在活动内存中以及所有调用的操作。此类可以处理算法以确定所有响应以及来自给定操作的响应的时间顺序。 (如果您认为主要动作 A 的附带效果可以在动作完成之前影响该动作 A 的后果,那么很明显正确的时间顺序是必要的,并且必须在调用每个动作之前更新附带行动。)

      微软关于观察者模式的文章:http://msdn.microsoft.com/en-us/library/ee817669.aspx

      DoFactory on Mediator 模式(带有 UML 图):http://www.dofactory.com/Patterns/PatternMediator.aspx

      DoFactory on Observer 模式(带有 UML 图):http://www.dofactory.com/Patterns/PatternObserver.aspx

      .Net 4 中的 IObserver 接口文档, http://msdn.microsoft.com/en-us/library/dd783449.aspx

      另一篇关于观察者模式的文章。 http://www.devx.com/cplus/Article/28013/1954

      【讨论】:

        【解决方案4】:

        哈,我也在做类似的事情。 我想知道您的框架是否最终会成为文本冒险creator,这就是我的项目。

        我的方法是创建一种 API,其中包含表示游戏中所有最基本动作的方法。然后使用“脚本”,它们基本上是包含这些基本动作组合的方法。 这些基本行动可能涉及:

        • 打印消息
        • 更改对象/房间的描述
        • “锁定”或“解锁”对象。这意味着“检查腰带”会说“你在这里没有看到任何腰带”,直到执行“检查尸体”以得知“尸体的腰部有一条闪亮的腰带”。
        • 锁定或解锁房间的出口
        • 将播放器移动到某个房间
        • 从玩家的物品栏中添加/移除某些东西
        • 设置/更改一些游戏变量,例如。 “movedGlowingRock = true”或“numBedroomVisits = 13”等。

        等等...这是我目前的想法。 这些都是 API 类中的所有方法,并根据需要采用各种参数。

        现在,有房间了。房间有物品。某些命令对每个对象都有效。一种简单的方法是让每个房间对象都保存一个允许命令的字典。 Script 是一个指向你的动作脚本的委托。想一想:

        delegate void Script();
        
        class GameObject
        {
            public Dictionary<string, Script> Scripts {get; set;}
            public string Name {get; set;}
        
            //etc...
        }
        

        还有你的脚本,存储在相关的 Room 实例中:

            //In my project, I plan to have such an abstract class, and since it is a game _creator_, the app will generate a C# file that contains derived types containing info that users will specify using a GUI Editor.
        abstract class Room
        {
            protected Dictionary<string, GameObject> objects;
        
            public GameObject GetObject(string objName) {...//get relevant object from dictionary}
        }
        
        
        class FrontYard : Room
        {
        
            public FrontYard()
            {
                GameObject bottle;
                bottle.Name = "Bottle";
                bottle.Scripts["FillWithWater"] = Room1_Fill_Bottle_With_Water;
                bottle.Scripts["ThrowAtWindow"] = Room1_Throw_Bottle_At_Window;
                //etc...
            }
        
            void void Room1_Fill_Bottle_With_Water()
            {
                 API.Print("You fill the bottle with water from the pond");
                 API.SetVar("bottleFull", "true");         
            }
        
            void Room1_Throw_Bottle_At_Window()
            {
                 API.Print("With all your might, you hurl the bottle at the house's window");
                 API.RemoveFromInventory("bottle");
                 API.UnlockExit("north");
                 API.SetVar("windowBroken", "true");    
                 //etc...     
            }    
        }
        

        所有这些都是我所想的概略视图(我注意到了许多微妙之处,但这足以作为示例)。可悲的是,我什至没有为我的项目编写一个单词,呵呵。一切都在纸上。

        所以...所有这些可能会给你一些想法来修补你自己的项目。如果有不清楚的地方,请询问。希望我没有偏离您的问题或其他问题。

        我想我花了太多时间输入所有这些>_>

        编辑: PS:我的骨架示例并没有完全展示如何管理涉及多个游戏对象的命令(这只是我暗示的许多微妙之处之一)。对于“向窗口扔瓶子”之类的东西,您需要考虑如何管理此类语法,例如。我对此解决方案的一种尝试是解析并发现正在发出什么命令……“将 GO 扔到 GO”。找出游戏对象是什么,然后查看当前房间是否有它们。等等等等。

        更重要的是,这还可以防止您将脚本保存在游戏对象实例中,因为一个命令涉及多个游戏对象。现在可能更好地将 Dictionary 存储在 Room 实例中。 (这就是我的项目所在。)

        请原谅我的胡言乱语... >_>

        【讨论】:

        • 说实话,你是对的,它开始看起来像一个文字冒险创作者!我认为我正在寻找的解决方案更通用。最终我不想以成百上千的 void Room1_Throw_Bottle 类型方法告终。
        • 好吧,最后,如果您希望不同游戏对象上的不同命令执行不同的操作,那么此类脚本是组织动作序列块的好方法。一个单独的 GUI“编辑器”应用程序将生成所需的方法。无论如何,正如您所提到的,许多交互对象也会激增,不是吗?强类型操作看起来确实不错,但我想知道它们提供的灵活性。 “向窗口扔瓶子”和“向红色按钮扔石头”必须导致共同的结果行为。为了获得更大的灵活性,他们可能不得不调用一个单独的方法。
        【解决方案5】:

        我认为你应该确定一组你会识别的动词,然后为每个对象决定它能够响应哪些动词。

        锁定对象识别动词

        • UseItemOn(Key001, LockPicks, Sledgehammer, ...)
        • 打孔

        这样你就可以处理它无法识别的动词,例如“你不能,并处理它通过事件或其他方式识别的动词。

        编辑

        根据您的评论,我显然只是扫描了您的问题(对我来说太长了)。不过,我看不出有什么区别,真的。关键是,一个对象参与了一个事件。从瓶子的角度来看,它被墙击中了。从墙的角度来看,它被瓶子击中。两个对象都有一个动词列表,它们将以某种方式响应。

        因此,如果您计划让墙响应任何抛出的物体,那么您需要在其列表中添加一个 Collide 动词。您需要指定它应该关心与哪些对象发生碰撞,也许对于每个对象,它应该如何响应特定大小的力,等等。

        但是原理是一样的。任何一个事件都有多个参与者,每个参与者都会有它关心的某些刺激,而对于这些刺激,它也会有它关心的某些刺激源对象。如果它是一个它关心的动词,但它的起源不是它关心的一个对象,那么它会有效地忽略它——或者以某种普通的方式回应。

        瓶子参与了与墙的碰撞。 The Bottle 在其动词列表中有碰撞交互类型。它可能有一个它关心碰撞的对象,或者它的值可能是 Any、AnySolid 或其他。有一百万种方法来构建它。在任何情况下,Wall 也参与其中,并且可能在其动词列表中也有 Collide 交互类型。但它只关心与 Sledgehammer 对象的碰撞 - 或者可能是质量为 10 或更大的 AnySolid...

        您也可以使用接口来执行此操作。您可以拥有一个实现 ICollidible 接口的 LootableObject 或其他任何东西。当任何 ICollidible(比如一个瓶子)执行它的 Collide 方法时,它需要一些参数:它有多脆弱,它接收到的力有多大,碰撞的对象是否是它关心的东西,等等。

        它可能充满了液体,因此它会实现一个具有 Spill 方法的 IContainer 接口,以及一个具有 Drink 方法的 IConsumeable 接口。它可能是一个实现 ILockable 接口的锁,该接口具有 Unlock(obj Key) 方法和 Pick(int PickSkill) 方法。这些方法中的每一个都可以对交互中的对象和其他参与者产生特定的状态变化。如果您愿意,可以使用事件来执行此操作。

        基本上,您需要确定您想要的(非)可预测性级别,然后组成一个交互矩阵(不一定是物理,而是您计划操作的任何类型的交互 - 开锁事件、碰撞事件、饮酒事件)涉及某些可预测的属性。

        【讨论】:

        • 我基本上已经完成了,这是我帖子的第一部分。我的困难在于改变特定交互的后果数量。例如,向墙壁扔瓶子会砸碎瓶子,但向墙壁扔砖头不会砸碎砖块。扔瓶子有两个后果(响应和“破瓶”的变化),扔砖有一个后果(响应)。
        • 老实说,我认为您正在解决与我要问的问题不同的问题。也许你是在建议我重新考虑整个事情?我在问题中添加了一个较小的摘要,如果有帮助吗?
        • 我认为给出的答案非常好。它使您能够从每个单独对象的角度定义行为。也许你还需要一连串的后果。假设你有一个带手电筒的房间。火炬会对任何液体产生反应并熄灭(因此您无需编程当水、酒或其他液体溅到它时会发生什么)。然后你需要的是一个对熄灭手电筒有反应的房间(即变暗)。这使得交互更加灵活。
        • 这是一个很好的建议,但这不是我想要的。无论如何,我很感激这个答案。
        【解决方案6】:

        你有两件事:玩家和环境(你可能还有其他玩家)。

        将它们都传递给每个交互:

        interaction.ActOn(environment, player);
        
        //eg:
        smash.ActOn(currentRoom, hero);
        

        然后让每个交互都弄清楚要做什么:

        environment.ReplaceObject("window", new Window("This window is broken. Watch out for the glass!");
        player.Inventory.RemoveObject("bottle");
        player.Hears("The window smashes. There is glass all over the floor! If only John McLane were here...").
        

        通过常规检查确保环境确实有一个窗口、玩家有瓶子等等。

        player.Inventory.ReplaceObject("bottle", new BottleOfWater());
        

        然后交互成为一个通用接口,您可以将其附加到系统中的任何东西,无论是环境、播放器、瓶子等。您可能可以制定一些特定类型的交互,您可以使用它们来删除重复,但我'会从简单开始,然后从那里开始。

        另见Double Dispatch

        【讨论】:

        • 为每个可能的操作单独交互是不切实际的,它需要是通用的。可能有数百个。
        • 顺便说一句,我不是故意忘恩负义的!感谢您的建议! :)
        • 我会从一个单独的交互开始并删除重复,直到你弄清楚你有什么类型的交互。你可能最终会得到EquipItemInteractionChangeEnvironmentInteractionUseUpObjectInteraction 之类的东西,你可以使用AndInteraction 将它们串在一起,AndInteraction 代表两者,这样你就可以进行既用完对象又可以更改的交互环境。
        • 我认为您正在做一些事情,但我不太明白我将如何实现这一点。因此,对于各种后果,我将有许多不同的交互类型......但是我如何将多个交互类型用于单个操作?请记住,诸如“投掷”之类的“动作”与单个交互对象相关联。
        • @Liggi 单独的操作是可行的——这就是继承的目的!虽然每个对象都必须有一个 Take 方法,但您不需要编写 object.Take 方法,因为您可以从一个 TakeableObject 或一个表示您不能接受它的 FixedObject 继承它们。
        【解决方案7】:

        您描述的所有操作都包含以下内容:

        • 动词(例如“throw”)
        • 一个对象(例如“瓶子”)
        • 一个可选的附加参数进一步描述动作(例如“在窗口”)

        如何将每个可操作对象建模为派生自共同祖先的类,并让该类自己处理操作。类似的东西

        public interface IObjectBase
        {
           bool HandleAction(string verb,string [] params)
        }
        
        public class Bottle: IObjectBase
        {
           bool HandleAction(string verb,string [] params)
           {
             //analyze verb and params to look for appropriate actions
             //handle action and return true if a match has been found
           }
        }
        

        【讨论】:

        • 这是我考虑过的一种可能性,但是在“向窗口扔瓶子”的情况下,它比您分析的要复杂。 Window 实际上是第二个“对象”。比如“用钥匙开门”,有门和钥匙两个主题,动作是“用钥匙开门”。此外,这不包括用另一个替换当前项目、修改玩家的物品栏或当前区域的描述,或修改环境中可能受该操作影响的其他项目。
        猜你喜欢
        • 2023-04-04
        • 2016-08-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多