(16-2)多智能体强化学习实战:Predator-Prey游戏(2)

16.4  环境准备

在开始编写程序代码之前需要完成环境准备的工作,主要包括安装依赖库、导入模块和配置环境等工作。在完成这些工作后,就可以开始编写和执行项目的代码,利用所导入的库和模块来实现项目的功能。环境准备是项目开发的重要一步,它确保了你的代码能够在特定的环境中顺利运行。

16.4.1  安装OpenAI gymnasium

本项目需要使用使用OpenAI的强化学习环境实现,从2021年开始,OpenAI的强化学习环境的接口从gym变成了gymnasium。本项目使用的是OpenAI的最新版的强化学习环境,在使用之前需要通过如下命令进行安装:

pip install gymnasium

OpenAI gymnasium是一个用于强化学习研究和开发的Python库,提供了一系列标准化的强化学习环境,让开发者能够轻松地创建、测试和比较不同强化学习算法。这个库的目标是为强化学习领域提供一个公共的基准测试平台,以帮助研究人员和开发者共同探索强化学习算法的性能和效果。

OpenAI gymnasium提供了各种各样的环境,包括经典的强化学习问题如CartPole、MountainCar和Atari游戏等。它还支持用户自定义环境的创建,以适应各种不同的强化学习任务。通过使用 OpenAI gymnasium,研究人员和开发者可以轻松地测试和比较不同的强化学习算法,以便进一步推进强化学习领域的研究和应用。

16.4.2  导入库

导入本项目所需要的多个Python库和模块,并设置了使用GPU(如果可用)设备。具体实现代码如下所示。

from collections import namedtuple, deque
import random 
import gymnasium as gym
from gymnasium import spaces
import pygame
import numpy as np
import pandas as pd
import time  # for sleep
from itertools import count
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

在上述代码中,“device”的功能是根据可用的CUDA(GPU)设备来选择在GPU上还是CPU上运行PyTorch代码。

16.5  捕食者-猎物(Predator-Prey)的环境

创建一个名为PredatorPreyEnv的自定义强化学习环境类,用于模拟实现“捕食者-猎物”(Predator-Prey)的环境。这是一个捕食者游戏的模拟环境,其中有捕食者和猎物,但只有一个捕食者需要捕食猎物,而所有的参与者都会获得奖励。对环境PredatorPreyEnv的具体说明如下:

  1. Predator Prey(捕食者-猎物):这个环境中包括两种类型的角色,一种是捕食者(Predator),另一种是猎物(Prey)。捕食者的目标是捕获猎物,而猎物的目标是躲避捕食者。
  2. Only One Predator needs to eat(只需一个捕食者吃到猎物):在这个环境中,只要有一个捕食者成功捕食到猎物,所有的捕食者都会获得奖励。这意味着所有的捕食者共享奖励,无论哪一个实际上吃到了猎物。

环境PredatorPreyEnv通常用于研究协作策略和共享奖励的情况,捕食者必须合作以确保至少有一个成功捕食猎物,以便让所有的捕食者都能获得奖励。在本节的内容中,将详细讲解这个环境的实现过程。

16.5.1  定义自定义强化学习环境类

定义了一个名为 PredatorPreyEnv 的自定义强化学习环境类,用于模拟捕食者-猎物的情境。具体实现代码如下所示。

