游戏策划咨讯
做一个游戏并不难,难的是做一个好游戏;完美在于积累!

当今网络游戏在中国大陆已经在大范围的蔓延,暂且不论这样的一种趋势会带来什么样的游戏产业趋势。这里只就网络游戏的制作和大家进行交流,同时将自己的制作经验写处理,希望为中国的游戏业的发展做出一点点的贡献。。
   
网络游戏的程序开发从某种意义上来看,最重要的应该在于游戏服务器端的设计和制作。对于服务器端的制作。将分为以下几个模块进行:

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模型。

posted @ 2005-02-13 11:49 蓝色雪焰 阅读(740) | 评论 (0)编辑 收藏
 
        游戏引擎的结构很多,不过基本上都是在一个游戏主循环内实现。程序里面的主循环包含了程序框架的最主要的结构体。J2me的程序一般都包含两个class文件,一个是MIDlet,一个是Displayable。一般我都是把游戏的主要代码放在Displayable这个类里面。这个类是基于事件驱动的程序,有三个主要相应函数void paint(Graphics g),void keyPressed(int keyCode),void keyReleased(int keyCode)

 

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的平台上做,这样不需要线程,道理上来说节约了一些内存,如果不是内存很紧张的机型,那么最好还是使用上一种办法。

 

游戏的主循环放在MIDletclass里面,具体做法如下:

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;

  }

 

}

posted @ 2005-02-13 11:47 蓝色雪焰 阅读(269) | 评论 (0)编辑 收藏
 
我公司欲大量购买java手机游戏,要求创意新颖,画面精美。

要求有源代码和版权,个人/团队作品最好。

游戏面向机型可包括:
诺基亚40系列、诺基亚60系列、索尼-爱立信系列
摩托罗拉系列、NEC系列、SHARP夏普系列、诺基亚80系列等

游戏类型包括:
角色扮演类、动作类、棋牌类、射击类 
运动类、策略类、养成类,联网类等

游戏大小:不限

联系人:陈先生
QQ:251158873
MSN:linxiaochen79@hotmail.com
Email:childin@163.com

posted @ 2005-02-10 21:47 蓝色雪焰 阅读(777) | 评论 (5)编辑 收藏
 

