使用代理为 NPC 注入活力 | 作者:MichaelT Shomsky | 2024年12月 | Medium
- Rifx.Online
- Programming , Roleplay , Natural Language Processing
- 20 Jan, 2025
大纲
- 模拟2D社会
- 创建一个2D俯视视角的平台游戏
- 设置具有个人历史的非玩家角色
- 开始一个框架,以允许NPC在2D可玩空间中自我行为
- Github仓库
- 结果/结论
模拟二维社会
模拟世界对我来说非常重要,以至于在我本科时,我为一个物理小组模拟了聚合物,并为我的导师小组编写了自由空间中的光传播代码。这种“想要模拟”的愿望无疑受到我荣誉工程课程的指导;因为在我大学的第一年,我们几乎完全专注于物理模拟。我在那门课上的最终项目是用 C++ 模拟一个二维世界,并由两组虚拟生命体构成:学生研究者和教师。当一个模拟的学生研究者发现一个有价值的特征时,它可以通过一个非移动的教师分享该信息,以传播知识。回想起来,这个自主项目并不是一个很好的项目,因为它主要展示了知识共享限制了对更大群体有益的有用属性的共享。这个项目所实现的,是走出课堂的常规,思考一个与物理不完全相关的共享元信息的模拟社会。
最近在2023年,我遇到了一篇关于二维社会的令人兴奋的论文,名为 Smallville ( https://arxiv.org/pdf/2304.03442 ),这让我想起了我第一个二维社会。Smallville以一种新的方式使用了LLMs:不仅生成信息,还维护一组虚拟居民。我对它如何使 NPC(非可玩角色)维持背景故事、个人历史以及过上丰富的虚拟生活(由大型语言模型决定)感到好奇。
对使用 LLMs 为一小群 NPC 注入生命的想法让我好奇我能多快地搭建类似的东西,以及我能从这个努力中学到什么。
对话派对: (灵感来源于《小镇故事》)
想象一个游戏,在这个游戏中,虚拟玩家可以自主聊天、分享故事并互相回应。项目“对话派对”是游戏与人工智能的独特结合。你作为玩家可以通过添加角色背景故事来控制模拟,并通过将角色移动得更近来引导互动。
创建一个2D俯视平台游戏
技术概述
为了实现这个互动模拟,我们采用了:
- Pygame:一个强大的游戏库,用于渲染视觉效果、处理事件以及促进玩家在游戏网格中的移动。
- OpenAI:生成AI驱动的对话响应的核心,提供虚拟玩家之间的动态互动。
- Faker:一个生成独特玩家档案的必要工具,具有随机名称和背景故事,为我们的虚拟角色增添深度。
角色与世界特征
- 玩家移动(从随机开始)
移动逻辑: 玩家在网格中导航,同时遵循边界。岩石作为障碍物,要求玩家制定路径策略,或者简单地阻碍所有方向的移动。
模拟自主性(版本 1\): 最初我们从随机角色移动开始,如果两个角色靠得很近,他们会交谈,然后分开移动。
玩家/观察者移动: 玩家通过点击一个角色来分配角色,当分配后,该角色可以由玩家/观察者移动。
未来特性: 我们从随机移动开始,但当我们启用 NPC 时,我们希望有更有意义的移动。
AI 对话
- 集成 OpenAI 以实现玩家对话
- 保存对话以保持角色深度
角色对话: 玩家通过 AI 生成的响应进行对话。
对话连续性: AI 负责根据历史对话记录生成连贯的响应,为玩家互动嵌入真实感。对话数据被持久化以确保运行之间的连续性。
对话历史: 记录的对话创建了一个连续的互动历史,增强了故事深度和玩家在多个会话中的参与感。
数据文件:
- 世界由一个 CSV 文本文件定义
- 玩家姓名 + 简介也存储在 CSV 中
游戏状态,包括世界设置和玩家数据,保存到 CSV 文件中。这确保了游戏进度可以被保存和重新加载。
这是我开始时的一个示例世界:
world.csv
grass,grass,grass,grass,rock,grass,grass,water,grass,grass
grass,grass,grass,grass,water,grass,rock,grass,water,grass
grass,water,grass,grass,grass,grass,grass,grass,water,water
water,grass,grass,grass,rock,grass,grass,grass,grass,grass
grass,grass,grass,grass,grass,grass,grass,grass,water,water
grass,grass,grass,grass,grass,water,water,grass,rock,water
grass,grass,water,grass,grass,grass,grass,rock,grass,grass
water,water,grass,water,grass,grass,grass,grass,grass,rock
water,grass,grass,grass,grass,grass,grass,water,water,water
water,grass,grass,grass,grass,water,grass,grass,rock,grass
player,Carly Cummings,4,1,1,100,100, Loves bacon, searching for bacon
player,Katherine Jones,3,0,100,100,100,Build a house,loves animals
player,Ashley Brown,1,7,100,100,100,finds nemo,loves fish
我们的初始游戏
- python Faker 库为我们的用户生成合成的名字和姓氏
- 作为玩家,生物信息和任务可以在游戏暂停时进行编辑。这将用于引导我们角色的对话
- 当游戏恢复时:一些通过偶遇的互动会基于个人的生物信息、任务和历史产生对话
简单游戏概述
简单游戏描述
游戏初始化:
- 加载资源(图像、世界数据)并初始化玩家。
- 设置初始位置并加载对话历史。
游戏循环:
- 事件处理(用户输入):
- 检测用户交互,如键盘按键和鼠标点击。
- 当点击暂停按钮时暂停或恢复游戏。
- 移动命令:上、下、左、右。
- 如果点击了玩家,则更新所选玩家。
2. 更新状态:
- 如果游戏没有暂停:
- - 移动活动玩家。
- - 移动非活动玩家并检查附近的交互以开始对话。
- - 处理玩家交互,包括对发起对话的响应。
- 如果游戏已暂停:
- - 点击玩家以查看/编辑并使其成为活动状态(这将使其在取消暂停时能够移动)
- - 更新活动用户的个人资料或任务(注意:可能会超出屏幕,但如果这是个问题,也可以更新CSV)
- - 使用上/下箭头滚动查看最近的对话
3. 渲染:
- 绘制游戏世界和玩家。
- 在暂停时从输入框更新玩家统计数据、个人资料和任务。
- 渲染UI元素,如聊天通知和暂停按钮。
玩家交互:
- 接近检测:当一个玩家接近另一个玩家时,可以发起对话。
- 对话处理:
- - 生成对话文本并使用OpenAI客户端请求回应。
- 保存并记录对话历史。
Python游戏需求文件
requirements.txt
pygame
faker
openai
Pygame 的图像资源:
- https://github.com/mtshomskyieee/agenticCity/tree/main/assets
对话派对 Python 游戏
conversation_party.py
import pygame
import random
import os
import csv
from faker import Faker
import openai
from openai import OpenAI
## 如果您想设置 openai 密钥,或者如果它未在环境中设置,请取消下面的注释
## os.environ["OPENAI_API_KEY"] = "在此处添加密钥"
## 初始化 Pygame 和 Faker
pygame.init()
fake = Faker()
## 屏幕尺寸和其他常量
GRID_SIZE = 10
CELL_SIZE = 40
WIDTH, HEIGHT = GRID_SIZE * CELL_SIZE * 2, GRID_SIZE * CELL_SIZE
FPS = 30
NUM_PLAYERS = 3 # 游戏中的玩家数量
SCROLL_SPEED = 5
RESPONSE_BOX_HEIGHT = 100
## 显示设置
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("对话派对")
clock = pygame.time.Clock()
WRAPPED_LINES = ""
## 跟踪聊天状态的全局变量
is_chatting = False
## 加载和调整图像大小的函数
def load_image(filename, size):
image = pygame.image.load(filename)
return pygame.transform.scale(image, size)
## 加载和调整图像大小
assets_dir = 'assets'
grass_img = load_image(os.path.join(assets_dir, 'grass.png'), (CELL_SIZE, CELL_SIZE))
rock_img = load_image(os.path.join(assets_dir, 'rock.png'), (CELL_SIZE, CELL_SIZE))
water_img = load_image(os.path.join(assets_dir, 'water.png'), (CELL_SIZE, CELL_SIZE))
player_imgs = [load_image(os.path.join(assets_dir, f'player_{i}.png'), (CELL_SIZE, CELL_SIZE)) for i in range(5)]
## 创建 2D 数组作为世界或从 CSV 加载
def generate_world(grid_size):
world = [['grass' for _ in range(grid_size)] for _ in range(grid_size)]
for row in range(grid_size):
for col in range(grid_size):
rand = random.random()
if rand < 0.1:
world[row][col] = 'rock'
elif rand < 0.3:
world[row][col] = 'water'
return world
def load_world_from_csv(filename):
with open(filename, mode='r') as file:
reader = csv.reader(file)
return [row for row in reader]
def save_world_to_csv(filename, world, players):
with open(filename, mode='w', newline='') as file:
writer = csv.writer(file)
for row in world:
writer.writerow(row)
# 保存玩家位置、姓名、简介和任务
for player in players:
writer.writerow(['player', player.name, player.x, player.y, player.stats['Health'], player.stats['Speed'],
player.stats['Strength'], player.bio, player.tasks])
def generate_unique_position(existing_positions):
while True:
x, y = random.randint(0, GRID_SIZE - 1), random.randint(0, GRID_SIZE - 1)
if (x, y) not in existing_positions:
return x, y
conversation_dir = 'conversations'
os.makedirs(conversation_dir, exist_ok=True)
## 全局对话日志
global_conversation_log = []
def save_conversation_history(player):
filename = os.path.join(conversation_dir, f"{player.name.replace(' ', '_')}_conversation.txt")
with open(filename, 'w') as file:
file.write("\n".join(player.all_responses))
def load_conversation_history(player):
filename = os.path.join(conversation_dir, f"{player.name.replace(' ', '_')}_conversation.txt")
if os.path.exists(filename):
with open(filename, 'r') as file:
player.all_responses = file.read().splitlines()
def save_global_conversations():
filename = os.path.join(conversation_dir, 'global_conversation.txt')
with open(filename, 'w') as file:
for entry in global_conversation_log:
file.write(entry + "\n")
client = OpenAI(
# 这是默认值,可以省略
api_key=os.environ.get("OPENAI_API_KEY"),
)
def llm_request(conversation_list, request_string):
global is_chatting
is_chatting = True
# 在进行阻塞调用之前,更新显示以显示聊天状态。
draw_chatting_notification(screen)
pygame.display.flip()
# 将对话历史与请求字符串结合起来。
conversation_history = "\n".join(conversation_list)
# 构造完整的提示
prompt = f"{conversation_history}\n{request_string}"
try:
# 使用基于聊天的端点进行 API 调用
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "你是一个人类在交谈。"},
{"role": "user", "content": prompt}
],
)
# 从消息中提取并返回响应文本
response_message = response.choices[0].message.content
is_chatting = False
return response_message
except Exception as e:
is_chatting = False
# 适当地处理错误
return f"发生错误: {str(e)}"
def wrap_text(text, font, max_width):
"""将文本换行以适应指定的宽度。"""
words = text.split(' ')
wrapped_lines = []
current_line = ""
for word in words:
# 检查如果添加这个单词,新行的宽度
if font.size(current_line + word)[0] <= max_width:
current_line += word + " "
else:
wrapped_lines.append(current_line)
current_line = word + " "
if current_line:
wrapped_lines.append(current_line)
return wrapped_lines
## 处理暂停/继续按钮的按钮类
class Button:
def __init__(self, x, y, w, h, text):
self.rect = pygame.Rect(x, y, w, h)
self.text = text
self.color = (173, 216, 230) # 浅蓝色
self.outline_color = (0, 0, 0) # 黑色轮廓
self.font = pygame.font.Font(None, 36)
def draw(self, screen):
# 绘制轮廓
pygame.draw.rect(screen, self.outline_color, self.rect, 2) # 2 像素边框
# 绘制按钮内部
pygame.draw.rect(screen, self.color, self.rect.inflate(-4, -4))
# 渲染文本
text_surf = self.font.render(self.text, True, (0, 0, 0))
text_rect = text_surf.get_rect(center=self.rect.center)
screen.blit(text_surf, text_rect)
def is_clicked(self, event):
return event.type == pygame.MOUSEBUTTONDOWN and self.rect.collidepoint(event.pos)
## 按钮设置
button_width, button_height = 120, 40
pause_button = Button(WIDTH - button_width - 10, HEIGHT - button_height - 10, button_width, button_height, "暂停")
class Player:
def __init__(self, x, y, image):
self.x = x
self.y = y
self.image = image
self.name = fake.name()
self.stats = {'Health': 100, 'Speed': 5, 'Strength': 10}
self.bio = fake.text(max_nb_chars=100)
self.tasks = fake.text(max_nb_chars=100)
self.current_conversation = ""
self.all_responses = []
self.health = 100
self.speed = 100
self.strength = 100
self.current_scroll = 0
load_conversation_history(self)
def who_am_i_string(self):
return f"我是 {self.name}. 我的背景是: {self.bio}. 我目前 {self.tasks}."
def generate_conversation(self):
self.current_conversation = self.who_am_i_string()
response_string = llm_request(self.all_responses, self.current_conversation)
self.all_responses.append(response_string)
return response_string
def respond_conversation(self, text, other_player_name):
self.current_conversation = self.who_am_i_string()
self.current_conversation += f"我正在和一个人交谈,他说: {text}"
self.current_conversation += "用一句话,我应该如何回应"
response_string = llm_request(self.all_responses, self.current_conversation)
self.all_responses.append(response_string)
print(f"{self.name} 对话:{self.current_conversation}")
print(f"{self.name} 响应:{response_string}")
# 添加到全局对话日志
global_conversation_log.append(f"{self.name} (与 {other_player_name} 交谈): {response_string}")
save_conversation_history(self)
return response_string
def find_nearby_player(player, players):
for other_player in players:
if other_player != player:
if abs(player.x - other_player.x) <= 1 and abs(player.y - other_player.y) <= 1:
return other_player
return None
## 检查 world.csv 是否存在
world_filename = 'world.csv'
if os.path.exists(world_filename):
data = load_world_from_csv(world_filename)
world = [row for row in data if row[0] not in ['player']]
players_data = [row for row in data if row[0] == 'player']
players = []
for player_data in players_data:
_, name, x, y, health, speed, strength, bio, tasks = player_data
player = Player(int(x), int(y), player_imgs[len(players)])
player.name = name
player.stats = {'Health': int(health), 'Speed': int(speed), 'Strength': int(strength)}
player.bio = bio
player.tasks = tasks
load_conversation_history(player)
players.append(player)
else:
world = generate_world(GRID_SIZE)
# 用唯一位置初始化玩家
players = []
existing_positions = set()
for i in range(NUM_PLAYERS):
x, y = generate_unique_position(existing_positions)
existing_positions.add((x, y))
players.append(Player(x, y, player_imgs[i]))
save_world_to_csv(world_filename, world, players)
active_player_idx = random.randint(0, NUM_PLAYERS - 1)
def move_player(player, dx, dy):
new_x, new_y = player.x + dx, player.y + dy
if 0 <= new_x < GRID_SIZE and 0 <= new_y < GRID_SIZE and world[new_x][new_y] != 'rock':
# 检查新位置是否被其他玩家占据
if not any(p.x == new_x and p.y == new_y for p in players):
player.x, player.y = new_x, new_y
def move_away(player, other_player):
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
random.shuffle(directions)
for dx, dy in directions:
new_x, new_y = player.x + dx, player.y + dy
if 0 <= new_x < GRID_SIZE and 0 <= new_y < GRID_SIZE and world[new_x][new_y] != 'rock':
if not any(p.x == new_x and p.y == new_y for p in players):
player.x, player.y = new_x, new_y
break
def move_inactive_players(players, active_idx):
for i, player in enumerate(players):
if i != active_idx:
nearby_player = find_nearby_player(player, players)
if nearby_player:
conversation = player.generate_conversation()
response = nearby_player.respond_conversation(conversation, player.name)
print(f"{player.name}: 对话 {conversation}")
print(f"{nearby_player.name}: 响应 {response}")
player.all_responses.append(response)
move_away(player, nearby_player)
move_away(nearby_player, player)
else:
dx, dy = random.choice([(0, 1), (1, 0), (0, -1), (-1, 0)])
move_player(player, dx, dy)
def draw_world(surface, world, players):
for row in range(GRID_SIZE):
for col in range(GRID_SIZE):
image = grass_img if world[row][col] == 'grass' else rock_img if world[row][col] == 'rock' else water_img
surface.blit(image, (col * CELL_SIZE, row * CELL_SIZE))
for player in players:
surface.blit(player.image, (player.y * CELL_SIZE, player.x * CELL_SIZE))
## 更新 draw_stats 以显示最近的响应
def draw_stats(surface, player, x_offset, y_offset):
global WRAPPED_LINES # 用于 UI 计算
font = pygame.font.Font(None, 36)
y = y_offset
# 显示玩家姓名
name_text = font.render(player.name, True, (0, 0, 0))
surface.blit(name_text, (x_offset, y))
y += 40
# 简介和任务的静态位置
bio_label_y = y
tasks_label_y = y + 40
# 渲染标签
bio_label = font.render('简介:', True, (0, 0, 0))
tasks_label = font.render('任务:', True, (0, 0, 0))
# 在同一部分显示简介和任务
surface.blit(bio_label, (x_offset, bio_label_y))
surface.blit(tasks_label, (x_offset, tasks_label_y))
# 显示最近的响应
if player.all_responses:
y+=80
wrapped_lines = wrap_text('最近: ' + player.all_responses[-1], font, WIDTH - x_offset - 20)
WRAPPED_LINES = wrapped_lines
# 滚动调试
# print(f"{player.current_scroll}, {len(wrapped_lines)} : {player.current_scroll + (RESPONSE_BOX_HEIGHT // 20)} : {WRAPPED_LINES},")
for line in WRAPPED_LINES[player.current_scroll: player.current_scroll + (RESPONSE_BOX_HEIGHT // 20)]:
surface.blit(font.render(line, True, (0, 0, 0)), (x_offset, y))
y += 20
# 滚动指示器(可选)
if len(wrapped_lines) > (RESPONSE_BOX_HEIGHT // 20) and paused:
surface.blit(font.render("[上/下]:滚动", True, (155, 155, 155)), (x_offset, y+10))
# 在暂停模式下绘制输入框
if paused:
bio_box.rect.topleft = (x_offset + 80, bio_label_y)
tasks_box.rect.topleft = (x_offset + 80, tasks_label_y)
bio_box.draw(screen)
tasks_box.draw(screen)
else:
# 未暂停,显示当前简介和任务
bio_text = font.render(player.bio, True, (0, 0, 0))
tasks_text = font.render(player.tasks, True, (0, 0, 0))
surface.blit(bio_text, (x_offset + 80, bio_label_y))
surface.blit(tasks_text, (x_offset + 80, tasks_label_y))
## 绘制“聊天中...”通知的函数
def draw_chatting_notification(surface):
notification_rect = pygame.Rect(WIDTH - 300, HEIGHT - 50, 150, 40)
pygame.draw.rect(surface, (200, 200, 255), notification_rect) # 白色框
pygame.draw.rect(surface, (0, 0, 0), notification_rect, 2) # 黑色边框
font = pygame.font.Font(None, 30)
text_surf = font.render("聊天中...", True, (0, 0, 0))
surface.blit(text_surf, (notification_rect.x + 10, notification_rect.y + 10))
class InputBox:
def __init__(self, x, y, w, h, text=''):
self.rect = pygame.Rect(x, y, w, h)
self.color_active = (173, 216, 230) # 浅蓝色
self.color_inactive = (0, 0, 0) # 黑色
self.text = text
self.txt_surface = pygame.font.Font(None, 36).render(text, True, self.color_inactive)
self.active = False
self.enter_pressed = False # 跟踪是否按下回车
def handle_event(self, event, paused):
if event.type == pygame.MOUSEBUTTONDOWN:
# 切换活动变量。
if self.rect.collidepoint(event.pos):
if paused: # 仅在游戏暂停时激活
self.active = not self.active
else:
self.active = False
if event.type == pygame.KEYDOWN:
if self.active:
if event.key == pygame.K_RETURN:
self.enter_pressed = True # 标记回车为已按下
elif event.key == pygame.K_BACKSPACE:
self.text = self.text[:-1]
else:
self.text += event.unicode
# 重新渲染文本。
self.txt_surface = pygame.font.Font(None, 36).render(self.text, True, self.color_active if self.active else self.color_inactive)
def draw(self, screen):
# 绘制文本。
screen.blit(self.txt_surface, (self.rect.x + 5, self.rect.y + 5))
# 绘制矩形。
pygame.draw.rect(screen, self.color_active if self.active else self.color_inactive, self.rect, 2)
def update_text(self, text):
self.text = text
self.txt_surface = pygame.font.Font(None, 36).render(text, True,
self.color_active if self.active else self.color_inactive)
def reset_enter_pressed(self):
self.enter_pressed = False
def get_player_at_pos(players, x, y):
for i, player in enumerate(players):
player_rect = pygame.Rect(player.y * CELL_SIZE, player.x * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if player_rect.collidepoint(x, y):
return i
return None
## 初始配置
sub_epoch = 30 # 时间增量
idle_mod = 20 % sub_epoch # 空闲事物的更新周期
idle_count = 0
running = True
paused = False
selected_player = players[active_player_idx]
## 为玩家属性创建输入框
stat_boxes = [
InputBox(GRID_SIZE * CELL_SIZE + 100, 100 + i * 40, 100, 32, str(getattr(selected_player, stat.lower())))
for i, stat in enumerate(['Health', 'Speed', 'Strength'])
]
## 260, 350
bio_box = InputBox(GRID_SIZE * CELL_SIZE + 20, 20, 300, 32, selected_player.bio)
tasks_box = InputBox(GRID_SIZE * CELL_SIZE + 20, 50, 300, 32, selected_player.tasks)
#input_boxes = stat_boxes + [bio_box, tasks_box]
input_boxes = [bio_box, tasks_box]
while running:
idle_count = (idle_count + 1) % sub_epoch
for event in pygame.event.get():
if pause_button.is_clicked(event):
paused = not paused
pause_button.text = "继续" if paused else "暂停"
if event.type == pygame.QUIT:
save_world_to_csv(world_filename, world, players)
for player in players:
save_conversation_history(player)
save_global_conversations()
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = event.pos
clicked_player_idx = get_player_at_pos(players, mouse_x, mouse_y)
if clicked_player_idx is not None:
active_player_idx = clicked_player_idx
selected_player = players[active_player_idx]
for stat, box in zip(selected_player.stats.values(), stat_boxes):
box.update_text(str(stat))
bio_box.update_text(selected_player.bio)
tasks_box.update_text(selected_player.tasks)
# 即使暂停也处理输入框
for box in input_boxes:
box.handle_event(event, paused)
# 仅在未暂停时处理其他游戏逻辑
if not paused:
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
move_player(players[active_player_idx], -1, 0)
if keys[pygame.K_DOWN]:
move_player(players[active_player_idx], 1, 0)
if keys[pygame.K_LEFT]:
move_player(players[active_player_idx], 0, -1)
if keys[pygame.K_RIGHT]:
move_player(players[active_player_idx], 0, 1)
if idle_count % idle_mod == 0:
move_inactive_players(players, active_player_idx)
else: # 暂停时,允许滚动
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
if selected_player.current_scroll > 0:
selected_player.current_scroll -= SCROLL_SPEED
elif keys[pygame.K_DOWN]:
if selected_player.current_scroll < len(WRAPPED_LINES) - (RESPONSE_BOX_HEIGHT // 20):
selected_player.current_scroll += SCROLL_SPEED
screen.fill((255, 255, 255))
draw_world(screen, world, players)
for stat, box in zip(selected_player.stats.keys(), stat_boxes):
try:
value = int(box.text)
selected_player.stats[stat] = value
except ValueError:
pass
selected_player.bio = bio_box.text
selected_player.tasks = tasks_box.text
draw_stats(screen, selected_player, GRID_SIZE * CELL_SIZE + 20, 10)
# 如果暂停,则允许输入更新
if paused:
for box in input_boxes:
box.draw(screen)
# 绘制暂停/继续按钮
pause_button.draw(screen)
# 如果聊天启用,则绘制聊天通知
if is_chatting:
draw_chatting_notification(screen)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
结果/结论
- 游戏,如同模拟,关心某种时间的模拟。在我们的案例中,时间是通过主循环的迭代来衡量的。由于主循环不断运行,但有时也允许编辑,这可能会中断,因此增加了一个“暂停/恢复”按钮。这样我们就可以在必要时停止时间。
- 初始角色设定为有“喜欢动物”、“喜欢鱼”和“寻找培根”等背景故事,但缺乏应有的深度,这应该会导致更丰富的互动。
- 在当前简单的设置下,出现了大量的食谱分享;这证实了基础游戏的运作足够良好,是时候改变参数,看看会发生什么样的对话。
下一步
- 与不同人群的对话分析
- 增加玩家的深度,并允许他们与环境互动
Github Repo
如果您觉得这篇文章很有启发性,请考虑为这篇文章点赞——这不仅支持作者,还帮助其他人发现有价值的见解。此外,别忘了订阅以获取更多深入探讨创新技术和人工智能发展的文章。非常感谢您的参与!
参考文献
- Park, Joon Sung, et al. “Generative Agents: Interactive Simulacra of Human Behavior.” arXiv 预印本 arXiv:2304.03442 (2023). 可在以下网址获取: https://arxiv.org/pdf/2304.03442