PredatorPreyEnv(gym.Env):
    metadata = {'render.modes': ['human', 'rgb_array'],
                'render-fps': 4}
    
    def __init__(self,
                render_mode=None,
                size:int=10,
                vision:int=5,
                predator:int =3,
                prey:int =1,
                error_reward:float=-2,
                success_reward:float=10,
                living_reward:float=-1,
                img_mode:bool=False,
                episode_length:int=100,
                history_length:int=4,
                communication_bits:int=0,
                cooperate:float=1):
        self.size  = size
        self.vision = vision
        self.window_size = 500
        self.render_mode = render_mode
        self.predator_num = predator
        self.prey_num = prey
        self.active_predator = [True for i in range(self.predator_num)]
        self.active_prey = [True for i in range(self.prey_num)]
        self.error_reward = error_reward
        self.success_reward = success_reward
        self.living_reward = living_reward
        self.episode_length = episode_length
        self.img_mode = img_mode
        self.steps = 0
        self.window = None
        self.clock = None
        self.cooperate = cooperate
        self.render_scale = 1
        self.observation_space = spaces.Dict({
            'predator': spaces.Sequence(spaces.Box(0, size-1, shape=(2,), dtype=np.int32)),
            'prey': spaces.Box(0, size-1, shape=(2,), dtype=np.int32),
        })
        total_actions = 5
        self.action_space_predator = spaces.MultiDiscrete([total_actions]*predator)
        self.action_space_prey = spaces.MultiDiscrete([total_actions]*prey)
        self.single_action_space = spaces.Discrete(total_actions)
        self._action_to_direction = {
            0: np.array([0, 1]),
            1: np.array([1, 0]),
            2: np.array([0, -1]),
            3: np.array([-1, 0]),
            4: np.array([0, 0])
        }
        self.frame_history = deque(maxlen=4)
        self.history_length = history_length
        self.communication_bits = communication_bits
        if self.communication_bits>0:
            self.pred_communication = np.zeros((self.predator_num))
            self.prey_communication = np.zeros((self.prey_num))

上述代码的实现流程如下:

  1. 定义了一个自定义强化学习环境类 PredatorPreyEnv,该类继承自gym.Env。
  2. 允许通过参数来配置环境的各种属性,包括环境大小、视觉范围、捕食者和猎物数量、奖励函数等。
  3. 定义了观察空间和动作空间,以规定代理程序的感知和行动范围。
  4. 实现了环境的初始化、状态重置、动作执行和渲染等核心方法。
  5. 可以在图形界面或以RGB数组的形式进行渲染。

初始化方法 __init__定义了环境的初始化逻辑,包括环境的参数设置、观察空间和动作空间的定义等。具体实现代码如下所示:

  1. 定义初始化环境的各种属性和参数,包括环境大小、视觉范围、捕食者和猎物数量、奖励函数等。
  2. 定义了观察空间和动作空间,观察空间是一个包含捕食者和猎物位置信息的字典,动作空间是分别为捕食者和猎物定义的多离散动作空间。
  3. 初始化环境的状态,包括捕食者和猎物的随机位置、时间步数等。
  4. 设置渲染相关的属性,如渲染模式、窗口大小等。

16.5.2  定义自定义强化学习环境类

分别定义两个方法 _get_obs 和 _get_np_arr_obs,用于获取环境的观察信息(observation)。具体实现代码如下所示。

    def _get_obs(self):
        if self.img_mode:
            return self._get_np_arr_obs()
        return {
            'predator': self._predator_location,
            'prey': self._prey_location
        }
    
    def _get_np_arr_obs(self):
        predator_states = []
        prey_states = []
        for i in range(len(self._predator_location)):
            state = self._render_predator_frame(predator_id=i)
            predator_states.append(state)
        for i in range(len(self._prey_location)):
            state = self._render_prey_frame(prey_id=i)
            prey_states.append(state)
        return {
            "predator":predator_states, 
            "prey":prey_states
        }

对上述代码的具体说明如下:

  1. 方法_get_obs:功能是根据环境的 img_mode 属性决定返回的观察信息的格式。如果 img_mode 为 True,则调用 _get_np_arr_obs 方法以图像数组的形式返回观察信息;如果 img_mode 为 False,则以字典的形式返回观察信息,包括捕食者和猎物的位置信息。
  2. 方法_get_np_arr_obs:功能是如果 img_mode 为 True,则调用此方法以获取以图像数组的形式表示的观察信息。该方法会遍历捕食者和猎物,分别调用 _render_predator_frame 和 _render_prey_frame 方法获取它们的图像状态。该返回一个字典,包括 "predator" 和 "prey" 两个键,对应的值是捕食者和猎物的图像状态列表。

