
您在此处看到一个大型语言模型:将语言模型集成到您的文本冒险游戏中
图片作者 | Midjourney
引言
基于文本的冒险游戏具有永恒的吸引力。它们让玩家能够通过语言的力量,想象出整个世界,从阴森的地下城和高耸的城堡,到未来的宇宙飞船和神秘的领域。如今,将ChatGPT这样的大型语言模型(LLMs)集成到这些游戏中,通过提供动态生成的描述、角色对话等,将这一概念提升到了新的高度——而且是实时生成的。
文字冒险游戏依赖于想象力。传统上,游戏开发者会将房间、物品和NPC(非玩家角色)交互的所有描述硬编码到游戏中,这意味着每一种可能的路径或场景都必须手工制作。这对于小型或更静态的游戏来说是没问题的,但一旦你的项目开始增长,它就会变得越来越麻烦。另一方面,使用LLM可以给你带来新鲜、多样化和动态的文本,而无需你编写每一个细节。
此外,现代LLM的功能不仅仅是生成文本;它们可以在一次交互到下一次交互之间保持上下文,从而能够构建连贯的叙事,适应玩家的选择,或者反映故事的进展。虽然我们必须注意token和上下文的限制,但文本冒险与语言模型之间的协同作用的可能性是巨大的。
在本教程中,我们将逐步介绍构建一个简单的基于Python的文字冒险游戏,然后使用LLM对其进行升级。我们将重点关注以下主要步骤:
- 设置一个简单的文字冒险框架
- 使用JSON表示游戏数据
- 集成ChatGPT以实现动态房间描述
- 使用ChatGPT生成NPC对话
- 整合所有内容
完成后,我们将得到一个完整、可运行的原型,您可以在此基础上进行扩展,并根据自己的创意进行定制。
本指南假设您对Python基础知识、API使用以及JSON格式数据处理有一定的了解。阅读完本文,您将看到在最简单的基于文本的互动小说中添加LLM驱动的叙事是多么直接(和强大)。
创建简单的文字冒险框架
在加入任何AI魔法之前,让我们用Python构建一个基础的文字冒险游戏。我们将首先创建一个最小的引擎,该引擎从JSON文件中加载游戏数据(房间、NPC、物品等),并允许玩家进行导航。在后续的步骤中,我们将添加LLM驱动的增强功能。
基本项目结构
典型的文件结构可能如下所示:
text_adventure/
├── game_data.json
├── text_adventure.py
└── README.md
game_data.json文件将存储我们的基本游戏数据——房间名称、描述、可用出口、物品等。text_adventure.py文件将加载此JSON文件,解析数据,并提供游戏循环(用户输入、响应、状态管理)。
创建game_data.json
以下是一个简单的JSON文件示例,用于演示我们如何构建数据,并为我们提供一些后续使用的内容。稍后,我们将进行扩展或修改以集成LLM。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
{ "rooms": { "room_entrance": { "name": "Castle Entrance", "description": "You are standing at the grand entrance to an ancient stone castle. Torches line the walls.", "exits": { "north": "room_hallway" } }, "room_hallway": { "name": "Great Hallway", "description": "An expansive hallway stretches before you with doors leading to unknown chambers.", "exits": { "south": "room_entrance", "east": "room_armory" } }, "room_armory": { "name": "Armory", "description": "Shields, swords, and arcane artifacts line the walls of this dimly lit armory.", "exits": { "west": "room_hallway" } } }, "player": { "start_room": "room_entrance", "inventory": [] } } |
此JSON声明了我们的游戏中的三个房间——room_entrance、room_hallway和room_armory——每个房间都有一个名称、一个描述以及一个指向其他房间的出口字典。player对象定义了开始房间和空的背包。我们可以实现这一点的方法有很多,但这种简单的方式对我们来说是有效的。
text_adventure.py中的Python框架
让我们来编写我们的基础文字冒险引擎的代码。这个版本不包含任何LLM集成——只有最基本的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import json class TextAdventureGame: def __init__(self, game_data_path): with open(game_data_path, 'r') as f: self.game_data = json.load(f) self.rooms = self.game_data.get("rooms", {}) self.player_data = self.game_data.get("player", {}) self.current_room = self.player_data.get("start_room", "") self.inventory = self.player_data.get("inventory", []) def describe_current_room(self): room = self.rooms[self.current_room] print(f"\n{room['name']}") print(room['description']) def get_user_input(self): return input("\n> ").strip().lower() def move_player(self, direction): room = self.rooms[self.current_room] room = self.rooms[self.current_room] if direction in exits: self.current_room = exits[direction] print(f"You move {direction} to the {self.rooms[self.current_room]['name']}.") else: print("You can't go that way.") def play(self): print("Welcome to the Text Adventure!") self.describe_current_room() while True: command = self.get_user_input() if command in ["quit", "exit"]: print("Thanks for playing!") break elif command in ["north", "south", "east", "west"]: self.move_player(command) self.describe_current_room() elif command in ["look", "examine"]: self.describe_current_room() else: print("I don't understand that command.") if __name__ == "__main__": game = TextAdventureGame("game_data.json") game.play() |
代码的分解如下:
- 初始化:我们加载game_data.json并将其解析为Python对象。
- 游戏状态:我们存储可用房间,跟踪当前房间,并维护背包。
- describe_current_room:打印当前房间的简短文字描述。
- move_player:如果游戏数据中存在有效出口,允许玩家在房间之间移动。
- 游戏循环
- 提示玩家输入。
- 解释诸如“north”、“south”、“east”、“west”或“quit”之类的命令。
- 在移动命令时,更新当前房间并进行描述。
- 如果输入“look”或“examine”,则调用describe_current_room。
有了这个结构,我们就拥有了一个简单的基于文本的游戏。接下来,我们将扩展我们的JSON数据以包含房间的“元描述”,集成LLM来生成最终的文本描述,并将其用于NPC对话。
然后,键入以下命令运行游戏:
1 |
python text_adventure.py |
这是我们目前游戏的样子。
集成ChatGPT以实现动态房间描述
核心思想是,将每个房间的元描述存储在我们的JSON中,而不是完整的描述。然后,我们将此元描述与一些指令一起传递给ChatGPT,并要求ChatGPT生成更详细或更具主题性的最终文本。这使得您的游戏能够在所有房间中保持一致的质量和风格,而无需手动编写大段文本。
在JSON中添加元描述
让我们修改我们的rooms条目以包含meta_description字段。为了演示,我们将删除手动编写的description,而完全依赖LLM生成文本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
{ "rooms": { "room_entrance": { "name": "Castle Entrance", "meta_description": "castle entrance, lined with torches, stone walls, imposing wooden doors, possible presence of guards", "exits": { "north": "room_hallway" } }, "room_hallway": { "name": "Great Hallway", "meta_description": "long hallway, stained glass windows, tapestries on the walls, echoes of footsteps, high vaulted ceiling", "exits": { "south": "room_entrance", "east": "room_armory" } }, "room_armory": { "name": "Armory", "meta_description": "room filled with weapons and armor, a faint smell of metal and oil, possible magical artifacts", "exits": { "west": "room_hallway" } } }, "player": { "start_room": "room_entrance", "inventory": [] } } |
设置ChatGPT API
要集成ChatGPT,您需要:
- 一个OpenAI API密钥。
- openai Python包(pip install openai)。
安装后,我们可以使用API向ChatGPT发送提示。让我们在TextAdventureGame类中举例说明一个新方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
import json import os from openai import OpenAI class TextAdventureGame: def __init__(self, game_data_path): with open(game_data_path, 'r') as f: self.game_data = json.load(f) self.rooms = self.game_data.get("rooms", {}) self.player_data = self.game_data.get("player", {}) self.current_room = self.player_data.get("start_room", "") self.inventory = self.player_data.get("inventory", []) # Initialize OpenAI client self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) def describe_current_room(self): room = self.rooms[self.current_room] # We'll call our LLM function here to get the final description description = self.generate_room_description(room) print(f"\n{room['name']}") print(description) def generate_room_description(self, room): # Build a prompt with the meta_description prompt = ( "You are a game narrative engine. Given a meta description, produce a vivid, " "immersive, and thematically consistent room description for players. " "Do not break character, keep it short (2-3 sentences). " f"Meta description: {room['meta_description']}" ) try: response = self.client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "You are a game narrative engine."}, {"role": "user", "content": prompt} ], max_tokens=100, temperature=0.7, n=1, stop=None ) return response.choices[0].message.content.strip() except Exception as e: print("Error calling OpenAI API:", e) # Fallback: return the meta_description directly return room['meta_description'] def get_user_input(self): return input("\n> ").strip().lower() def move_player(self, direction): room = self.rooms[self.current_room] room = self.rooms[self.current_room] if direction in exits: self.current_room = exits[direction] print(f"You move {direction} to the {self.rooms[self.current_room]['name']}.") else: print("You can't go that way.") def play(self): print("Welcome to the Text Adventure!") self.describe_current_room() while True: command = self.get_user_input() if command in ["quit", "exit"]: print("Thanks for playing!") break elif command in ["north", "south", "east", "west"]: self.move_player(command) self.describe_current_room() elif command in ["look", "examine"]: self.describe_current_room() else: print("I don't understand that command.") if __name__ == "__main__": game = TextAdventureGame("game_data.json") game.play() |
这是如何工作的:
- 我们创建了一个名为generate_room_description的新函数,该函数:
- 以room字典为输入。
- 构建一个ChatGPT的提示,引用房间的meta_description。
- 调用ChatGPT API生成最终的、充实的内容。
- 返回该文本,然后将其打印给玩家。
- 对于生产环境,请考虑进一步定制提示。例如,您可能希望包含关于游戏风格(例如,喜剧、黑暗奇幻、高奇幻、科幻等)的说明,或指定格式限制。
- 我们设置了一个备用方案,在API失败时返回meta_description。这确保了您的游戏在离线状态或出现问题时仍能运行。创建更好的默认描述可能是一个选择。
- model=”gpt-3.5-turbo”被使用,因为它比gpt-4-turbo运行得稍快一些,并减少了游戏延迟。
完成此操作后,您的文字冒险游戏现在将使用ChatGPT动态生成房间描述。每次玩家进入新房间时,他们都会看到新的文本,通过调整元描述或提示模板可以轻松修改。
要测试集成,请确保您已设置环境变量OPENAI_API_KEY为您自己的API密钥,方法是在命令提示符处键入:
1 |
export OPENAI_API_KEY="sk-xxxx" |
再次运行带有LLM集成的游戏。
1 |
python text_adventure.py |
使用ChatGPT生成NPC对话
使用LLM的另一个引人注目的功能是生成交互式对话。您可以通过存储NPC的性格、角色或知识的元描述来设计一个“角色”或“NPC”。然后,您可以使用这些详细信息以及最近的对话来提示ChatGPT,以生成他们的对话。
扩展JSON
让我们在game_data.json中添加一个NPC。我们将把NPC放在走廊里,并为它的性格和功能提供元描述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
{ "rooms": { "room_entrance": { "name": "Castle Entrance", "meta_description": "castle entrance, lined with torches, stone walls, imposing wooden doors, possible presence of guards", "exits": { "north": "room_hallway" } }, "room_hallway": { "name": "Great Hallway", "meta_description": "long hallway, stained glass windows, tapestries on the walls, echoes of footsteps, high vaulted ceiling", "exits": { "south": "room_entrance", "east": "room_armory" }, "npc": "npc_guard" }, "room_armory": { "name": "Armory", "meta_description": "room filled with weapons and armor, a faint smell of metal and oil, possible magical artifacts", "exits": { "west": "room_hallway" } } }, "npcs": { "npc_guard": { "name": "Castle Guard", "meta_description": "stern, loyal, short-tempered guard. Has knowledge of the castle's secrets but rarely shares." } }, "player": { "start_room": "room_entrance", "inventory": [] } } |
注意新的npcs部分,其中包含npc_guard。room_hallway包含一个“npc”: “npc_guard”字段,表示该卫兵在该房间里。
生成NPC对话
我们将添加处理NPC交互的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
import os import json import openai class TextAdventureGame: def __init__(self, game_data_path): with open(game_data_path, 'r') as f: self.game_data = json.load(f) self.rooms = self.game_data.get("rooms", {}) self.npcs = self.game_data.get("npcs", {}) self.player_data = self.game_data.get("player", {}) self.current_room = self.player_data.get("start_room", "") self.inventory = self.player_data.get("inventory", []) openai.api_key = os.getenv("OPENAI_API_KEY") def generate_room_description(self, room): prompt = ( "You are a game narrative engine. Given a meta description, produce a vivid, immersive, " "and thematically consistent room description for players. Keep it to 2-3 sentences. " f"Meta description: {room['meta_description']}" ) try: response = openai.Completion.create( engine="text-davinci-003", prompt=prompt, max_tokens=100, temperature=0.7 ) return response.choices[0].text.strip() except Exception as e: print("Error calling OpenAI API:", e) return room['meta_description'] def generate_npc_dialogue(self, npc_id, player_input=None): npc_data = self.npcs.get(npc_id, {}) prompt = ( f"You are a character in a text adventure game. Your personality: {npc_data['meta_description']}. " "The player has asked you something or approached you. Please respond in one or two sentences. " ) if player_input: prompt += f"\nPlayer input: {player_input}\n" try: response = openai.Completion.create( engine="text-davinci-003", prompt=prompt, max_tokens=100, temperature=0.7 ) return response.choices[0].text.strip() except Exception as e: print("Error calling OpenAI API:", e) return "The NPC stares silently, unable to respond." def describe_current_room(self): room = self.rooms[self.current_room] description = self.generate_room_description(room) print(f"\n{room['name']}") print(description) # 检查是否有NPC npc_id = room.get("npc", None) if npc_id: dialogue = self.generate_npc_dialogue(npc_id) npc_name = self.npcs[npc_id]['name'] print(f"\n{npc_name} says: \"{dialogue}\"") def get_user_input(self): return input("\n> ").strip().lower() def move_player(self, direction): room = self.rooms[self.current_room] room = self.rooms[self.current_room] if direction in exits: self.current_room = exits[direction] print(f"You move {direction} to the {self.rooms[self.current_room]['name']}.") else: print("You can't go that way.") def talk_to_npc(self): room = self.rooms[self.current_room] npc_id = room.get("npc", None) if npc_id: player_input = input("What do you say? ") response = self.generate_npc_dialogue(npc_id, player_input) print(f"{self.npcs[npc_id]['name']} replies: \"{response}\"") else: print("There's no one here to talk to.") def play(self): print("Welcome to the Text Adventure!") self.describe_current_room() while True: command = self.get_user_input() if command in ["quit", "exit"]: print("Thanks for playing!") break elif command in ["north", "south", "east", "west"]: self.move_player(command) self.describe_current_room() elif command.startswith("talk"): self.talk_to_npc() elif command in ["look", "examine"]: self.describe_current_room() else: print("I don't understand that command.") if __name__ == "__main__": game = TextAdventureGame("game_data.json") game.play() |
这是对我们最新游戏引擎迭代的解释
- 我们添加了一个从 JSON 加载的 **npcs** 字典
- 在 **describe_current_room** 中,如果当前房间有 NPC,我们会生成一个问候语或初始对话
- 一个新方法 **talk_to_npc** 读取玩家说的话,并将其发送到 ChatGPT 提示中,返回 NPC 的回应
- 当玩家输入“talk”或“talk guard”等内容时,将触发 **talk_to_npc** 方法
然后,再次运行游戏,输入
1 |
python text_adventure.py |
您现在应该可以使用“north”、“east”、“west”或“south”等命令在房间之间移动。当您在有守卫的走廊时,可以输入“talk”或“talk guard”进行对话。完成后,键入“quit”或“exit”即可结束游戏。
如果一切顺利,您应该会看到每个房间都有动态生成的描述,而守卫的回应应该与其脾气暴躁、忠诚的性格相符。
提示结构包括 NPC 的元描述,确保 ChatGPT 能够根据 NPC 的个性制作符合其个性的对话。我们还可以选择性地维护对话历史,以建立更深入、基于上下文的 NPC 交互。为了简洁起见,此示例仅保持简单,只使用单个提示。
整合
至此,我们的文本冒险框架结合了
- 基于 JSON 的游戏数据:存储房间、物品引用、 NPC 信息和元描述
- Python 游戏循环:处理用户输入以进行移动、交互和对话
- ChatGPT 集成
- 房间描述:从元描述生成,以产生沉浸式文本
- NPC 对话:用于与角色创建动态的、由个性驱动的交流
部署和扩展的注意事项
- API 成本:每次调用 ChatGPT API 都会产生相关费用。对于流量很大的游戏或持久性世界,请考虑您使用的 token 数量。您可以尝试使用更短的提示或专门的、更小的模型来快速响应。
- 缓存:对于重复的房间访问,您可能希望缓存生成的描述,以避免每次玩家返回房间时重新生成它们(从而产生更多费用)。
- 上下文管理:如果您希望 NPC 在多次对话中记住细节,您需要存储对话历史或相关状态。较大的内存可能会在 token 方面产生费用,因此请考虑总结或限制上下文窗口。
- 提示调整:LLM 输出的质量和风格在很大程度上取决于您如何构建提示。通过调整指令,您可以控制长度、风格、语气或游戏中的剧透内容。
总结
像 ChatGPT 这样的 LLM 为基于文本的互动小说开启了新的领域。您不必费力地写下每一句散文,而是可以为房间、物品或角色存储最小的元描述,然后让模型填补细节。本文概述的方法展示了如何使用 Python、JSON 和 ChatGPT API 将 LLM 驱动的房间描述、NPC 对话等内容无缝集成。
概括一下
- 我们用 Python 构建了一个简单的文本冒险框架,从 JSON 加载静态游戏数据
- 我们增加了 LLM 功能,将元描述转化为丰富、大气的文本
- 我们扩展了 NPC 系统,以便与玩家进行简短的、由个性驱动的对话
有了这个基础,可能性几乎是无限的。您可以增强物品描述,创建适应玩家进度的动态谜题,甚至可以即时生成整个故事线。虽然需要谨慎——尤其是在 token 使用、成本和生成模型的不确定性方面——但结果可能引人入胜、身临其境且异常灵活。
所以,大胆去尝试吧。使用基于 LLM 的生成,为您的文本冒险增添惊喜和自发性。将这些技术与您自己的设计创造力相结合,您将能够构建感觉鲜活、不受静态代码行限制的世界。您现在拥有让 LLM 塑造玩家探索的故事的工具。祝您冒险愉快!
暂无评论。