天行健 君子当自强而不息

二维图形的使用(2)

 

本篇是二维图形的使用(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();
}

该程序展示了平滑卷轴技术的使用,用上下左右键进行控制,截图如下:


posted on 2007-10-16 01:19 lovedday 阅读(403) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论