OpenTTD的界面通过viewport.cpp 实现,界面上由相对静止的网格Tile以及不断移动的交通工具vehicle组成,在Tile上可能是各种气候条件的陆地、海洋、工业设施、城镇等,交通工具则主要包括汽车、火车、轮船和飞机四大类。我们看一下网格Tile绘制的原理。viewport.cpp ViewportAddLandscape()是绘制Tile的关键,这个函数通过双重循环,先行row再列column 实现了各种气候地形和建筑物的绘制。
viewport.cpp Line1164
static void ViewportAddLandscape()
{
...
_tile_type_procs[tile_type]->draw_tile_proc(&tile_info); <- 说明1
...
说明1:函数指针,根据每个tile的定义完成对应的函数调用
如下图 _cmd.cpp 文件 定义 DrawTile_开头的函数实现,我们以海面的Tile绘制为例
构造海面Tile绘制调用链
viewport.cpp AddTileSpriteToDraw()
<- DrawGroundSpriteAt() // Draws a ground sprite at a specific world-coordinate relative to the current tile.
<- DrawGroundSprite() // Draws a ground sprite for the current tile.
<- water_cmd.cpp DrawSeaWater() // Draw a plain sea water tile with no edges
<- DrawWaterClassGround()
<- DrawTile_Water()
viewport.cpp ViewportAddLandscape()通过 TileTypeProcs类型的函数指针_tile_type_procs , 调用 DrawTile_Water() 完成海面的Tile绘制
viewport.cpp Line498
static void AddTileSpriteToDraw(SpriteID image, PaletteID pal, int32 x, int32 y, int z, const SubSprite *sub = nullptr, int extra_offs_x = 0, int extra_offs_y = 0)
{
...
TileSpriteToDraw &ts = _vd.tile_sprites_to_draw.emplace_back(); <- 说明1
...
}
说明1:emplace_back() 实现Vector容器插入操作
底层实现也是和交通工具移动一致,通过tile_sprites_to_draw 这个vector实现
viewport.cpp Line1713
ViewportDoDraw()
{
...
if (_vd.tile_sprites_to_draw.size() != 0) ViewportDrawTileSprites(&_vd.tile_sprites_to_draw); <- 说明1
...
说明1:<- ViewportDrawTileSprites()函数 通过循环体遍历Vector调用 blitter实现屏幕绘制的底层实现
下面我们回过头分析 ViewportAddLandscape() 双重循环的row和column
…
int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2;
int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2;
…
int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2;
和表格的定义一样,row根据垂直方向变化,如果键盘向下的话,row随之变大;column根据水平方向变化,键盘向左变小、向右变大,
通过增加下面2个DEBUG,运行程序,我们进一步了解到 每一个Tile对应一半的row和column ,那么如果是64*64的地图,row范围[1到128], column范围 [-64到64]
viewport.cpp Line1164
static void ViewportAddLandscape()
{
...
bool last_row = false;
DEBUG(misc,2,"ViewportAddLandscape(): begin loop left[%d,%d],right[%d,%d]",upper_left.x,upper_left.y,upper_right.x,upper_righ.y); <- 说明1
for (; !last_row; row++) {
last_row = true;
DEBUG(misc,2," row(%) column [%d to %d]",row, left_column, right_column); <- 说明2
for (int column = left_column; column <= right_column; column++) {
/* Valid row/column? */
if ((row + column) % 2 != 0) continue;
...
说明1:双循环启动前
说明2:每一个row循环,输出范围
当我们打开一个64*64的地图,把主视角放到最大
地图最上面 row[ -4 to 18] column [ 0 to 5] <- 思考:为什么row会小于0
地图最下面 row[127 to 131] column [ 0 to 5] <- 思考: 为什么row会大于128
地图最左边 row[ 57 to 69] column [-61 to -57]
地图最右边 row[ 57 to 69] column [ 62 to 66] <- 思考:为什么column会大于64
现在Tile的绘制逻辑就很清晰了,程序根据当前屏幕左上角和右上角的坐标点确定 column,根据左上角顶点确定第一行row,然后通过双循环,把屏幕内的所有Tile刷新一遍