1.網絡通信模塊
2.協議模塊
3.線程池模塊
4.內存管理模塊
5.游戲規則處理模塊
6.后臺游戲仿真世界模塊。
現在就網絡中的通信模塊處理談一下自己的看法!!
在網絡游戲客戶端和服務器端進行交互的雙向I/O模型中分別有以下幾種模型:
1. Select模型
2. 事件驅動模型
3. 消息驅動模型
4. 重疊模型
5. 完成端口重疊模型。
在這樣的幾種模型中,能夠通過硬件性能的提高而提高軟件性能,并且能夠同時處理成千上百個I/O請求的模型。服務器端應該采用的最佳模型是:完成端口模型。然而在眾多的模型之中完成端口的處理是最復雜的,而它的復雜之處就在于多服務器工作線程并行處理客戶端的I/O請求和理解完成端口的請求處理過程。
對于服務器端完成端口的處理過程總結以下一些步驟:
1. 建立服務器端SOCKET套接字描述符,這一點比較簡單。
例如:
SOCKET server_socket;
Server_socket = socket(AF_INET,SOCK_STREAM,0);
2.綁定套接字server_socket。
Const int SERV_TCP_PORT = 5555;
struct sockaddr_in server_address.
memset(&server_address, 0, sizeof(struct sockaddr_in));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(SERV_TCP_PORT);
//綁定
Bind(serve_socket,( struct sockaddr *)&server_address, sizeof(server_address));
2. 對于建立的服務器套接字描述符偵聽。
Listen(server_socket ,5);
3. 初始化我們的完成端口,開始的時候是產生一個新的完成端口。
HANDLE hCompletionPort;
HCompletionPort = CreateIoCompletionPort(NULL,NULL,NULL,0);
4. 在我們已經產生出來新的完成端口之后,我們就需要進行系統的偵測來得到系統的硬件信息。從而來定出我們的服務器完成端口工作線程的數量。
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
在我們知道我們系統的信息之后,我們就需要做這樣的一個決定,那就是我們的服務器系統該有多少個線程進行工作,我一般會選擇當前處理器的2倍來生成我們的工作線程數量(原因考慮線程的阻塞,所以就必須有后備的線程來占有處理器進行運行,這樣就可以充分的提高處理器的利用率)。
代碼:
WORD threadNum = system_info. DwNumberOfProcessors*2+2;
for(int i=0;I
HANDLE hThread;
DWORD dwthreadId;
hThread = _beginthreadex(NULL,ServerWorkThrea,
(LPVOID)hCompletePort,0,&dwthreadId);
CloseHandle(hThread);
}
CloseHandle(hThread)在程序代碼中的作用是在工作線程在結束后,能夠自動銷毀對象作用。
6. 產生服務器檢測客戶端連接并且處理線程。
HANDLE hAcceptThread;
DWORD dwThreadId;
hAcceptThread= _beginthreadex(NULL,AcceptWorkThread,NULL,
&dwThreadId);
CloseHandle(hAcceptThread);
7.連接處理線程的處理,在線程處理之前我們必須定義一些屬于自己的數據結構體來進行網絡I/O交互過程中的數據記錄和保存。
首先我要將如下幾個函數來向大家進行解析:
1.
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, // handle to file
HANDLE ExistingCompletionPort, // handle to I/O completion port
ULONG_PTR CompletionKey, // completion key
DWORD NumberOfConcurrentThreads // number of threads to execute concurrently
);
參數1:
可以用來和完成端口聯系的各種句柄,在這其中可以包括如下一些:
套接字,文件等。
參數2:
已經存在的完成端口的句柄,也就是在第三步我們初始化的完成端口的句柄就可以了。
參數3:
這個參數對于我們來說將非常有用途。這就要具體看設計者的想法了, ULONG_PTR對于完成端口而言是一個單句柄數據,同時也是它的完成鍵值。同時我們在進行
這樣的GetQueuedCompletionStatus(….)(以下解釋)函數時我們可以完全得到我們在此聯系函數中的完成鍵,簡單的說也就是我們在CreateIoCompletionPort(…..)申請的內存塊,在GetQueuedCompletionStatus(……)中可以完封不動的得到這個內存塊,并且使用它。這樣就給我們帶來了一個便利。也就是我們可以定義任意數據結構來存儲我們的信息。在使用的時候只要進行強制轉化就可以了。
參數4:
引用MSDN上的解釋
[in] Maximum number of threads that the operating system allows to concurrently process I/O completion packets for the I/O completion port. If this parameter is zero, the system allows as many concurrently running threads as there are processors in the system.
這個參數我們在使用中只需要將它初始化為0就可以了。上面的意思我想大家應該也是了解的了!嘿嘿!!
我要向大家介紹的第二個函數也就是
2.
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, // handle to completion port
LPDWORD lpNumberOfBytes, // bytes transferred
PULONG_PTR lpCompletionKey, // file completion key
LPOVERLAPPED *lpOverlapped, // buffer
DWORD dwMilliseconds // optional timeout value
);
參數1:
我們已經在前面產生的完成端口句柄,同時它對于客戶端而言,也是和客戶端SOCKET連接的那個端口。
參數2:
一次完成請求被交換的字節數。(重疊請求以下解釋)
參數3:
完成端口的單句柄數據指針,這個指針將可以得到我們在CreateIoCompletionPort(………)中申請那片內存。
借用MSDN的解釋:
[out] Pointer to a variable that receives the completion key value associated with the file handle whose I/O operation has completed.A completion key is a per-file key that is specified in a call to CreateIoCompletionPort.
所以在使用這個函數的時候只需要將此處填一相應數據結構的空指針就可以了。上面的解釋只有大家自己擺平了。
參數4:
重疊I/O請求結構,這個結構同樣是指向我們在重疊請求時所申請的內存塊,同時和lpCompletionKey,一樣我們也可以利用這個內存塊來存儲我們要保存的任意數據。以便于我們來進行適當的服務器程序開發。
[out] Pointer to a variable that receives the address of the OVERLAPPED structure that was specified when the completed I/O operation was started.(MSDN)
3.
int WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
這個函數也就是我們在進行完成端口請求時所使用的請求接受函數,同樣這個函數可以用ReadFile(………)來代替,但不建議使用這個函數。
參數1:
已經和Listen套接字建立連接的客戶端的套接字。
參數2:
用于接受請求數據的緩沖區。
[in/out] Pointer to an array of WSABUF structures. Each WSABUF structure contains a pointer to a buffer and the length of the buffer.(MSDN)。
參數3:
參數2所指向的WSABUF結構的數量。
[in] Number of WSABUF structures in the lpBuffers array.(MSDN)
參數4:
[out] Pointer to the number of bytes received by this call if the receive operation completes immediately. (MSDN)
參數5:
[in/out] Pointer to flags.(MSDN)
參數6:
這個參數對于我們來說是比較有作用的,當它不為空的時候我們就是提出我們的重疊請求。同時我們申請的這樣的一塊內存塊可以在完成請求后直接得到,因此我們同樣可以通過它來為我們保存客戶端和服務器的I/O信息。
參數7:
[in] Pointer to the completion routine called when the receive operation has been completed (ignored for nonoverlapped sockets).(MSDN)
4.
int WSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
參數解釋可以參考上面或者MSDN。在這里就不再多說了。
下面就關client端用戶連接(connect(……..))請求的處理方式進行
舉例如下:
const int BUFFER_SIZE = 1024;
typedef struct IO_CS_DATA
{
SOCKET clisnt_s; //客戶端SOCKET
WSABUF wsaBuf;
Char inBuffer[BUFFET_SIZE];
Char outBuffer[BUFFER_SIZE];
Int recvLen;
Int sendLen;
SYSTEM_TIME start_time;
SYSTEM_TIME start_time;
}IO_CS_DATA;
UINT WINAPI ServerAcceptThread(LPVOID param)
{
SOCKET client_s;
HANDLE hCompltPort = (HANDLE) param;
struct sockaddr_in client_addr;
int addr_Len = sizeof(client_addr);
LPHANDLE_DATA hand_Data = NULL;
while(true)
{
If((client_s=accept(server_socket,NULL,NULL))
==SOCKET_ERROR)
{
printf("Accept() Error: %d",GetLastError());
return 0;
}
hand_Data = (LPHANDLE_DATA)malloc(sizeof(HANDLE_DATA));
hand_Data->socket = client_s;
if(CreateIoCompletionPort((HANDLE)client_s,hCompltPort,
(DWORD)hand_Data,0)==NULL)
{
printf("CreateIoCompletionPort()Error: %d",
GetLastError());
}
else
{
game_Server->RecvDataRequest(client_s);
}
}
return 0;
}
在這個例子中,我們要闡述的是使用我們已經產生的接受連接線程來完成我們響應Client端的connect請求。關于這個線程我們同樣可以用我們線程池的方式來進行生成多個線程來進行處理,其他具體的函數解釋已經在上面解釋過了,希望不懂的自己琢磨。
關于game_Sever object的定義處理將在下面進行介紹。
class CServerSocket : public CBaseSocket
{
public:
CServerSocket();
virtual ~CServerSocket();
bool StartUpServer(); //啟動服務器
void StopServer(); //關閉服務器
//發送或者接受數據(重疊請求)
bool RecvDataRequest(SOCKET client_s);
bool SendDataRequest(SOCKET client_s,char *buf,int b_len);
void ControlRecvData(SOCKET client_s,char *buf,int b_len);
void CloseClient(SOCKET client_s);
private:
friend UINT WINAPI GameServerThread(LPVOID completionPortID); //游戲服務器通信工作線程
private:
void Init();
void Release();
bool InitComplePort();
bool InitServer();
bool CheckOsVersion();
bool StartupWorkThread();
bool StartupAcceptThread();
private:
enum { SERVER_PORT = 10006};
UINT cpu_Num; //處理器數量
CEvent g_ServerStop; //服務器停止事件
CEvent g_ServerWatch; //服務器監視事件
public:
HANDLE hCompletionPort; //完成端口句柄
};
在上面的類中,是我們用來處理客戶端用戶請求的服務器端socket模型。
1. 使用Runnable和創建線程的主循環
一般主體的做法就是讓Displayable這個類實現Runnable這個接口,然后在其構造函數中創建一個線程,啟動其run()函數,而run函數里面就包含了游戲的主循環。下面是我在仙劍里面的片斷代碼。
public class GameMIDlet extends MIDlet {
static GameMIDlet instance;
Display display;
GameDisplayable displayable = null;
public GameMIDlet() {
instance = this;
display = Display.getDisplay(this);
displayable = new GameDisplayable();
}
public void startApp() {
display.setCurrent(displayable);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
displayable.running = false;
}
public static void quitApp() {
instance.destroyApp(true);
instance.notifyDestroyed();
instance = null;
}
}
public class GameDisplayable extends FullCanvas implements Runnable {
/** 主控制線程 */
Thread MainThread = null;
/** 游戲時鐘間隔 毫秒為單位 */
public static long timeinterval = 20;
public static boolean Isstable = true;
/* 用于游戲時鐘的變量 */
public static long timeold = 0;
public static long timenow = 0;
public long interval = 0;
public static long frames_per_second = 0;
int count = 0;
long second = 0;
public static boolean running = true;
public GameDisplayable() {
// 開始主線程
Thread MainThread = new Thread(this);
MainThread.start();
}
public void run() {
while (running) {
timenow = System.currentTimeMillis();
interval = timenow - timeold;
if (interval >= timeinterval) {
timeold = timenow;
Game_Process();
if (second != (System.currentTimeMillis() / 1000)) {
second = System.currentTimeMillis() / 1000;
frames_per_second = count;
count = 1;
}
else
count++;
}
lib.sleep(30);
}
}
其中關于控制主循環速度的代碼可以不要,但是lib.sleep(30)必須保留,因為在Nokia 60的手機上,如果去除了sleep(30),那么游戲將無法切換回來。同時,在游戲中任何一個內部循環中,也必須加入sleep(30)這個等待,才能讓游戲可以切換回來,至于為什么這樣做,我暫時還不清楚。30ms是我測試過沒有問題的數值,可能比30ms還小的值也是沒有問題的。
同時,在MOTO的手機上,必須將游戲的主循環放在一個線程中,游戲才能切換回來,不過可以不加上面說的sleep(30)延時。
2. 不使用線程的主循環辦法
這個辦法只能在Nokia的平臺上實現,而我只建議在Nokia 40的平臺上做,這樣不需要線程,道理上來說節約了一些內存,如果不是內存很緊張的機型,那么最好還是使用上一種辦法。
游戲的主循環放在MIDlet的class里面,具體做法如下:
public class GameMIDlet extends MIDlet {
GameDisplayable displayable = null;
/** 游戲時鐘間隔 毫秒為單位 */
public static long timeinterval = 0;
//用于游戲時鐘的變量
public static long timeold = 0;
public static long timenow = 0;
public long interval = 0;
public static long frames_per_second=0;
int count=0;
long second =0;
public static boolean running = false;
static boolean exitApp =false;
public GameMIDlet() {
displayable = new GameDisplayable();
running =true;
}
public void startApp() {
running =true;
Display.getDisplay(this).setCurrent(displayable);
while(running) {
timenow = System.currentTimeMillis();
interval = timenow - timeold;
if (interval >= timeinterval) {
timeold = timenow;
displayable.Game_Process();
if(second != (System.currentTimeMillis() /1000)){
second = System.currentTimeMillis()/1000;
frames_per_second = count;
count = 1;
}else
count ++;
}
}
if(exitApp) {
destroyApp(true);
notifyDestroyed();
}
}
public void pauseApp() {
running =false;
}
public void destroyApp(boolean unconditional) {
running = false;
}
public static void quitApp() {
running =false;
exitApp =true;
}
}
要求有源代碼和版權,個人/團隊作品最好。
游戲面向機型可包括:
諾基亞40系列、諾基亞60系列、索尼-愛立信系列
摩托羅拉系列、NEC系列、SHARP夏普系列、諾基亞80系列等
游戲類型包括:
角色扮演類、動作類、棋牌類、射擊類
運動類、策略類、養成類,聯網類等
游戲大小:不限
聯系人:陳先生
QQ:251158873
MSN:linxiaochen79@hotmail.com
Email:childin@163.com
角色扮演游戲引擎的設計原理
角色扮演游戲(RPG)是深受廣大游戲迷們喜愛的一種游戲, 它以獨特的互動性和故事性吸引了無數的玩家。它向人們提供了超出現實生活的廣闊的虛擬世界,使人們能夠嘗試扮演不同的角色,去經歷和體驗各種不同的人生旅程或奇幻經歷。這些體驗都是在現實生活中無法實現的。在玩過許多游戲后,許多玩家都不再僅僅滿足于一個游戲玩家的身份,而會思考游戲是如何制作的,并且打算制作一個自己的游戲,網上的各種游戲制作小組更是如雨后春筍般涌現。下面我就給大家介紹一下角色扮演游戲引擎的原理與制作,希望能對游戲制作愛好者有所幫助。
一 游戲引擎的原理
說到引擎,游戲迷們都很熟悉。游戲引擎是一個為運行某一類游戲的機器設計的能夠被機器識別的代碼(指令)集合。它象一個發動機,控制著游戲的運行。一個游戲作品可以分為游戲引擎和游戲資源兩大部分。游戲資源包括圖象,聲音,動畫等部分,列一個公式就是:游戲=引擎(程序代碼)+資源(圖象,聲音,動畫等)。游戲引擎則是按游戲設計的要求順序的調用這些資源。
二 角色扮演游戲的制作
一個完整的角色扮演游戲的制作從大的分工來說可以分為:策劃,程序設計,美工,音樂制作以及項目管理,后期的測試等。
策劃主要任務是設計游戲的劇情,類型以及模式等,并分析游戲的復雜性有多大,內容有多少,策劃的進度要多快等因素。
程序設計的任務是用某種編程語言來完成游戲的設計,并與策劃配合,達到預期的目的。
美工主要是根據游戲的時代背景與主題設計游戲的場景及各種角色的圖象。
音樂制作是根據游戲的劇情和背景制作游戲的音樂與音效。
項目管理主要是控制游戲制作的進程,充分利用現有的資源(人員,資金,設備等),以達到用盡量少的資金實現最大的收益。
后期的測試也是非常重要的一個環節,對于一個幾十人花費幾個月甚至是幾年時間制作的游戲,測試往往能找到許多問題,只有改進程序才能確保游戲的安全發行。
由于文章主要是講解游戲程序的制作的,所以策劃,美工,音樂制作等方面請讀者參考其它文章,下面我就講講游戲程序的設計。
(一) 開發工具與主要技術
1.件開發工具
游戲程序開發工具有很多,在不同游戲平臺上有不同的開發工具。在個人計算機上,可以用目前流性的軟件開發工具,比如:C,C++,VC++,Delphi,C++ Builder等。由于Windows操作系統的普及和其強大的多媒體功能,越來越多的游戲支持Windows操作系統。由于VC是微軟的產品,用它來編寫Windows程序有強大的程序接口和豐富的開發資源的支持,加之VC嚴謹的內存管理,在堆棧上良好的分配處理,生成代碼的體積小,穩定性高的優點,所以VC++就成為目前游戲的主流開發工具。
2.DirectX組件的知識
談到Windows系統下的游戲開發,我們就要說一下微軟的DirectX SDK。
Windows系統有一個主要優點是應用程序和設備之間的獨立性。然而應用程序的設備無關性是通過犧牲部分速度和效率的到的,Windows在硬件和軟件間添加了中間抽象層,通過這些中間層我們的應用程序才能在不同的硬件上游刃有余。但是,我們因此而不能完全利用硬件的特征來獲取最大限度的運算和顯示速度。這一點在編寫Windows游戲時是致命的,DirectX便是為解決這個問題而設計的。DirectX由快速的底層庫組成并且沒有給游戲設計添加過多的約束。微軟的DirectX軟件開發工具包(SDK)提供了一套優秀的應用程序編程接口(APIs),這個編程接口可以提供給你開發高質量、實時的應用程序所需要的各種資源。
DirectX的6個組件分別是:
DirectDraw: 使用頁面切換的方法實現動畫,它不僅可以訪問系統內存,還可以訪問顯示內存。
Direct3D: 提供了3D硬件接口。
DirectSound: 立體聲和3D聲音效果,同時管理聲卡的內存。
DirectPlay: 支持開發多人網絡游戲,并能處理游戲中網絡之間的通信問題。
DirectInput: 為大量的設備提供輸入支持。
DirectSetup: 自動安裝DirectX驅動程序。
隨著DirectX版本的提高,還增加了音樂播放的DirectMusic。
3.AlphaBlend 技術
現在許多游戲為了達到光影或圖象的透明效果都會采用AlphaBlend 技術。所謂AlphaBlend技術,其實就是按照"Alpha"混合向量的值來混合源像素和目標像素,一般用來處理半透明效果。在計算機中的圖象可以用R(紅色),G(綠色),B(藍色)三原色來表示。假設一幅圖象是A,另一幅透明的圖象是B,那么透過B去看A,看上去的圖象C就是B和A的混合圖象,設B圖象的透明度為alpha(取值為0-1,0為完全透明,1為完全不透明),Alpha混合公式如下:
R(C)=alpha*R(B)+(1-alpha)*R(A)
G(C)=alpha*G(B)+(1-alpha)*G(A)
B(C)=alpha*B(B)+(1-alpha)*B(A)
R(x)、G(x)、B(x)分別指顏色x的RGB分量原色值。從上面的公式可以知道,Alpha其實是一個決定混合透明度的數值。應用Alpha混合技術,可以實現游戲中的許多特效,比如火光、煙霧、陰影、動態光源等半透明效果。
4.A*算法
在許多游戲中要用鼠標控制人物運動,而且讓人物從目前的位置走到目標位置應該走最短的路徑。這就要用到最短路徑搜索算法即A*算法了。
A*算法實際是一種啟發式搜索,所謂啟發式搜索,就是利用一個估價函數評估每次的的決策的價值,決定先嘗試哪一種方案。如果一個估價函數可以找出最短的路徑,我們稱之為可采納性。A*算法是一個可采納的最好優先算法。A*算法的估價函數可表示為:
f(n) = g(n) + h(n)
這里,f(n)是節點n的估價函數,g(n)是起點到終點的最短路徑值,h(n)是n到目標的最斷路經的啟發值。由于A*算法比較復雜,限于篇幅,在此簡單介紹一下,具體理論朋友們可以看人工智能方面的書籍了解詳細的情況。
其它技術還有粒子系統,音頻與視頻的調用,圖象文件的格式與信息存儲等,大家可以在學好DirectX的基礎上逐漸學習更多的技術。
(二)游戲的具體制作
1.地圖編輯器的制作
RPG游戲往往要有大量的場景,場景中根據需要可以有草地,湖泊,樹木,房屋,家具等道俱,由于一個游戲需要很多場景且地圖越來越大,為了節省空間,提高圖象文件的可重用性,RPG游戲的畫面采用很多重復的單元(可以叫做“圖塊”)所構成的,這就要用到地圖編輯器了。我們在制作游戲引擎前,要完成地圖編輯器的制作。在 RPG游戲里,場景的構成,是圖塊排列順序的記錄。首先制定一個場景構成文件的格式,在這個文件里記錄構成場景所需要的圖塊的排列順序,因為我們已經為每個圖塊建立了索引,所以只需要記錄這些索引就可以了。一個場景的構成,是分成幾層來完成的:地面,建筑和植物,家具擺設,和在場景中活動的人物或者物體(比如飄揚的旗幟),按照一定的順序把它們依次顯示到屏幕上,就形成了一個豐富多采的場景。我們可以用數組來表示地圖場景的生成過程。
MapData[X][Y]; //地圖數據,X表示地圖寬度,Y表示地圖高度
Picture[num]; //道具的圖片,num表示道具的總數
void MakeBackGround() //生成場景函數
{
int n;
for( int i=0; i<Y; i++) //共Y行
for( int j=0; j<X; j++) //共X列
{
n=MapData[ i ][ j ]; //取得該位置的道具編號
Draw( j*32, i*32, Picture[n]); //在此位置(j*32,i*32)畫道具
}
}
2.游戲的模塊的劃分
游戲按功能分為:消息處理系統、場景顯示及行走系統、打斗系統三大主要部分。其中又以消息處理系統為核心模塊,其余部分緊緊圍繞它運行。
一:消息處理系統
消息處理系統是游戲的核心部分。游戲用到的消息處理系統先等待消息,然后根據收到的消息轉到相應的函數進行處理。比如:主角碰到敵人后,我們就讓程序產生‘打斗消息’,消息處理系統收到這個消息后就會馬上轉到打斗模塊中去。消息處理的大體框架如下:
//定義程序中要用到的變量
DWORD Message; //消息變量
WinMain() //進入程序
{
初始化主窗口;
初始化DirectDraw環境,并調入程序需要的圖形、地圖數據;
while( 1 ) //消息循環
{
switch( Message )
{
case 行走消息: 行走模塊();
case 打斗消息: 打斗模塊();
case 事件消息: 事件模塊();
}
}
}
二:場景顯示及行走系統
作為RPG游戲,其所有事件的發生幾乎都是和場景有關,例如:不同的地方會碰到不同的敵人、與不同的人對話得知不同的事情等。鑒于這部分的重要性,我們可再將它劃分為:背景顯示、行走 和 事件發生 三個子模塊,分別處理各自的功能。下面進行具體分析。
(一)背景顯示
程序運行后,先讀取前面地圖編輯器制作的場景所需要的圖塊的排列順序,按照排列順序將圖象拼成一個完整的場景,一般做法是:在內存中開辟一到兩個屏幕緩存區,事先把即將顯示的圖象數據準備在緩存區內,然后一次性搬家:把它們傳送到真正的屏幕緩沖區內。
游戲用到的圖片則事先制作好并存于另外的圖形文件中。地圖編輯器制作的場景文件僅僅是對應的數據,而不是真正的圖片。在游戲中生成場景就是地圖編輯的逆過程,一個是根據場景生成數據,而另一個是根據數據生成場景。
(二)行走
要讓主角在場景中行走,至少要有上、下、左、右四個行走方向,每個方向4幅圖(站立、邁左腿、邁右腿、邁左腿),如圖:游戲中一定要將圖片的背景設為透明,這樣在畫人物的時候就不會覆蓋上背景色了(這一技術DirectDraw中只要用SetColorKey()函數將原圖片背景色過濾掉就行了)。我們讓主角位置不動,而使場景移動,即采用滾屏技術來實現角色在場景上移動。這樣角色一直保持在屏幕的正中間,需要做的工作只是根據行走方向和步伐不停變換圖片而已。行走時的障礙物判斷也是每一個場景中必定要有的,有一些道具如樹木、房屋等是不可跨越的。對此我主要用一個二維數組來對應一個場景,每一個數組值代表場景的一小格(見圖3)。有障礙的地方,該數組的對應值為1,可通過的地方的值為0。
(三)事件發生
事件發生原理就是把相應事件的序號存儲在地圖的某些格子中,當主角一踏入這個格子就會觸發對應事件。例如:在游戲開始時,主角是在他的家里。他要是想出去的話,就需要執行場景切換這個處理函數。我們假定該事件的編號為001,那么在地圖上把家門外路口處的格子值設為001。這樣主角走到路口時,編號為001的場景切換函數就會被觸發,于是主角便到了下一個場景中。程序具體如下:
void MessageLoop( int Msg ) //消息循環
{
switch( Msg )
{
char AddressName[16]; //數組AddressName[16]用來存儲主角所在地點的名稱
case ADDRESS == 001: // 由ADDRESS的值決定場景值(出門)
ScreenX=12; ScreenY=0; //初始化游戲背景位置
Hero.x=360; Hero.y=80;//主角坐標
Move();//主角移動函數
//以下程序用來顯示主角所在地點
sprintf(AddressName,"下一幅游戲場景的名稱");
PrintText(lpDDSPrimary, 280, 330,AddressName , RGB(255,255,255));//在屏幕上顯示出場景的名稱
break;
}
}
三:打斗系統
絕大多數的RPG都是有戰斗存在的,因此,打斗系統就成為RPG系統中很重要的一環。有不少RPG游戲采用回合制打斗方式,因為實現起來較為簡單。和打斗緊密相關的是升級,通常在一場戰斗結束后,主角的經驗值都會增加。而當經驗值到達一定程度時,角色就升級了。
上面我簡要的介紹了角色扮演游戲的制作,由于寫這篇文章的目的是讓讀者對角色扮演游戲的制作有一個基本的了解,所以讀者朋友們可以研究相關資料。
中文移動開發者博客:http://mobisoft.cn/blog/
Nokia論壇: http://forum.nokia.com
KVM-INTEREST: http://archives.java.sun.com/archives/kvm-interest.html
移動開發者論壇: http://mobisoft.cn/bbs/
過年了,徹底的休息了幾天真痛快,不過后面還有很多很艱巨的任務。
又要開始學習咯~祝大家新年快樂!雞年大吉!
祝男孩子們“雞”肉發達,女孩子們“雞”膚光滑~哈哈
昨天的問題解決了,成功的編譯了Hello World!,真是高興呀!
經過了N次的上網查找資料,不但解決了手上的難題,同時更是得到了很多以外的收獲。
萬事開頭難,好不容易解決了第一個難題,跨出來學習JAVA的第一步。我相信這一小步,必將引導著我今后的學習不斷向前,不斷突破,贏得最后的勝利!
做J2ME竟然做到連HelloWorld都搞不出來,郁悶呀~
在網上搜索了半天相關資料,并沒有解決問題,不過找到了不少有用的東西,對J2ME的操作及其程序的解釋都有很多新的認識。
學習,就像一場障礙賽,躍過障礙努力向前,不斷的向終點接近,收獲就在哪里!
【轉貼】德國、美國、中國三國的士兵手冊(很不錯!)
德國士兵的十戒(寫在每個德國士兵的筆記本上)!
(一)德國士兵為爭取勝利而戰斗時,必須遵守英勇作戰的規則。殘酷與無意義的破壞都與他的身份不稱。
(二)戰斗員必須穿制服,或佩帶特別指定的和清楚易辨的臂章。禁止穿便服或不帶這種臂章作戰。
(三)投降的敵人,包括游擊隊和間諜,一概不準殺害。他們應由法庭判以適當的懲罰。
(四)不準虐待或侮辱俘虜。武器、地圖、文件從他們身上拿走以后,其他的個人財物不準侵犯。
(五)達姆彈禁止使用,任何子彈一律禁止改成達姆彈。
(六)紅十字會是神圣不可侵犯的。受傷的敵人應給予人道的待遇。醫務人員和隨軍牧師在執行其醫務和宗教活動時不得阻礙。
(七)平民是神圣不可侵犯的,士兵不準掠奪和任意破壞。古跡和用作宗教、藝術、科學或慈善事業的建筑物必須特別尊重。只有奉上級命令和給報酬時,才能征收實物和使用民役。
(八)中立國的土地,飛機既不得進入,也不得越過,更不得射擊;它不得成為任何軍事行動的目標。
(九)德國士兵作為俘虜時,如果被詢問,可以說出自己的姓名和軍階。但在任何情況下不得泄露其所屬單位,也不得泄露德國軍事、政治和經濟方面的任何情報。任憑威逼利誘都不得泄露。
(十)違犯上述各條規者將予以懲罰。敵人違犯了第一至第八條應報告。只有得到高級指揮官的允許才能進行報復。
美國士兵守則
1、你不是超人。(不要無謂的冒險、不要做傻事)
2、如果一個蠢方法有效,那它就不是一個蠢方法。
3、不要太顯眼,因為那會引來對方火力攻擊。(這就是航母被稱為“炸彈磁鐵”的原因。)
4、別和比你勇敢的戰友躲在同一個散兵坑里。
5、別忘了你手上的武器是由最低價的承包商得標制造的。
6、如果你的攻擊進行得很順利,那一定是你中了圈套。
7、所有五秒的手榴彈引線都會在三秒內燒完。
8、盡量顯得是一個無關緊要的人,因為敵人可能彈藥不夠了。(他會先打最重要的人)
9、每當你要攻擊前進時,炮兵往往也快要用完了炮彈。
10、那支你以為是敵軍疑兵而不加注意的部隊恰恰就是敵人的攻擊主力。
11、重要的事總是簡單的。
12、簡單的事總是難作到。
13、好走的路總是已被敵軍布上了地雷。
14、如果你除了敵人不缺,其它什么都缺,那你往往就要面臨作戰了。
15、飛來的子彈有優先通行權。(擋它的道你就要倒大楣!)
16、如果敵人正在你的射程內,別忘了你也在他的射程內。
17、從沒有一支完成戰備的單位能通過校閱。
18、必須要裝配在一起才能發揮效力的武器裝備通常不會一起運來。
19、無線電通訊會有可能在你急需火力支援時失靈。
20、你作的任何事都可能挨槍子兒 -- 包括你什么都不做。
21、曳光彈可以幫你找到敵蹤;但也會讓敵人找到你。
22、唯一比敵人火力還精確的是友軍打過來的炮火。(誤射)
23、當你防守嚴密到敵人攻不進來時,那往往你自己也打出不去。
24、如果你多報戰功,那下次你會被給予超過你能力的目標讓你去打。(自討苦吃)
25、當兩軍都覺得自己快輸時,那他們可能都是對的。
26、專業士兵的行為是你能預測的,可惜戰場上業余的士兵占多數,因此敵人的行為大部分是你所無法預測的。
中國人民解放軍士兵守則
熱愛中國共產黨,,熱愛社會主義祖國,熱愛中國人民解放軍,全心全意為人民服務。執行黨的路線,方針,政策,遵守國家的法律,法規,執行軍隊的條令,條例和規章制度,服從命令,聽從指揮。努力學習軍事,政治,科學文化,苦練殺敵本領,愛護武器裝備,保守軍事秘密,發揚優良傳統,英勇戰斗,不怕犧牲,保衛社會主義祖國,保衛人民的和平勞動,在任何情況下決不背叛祖國,叛離軍隊。
知道“二十四孝”的人,已經不多了;知道“二十四孝”的年輕人,更是少之又少。“孝”在今日社會,似乎已是一個“過氣”的詞,人老珠黃般,逗不起眾人的欲望了。我們讀《二十四孝》,感覺那似乎是十分遙遠的故事。其實細細想來,它好像又近在咫尺,離我們并不遠。
01 孝感動天 02 親嘗湯藥 03 嚙指痛心 04 百里負米
05 蘆衣順母 06 鹿乳奉親 07 戲彩娛親 08 賣身葬父
09 刻木事親 10 行傭供母 11 懷橘遺親 12 埋兒奉母
13 扇枕溫衾 14 拾葚異器 15 涌泉躍鯉 16 聞雷泣墓
17 乳姑不怠 18 臥冰求鯉 19 恣蚊飽血 20 扼虎救父
21 哭竹生筍 22 嘗糞憂心 23 棄官尋母 24 滌親溺器