上述方法的目的是获取环境的观察信息,这些信息可以用于代理程序感知环境的当前状态。根据 img_mode 的设置,可以选择返回图像状态或位置信息,以适应不同类型的强化学习代理程序。在 _get_np_arr_obs 方法中,捕食者和猎物的状态是通过调用 _render_predator_frame 和 _render_prey_frame 方法生成的图像帧来表示的。

16.5.3  环境重置

定义reset 方法,用于重置环境状态,开始新的回合(episode)。在reset方法中,随机生成捕食者和猎物的初始位置。将时间步数(steps)重置为0,同时将所有捕食者和猎物标记为“活跃”状态。如果渲染模式为 'human',则调用 _render_frame 方法渲染初始状态。最后调用 _save_frame_history 方法保存当前帧的状态历史,返回观察信息和附加信息的元组。具体实现代码如下所示。

    def reset(self, *, seed: int=1, options=None):
        self._predator_location = np.random.randint(0, self.size, size=(self.predator_num, 2))
        self._prey_location = np.random.randint(0, self.size, size=(self.prey_num, 2))
        self.steps = 0
        self.active_predator = [True for i in range(self.predator_num)]
        self.active_prey = [True for i in range(self.prey_num)]
        if self.render_mode == 'human':
            self._render_frame()
        self._save_frame_history()
        return self._get_frame_history(self.history_length), self._get_info()

16.5.4  计算捕食者和猎物的奖励

定义方法_get_reward 和方法_get_prey_reward,分别用于计算捕食者和猎物的奖励,这些奖励将在每个时间步之后返回给代理程序,以便进行强化学习训练。具体实现代码如下所示。 

    def _get_reward(self):
        # if any predator reaches prey, success. else, living reward
        rewards = [self.living_reward for i in range(self.predator_num)]
        for i in range(self.predator_num):
            for j in range(self.prey_num):
                if self.active_predator[i]:
                    if np.all(self._predator_location[i]==self._prey_location[j]):
                        rewards = [self.cooperate*self.success_reward for i in range(self.predator_num)]
                        rewards[i] = self.success_reward
    #                     print("EATEN")
                        return rewards
        return rewards
    
    def _get_prey_reward(self):
        # if any predator reaches prey, success. else, living reward
        rewards = [self.success_reward for i in range(self.prey_num)]
        for i in range(self.prey_num):
            if self._prey_location[i] in self._predator_location:
                rewards[i] = 0
        return rewards

对上述代码的具体说明如下:

  1. _get_reward 方法:用于计算捕食者的奖励。如果任何一个捕食者达到猎物,将获得成功奖励 self.success_reward,否则将获得生存奖励 self.living_reward。返回一个奖励列表,其中每个元素对应一个捕食者的奖励。
  2. _get_prey_reward 方法:用于计算猎物的奖励。如果任何一个猎物被捕食者捕获,其奖励将为0(被吃掉),否则将获得成功奖励 self.success_reward。返回一个奖励列表,其中每个元素对应一个猎物的奖励。

16.5.5  判断回合是否结束

定义方法_is_done,用于判断当前回合是否结束。返回一个布尔值,表示回合是否结束。如果当前时间步数达到设定的回合长度(self.episode_length)或者所有猎物都被捕食者捕获,回合将被标记为结束。具体实现代码如下所示。

    def _is_done(self):
        # if all prey are gone or episode length is reached, done
        if self.steps >= self.episode_length:
            return True
        if np.sum(self.active_prey) == 0:
            return True
        return False

16.5.6  检查动作的合法性

