词条 | 平滑卷轴效果 |
释义 | 对于J2ME框架下的手机游戏程序的开发,其地图滚动的重绘有多种算法,由于手机性能的限制和开发周期等其他非技术条件,需要根据情况灵活选择所需的技术。但在及其苛刻条件下,如系统CPU资源不足,地图块尺寸较小等,会造成屏幕闪耀,帧数过低等情况,严重影响到游戏体验。在开发中如此类问题无法绕过以及避免(指通过修改策划方案,以及程序使用的技术框架),则需要考虑使用地图缓冲绘制技术,卡马克卷轴就是一种最经典的地图缓冲绘制技术。可有效的改善在地图绘制中的屏幕闪耀,帧数过低等情况。 平滑卷轴效果 点子是卡马克提出来的。他正在尝试一项编程上的突破:让游戏的世界不再局限于屏幕的边界——所谓的“卷轴效果”。街机是这项技术的样板,早期街机游戏的移动也是局限在一个静态的屏幕内,譬如《乒乓》,用于把球击来击去的球拍只能在屏幕的底部和顶部之间移动,再譬如《吃豆子》里的迷宫,也是局限于屏幕那么大,还有《太空入侵者》,玩家控制的飞船只能在屏幕下方左右移动,外星飞船们则是从屏幕顶部涌现。所有这些游戏都把玩家局限在那一小方天地里,缺乏一种宽广的可延伸的感觉,这种状况一直持续到1980年,在那一年,威廉斯公司推出了一款名叫《防御者》(Defender)的街机游戏,这是第一个流行的卷轴游戏,在这个科幻射击游戏里,玩家不再受屏幕大小的限制,他操纵飞船在行星的表面水平移动,一路上击落敌机营救人质,屏幕上的一幅小地图显示着玩家在整个世界里的当前位置。如果把地图扩展为正常尺寸,这个世界大概有三个半屏幕那么大。与其他街机比起来,《防御者》显得宏大得多,玩家就像进入了一个更为广阔的虚拟空间。它很快变得和《太空入侵者》一样流行,还胜过《吃豆子》成为了业界的年度游戏。无数的卷轴游戏随之出现,到1989年的时候,卷轴技术已经是新游戏的一项必不可少的标准,这其中最成功的莫过于任天堂红白机上的《超级马里奥兄弟3》(Super Mario Brothers 3)。 但在那时,1990年9月,还没有人研究出如何在PC上实现卷轴效果,大家都用一些蹩脚的技巧来让玩家觉得游戏的世界比屏幕要大,譬如当玩家移动到屏幕最右边的时候,游戏会停顿一会,然后右边的场景出现在屏幕上。部分原因是PC的性能还很差,无论是街机还是苹果机,或是任天堂那样的家用机都比PC强不少。而卡马克下定决心要找出一种办法来在PC上实现像《防御者》或《超级马里奥》那样的平滑卷轴效果。 《玩家之刃》的下一个游戏就要朝着这个方向走。当大伙聚在一起讨论的时候,卡马克给他们演示了他最新的成果,他已经可以让屏幕上的内容平滑地往下方移动,和那些成熟的卷轴游戏比起来,这项技术还很粗糙,它就像是一条传送带,图像按照固定的速度和路线落下,玩家还不可能随心所欲地在里面畅游,那就像是拖动演员背后的舞台布景。 罗梅洛这个博览过几乎每一款PC游戏的玩家,没有见过这种效果。这对他们而言是一个成为先行者的好机会。他们给游戏命名为《搜捕》(Slordax),一个简单的飞船射击游戏,就像《太空入侵者》或《小蜜蜂》(Galaga)一样。 (此为一种实现方式的具体介绍) 操作贴片引擎时,大型的地图需要卷轴以便玩家可以看到整个地图,具体说,在绘制地图时尝试去改变坐标,贴片引擎就会产生一个急促的运动。为了提高引擎的视觉质量,需要使用一种称之为平滑卷轴(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(); } 该程序展示了平滑卷轴技术的使用,用上下左右键进行控制,截图如下: |
随便看 |
百科全书收录4421916条中文百科知识,基本涵盖了大多数领域的百科知识,是一部内容开放、自由的电子版百科全书。