10

编辑:如果您懒得阅读这个庞大的问题,我在底部做了一个总结。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

正如你所看到的,我需要一种通用的方式来定义每个“交互”的基础,该操作的后果是什么,并适当地更新其他对象,如项目、播放器(用于库存)和区域。

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

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

例如,在上面的示例中,对于第一个交互“加水”,我会告诉它返回一个响应(“你已经给瓶子装满了水”),并调用一个 ReplaceItem 方法来替换“瓶”主题与“瓶水”。

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

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

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

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

我要解决的问题是交互还需要做许多其他事情,这些事情因每个特定的交互而异。让我们以玻璃瓶为例。

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

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

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

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

4

7 回答 7

2

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

锁定对象识别动词

  • UseItemOn(Key001, LockPicks, 大锤, ...)
  • 冲床

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

编辑

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

因此,如果您计划让墙响应任何抛出的物体,那么您需要将 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) 方法。这些方法中的每一个都可以对交互中的对象和其他参与者产生特定的状态变化。如果您愿意,可以使用事件来执行此操作。

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

于 2010-10-02T13:39:00.397 回答
1

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

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

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

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
   }
}
于 2010-10-02T12:00:20.547 回答
0

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

对于您的“装满水的瓶子”示例,交互将是:

  • 动词: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")

然后,如果您需要“向窗户扔瓶水”:

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

于 2010-10-03T14:46:48.563 回答
0

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

我认为结合 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

于 2010-10-03T05:59:21.563 回答
0

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

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

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

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

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

于 2010-10-02T22:17:53.383 回答
0

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

我的方法是拥有一种 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 实例中。(这就是我的项目所在。)

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

于 2010-10-02T17:45:43.563 回答
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());

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

另请参阅双重调度

于 2010-10-02T12:12:32.097 回答