分别定义方法_is_valid_predator 和方法_is_valid_prey,用于检查动作的合法性。具体实现代码如下所示。

    def _is_valid_predator(self, location, index):
        # check if location is valid
        if location[0] < 0 or location[0] >= self.size or location[1] < 0 or location[1] >= self.size:
            return False
        if location in np.delete(self._predator_location, index, axis=0):
            return False
        return True
    
    def _is_valid_prey(self, location, index):
        # check if location is valid for prey of i'th index
        if location[0] < 0 or location[0] >= self.size or location[1] < 0 or location[1] >= self.size:
            return False
        if location in np.delete(self._prey_location, index, axis=0):
            return False
        return True

对上述代码的具体说明如下:

  1. 方法_is_valid_predator:用于检查捕食者的新位置是否合法。返回一个布尔值,表示新位置是否合法。检查捕食者是否移动到了环境边界之外或与其他捕食者重叠。
  2. 方法_is_valid_prey:用于检查猎物的新位置是否合法。返回一个布尔值,表示新位置是否合法。检查猎物是否移动到了环境边界之外或与其他猎物重叠。

 16.5.7  记录和获取状态历史

分别定义方法_save_frame_history 和方法_get_frame_history,用于记录和获取状态历史,以供代理程序使用。具体实现代码如下所示。

    def _save_frame_history(self):
        self.frame_history.append(self._get_obs())


    def _get_frame_history(self, history=4):

        if len(self.frame_history) < history:
            return None

        return list(self.frame_history)[-history:]

对上述代码的具体说明如下:

  1. 方法_save_frame_history:用于保存当前帧的状态历史,将当前观察信息添加到状态历史列表中。
  2. 方法_get_frame_history 方法:用于获取状态历史。如果状态历史的长度小于指定的历史长度(history),则返回 None。否则返回最近的若干个状态历史,用于构建观察信息。

16.5.8  实现step方法

定义了环境类 PredatorPreyEnv 中的 step 方法,该方法用于执行动作并进行环境交互。step 方法是环境中最重要的方法之一,它实现了环境与代理程序之间的互动,允许代理程序执行动作并获取观察、奖励和回合状态。具体实现代码如下所示。

    def step(self, action_pred, action_prey, pred_communication=None, prey_communication=None):

        if self._is_done():
            raise RuntimeError("Episode is done")
        self.steps += 1
        # move predator
        for i in range(self.predator_num):
            if self.active_predator[i] == False:  # if predator is dead,
                continue
            if i < len(action_pred):
                action = action_pred[i]
            else:
                action = self.single_action_space.sample()
            new_location = self._predator_location[i] + self._action_to_direction[action]
            if self._is_valid_predator(new_location, i):
                self._predator_location[i] = new_location

        # move prey
        for i in range(self.prey_num):
            if self.active_prey[i] == False:  # if prey is dead,
                continue
            if i < len(action_prey):
                action = action_prey[i]
            else:
                action = self.single_action_space.sample()
            
            new_location = self._prey_location[i] + self._action_to_direction[action]
            if self._is_valid_prey(new_location, i):
                self._prey_location[i] = new_location

        # check if any predator reaches prey and give reward
        pred_reward = self._get_reward()
        prey_reward = self._get_prey_reward()
        for i in range(self.predator_num):
            for j in range (self.prey_num):
                if np.all(self._predator_location[i] == self._prey_location[j]):
#                     print("EATEN !!!")
                    self.active_prey[j] = False
        
        #save communication of agents
        if self.communication_bits > 0:
            self.pred_communication = pred_communication
            self.prey_communication = prey_communication

        done = self._is_done()
        reward = {
            'predator': pred_reward,
            'prey': prey_reward
        }
        if self.render_mode == 'human':
            self._render_frame()
        self._save_frame_history()
        return self._get_frame_history(self.history_length), reward, done, self._get_info()

对上述代码的具体说明如下:

