簡體   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