天行健 君子当自强而不息

游戏程序流、状态处理机、进程管理器、数据包系统的实现


要在游戏开发最初清楚地知道需要做些什么,就必须构造程序的操作流,并确保能够很容易地对程序进行修改。
一个典型的程序,首先要初始化所有的系统和数据,然后进入主循环,大部分事情都出现在主循环中,根据游戏状态(标题画面,菜单画面,游戏画面等),需要对输入和输出进行不同的处理。

下面是一个标准游戏应用程序应该遵循的步骤:

1、初始化系统(Windows,图形,输入等)。
2、准备数据(加载配置文件)。
3、配置缺省状态(一般是标题画面)。
4、开始主循环。
5、判定状态并通过获取输入、输出并对之进行处理。
6、如果应用程序没结束,则返回第5步,否则进行第7步。
7、清除数据(释放内存资源等)。
8、释放系统(Windows,图形,输入等)。

状态处理机的实现

状态是运行状态的简称,表示正在运行的应用程序所包含的当前处理过程。

基于状态的编程(state-based programming)本质上是根据状态的一个栈分支执行,每个状态表示一个对象或函数集,如果需要添加函数,只需要将它们加入到栈中。当用完函数后,再从栈中移除掉它们。

使用状态管理器可以添加删除处理状态,当添加一个状态时,这个状态就被压入栈中,这样当管理器还在处理时,就获得了当前的控制权,一旦状态被弹出栈,最上面的状态就被丢弃了,接着就会处理第二高的状态。

如下如所示:这是一个可以在需要时将状态压入(push)和弹出(pop)的栈



因此需要实现一个接收指向函数(此函数表示状态)指针的状态管理器,压入一个状态也就变成了将函数指针加入到栈中,调用状态管理器就会处理栈中最上面的状态。

以下是一个简单的实现:

