跳转至

2.3 加载地图

约 819 个字 177 行代码 预计阅读时间 5 分钟

一、添加地图模块

我们的地图是布满窗口的网格。之所以要做这个地图显示,主要就是增强小车移动时的显示效果。 在car_part包中新建文件map.py,代码内容如下:

map.py
import pygame


class Map:
    """
    地图
    """

    def __init__(self, car):
        self.car = car
        self.win = car.win
        self.grid_color = (200, 200, 200)
        self.grid_width, self.grid_height = 50, 50
        pass

    def update(self):
        win_rect = self.win.get_rect()
        world_rect = self.car.win_rect_to_world_rect(win_rect)
        offset_y = world_rect.y % self.grid_height
        row_numbers = win_rect.height // self.grid_height
        for row_number in range(row_numbers + 1):
            row_y = offset_y + row_number * self.grid_height
            pygame.draw.line(
                self.win, self.grid_color, (0, row_y), (win_rect.width, row_y)
            )
        offset_x = world_rect.x % self.grid_width
        column_numbers = win_rect.width // self.grid_width
        for column_number in range(column_numbers + 2):
            column_x = -offset_x + column_number * self.grid_width
            pygame.draw.line(
                self.win, self.grid_color, (column_x, 0), (column_x, win_rect.height)
            )
        pass
__init__函数中,我们定义了网格的颜色和网格的大小,这里我们设置的是50*50的网格。 update()是显示网格的函数。这里,我们先获取窗口的矩形区域(也就是窗口的坐标,尺寸信息),然后,我们通过调用car.py中的win_rect_to_world_rect()函数来计算窗口矩形区域在地图坐标系中对应的矩形区域。这样,我们就确定了要在窗口中绘制的地图区域。
用得到的矩形区域的y坐标对网格的高度取余,就得到了第一条网格横线的位置。用矩形区域的高度除以网格的高度,取整就得到了网格横线的数量。然后,我们通过循环调用pygame.draw_line()方法就可以绘制出矩形区域的网格了。绘制竖线和绘制横线道理一致,参见代码,就不再赘述了。

二、在主窗口中绘制地图

car.py修改内容如下,注意标亮行:

car.py
import pygame
import sys
import os
import getopt
from ament_index_python.packages import get_package_share_directory

def run_as_debug() -> bool:
    """
    是否传递了参数debug
    """
    debug = False
    # 获取文件名(含后缀)
    name = os.path.basename(__file__)
    try:
        """
        options, args = getopt.getopt(args, shortopts, longopts=[])

        参数args:一般是sys.argv[1:]。过滤掉sys.argv[0],它是执行脚本的名字,不算做命令行参数。
        参数shortopts:短格式分析串。例如:"hp:i:",h后面没有冒号,表示后面不带参数;p和i后面带有冒号,表示后面带参数。
        参数longopts:长格式分析串列表。例如:["help", "ip=", "port="],help后面没有等号,表示后面不带参数;ip和port后面带等号,表示后面带参数。

        返回值options是以元组为元素的列表,每个元组的形式为:(选项串, 附加参数),如:('-i', '192.168.0.1')
        返回值args是个列表,其中的元素是那些不含'-'或'--'的参数。
        """
        opts, args = getopt.getopt(sys.argv[1:], "hd:", ["help", "debug"])
        # 处理 返回值options是以元组为元素的列表。
        for opt, arg in opts:
            if opt in ("-h", "--help"):
                print(f"{name} -d")
                print(f"or: {name} --debug")
                sys.exit()
            elif opt in ("-d", "--debug"):
                debug = True
        if debug:
            print("debug模式")

        # 打印 返回值args列表,即其中的元素是那些不含'-'或'--'的参数。
        for i in range(0, len(args)):
            print("参数 %s 为:%s" % (i + 1, args[i]))
    except getopt.GetoptError:
        print(f"Error: {name} -d")
        print(f"   or: {name} --debug")
    return debug

if run_as_debug():
    from car_part.model import Model
    from car_part.map import Map
else:
    from kitt.car_part.model import Model
    from kitt.car_part.map import Map

