平滑捲軸效果

平滑捲軸效果

對於J2ME框架下的手機遊戲程式的開發,其地圖滾動的重繪有多種算法,由於手機性能的限制和開發周期等其他非技術條件,需要根據情況靈活選擇所需的技術。但在及其苛刻條件下,如系統CPU資源不足,地圖塊尺寸較小等,會造成螢幕閃耀,幀數過低等情況,嚴重影響到遊戲體驗。在開發中如此類問題無法繞過以及避免(指通過修改策劃方案,以及程式使用的技術框架),則需要考慮使用地圖緩衝繪製技術,卡馬克捲軸就是一種最經典的地圖緩衝繪製技術。可有效的改善在地圖繪製中的螢幕閃耀,幀數過低等情況。

基本介紹

  • 中文名:平滑捲軸效果
  • 外文名:Smooth scroll effect
  • 實質:有效的改善
  • 特點:幀數過低
平滑捲軸效果
點子是卡馬克提出來的。他正在嘗試一項編程上的突破:讓遊戲的世界不再局限於螢幕的邊界——所謂的“捲軸效果”。街機是這項技術的樣板,早期街機遊戲的移動也是局限在一個靜態的螢幕內,譬如《桌球》,用於把球擊來擊去的球拍只能在螢幕的底部和頂部之間移動,再譬如《吃豆子》里的迷宮,也是局限於螢幕那么大,還有《太空入侵者》,玩家控制的飛船只能在螢幕下方左右移動,外星飛船們則是從螢幕頂部湧現。所有這些遊戲都把玩家局限在那一小方天地里,缺乏一種寬廣的可延伸的感覺,這種狀況一直持續到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();
}
該程式展示了平滑捲軸技術的使用,用上下左右鍵進行控制,截圖如下:

相關詞條

熱門詞條

聯絡我們