/*******************************************************************************
PURPOSE:
    State-based processing Demo.
******************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>

class STATE_MANAGER
{
private:
    typedef 
void (*STATE_FUNC_PTR)();

    
// A structure that stores a function pointer and linker list
    struct STATE
    {
        STATE_FUNC_PTR  func;
        STATE
* next;
    };

protected:
    STATE
* _state_parent;        // the top state in the stack (the head of the stack)

public:
    STATE_MANAGER()
    {
        _state_parent 
= NULL;
    }

    
~STATE_MANAGER()
    {
        STATE
* state_ptr;

        
// remove all states from the stack
        while((state_ptr = _state_parent) != NULL)
        {
            _state_parent 
= state_ptr->next;
            delete state_ptr;
        }
    }

    
// push a function on to the stack
    void Push(STATE_FUNC_PTR func)
    {
        
// do not push a null value
        if(func == NULL)
            
return;

        
// allocate a new state and push it on stack

        STATE
* state_ptr = new STATE;

        state_ptr
->next  = _state_parent;
        state_ptr
->func  = func;

        _state_parent    
= state_ptr;        
    }

    BOOL Pop()
    {
        STATE
* state_ptr = _state_parent;

        
// remove the head of statck (if any)
        if(state_ptr != NULL)
        {
            _state_parent 
= state_ptr->next;
            delete state_ptr;
        }

        
// return TRUE if more states exist, FALSE otherwise.
        return (_state_parent != NULL);
    }

    BOOL Process()
    {
        
// return an error if no more states
        if(_state_parent == NULL)
            
return FALSE;

        
// process the top-most state (if any)
        _state_parent->func();

        
return TRUE;
    }
};

STATE_MANAGER g_state_manager;

// macro to ease the use of MessageBox function
#define MB(s) MessageBox(NULL, s, s, MB_OK);

// state function prototypes - must follow this protytype!

void Func1()
{
    MB(
"1");
    g_state_manager.Pop();
}

void Func2()
{
    MB(
"2");
    g_state_manager.Pop();
}

void Func3()
{
    MB(
"3");
    g_state_manager.Pop();
}

int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    g_state_manager.Push(Func1);
    g_state_manager.Push(Func2);
    g_state_manager.Push(Func3);

    
while(g_state_manager.Process() == TRUE)
        ;

    
return 0;
}

进程管理器的实现

如果正在使用单独的模块来处理所有的中间函数(称为进程),比如输入,网络以及声音处理过程,而不是一个一个单独调用,就可以创建一个对象来一次全部处理。

下图表示一个由频繁调用的函数组成的进程栈,当调用时,添加到管理器的每个函数都是按序执行的。



如下是一个简单的实现:

/*******************************************************************************
PURPOSE:
    Stack Process Demo.
******************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>

class PROCESS_MANAGER
{
private:
    typedef 
void (*PROCESS_FUNC_PTR)();

    
// A structure that stores a function pointer and linked list
    struct PROCESS
    {
        PROCESS_FUNC_PTR func;
        PROCESS
* next;
    };

protected:
    PROCESS
* process_parent;    // the top process in the stack (the head of the stack)

public:
    PROCESS_MANAGER()
    {
        process_parent 
= NULL;
    }

    
~PROCESS_MANAGER()
    {
        PROCESS
* process_ptr;

        
// remove all processes from the stack
        while((process_ptr = process_parent) != NULL)
        {
            process_parent 
= process_ptr->next;
            delete process_ptr;
        }
    }

    
// add function on to the stack
    void Add(PROCESS_FUNC_PTR process)
    {
        
// do not push a NULL value
        if(process == NULL)
            
return;

        
// allocate a new process and push it onto stack
        PROCESS* process_ptr = new PROCESS;

        process_ptr
->func = process;
        process_ptr
->next = process_parent;

        process_parent  
= process_ptr;
    }

    
// process all functions
    void Process()
    {
        PROCESS
* process_ptr = process_parent;

        
while(process_ptr != NULL)
        {
            process_ptr
->func();
            process_ptr 
= process_ptr->next;
        }
    }
};

PROCESS_MANAGER g_process_manager;


// Macro to ease the use of MessageBox function
#define MB(s) MessageBox(NULL, s, s, MB_OK);

// Processfunction prototypes - must follow this prototype!
void Func1() { MB("1"); }
void Func2() { MB("2"); }
void Func3() { MB("3"); }

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdLine, int cmdShow)
{
    g_process_manager.Add(Func1);
    g_process_manager.Add(Func2);
    g_process_manager.Add(Func3);

    g_process_manager.Process();
    g_process_manager.Process();

    
return 0;
}

这个对象PROCESS_MANAGER类似于STATE_MANAGER,但有点不同,PROCESS_MANAGER只添加进程而不删除它们。

数据包系统的实现

处理应用程序数据,最容易的方法就是创建一个数据包系统,来处理保存和加载数据的工作。通过创建一个包含数据缓冲区的对象,就能添加一个新函数来保存和加载数据。

下图形象的给出了数据存储的方式,因为数据缓冲区足够大,可以存储每个人名实例。在这种情况下,存储了两个名字,每个名字使用32字节,加起来一共使用了64字节大小的缓冲区。



下面是一个简单的实现:

/*******************************************************************************
PURPOSE:
    Data Storage Demo.
******************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>

#pragma warning(disable : 
4996)

class DATA_PACKAGE
{
protected:
    
// data buffer and size
    void* _buf;
    unsigned 
long _size;

public:
    DATA_PACKAGE()
    {
        _buf  
= NULL;
        _size 
= 0;
    }

    
~DATA_PACKAGE()
    {
        Free();
    }

    
void* Create(unsigned long size)
    {
        
// free a previous created buffer
        Free();

        
// allocate some memory and return a pointer
        _size = size;

        
if((_buf = (void*new char[size]) == NULL)
            
return NULL;

        memset(_buf, 
0, size);
        
return _buf;
    }

    
// free the allocated memory
    void Free()
    {
        delete _buf; _buf 
= NULL;
        _size 
= 0;
    }

    BOOL Save(
const char* filename)
    {
        FILE
* fp;

        
// make sure there is something to write
        if(_buf == NULL || _size == 0)
            
return FALSE;

        
// open file, write size and data.
        if((fp = fopen(filename, "wb")) == NULL)
            
return FALSE;

        fwrite(
&_size, 14, fp);
        fwrite(_buf, 
1, _size, fp);

        fclose(fp);
        
return TRUE;
    }

    
void* Load(const char* filename, unsigned long* size)
    {
        FILE
* fp;

        
// free a prior buffer
        Free();

        
if((fp = fopen(filename, "rb")) == NULL)
            
return NULL;

        
// read in size and data
        fread(&_size, 14, fp);

        
if((_buf = (void*new char[_size]) == NULL)
            
return NULL;

        fread(_buf, 
1, _size, fp);
        fclose(fp);

        
// store size to return
        if(size != NULL)
            
*size = _size;

        
// return pointer
        return _buf;
    }
};

// a structure to contain a name
struct NAME
{
    
char name[32];
};

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdLine, int cmdShow)
{
    DATA_PACKAGE data_package;
    DWORD size;

    
// create the data package (w/64 bytes) and get the pointer, casting it to an NAME structure type.
    NAME* names = (NAME*) data_package.Create(64);

    
// since there are 64 bytes total, and each name uses 32 bytes, then I can have 2 names stored.
    strcpy(names[0].name, "Jim");
    strcpy(names[
1].name, "Adams");

    
const char* filename = "names.data";

    
// save the names to disk
    data_package.Save(filename);    
    
// load the names from disk, size will equal 64 when the load function returns.
    names = (NAME*) data_package.Load(filename, &size);

    
// display the names
    MessageBox(NULL, names[0].name, "1st name", MB_OK);
    MessageBox(NULL, names[
1].name, "2nd name", MB_OK);

    
return 0;
}

程序框架的实现

从最基本的层面而言,框架应该包含初始化应用程序窗口的代码,各种引擎(图形,输入,网络,声音),处理初始化,每帧渲染,以及退出清理函数。

以下是一个简单的实现:

/*****************************************************************************
PURPOSE:
    Shell Application.
 ****************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>

#define WINDOW_WIDTH    400
#define WINDOW_HEIGHT   400

// window handles, class and caption text.
HWND        g_hwnd;
HINSTANCE    g_inst;
static char g_classname[] = "ShellClass";
static char g_caption[]   = "Shell Application";

long FAR PASCAL Window_Proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL Do_Init();
BOOL Do_Shutdown();
BOOL Do_Frame();

int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASSEX wnd_class;
    MSG msg;

    g_inst 
= inst;

    
// create the window class here and register it
    wnd_class.cbSize        = sizeof(wnd_class);
    wnd_class.style            
= CS_CLASSDC;
    wnd_class.lpfnWndProc    
= Window_Proc;
    wnd_class.cbClsExtra    
= 0;
    wnd_class.cbWndExtra    
= 0;
    wnd_class.hInstance        
= inst;
    wnd_class.hIcon            
= LoadIcon(NULL, IDI_APPLICATION);
    wnd_class.hCursor        
= LoadCursor(NULL, IDC_ARROW);
    wnd_class.hbrBackground    
= NULL;
    wnd_class.lpszMenuName    
= NULL;
    wnd_class.lpszClassName 
= g_classname;
    wnd_class.hIconSm        
= LoadIcon(NULL, IDI_APPLICATION);

    
if(! RegisterClassEx(&wnd_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateWindow(g_classname, g_caption, WS_CAPTION | WS_SYSMENU, 
                          
00, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, inst, NULL);

    
if(! g_hwnd)
        
return FALSE;

    ShowWindow(g_hwnd, SW_NORMAL);
    UpdateWindow(g_hwnd);

    
// run init function and return on error
    if(! Do_Init())
        
return FALSE;

    
// start message pump, waiting for singal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }

        
if(! Do_Frame())
            
break;
    }
    
    
// run shutdown function
    Do_Shutdown();

    UnregisterClass(g_classname, inst);

    
return int(msg.wParam);
}

long FAR PASCAL Window_Proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return long(DefWindowProc(hWnd, msg, wParam, lParam));
}

BOOL Do_Init()
{
    
return TRUE;
}

BOOL Do_Shutdown()
{
    
return TRUE;
}

BOOL Do_Frame()
{
    
return TRUE;
}

posted on 2007-06-10 21:23 lovedday 阅读(994) 评论(0)  编辑 收藏 引用 所属分类: ■ RPG Program


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


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论