繁体   English   中英

在文本冒险中编码交互

[英]Coding interactions in a text adventure

编辑:如果你不能阅读这个庞大的问题,我会在底部写一个摘要。

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

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

这一切都已完成,但这就是我被困的观点:

一个行动有任何数量的潜在后果,每个潜在的互动都会有所不同。 这些是:

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

EITHER - 动作的主题(库存项目,环境项目或角色)改变它的描述EG。 “冲墙”可以改变墙的描述以描述墙上的凹痕 或者 - 动作的主体被另一个项目EG替换。 “粉碎瓶子”导致“瓶子”变成“破瓶子”或“杀死约翰”导致角色约翰被环境项目“约翰的尸体”取代。

- 返回描述先前变化EG的响应。 “破碎的瓶子散落在地板上。”

- 区域的描述已更改。 例如。 “粉碎灯泡”导致房间的描述改变以描述黑色的房间

- 从库存或环境EG添加/删除项目。 “拿起瓶子”。 您现在在库存中有一个瓶子,瓶子已从环境中移除。

- 可以移动的方向和它们导致的区域改变EG。 “用钥匙解锁门”允许您将东移到另一个房间

- 将玩家移动到新区域EG。 “往北走”带你到另一个地方。

我需要以某种方式确定特定交互应该调用哪些后果,并调用它们。 一项行动可能会使用其中的一些后果,或只是一种。

例如,如果项目是瓶子:

用水填充瓶子 ”将首先返回一个回复,描述您已用水填充瓶子。 然后它将用“瓶装水”物品替换“瓶子”物品。 这是两个后果,返回响应并替换项目。

说你当时要“ 在窗口扔一瓶水 ”。 这更复杂。 它将首先返回描述发生的事件的响应,瓶子和窗口都会粉碎,水会到处流动。 瓶子将从玩家的库存中移除。 接下来,“ 瓶装水 ”将被“破瓶”取代,“窗户”将被“破窗”取代。 区域描述也会改变以反映这一点。 这是五个后果,返回响应,从库存中删除项目,替换两个项目并更新当前区域的描述。

正如您所看到的,我需要一种通用的方法,能够在每个“交互”的基础上定义该操作的后果,并适当地更新其他对象,例如Item,Player(用于库存)和Area。

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

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

例如,在上面的例子中,对于第一次交互,“填充水”,我会告诉它返回一个响应(“你已用水填充瓶子”)并且还调用一个ReplaceItem方法来取代“瓶子“用”一瓶水“。

对于第二次交互,我会告诉它返回一个响应(“瓶子通过空气冲进......”),在动作主题上调用RemoveFromInventory,在瓶子上调用UpdateStatus(“瓶子被粉碎”)并且窗口(“窗口被粉碎”)并调用UpdateAreaDescription来更改当前区域的描述(“你正站在一个单一窗口的房间里,玻璃碎成碎片”)。

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

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

在我的游戏中,有Actionable对象(一个瓶子,一面墙,John)。 每个Actionable对象都有一个Interaction对象列表,描述了玩家如何与它们进行交互。 此时,Interaction具有“Name”属性(“throw”,“hit”,“break”)并返回Response(“You throw the”)。

我试图解决的问题是,交互还需要做许多其他事情,每个特定的交互都会有所不同。 我们以一个玻璃瓶为例。

“扔玻璃瓶”
- 返回响应(“你扔了玻璃瓶。”)
- “瓶子”将从玩家的库存中删除。
- 用新的替换来反映变化。 (“瓶子”替换为“破瓶子”)。
- 返回第二个响应(“玻璃瓶的碎片散落在地板上”)。

“在窗口扔玻璃瓶”
- 返回响应(“你把玻璃瓶扔在窗户上。”)
- 从玩家的库存中删除对象“瓶子”。
- 将对象替换为新对象以反映更改。 (“瓶子”替换为“破瓶子”)。
- 第二个可选对象替换为new以反映更改。 (“窗口”替换为“破窗”)。
- 更新当前Area的“Description”属性。 (“你站在一个房间里,只有一个破窗户。”)。

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

如果你需要更多上述行动的例子,请告诉我,我会再做一些。

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

锁定对象识别的动词

  • UseItemOn(Key001,LockPicks,Sledgehammer,...)
  • 冲床

这样你就可以通过像“你不能<动词> <对象>这样的响应来处理它无法识别的动词,并处理它确实用事件或其他什么来识别的动词。

编辑

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

因此,如果您计划让墙响应任何抛出的对象,那么您需要在其列表中添加一个Collide动词。 你需要指定它应该关注哪些对象,并且可能针对每一个对象,它应该如何响应特定的力量等等。

但原则是一样的。 对于任何事件,有许多参与者,并且每个参与者将具有它关心的某些刺激,并且对于那些刺激,它将具有它关心的某些刺激起源对象。 如果它是一个它关心的动词,但它的起源不是它关心的对象,那么它将有效地忽略它 - 或者以一些普通的方式回应。

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

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

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

基本上你需要决定你想要什么级别的(un)可预测性,然后组成一个交互矩阵(不一定是物理,但你打算进行任何类型的交互 - 一个锁定事件,一个碰撞事件,一个饮酒事件)涉及某些可预测的属性。

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

  • 动词(例如“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
   }
}

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

将它们传递给每次互动:

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

哈,我也在做类似的事情。 我想知道你的框架最终会成为一个文本冒险创建者 ,这就是我的项目。

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

  • 打印一条消息
  • 更改对象/房间的描述
  • “锁定”或“解锁”对象。 这意味着“检查皮带”会说“你没有看到任何皮带在这里”直到“检查尸体”已被执行以了解“尸体的腰部有一条闪亮的腰带”。
  • 锁定或解锁房间的出口
  • 将播放器移动到某个房间
  • 从播放器的广告资源中添加/删除内容
  • 设置/更改一些游戏变量,例如。 “movingGlowingRock = true”或“numBedroomVisits = 13”等。

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

现在,有房间。 房间有物品。 某些命令对每个对象都有效。 一种简单的方法是让每个房间对象都包含一个允许命令的字典。 脚本是指向您的操作脚本的委托。 考虑一下:

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实例中。 (这与我的项目有关。)

请原谅我的随意......> _>

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

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

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

我还添加了一个GlobalActionReference,每次执行一个动作时都会更新,ActionResult可以通过它来更新它需要更新的对象。

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

您的问题似乎是管理事件的传播。 Microsoft使用Observer模式/事件处理此问题(用于不太多彩的目的)。

我认为将Gamma等Observer和Mediator设计模式结合起来的“设计模式”一书对你非常有帮助。 这本书有一个示例ChangeManager类可能会有所帮助,但我附上了一些其他应该为您提供服务的链接。

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

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

Mediator模式的DoFactory(使用UML图): http//www.dofactory.com/Patterns/PatternMediator.aspx

观察者模式上的DoFactory(使用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

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

对于你的“用水填充瓶子”的例子,互动将是:

  • 动词:填充({“填充”,“倒”})
  • 过滤器列表: 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”,“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")

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

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

你可以通过一些方便的方法使事情更具可读性,玩家可以使用Player.have(可操作)便捷方法,这样你就可以编写player.have(“瓶装水”),它不会返回瓶子对象本身,而是IFilter对象,用于在调用.check()方法时检查玩家是否有“瓶装水”。 基本上,使对象变得懒惰。

暂无
暂无

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

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