在文本冒险中编码交互

时间:2010-10-02 11:29:39

标签: c# .net text adventure

7 个答案:

答案 0 :(得分:2)

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

锁定对象识别的动词

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

通过这种方式,您可以通过“您不能<动词>< object>”这样的响应来处理它无法识别的动词,并处理它通过事件或其他事件识别的动词。

修改

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

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

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

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

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

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

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

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

将它们传递给每次互动:

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

答案 3 :(得分:0)

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

我的方法是拥有一种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...     
    }    
}

所有这些都是我心中想到的一个骨架视图(我注意到了很多细微之处,但这对于一个例子来说已经足够了)。可悲的是,我甚至没有为我的项目编写一个单词,呵呵。纸上的一切。

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

我想我花了太多时间输入所有这些&gt; _&gt;

修改 PS:我的骨架示例并没有完全展示如何管理涉及多个游戏对象的命令(这只是我暗示的许多细微之处)。对于像“在窗口扔瓶子”这样的东西,你需要考虑如何管理这样的语法,例如。尝试解决这个问题的方法是解析并发现正在发布的命令......“把GO扔到GO”。找出游戏对象是什么,然后查看当前房间是否有它们。等等。

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

请原谅我的谣言......&gt; _&gt;

答案 4 :(得分:0)

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

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

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

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

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

答案 5 :(得分:0)

似乎您的问题是管理事件的传播。 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

答案 6 :(得分:0)

互动可以定义为“动词+ {过滤器列表} + {回复列表}”

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

  • 动词:填充({“填充”,“倒”})
  • 过滤器列表: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()方法时检查玩家是否有“瓶装水”。基本上,让对象变得懒惰。