角色扮演游戏引擎的设计原理

  角色扮演游戏(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游戏采用回合制打斗方式,因为实现起来较为简单。和打斗紧密相关的是升级,通常在一场战斗结束后,主角的经验值都会增加。而当经验值到达一定程度时,角色就升级了。
  上面我简要的介绍了角色扮演游戏的制作,由于写这篇文章的目的是让读者对角色扮演游戏的制作有一个基本的了解,所以读者朋友们可以研究相关资料。

posted @ 2005-02-10 21:43 蓝色雪焰 阅读(325) | 评论 (0)编辑 收藏
 
优秀手机开发资源推荐:

中文移动开发者博客:http://mobisoft.cn/blog/

Nokia论坛:     http://forum.nokia.com

KVM-INTEREST:      http://archives.java.sun.com/archives/kvm-interest.html

移动开发者论坛:    http://mobisoft.cn/bbs/

posted @ 2005-02-10 21:38 蓝色雪焰 阅读(845) | 评论 (1)编辑 收藏
 

过年了,彻底的休息了几天真痛快,不过后面还有很多很艰巨的任务。

又要开始学习咯~祝大家新年快乐!鸡年大吉!

祝男孩子们“鸡”肉发达,女孩子们“鸡”肤光滑~哈哈

posted @ 2005-02-10 21:26 蓝色雪焰 阅读(154) | 评论 (0)编辑 收藏
 

昨天的问题解决了,成功的编译了Hello World!,真是高兴呀!

经过了N次的上网查找资料,不但解决了手上的难题,同时更是得到了很多以外的收获。

万事开头难,好不容易解决了第一个难题,跨出来学习JAVA的第一步。我相信这一小步,必将引导着我今后的学习不断向前,不断突破,赢得最后的胜利!

posted @ 2005-02-07 16:00 蓝色雪焰 阅读(130) | 评论 (0)编辑 收藏
 

做J2ME竟然做到连HelloWorld都搞不出来,郁闷呀~

在网上搜索了半天相关资料,并没有解决问题,不过找到了不少有用的东西,对J2ME的操作及其程序的解释都有很多新的认识。

学习,就像一场障碍赛,跃过障碍努力向前,不断的向终点接近,收获就在哪里!

posted @ 2005-02-06 23:05 蓝色雪焰 阅读(139) | 评论 (0)编辑 收藏
 

【转贴】德国、美国、中国三国的士兵手册(很不错!)

德国士兵的十戒(写在每个德国士兵的笔记本上)!
  
  (一)德国士兵为争取胜利而战斗时,必须遵守英勇作战的规则。残酷与无意义的破坏都与他的身份不称。
  (二)战斗员必须穿制服,或佩带特别指定的和清楚易辨的臂章。禁止穿便服或不带这种臂章作战。
  (三)投降的敌人,包括游击队和间谍,一概不准杀害。他们应由法庭判以适当的惩罚。
  (四)不准虐待或侮辱俘虏。武器、地图、文件从他们身上拿走以后,其他的个人财物不准侵犯。
  (五)达姆弹禁止使用,任何子弹一律禁止改成达姆弹。
  (六)红十字会是神圣不可侵犯的。受伤的敌人应给予人道的待遇。医务人员和随军牧师在执行其医务和宗教活动时不得阻碍。
  (七)平民是神圣不可侵犯的,士兵不准掠夺和任意破坏。古迹和用作宗教、艺术、科学或慈善事业的建筑物必须特别尊重。只有奉上级命令和给报酬时,才能征收实物和使用民役。
  (八)中立国的土地,飞机既不得进入,也不得越过,更不得射击;它不得成为任何军事行动的目标。
  (九)德国士兵作为俘虏时,如果被询问,可以说出自己的姓名和军阶。但在任何情况下不得泄露其所属单位,也不得泄露德国军事、政治和经济方面的任何情报。任凭威逼利诱都不得泄露。
  (十)违犯上述各条规者将予以惩罚。敌人违犯了第一至第八条应报告。只有得到高级指挥官的允许才能进行报复。
  
  美国士兵守则
  
  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、专业士兵的行为是你能预测的,可惜战场上业余的士兵占多数,因此敌人的行为大部分是你所无法预测的。
  
  中国人民解放军士兵守则
  
  热爱中国共产党,,热爱社会主义祖国,热爱中国人民解放军,全心全意为人民服务。执行党的路线,方针,政策,遵守国家的法律,法规,执行军队的条令,条例和规章制度,服从命令,听从指挥。努力学习军事,政治,科学文化,苦练杀敌本领,爱护武器装备,保守军事秘密,发扬优良传统,英勇战斗,不怕牺牲,保卫社会主义祖国,保卫人民的和平劳动,在任何情况下决不背叛祖国,叛离军队。

posted @ 2005-02-03 23:15 蓝色雪焰 阅读(274) | 评论 (1)编辑 收藏
 

         知道“二十四孝”的人,已经不多了;知道“二十四孝”的年轻人,更是少之又少。“孝”在今日社会,似乎已是一个“过气”的词,人老珠黄般,逗不起众人的欲望了。我们读《二十四孝》,感觉那似乎是十分遥远的故事。其实细细想来,它好像又近在咫尺,离我们并不远。

01 孝感动天      02 亲尝汤药     03 啮指痛心  04 百里负米 

05 芦衣顺母      06 鹿乳奉亲     07 戏彩娱亲     08 卖身葬父

09 刻木事亲      10 行佣供母     11 怀橘遗亲     12 埋儿奉母

13 扇枕温衾      14 拾葚异器     15 涌泉跃鲤     16 闻雷泣墓

17 乳姑不怠      18 卧冰求鲤     19 恣蚊饱血     20 扼虎救父

21 哭竹生笋      22 尝粪忧心     23 弃官寻母     24 涤亲溺器

详见文章 http://www.blogjava.net/yangsail/articles/979.html

posted @ 2005-02-03 22:03 蓝色雪焰 阅读(227) | 评论 (0)编辑 收藏
仅列出标题
共13页: First 上一页 5 6 7 8 9 10 11 12 13 下一页