Type something to search...
使用代理为 NPC 注入活力 | 作者:MichaelT Shomsky | 2024年12月 | Medium

使用代理为 NPC 注入活力 | 作者:MichaelT Shomsky | 2024年12月 | Medium

大纲

  • 模拟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 库为我们的用户生成合成的名字和姓氏
  • 作为玩家,生物信息和任务可以在游戏暂停时进行编辑。这将用于引导我们角色的对话
  • 当游戏恢复时:一些通过偶遇的互动会基于个人的生物信息、任务和历史产生对话

简单游戏概述

简单游戏描述

游戏初始化:

  • 加载资源(图像、世界数据)并初始化玩家。
  • 设置初始位置并加载对话历史。

游戏循环:

  1. 事件处理(用户输入):
  • 检测用户交互,如键盘按键和鼠标点击。
  • 当点击暂停按钮时暂停或恢复游戏。
  • 移动命令:上、下、左、右。
  • 如果点击了玩家,则更新所选玩家。

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

Related Posts

使用 ChatGPT 搜索网络功能的 10 种创意方法

使用 ChatGPT 搜索网络功能的 10 种创意方法

例如,提示和输出 你知道可以使用 ChatGPT 的“搜索网络”功能来完成许多任务,而不仅仅是基本的网络搜索吗? 对于那些不知道的人,ChatGPT 新的“搜索网络”功能提供实时信息。 截至撰写此帖时,该功能仅对使用 ChatGPT 4o 和 4o-mini 的付费会员开放。 ![](https://images.weserv.nl/?url=https://cdn-im

阅读更多
在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和科技这样一个动态的行业中,保持领先意味着不断提升你的技能。无论你是希望深入了解人工智能模型性能、掌握数据分析,还是希望通过人工智能转变传统领域如法律,这些课程都是你成功的捷径。以下是一个精心策划的高价值课程列表,可以助力你的职业发展,并让你始终处于创新的前沿。 1. 生成性人工智能简介课程: [生成性人工智能简介](https://genai.works

阅读更多
10 个强大的 Perplexity AI 提示,让您的营销任务自动化

10 个强大的 Perplexity AI 提示,让您的营销任务自动化

在当今快速变化的数字世界中,营销人员总是在寻找更智能的方法来简化他们的工作。想象一下,有一个个人助理可以为您创建受众档案,建议营销策略,甚至为您撰写广告文案。这听起来像是一个梦想? 多亏了像 Perplexity 这样的 AI 工具,这个梦想现在成为现实。通过正确的提示,您可以将 AI 转变为您的 个人营销助理。在本文中,我将分享 10 个强大的提示,帮助您自动

阅读更多
10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

人工智能技术,如机器学习、自然语言处理和数据分析,正在重新定义传统设计方法。从自动化重复任务到实现个性化用户体验,人工智能使设计师能够更加专注于战略思维和创造力。随着这一趋势的不断增长,UI/UX 设计师越来越多地采用 AI 驱动的工具来促进他们的工作。利用人工智能不仅能提供基于数据的洞察,还为满足多样化用户需求的创新设计解决方案开辟了机会。 1. 用户角色开发 目的

阅读更多
在几分钟内完成数月工作的 100 种人工智能工具

在几分钟内完成数月工作的 100 种人工智能工具

人工智能(AI)的快速发展改变了企业的运作方式,使人们能够在短短几分钟内完成曾经需要几周或几个月的任务。从内容创作到网站设计,AI工具帮助专业人士节省时间,提高生产力,专注于创造力。以下是按功能分类的100个AI工具的全面列表,以及它们在现实世界中的使用实例。 1. 研究工具 研究可能耗时,但人工智能工具使查找、分析和组织数据变得更加容易。**ChatGPT, Cop

阅读更多
你从未知道的 17 个令人惊叹的 GitHub 仓库

你从未知道的 17 个令人惊叹的 GitHub 仓库

Github 隐藏的宝石!! 立即收藏的代码库 学习编程相对简单,但掌握编写更好代码的艺术要困难得多。GitHub 是开发者的宝藏,那里“金子”是其他人分享的精心编写的代码。通过探索 GitHub,您可以发现如何编写更清晰的代码,理解高质量代码的样子,并学习成为更熟练开发者的基本步骤。 1. notwaldorf/emoji-translate *谁需

阅读更多