class Car:
    def __init__(self) -> None:
        """初始化窗口并加载显示资源"""
        pygame.init()

        if run_as_debug():
            self.share_path = os.path.join(os.path.dirname(__file__), "../resource")
        else:
            self.share_path = get_package_share_directory("kitt")

        # 设置窗口大小
        self.win = pygame.display.set_mode((800, 600), pygame.RESIZABLE)
        # 设置窗口标题
        pygame.display.set_caption("Car")
        self.world_x = float(0)  # 车世界坐标x
        self.world_y = float(0)  # 车世界坐标y
        self.model = Model(self)
        self.map = Map(self)
        pass

    def run(self):
        """运行模拟器"""
        while True:
            self._check_events()
            self._update_win()

    def _check_events(self):
        """监视键盘、鼠标事件"""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

    def _update_win(self):
        """更新窗口显示"""
        color = (150, 150, 150)
        # 给窗口填充背景色
        self.win.fill(color=color)
        # 更新地图
        self.map.update()
        # 更新汽车模型
        self.model.update()
        # 让最近绘制的窗口可见
        pygame.display.flip()

    def winx_to_worldx(self, win_x: int) -> int:
        """
        窗口坐标x转换为世界坐标x
        参数:
            win_x:窗口坐标x
        返回:
            世界坐标x
        """
        win_width = self.win.get_rect().width
        world_left = int(self.world_x - win_width / 2)
        world_x = world_left + win_x
        return world_x

    def winy_to_worldy(self, win_y) -> int:
        """
        窗口坐标y转换为世界坐标y
        参数:
            win_y:窗口坐标y
        返回:
            世界坐标y
        """
        win_height = self.win.get_rect().height
        world_top = int(self.world_y + win_height / 2)
        world_y = world_top + win_y
        return world_y

    def win_rect_to_world_rect(self, win_rect) -> pygame.Rect:
        """
        窗口坐标矩形转换为世界坐标矩形
        参数:
            win_rect:窗口坐标矩形
        返回:
            世界坐标矩形
        """
        world_x = self.winx_to_worldx(win_rect.x)
        world_y = self.winy_to_worldy(win_rect.y)
        world_rect = pygame.Rect(world_x, world_y, win_rect.width, win_rect.height)
        return world_rect

def main():
    """创建模拟器,并运行"""
    car = Car()
    car.run()


if __name__ == '__main__':
    main()
首先,我们导入了Map模块。在__init__函数中,我们增加了world_x``world_y两个坐标变量。这两个坐标变量指的是:当我们移动汽车时,为了显示出移动汽车的效果,我们需要将地图向相反方向移动,这两个值就是指的移动距离。同时,我们也创建了map实例,在__update_win()函数中,我们调用map的update()方法,完成了地图的绘制。

三、坐标转换

我们在Car中,添加了三个方法winx_to_worldx()winy_to_worldy()win_rect_to_world_rect()。这三个方法就是将窗口坐标或矩形信息转换成地图对应的坐标或矩形信息。为什么需要转换,我们看下图。

win2world

窗口比地图小。所以,当我们移动地图时,窗口中就需要及时显示能观察到的地图信息。这样就可以给我们一种移动的效果。那么,窗口中的这块地图信息如何计算呢?我们需要将窗口矩形信息转换为地图坐标时对应的矩形信息。然后,我们就知道要截取哪部分地图信息显示到窗口中。比如上图中,初始时,窗口在地图中央。当我们将地图向左下方移动时,我们窗口中就要显示地图右上方的部分。

示例一

地图向左下方移动,窗口由中间窗口转换为右上方窗口:

  • 中间窗口左上角坐标(0, 0)
  • 地图移动距离(470, 320)
  • 那么,窗口左上角坐标将要变为(320, 420)

示例二

地图向右上方移动,窗口由中间窗口转换为左下方窗口:

  • 中间窗口左上角坐标(0, 0)
  • 地图移动距离(-470, -320)
  • 那么,窗口左上角坐标将要变为(-620, -220)

四、运行程序

运行界面如下:

show_map

源码下载