(1)step方法接受四个参数:action_pred、action_prey、pred_communication 和 prey_communication。其中:action_pred 是一个包含每个捕食者动作的列表,action_prey 是一个包含每个猎物动作的列表,pred_communication 是一个包含捕食者通信信息的列表(可选,根据 communication_bits 是否大于 0 决定是否使用),prey_communication 是一个包含猎物通信信息的列表(可选,根据 communication_bits 是否大于 0 决定是否使用)。

(2)step方法首先检查当前回合是否已结束(通过调用 _is_done 方法),如果回合结束,则抛出运行时错误。

(3)step方法递增当前时间步数 self.steps,表示环境中经过的时间步数。

(4)然后,step方法迭代每个捕食者和猎物,并根据其动作进行移动:

  1. 对于每个活动的捕食者,根据其动作更新其位置,但在以下情况下动作不会生效:
  • 如果动作导致两个捕食者重叠在一起。
  • 如果动作导致捕食者与猎物重叠在一起。
  • 如果动作导致捕食者移动到边界或墙上。
  1. 对于每个活动的猎物,根据其动作更新其位置,但在以下情况下动作不会生效:
  • 如果动作导致猎物移动到边界或墙上。

(5)接下来,方法检查是否有捕食者捕获了猎物,并根据情况给予奖励:

  1. 如果捕食者的位置与猎物的位置完全重叠,捕食者将捕获猎物,猎物将被标记为不活跃(active_prey[j] = False)。
  2. 使用 _get_reward 方法计算捕食者的奖励,并使用 _get_prey_reward 方法计算猎物的奖励。

(6)如果通信比特数大于 0,方法还会保存捕食者和猎物的通信信息(如果提供)。

(7)最后,step方法检查回合是否已结束,构建并返回观测、奖励、结束标志和信息。

(8)如果渲染模式为 'human',则在每个时间步上调用 _render_frame 方法以可视化环境。

(9)最后,step方法保存当前状态历史并返回状态历史以供代理程序使用。

16.5.9  生成视图帧

