本篇是二维图形的使用(1)的续篇。
从计算机游戏产生之初到现在,创建一个贴片引擎的首选方法仍然是最常用的二维图形技术。绘制基本的贴片地图是一个快速而轻松的过程,只需要循环那些行与列,并绘制贴片即可,所绘制的贴片总数是基于贴片的大小以及显示率的。
大多数基于贴片的游戏运用了多重层次(场景被堆叠起来),以便创建出一些非常酷的效果,如下图所示:
比如,首先绘制地面,再在地面上绘制角色,然后再绘制其他相互重叠的对象层次,就可以模拟出一个三维的场景。
为了使用多重层次,可以申明另外不同的地图数组(每个层次都有一个地图数组),并使用它自己的贴片信息来填充它。从第一个层次开始,绘制包含在该层次中的每个贴片。在绘制好层次中的最后一个贴片后,移动到下一个层次继续绘制它的贴片,直到绘制完所有的层次。
添加对象
角色以及其他移动的对象只需被作为自由浮动的贴片来进行绘制,而不需要为他们使用一个地图数组。更确切的说,要根据所有的角色和对象在世界中的各自坐标来记录它们,然后把那些对象的坐标转换为它们准备在屏幕上进行绘制的坐标,也就是它们出现在视野里的坐标。
为了使事情保持简单,设置一个结构体来存储对象的坐标和贴片索引。
typedef struct OBJECT_INFO
{
long x_pos, y_pos;
char tile_index;
} *OBJECT_INFO_PTR;
将把每件可以自由移动的事物认为是一个对象,包括玩家的角色。
平滑卷轴
操作贴片引擎时,大型的地图需要卷轴以便玩家可以看到整个地图,具体说,在绘制地图时尝试去改变坐标,贴片引擎就会产生一个急促的运动。为了提高引擎的视觉质量,需要使用一种称之为平滑卷轴(smooth
scrolling)的技术来使运动平滑地进行。
为了实现平滑卷轴,将贴片绘制的地图想象成一个很大的位图。在位图中的每个像素都有它自己的一对坐标,即所谓的地图的精细坐标,代表贴片像素的每个分组被赋予它自己的地图坐标集,如下图所示:
举个例子,如果贴片是16 x 16像素大小,同时地图数组为10 x 10,当完全渲染时,地图将会为160 x
160像素大小(这就意味着地图有一个分辨率为160 x 160 的精细坐标)。
创建一个地图类
为了使游戏保持运行的平滑,首先需要对每一帧所绘制的自由浮动贴片的数量(子画面)进行限制,一个宏定义将出色地完成这个工作,它会通知地图类在每一帧中绘制了多少子画面:
#define MAX_OBJECTS 1024
每个地图类的实例可以存储大量的层次(甚至超过一百万个),将每个层次的贴片数据存储到一个数组_map_info里。因为地图的尺寸大小一旦被创建,将是固定不变的,可以通过计算在_map_info数组里的当前位移,并利用一个指针对每个层次的贴片数据进行读取或写入。
来看看MAP类的定义:
#define MAX_OBJECTS 1024
typedef struct OBJECT_INFO
{
long x_pos, y_pos;
char tile_index;
} *OBJECT_INFO_PTR;
//=========================================================================================
// This class encapsulate 2D map draw.
//=========================================================================================
typedef class MAP
{
public:
MAP();
~MAP();
// function to create and free a map class
BOOL create(long num_layers, long map_column, long map_row);
void free();
// function to set a map's layer data
BOOL set_map_layer_data(long layer_index, char* layer_data);
// function to clear and add an object to list
void clear_object_list();
BOOL add_object(long x_pos, long y_pos, char tile_index);
char* get_ptr(long layer_index); // get pointer to map array
long get_map_column(); // get column of map
long get_map_row(); // get row of map
// assign TILE object to use for drawing map tiles
BOOL use_tile(TILE_PTR tile);
// Render map using specified top-left map coordinates,
// as well as number of columns and rows to draw, plus layer used to draw objects.
BOOL render(long pos_x, long pos_y,
long num_rows, long num_columns,
long object_layer,
D3DCOLOR color = 0xFFFFFFFF,
float scale_x = 1.0f, float scale_y = 1.0f);
private:
long _map_column; // column of map
long _map_row; // row of map
long _per_layer_size; // size of per map
long _num_layers; // number of layers
char* _map_info; // array for tile informarion
TILE_PTR _tile; // pointer to TILE object
long _num_objects_to_draw; // number of object need to be drawed
OBJECT_INFO _objects_info[MAX_OBJECTS]; // object information array
} *MAP_PTR;
实现:
/*************************************************************************
PURPOSE:
Implement for 2D map.
*************************************************************************/
#include "core_global.h"
#include "tile.h"
#include "map.h"
//----------------------------------------------------------------------------------
// Constructor, zero member data.
//----------------------------------------------------------------------------------
MAP::MAP()
{
memset(this, 0, sizeof(*this));
}
//----------------------------------------------------------------------------------
// Destructor, release allocated resources.
//----------------------------------------------------------------------------------
MAP::~MAP()
{
free();
}
//----------------------------------------------------------------------------------
// Release allocated resources.
//----------------------------------------------------------------------------------
void MAP::free()
{
// free map information array
delete[] _map_info;
_map_info = NULL;
_map_column = _map_row = 0;
_num_layers = 0;
}
//----------------------------------------------------------------------------------
// Create map object.
//----------------------------------------------------------------------------------
BOOL MAP::create(long num_layers, long map_column, long map_row)
{
// free a prior map
free();
// save number of layers, map column and row.
_num_layers = num_layers;
_map_column = map_column;
_map_row = map_row;
_per_layer_size = map_column * map_row;
long total_map_size = num_layers * _per_layer_size;
// allocate map data memory
if((_map_info = new char[total_map_size]) == NULL)
return FALSE;
// clear it out
ZeroMemory(_map_info, total_map_size);
// reset number of objexts to draw
_num_objects_to_draw = 0;
return TRUE;
}
//----------------------------------------------------------------------------------
// Set map data.
//----------------------------------------------------------------------------------
BOOL MAP::set_map_layer_data(long layer_index, char* layer_data)
{
// error checking
if(layer_index >= _num_layers)
return FALSE;
// copy over data
memcpy(&_map_info[layer_index * _per_layer_size], layer_data, _per_layer_size);
return TRUE;
}
//----------------------------------------------------------------------------------
// Clear object list which need to be drawed.
//----------------------------------------------------------------------------------
void MAP::clear_object_list()
{
_num_objects_to_draw = 0;
}
//----------------------------------------------------------------------------------
// Add object to object list.
//----------------------------------------------------------------------------------
BOOL MAP::add_object(long x_pos, long y_pos, char tile_index)
{
if(_num_objects_to_draw < MAX_OBJECTS)
{
_objects_info[_num_objects_to_draw].x_pos = x_pos;
_objects_info[_num_objects_to_draw].y_pos = y_pos;
_objects_info[_num_objects_to_draw].tile_index = tile_index;
_num_objects_to_draw++;
return TRUE;
}
return FALSE;
}
//----------------------------------------------------------------------------------
// Return pointer to specfied layer map data.
//----------------------------------------------------------------------------------
char* MAP::get_ptr(long layer_index)
{
if(layer_index >= _num_layers)
return NULL;
return &_map_info[layer_index * _per_layer_size];
}
//----------------------------------------------------------------------------------
// Return map columns.
//----------------------------------------------------------------------------------
long MAP::get_map_column()
{
return _map_column;
}
//----------------------------------------------------------------------------------
// Return map rows.
//----------------------------------------------------------------------------------
long MAP::get_map_row()
{
return _map_row;
}
//----------------------------------------------------------------------------------
// Set tile to map.
//----------------------------------------------------------------------------------
BOOL MAP::use_tile(TILE_PTR tile)
{
if((_tile = tile) == NULL)
return FALSE;
return TRUE;
}
//----------------------------------------------------------------------------------
// Render map.
//----------------------------------------------------------------------------------
BOOL MAP::render(long pos_x, long pos_y,
long num_rows, long num_columns,
long object_layer,
D3DCOLOR color,
float scale_x, float scale_y)
{
// error checking
if(_map_info == NULL || _tile == NULL)
return FALSE;
long tile_width = _tile->get_tile_width(0);
long tile_height = _tile->get_tile_height(0);
// calculate smooth scrolling variables
long map_x = pos_x / tile_width;
long map_y = pos_y / tile_height;
long off_x = pos_x % tile_width;
long off_y = pos_y % tile_height;
// loop through each layer
for(long layer = 0; layer < _num_layers; layer++)
{
// get a pointer to the map data
char* map_ptr = &_map_info[layer * _per_layer_size];
// loop for each row and column
for(long row = 0; row < num_rows+1; row++)
{
for(long column = 0; column < num_columns+1; column++)
{
// get the tile index to draw
char tile_index = map_ptr[(row + map_y) * _map_column + column + map_x];
long screen_x = column * tile_width - off_x;
long screen_y = row * tile_height - off_y;
// draw tile
_tile->draw_tile(0, tile_index, (DWORD)screen_x, (DWORD)screen_y, color, scale_x, scale_y);
}
}
// draw objects if on object layer
if(layer == object_layer)
{
for(long i = 0; i < _num_objects_to_draw; i++)
{
_tile->draw_tile(0, _objects_info[i].tile_index,
_objects_info[i].x_pos - off_x, _objects_info[i].y_pos - off_y,
color, scale_x, scale_y);
}
}
}
return TRUE;
}
我们接着编写两个例子来测试,第一个例子演示了基本贴片技术的使用,第二个例子演示了平滑卷轴的使用。
来看看第一个例子:
下载源码和工程
/*****************************************************************************
PURPOSE:
Test for class TILE and MAP.
*****************************************************************************/
#include "Core_Global.h"
#include "tile.h"
#include "map.h"
#pragma warning(disable : 4996)
class APP : public APPLICATION
{
public:
APP()
{
_width = 384;
_height = 384;
_style = WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;
strcpy(_class_name, "scale_tile_class");
strcpy(_caption, "scale tile demo");
}
BOOL init()
{
// initialize the graphics device and set display mode
if(! _graphics.init())
return FALSE;
if(! _graphics.set_mode(get_hwnd() , TRUE, FALSE))
return FALSE;
// create and load the tile set
if(! _tile.create(&_graphics, 1))
return FALSE;
if(! _tile.load_texture(0, "tiles.bmp", 64, 64))
{
err_msg_box("load texture failed.");
return FALSE;
}
// create and set the map
char map_data[3][3] = {
{ 0, 1, 0 },
{ 2, 2, 2 },
{ 1, 2, 3 }
};
_map.create(1, 3, 3);
_map.set_map_layer_data(0, (char*) &map_data);
_map.use_tile(&_tile);
return TRUE;
}
BOOL APP::frame()
{
// calculate elapsed time
static DWORD s_last_time = timeGetTime();
DWORD now_time = timeGetTime();
DWORD elapsed_time = now_time - s_last_time;
// frame lock to 30ms per frame
if(elapsed_time < 30)
return TRUE;
s_last_time = now_time;
if(_graphics.begin_scene())
{
if(_graphics.begin_sprite())
{
D3DCOLOR color;
static uchar s_red = 0, s_green = 0, s_blue = 0;
static BOOL s_increment_color = TRUE;
if(s_increment_color)
{
color = D3DCOLOR_RGBA(s_red++, s_green++, s_blue++, 255);
if(s_red >= 255)
s_increment_color = FALSE;
}
else
{
color = D3DCOLOR_RGBA(s_red--, s_green--, s_blue--, 255);
if(s_red <= 0)
s_increment_color = TRUE;
}
// draw the map
_map.render(0, 0, 3, 3, 0, color, 2.0f, 2.0f);
_graphics.end_sprite();
}
_graphics.end_scene();
_graphics.display();
}
return TRUE;
}
BOOL shutdown()
{
return TRUE;
}
private:
GRAPHICS _graphics;
TILE _tile;
MAP _map;
};
int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
APP app;
return app.run();
}
该程序淡入淡出地改变贴图的颜色,截图如下:
接着来看第二个例子:
下载源码和工程
/*****************************************************************************
PURPOSE:
Test for class TILE and MAP.
*****************************************************************************/
#include "Core_Global.h"
#include "tile.h"
#include "map.h"
#pragma warning(disable : 4996)
#define TILE_WIDTH 64
#define TILE_HEIGHT 64
#define MAP_COLUMNS 16
#define MAP_ROWS 16
#define TOTAL_MAP_SIZE 1024
class APP : public APPLICATION
{
public:
APP()
{
_width = 640;
_height = 480;
_num_columns_to_draw = _width / TILE_WIDTH;
_num_rows_to_draw = _height / TILE_HEIGHT;
_max_move_width = TOTAL_MAP_SIZE - _width;
_max_move_height = TOTAL_MAP_SIZE - _height;
_style = WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;
strcpy(_class_name, "map class");
strcpy(_caption, "map demo");
}
BOOL init()
{
// initialize the graphics device and set display mode
if(! _graphics.init())
return FALSE;
if(! _graphics.set_mode(get_hwnd(), TRUE, FALSE))
return FALSE;
// create and load the tile set
if(! _tile.create(&_graphics, 1))
return FALSE;
if(! _tile.load_texture(0, "tiles.bmp", TILE_WIDTH, TILE_HEIGHT))
{
err_msg_box("load texture failed.");
return FALSE;
}
// create and set the map
char map_data[MAP_ROWS][MAP_COLUMNS] = {
{ 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0 },
{ 1, 2, 2, 1, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0 },
{ 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 0, 2, 0 },
{ 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 0, 0, 0, 2, 0 },
{ 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0 },
{ 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0 },
{ 3, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0 },
{ 3, 0, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0 },
{ 0, 0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 2, 2, 1, 1, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 0 },
{ 0, 1, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 1, 1, 2, 0 },
{ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0 },
{ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0 },
{ 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
_map.create(1, MAP_COLUMNS, MAP_ROWS);
_map.set_map_layer_data(0, (char*) &map_data);
_map.use_tile(&_tile);
return TRUE;
}
BOOL APP::frame()
{
static long s_x_pos = 0, s_y_pos = 0;
// calculate elapsed time
static DWORD s_last_time = timeGetTime();
DWORD now_time = timeGetTime();
DWORD elapsed_time = now_time - s_last_time;
// frame lock to 33ms per frame
if(elapsed_time < 33)
return TRUE;
s_last_time = now_time;
if(_graphics.begin_scene())
{
if(_graphics.begin_sprite())
{
// draw the map
_map.render(s_x_pos, s_y_pos, _num_rows_to_draw, _num_columns_to_draw, 0, 0xFFFFFFFF, 1.0f, 1.0f);
// press arrows to scroll map around
if(GetAsyncKeyState(VK_LEFT)) s_x_pos -= 8;
if(GetAsyncKeyState(VK_RIGHT)) s_x_pos += 8;
if(GetAsyncKeyState(VK_UP)) s_y_pos -= 8;
if(GetAsyncKeyState(VK_DOWN)) s_y_pos += 8;
// bounds check map coordinates
if(s_x_pos < 0)
s_x_pos = 0;
if(s_x_pos > _max_move_width)
s_x_pos = _max_move_width;
if(s_y_pos < 0)
s_y_pos = 0;
if(s_y_pos > _max_move_height)
s_y_pos = _max_move_height;
_graphics.end_sprite();
}
_graphics.end_scene();
_graphics.display();
}
return TRUE;
}
BOOL shutdown()
{
return TRUE;
}
private:
GRAPHICS _graphics;
TILE _tile;
MAP _map;
long _num_columns_to_draw;
long _num_rows_to_draw;
long _max_move_width;
long _max_move_height;
};
int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
APP app;
return app.run();
}
该程序展示了平滑卷轴技术的使用,用上下左右键进行控制,截图如下: