博文

DirectX学习手记(-)(转)(2007-03-09 22:43:00)

摘要:            HappyFire 2002/8/2

题记:玩了很多的游戏,不禁萌发了自己做游戏的念头,于是7月份从网上收集了N多资料。7月20几号在家开始了闭关式的学习,
一直到昨天,我的第一个地图类封装完毕,并乘胜追击到凌晨3点,做好了地图编辑器的0.9版。早上起来觉得有点累(才睡了几个小时,
呵呵),于是把这些天的学习经历回忆一下,权当是休息。这个过程是一个从对游戏编程一无所知到略识其理得过程,我想对于像我这样
的初学者应该有所帮助吧,至少可以少走些弯路。


一. 初识DirectX
在放假之前,我拜读了金点时空softboy的《圣剑英雄传--英雄救美制作手札》一文。此文简述了RPG游戏的基本原理,通俗易懂,
看完此文使人觉得游戏制作并非遥不可及,强烈推荐!但此文并没有提到有关DirectX编程的方法。所以做游戏,还要先过DirectX关。
于是我花了一天的时间,通读了老王翻译的《DirectX中文手册》,并分析了几个例程,在加上一个多星期的编程经历,总算有了一点感觉。

1.基础中的基础
既然我们讨论的是Windows平台下的游戏编程,当然要对Windows编程有所了解。我们使用的是Win32 SDK(API)编程。至于为什么
不用MFC? MFC是微软对API的封装(其实何止是封装),它适用于开发有统一程序架构和界面的大型商业软件,但其复杂的机制影响了速度,
这对于游戏编程是难以忍受的,因此基于C/C++的Win32API是我们必然的选择。(也听说有用VB开发的游戏,但那毕竟不是主流)。那么对
于Win32 API要掌握到什么程度呢?我想只要知道WinMain、窗口类、消息循环、窗口回调函数,能编个简单的HelloWorld就行了,至于GDI
只要稍作了解,而Windows的控件基本上用不到。这方面的文章很多,找几篇看看就行了。当然游戏编程不同于一般的编程,所以这其中也
有些变化,在下面我会提到的。

2.第一个DirectDraw例程
学DirectX当然要先学习它最基本也是最重要的部分Direc......

阅读全文(1461) | 评论:0

Windows编程的剪贴板机制  (2007-03-08 21:33:00)

摘要:Windows剪贴板

  Windows剪贴板是一种比较简单同时也是开销比较小的IPC(InterProcess Communication,进程间通讯)机制。Windows系统支持剪贴板IPC的基本机制是由系统预留的一块全局共享内存,用来暂存在各进程间进行交换的数据:提供数据的进程创建一个全局内存块,并将要传送的数据移到或复制到该内存块;接受数据的进程(也可以是提供数据的进程本身)获取此内存块的句柄,并完成对该内存块数据的读取。

  为使剪贴板的这种IPC机制更加完善和便于使用,需要解决好如下三个问题:提供数据的进程在结束时Windows系统将删除其创建的全局内存块,而接受数据的进程则希望在其退出后剪贴板中的数据仍然存在,可以继续为其他进程所获取;能方便地管理和传送剪贴板数据句柄;能方便设置和确定剪贴板数据格式。为完善上述功能,Windows提供了存在于USER32.dll中的一组API函数、消息和预定义数据格式等,并通过对这些函数、消息的使用来管理在进程间进行的剪贴板数据交换。

  Windows系统为剪贴板提供了一组API函数和多种消息,基本可以满足编程的需要。而且Windows还为剪贴板预定义了多种数据格式。通过这些预定义的格式,可以使接收方正确再现数据提供方放置于剪贴板中的数据内容。

  文本剪贴板和位图剪贴板的使用

  这两种剪贴板是比较常用的。其中,文本剪贴板是包含具有格式CF_TEXT的字符串的剪贴板,是最经常使用的剪贴板之一。在文本剪贴板中传递的数据是不带任何格式信息的ASCII字符。若要将文本传送到剪贴板,可以先分配一个可移动全局内存块,然后将要复制的文本内容写入到此内存区域。最后调用剪贴板函数将数据放置到剪贴板:

  DWORD dwLength = 100; // 要复制的字串长度
HANDLE hGlobalMemory = GlobalAlloc(GHND, dwLength + 1); // 分配内存
LPBYTE lpGlobalMemory = (LPBYTE)GlobalLock(hGlobalMemory); // 锁定内存
for (int i = 0; i 〈 dwLength; i++) // 将"*"......

阅读全文(2715) | 评论:0

实现多线程同步的三种方法及示例(2007-03-08 12:37:00)

摘要:1、利用事件对象实现多线程的同步: #include<windows.h>
#include<iostream.h> DWORD WINAPI FunProc1(
  LPVOID lpParameter   // thread data
); DWORD WINAPI FunProc2(
  LPVOID lpParameter   // thread data
); int ticket=100;
HANDLE hEvent;
void main()
{
 HANDLE hThread1,hThread2;
 hThread1=CreateThread(NULL,0,FunProc1,NULL,0,NULL);
 hThread2=CreateThread(NULL,0,FunProc2,NULL,0,NULL);
 CloseHandle(hThread1);
 CloseHandle(hThread2);
 hEvent=CreateEvent(NULL,false,false,"ticket");
 /*该函数为创建事件对象
  *第一个参数为NULL,表示使用缺省的安全性
  *第二个参数当为true,必须用ResetEvent人工地将事件对象设置为无信号状态
   当为false,在一个等待线程被释放后系统将事件对象自动设置为无信号状态
     *第三个参数指定事件对象的初始状态,当为true为有信号状态,否则为无信号状态
  *第四个参数为事件对象的命名,当为NULL该对象为匿名对象
  */
 if(hEvent)
 {
  if(GetLastError()==ERROR_ALREADY_EXISTS)
  {
   cout<<"t......

阅读全文(3445) | 评论:0

P2P之UDP穿透NAT地机制与完成(2007-03-06 23:15:00)

摘要:论坛上经常有对P2P原理的讨论,但是讨论归讨论,很少有实质的东西产生(源代码)。呵呵,在这里我就用自己实现的一个源代码来说明UDP穿越NAT的原理。 首先先介绍一些基本概念:
    NAT(Network Address Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要目的就是为了能够地址重用。NAT分为两大类,基本的NAT和NAPT(Network Address/Port Translator)。
    最开始NAT是运行在路由器上的一个功能模块。
   
    最先提出的是基本的NAT,它的产生基于如下事实:一个私有网络(域)中的节点中只有很少的节点需要与外网连接(呵呵,这是在上世纪90年代中期提出的)。那么这个子网中其实只有少数的节点需要全球唯一的IP地址,其他的节点的IP地址应该是可以重用的。
    因此,基本的NAT实现的功能很简单,在子网内使用一个保留的IP子网段,这些IP对外是不可见的。子网内只有少数一些IP地址可以对应到真正全球唯一的IP地址。如果这些节点需要访问外部网络,那么基本NAT就负责将这个节点的子网内IP转化为一个全球唯一的IP然后发送出去。(基本的NAT会改变IP包中的原IP地址,但是不会改变IP包中的端口)
    关于基本的NAT可以参看RFC 1631
   
    另外一种NAT叫做NAPT,从名称上我们也可以看得出,NAPT不但会改变经过这个NAT设备的IP数据报的IP地址,还会改变IP数据报的TCP/UDP端口。基本NAT的设备可能我们见的不多(呵呵,我没有见到过),NAPT才是我们真正讨论的主角。看下图:
                      &nb......

阅读全文(1463) | 评论:0

C++网络编程学习笔记(2007-03-05 18:42:00)

摘要:1、IP网络中的每台主机都有一个IP地址;
   它是逻辑地址;
   因特网上的IP地址具有全球唯一性;
   4个字节,192.168.0.16
2、OSI将网络分为     TCP/IP模型
   应用层            应用层
   表示层
   会话层
   传输层            传输层
   网络层            网络层
   数据链路层        网络接口
   物理层    相同层次之间不可以直接通信,是虚拟通信。下层向上层提供服务,实际通信在最底层完成。各层之 间单向依赖。
3、DNS是将DNS解析为IP地址。
   应用层:Telnet、FTP、HTTP、DNS、SMTP、POP3。
   传输层:TCP实现数据的完整性,例如下载安装包;UDP实现数据的及时性,例如网络视频电话。
4、数据封装:在数据前面加上特定的协议头部。
5、TCP和UDP可以使用同样的端口,1024以下的端口保留给预定义的服务。windows socket只支持 AF_INET网际域.
6、在网络协议中需要指定网络字节顺序,TCP/IP使用16位整数和32位整数的高位先存格式,PC机采用的 是低位先存格式。
套接字:流式套接字(基于TCP)、数据报套接字(基于UDP),原始套接字。
7、基于TCP的socket编程   ......

阅读全文(4569) | 评论:0

在堆上分配内存 (2007-01-13 23:22:00)

摘要:在堆上分配内存 关键词: 在堆上分配内存                                           可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲,stack上分配的内存系统自动释放,heap上分配的内存,系统不释放,哪怕程序退出,那一块内存还是在那里。stack一般是静态分配内存,heap上一般是动态分配内存。

由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。用free释放,不然就是术语——“内存泄露”(或是“内存漏洞”)—— Memory Leak。于是,系统的可分配内存会随malloc越来越少,直到系统崩溃。还是来看看“栈内存”和“堆内存”的差别吧。

    栈内存分配
    —————
    char*
    AllocStrFromStack()
    {
        char pstr[100];
        return pstr;
    }
   
   
    堆内存分配
    —————
    cha......

阅读全文(1418) | 评论:0

VC进行COM编程所必须掌握的理论知识(2007-01-05 14:04:00)

摘要:一、为什么要用COM



  软件工程发展到今天,从一开始的结构化编程,到面向对象编程,再到现在的COM编程,目标只有一个,就是希望软件能象积方块一样是累起来的,是组装起来的,而不是一点点编出来的。结构化编程是函数块的形式,通过把一个软件划分成许多模块,每个模块完成各自不同的功能,尽量做到高内聚低藕合,这已经是一个很好的开始,我们可以把不同的模块分给不同的人去做,然后合到一块,这已经有了组装的概念了。软件工程的核心就是要模块化,最理想的情况就是100%内聚0%藕合。整个软件的发展也都是朝着这个方向走的。结构化编程方式只是一个开始。下一步就出现了面向对象编程,它相对于面向功能的结构化方式是一个巨大的进步。我们知道整个自然界都是由各种各样不同的事物组成的,事物之间存在着复杂的千丝万缕的关系,而正是靠着事物之间的联系、交互作用,我们的世界才是有生命力的才是活动的。我们可以认为在自然界中事物做为一个概念,它是稳定的不变的,而事物之间的联系是多变的、运动的。事物应该是这个世界的本质所在。面向对象的着眼点就是事物,就是这种稳定的概念。每个事物都有其固有的属性,都有其固有的行为,这些都是事物本身所固有的东西,而面向对象的方法就是描述出这种稳定的东西。而面向功能的模块化方法它的着眼点是事物之间的联系,它眼中看不到事物的概念它只注重功能,我们平常在划分模块的时侯有没有想过这个函数与哪些对象有关呢?很少有人这么想,一个函数它实现一种功能,这个功能必定与某些事物想联系,我们没有去掌握事物本身而只考虑事物之间是怎么相互作用而完成一个功能的。说白了,这叫本末倒置,也叫急功近利,因为不是我们智慧不够,只是因为我们没有多想一步。面向功能的结构化方法因为它注意的只是事物之间的联系,而联系是多变的,事物本身可能不会发生大的变化,而联系则是很有可能发生改变的,联系一变,那就是另一个世界了,那就是另一种功能了。如果我们用面向对象的方法,我们就可以以不变应万变,只要事先把事物用类描述好,我们要改变的只是把这些类联系起来的方法,只是重新使用我们的类库,而面向过程的方法因为它构造的是一个不稳定的世界,所以一点小小的变化也可能导致整个系统都要改变。然而面向对象方法仍然有问题,问题在于重用的方法。搭积木式的软件构造方法的基础是有许许多多各种各样的可重用的部件、模块。我们首先想到的是类库......

阅读全文(1218) | 评论:0

用VC++在状态栏显示时钟 (2006-12-19 21:10:00)

摘要:在VC的控件中有个Status bar可以在窗体状态栏中添加日期和时间。其实通过简单的代码,你就能创建一个有时钟显示的状态栏,并且还能设置时钟栏的显示方式。举例如下:
  首先,用MFC AppWizard按默认方式创建一个单文档界面的应用程序框架。
  然后,编写代码:
  1)在String Table中添加New String,在此定义为ID_INDICATOR_CLOCK,将其Caption设为00:00:00(由于状态栏根据Caption确定时间窗格的缺省宽度,使用此值将为时间的显示预留空间)。注意,本步操作时有两种方式:
  一种建立新的String Table,并添加String;另一种则在原有String Table中添加。当用后一种方式操作时,若完成后,时钟栏并不显示时间,则需要将此New String在String Table中对应的Value值加1(可在resource.h中修改)。
  2)在MainFrm.cpp中indicators声明处添加ID_INDICATOR_CLOCK,代码如下:
  …
  static UINT indicators[] =
  {
  ID_SEPARATOR,  
  ID_INDICATOR_CLOCK,
  ID_INDICATOR_CAPS,
  ID_INDICATOR_NUM,
  ID_INDICATOR_SCRL,
  };
  …
  这一步中ID_INDICATOR_CLOCK的插入位置将影响时间窗格在状态栏中的显示位置。
  3)安装定时器:在MainFrm.cpp中OnCreate函数处添加代码如下:
  int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
  { ……
  SetTimer(1,1000,NULL);//安装定时器,并将其时间间隔设为1000毫秒
  return 0;
  }
  4)编写时间处理函数:利用ClassWizard为CMainFrame类加入WM_TIMER的消
  息处理函数OnTi......