定义_render_predator_frame 方法,用于生成以捕食者为中心的视图帧,以便监视捕食者和猎物的行为和交互。视图帧中的颜色和通道编码方式用于表示不同类型的对象和信息。具体实现代码如下所示。

    def _render_predator_frame(self, predator_id:int=0):
        if predator_id==None:
            return
        frame = np.zeros((4, self.vision, self.vision), dtype=np.uint8)
        # draw predator
        pred_loc = self._predator_location[predator_id]
        min_pred_loc = pred_loc - np.array([self.vision//2, self.vision//2])
        max_pred_loc = pred_loc + np.array([self.vision//2, self.vision//2])

        # add predator to centre of frame
        frame[1, self.vision//2, self.vision//2] = self.active_predator[predator_id]

        # for each predator or prey within min and max it will be added in the frame
        for i in range(self.predator_num):
            if i==predator_id:
                continue
            if (min_pred_loc[0] <= self._predator_location[i][0] <= max_pred_loc[0] 
            and 
            min_pred_loc[1] <= self._predator_location[i][1] <= max_pred_loc[1]):
                loc_x = self._predator_location[i][0]-min_pred_loc[0]
                loc_y = self._predator_location[i][1]-min_pred_loc[1]
                frame[2, loc_x, loc_y] = self.render_scale 
                # frame[2, loc_x, loc_y] = self.pred_communication[i]
                if self.communication_bits > 0:
                    frame[3, loc_x, loc_y] = self.pred_communication[i]
        
        
        for i in range(self.prey_num):
            if (min_pred_loc[0] <= self._prey_location[i][0] <= max_pred_loc[0] 
            and 
            min_pred_loc[1] <= self._prey_location[i][1] <= max_pred_loc[1]):
                loc_x = self._prey_location[i][0]-min_pred_loc[0]
                loc_y = self._prey_location[i][1]-min_pred_loc[1]
                frame[0, loc_x, loc_y] = self.render_scale 
                
        # create white for cells outside grid
        if min_pred_loc[0] < 0:
            frame[:, :abs(min_pred_loc[0]), :] = self.render_scale 
        if max_pred_loc[0] >= self.size:
            frame[:, -(max_pred_loc[0]-self.size+1):, :] = self.render_scale 
        if min_pred_loc[1] < 0:
            frame[:, :, :abs(min_pred_loc[1])] = self.render_scale 
        if max_pred_loc[1] >= self.size:
            frame[:, :, -(max_pred_loc[1]-self.size+1):] = self.render_scale 
        
        return frame

该方法的实现流程如下要:

  1. _render_predator_frame 方法接受一个可选的参数 predator_id,该参数表示要在视图帧中以中心位置显示的捕食者的ID。如果 predator_id 为 None,则函数立即返回。
  2. 创建一个大小为 (4, self.vision, self.vision) 的三维 NumPy 数组 frame,其中第一个维度代表通道(通常对应于不同的对象或信息),第二和第三个维度代表视图帧的宽度和高度。
  3. 将中心位置的像素设置为 self.active_predator[predator_id],表示捕食者的位置,并将其标记为绿色。
  4. 计算视图帧中的捕食者和猎物的位置范围,以确定哪些捕食者和猎物在视图帧中可见。
  5. 对于每个捕食者和猎物,检查其位置是否在视图帧的可见范围内,如果是,则将其添加到视图帧中。在添加时,不同的对象使用不同的通道表示。
  6. 如果通信比特数大于 0,则还将通信信息添加到相应的通信通道。
  7. 处理边界情况,如果捕食者靠近视图帧的边界,将在视图帧外部添加白色像素以表示边界。
  8. 返回生成的视图帧 frame,其中包含了捕食者、猎物和通信信息的可视化信息。

16.5.10  渲染环境的视图

定义方法_render_prey_frame 和方法_render_frame,用于渲染环境的视图。这两个方法的目的是提供环境的可视化,以便监视捕食者、猎物和它们之间的交互。视图的颜色和图像表示方式用于区分不同类型的对象,并可根据需要进行自定义。具体实现代码如下所示。

    def _render_prey_frame(self, prey_id:int=1):

        if prey_id==None:
            return
        frame = np.zeros((3, self.vision, self.vision), dtype=np.uint8)
        prey_loc = self._prey_location[prey_id]
        min_prey_loc = prey_loc - np.array([self.vision//2, self.vision//2])
        max_prey_loc = prey_loc + np.array([self.vision//2, self.vision//2])

        frame[1, self.vision//2, self.vision//2] = self.render_scale 
        # for each predator or prey within min and max it will be added in the frame
        for i in range(self.predator_num):
            if (min_prey_loc[0] <= self._predator_location[i][0] <= max_prey_loc[0] 
            and 
            min_prey_loc[1] <= self._predator_location[i][1] <= max_prey_loc[1]):
                frame[2, self._predator_location[i][0]-min_prey_loc[0], self._predator_location[i][1]-min_prey_loc[1]] = self.render_scale 
        
        for i in range(self.prey_num):
            if (min_prey_loc[0] <= self._prey_location[i][0] <= max_prey_loc[0] 
            and 
            min_prey_loc[1] <= self._prey_location[i][1] <= max_prey_loc[1]):
                frame[0, self._prey_location[i][0]-min_prey_loc[0], self._prey_location[i][1]-min_prey_loc[1]] = self.render_scale 
        
        if min_prey_loc[0] < 0:
            frame[:, :abs(min_prey_loc[0]), :] = self.render_scale 
        if max_prey_loc[0] >= self.size:
            frame[:, -(max_prey_loc[0]-self.size+1):, :] = self.render_scale 
        if min_prey_loc[1] < 0:
            frame[:, :, :abs(min_prey_loc[1])] = self.render_scale 
        if max_prey_loc[1] >= self.size:
            frame[:, :, -(max_prey_loc[1]-self.size+1):] = self.render_scale 
        
        return frame
    
    def _render_frame(self):

        if self.window is None and self.render_mode == 'human':
            pygame.init()
            pygame.display.init()
            self.window = pygame.display.set_mode((self.window_size, self.window_size))
            self.window = pygame.display.set_mode((self.window_size, self.window_size))
        if self.clock is None and self.render_mode == 'human':
            self.clock = pygame.time.Clock()

        canvas = pygame.Surface((self.window_size, self.window_size))
        canvas.fill((255, 255, 255))
        pixel_size = self.window_size // self.size

        # draw grid
        for i in range(self.size):
            pygame.draw.line(canvas, (0, 0, 0), (0, i*pixel_size), (self.window_size, i*pixel_size))
            pygame.draw.line(canvas, (0, 0, 0), (i*pixel_size, 0), (i*pixel_size, self.window_size))
        
        # draw prey as rectangle
        for i in range(self.prey_num):
            if self.active_prey[i]:
                pygame.draw.rect(canvas, (255, 0, 0), (self._prey_location[i][1]*pixel_size, self._prey_location[i][0]*pixel_size, pixel_size, pixel_size))

        # draw predator as circle
        for i in range(self.predator_num):
            if self.active_predator[i]:
                pygame.draw.circle(canvas, (0, 0, 255), (self._predator_location[i][1]*pixel_size+pixel_size//2, self._predator_location[i][0]*pixel_size+pixel_size//2), pixel_size//2)
        
        
        if self.render_mode == 'human':
            self.window.blit(canvas, canvas.get_rect())
            pygame.event.pump()
            pygame.display.update()

            self.clock.tick(self.metadata['render-fps'])
        else:
            return np.transpose(pygame.surfarray.array3d(canvas), (1, 0, 2))
    
    def close(self):
        if self.window is not None:
            pygame.quit()
            self.window = None
            self.clock = None

在上述代码中,方法_render_prey_frame的实现流程如下:

  1. _render_prey_frame 方法接受一个可选的参数 prey_id,该参数表示要在视图帧中以中心位置显示的猎物的ID。如果 prey_id 为 None,则函数立即返回。
  2. 创建一个大小为 (3, self.vision, self.vision) 的三维 NumPy 数组 frame,其中第一个维度代表通道(通常对应于不同的对象或信息),第二和第三个维度代表视图帧的宽度和高度。
  3. 将中心位置的像素设置为 self.render_scale,表示猎物的位置,并将其标记为红色。
  4. 计算视图帧中的猎物的位置范围,以确定哪些捕食者和猎物在视图帧中可见。
  5. 对于每个捕食者和猎物,检查其位置是否在视图帧的可见范围内,如果是,则将其添加到视图帧中。在添加时,不同的对象使用不同的通道表示。
  6. 处理边界情况,如果猎物靠近视图帧的边界,将在视图帧外部添加白色像素以表示边界。
  7. 返回生成的视图帧 frame,其中包含了捕食者、猎物和通信信息的可视化信息。

方法_render_frame的实现流程如下所示:

  1. _render_frame 方法用于渲染整个环境的视图,适用于逐帧的可视化。
  2. 如果窗口和时钟尚未创建,并且渲染模式为 'human',则初始化 Pygame 窗口和时钟。
  3. 创建一个 Pygame 表面 canvas,并用白色填充整个表面。
  4. 根据环境的大小和视图的比例计算像素的大小。
  5. 绘制网格线,以在视图中创建网格。
  6. 绘制每个活动猎物作为红色矩形。
  7. 绘制每个活动捕食者作为蓝色圆圈。
  8. 如果渲染模式为 'human',则将 canvas 显示在窗口上,并更新 Pygame 显示。
  9. 如果渲染模式不是 'human',则将 canvas 转换为 NumPy 数组并返回。

未完待续

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

感谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值