阅读全文(1521) | 评论:0

改变应用程序的外观(2006-12-19 20:21:00)

摘要:第一部分 改变应用程序的外观 一、 问题:要修改一个应用程序的外观,应该在应用程序创建之前还是在创建之后修改呢? 修改一幢楼房应在建成之前,应在窗口创建之前修改。要改变一个框架窗口的外观,应在CMainFrame::PreCreateWindow()中去改变,   CREATESTRUCT cs结构体的类型和个数与创建窗口的CreateWindowEx()的个数和类型是完全一致的。只是顺序正好相反。 PreCreateWindow(cs)的参数cs被声明为一个引用类型,如果在子类中修改了cs的值,这种改变会反应到MFC的底层代码中。   1、修改窗口的大小: BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) {        if( !CFrameWnd::PreCreateWindow(cs) )               return FALSE;        // TODO: Modify the Window class or styles here by modifying        // the CREATESTRUCT cs        cs.cx=300;        cs.cy=200;        return TRUE; } 2、修改窗口的标题 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) {        if( !CFrameWnd::PreCreateWindow(cs) ) &......

阅读全文(3990) | 评论:0

如何向 CImageList中加入位图文件(2006-12-12 18:45:00)

摘要:在MFC中CImageList类封装了图象列表控件的功能,图象列表控件是相同大小图象的一个集合,用于应用程序中大规模图标的存储,该控件是不可见控件,通常与其它控件一起使用,来为其它控件提供图标资源。图象的每个集合中均以0为图象索引基数,可以把这些图标看成是以数组方式存储的,图像列表通常由大图标或位图构成,其中包含位图模式,实际上,所有的在同一个图象列表中的图标都被存储在一个屏幕设备的位图中。 通常向CImageList对象中添加图像元素的方法是:首先把图标或位图调入资源文件中,然后调用CImageList:Add方法加入到图象列表控件中,将位图装入资源导致可执行文件增大,不利于软件发行,而且只能使用资源中有限的位图,无法选取其它位图。要弥补使用资源位图的不足,就必须直接使用BMP位图文件。 使用下列代码即可实现: HBITMAP hBitmap; CBitmap *pBitmap; CImageList *pImageList; pImageList=new CImageList; pImageList->Create (32,32,ILC_COLOR4,5,2); pBitmap=new  CBitmap; hBitmap=(HBITMAP)LoadImage(NULL,FilePathName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);//FilePathName 是位图文件完整路径字符串 pBitmap->Attach (hBitmap); pImageList->Add (pBitmap,RGB(0,0,0)); 下面结合一个实例详细说明的用法: 例程解析: 例程基于一个对话框,单击对话框中的‘添加bmp’按钮弹出文件选择框,选择一个bmp文件,即可把选种bmp文件加入到列表控件中。在MFC中,CLIstCtrl类封装了列表控件,主要用来以各种方式显示一组数据记录供用户进行各种操作,列表中的记录可以包括多个数据项,也可以包括表示数据内容的大小图标,用来表示数据记录的各种属性,Windows资源管理器的右侧框架就是一个非常典型的列表控件。       新建MFC AppWiazrd[exe]工程,工程名称ImageListAdd......

阅读全文(1276) | 评论:0