“文字对战游戏”开发实例
张建德
摘要:C#进行游戏编程一直受到一些C++程序员的怀疑和猜忌,是不是真的C#就不能进行游戏编程呢? 回答当然是否定的,因为,微软已经发布了DirectX9.0SDK开发包,使得C#进行游戏编程简单易行,本篇在.NET环境下,用C#调用DirectX进行游戏编程,通过简单而又明朗的编程实例揭示C# DirectX编程的奥秘,在本篇的开始,作者对.NET、C#、DirectX及游戏制作的相关知识都作了介绍和讲解,然后,一步步的制作“文字对战游戏”,相信读完本文,你一定会受益非浅。
关键词:C# DirectX .NET “文字对战游戏”

一、.NET概述
    1. .NET是什么,刚刚开始接触.NET的人不竟要问,是啊,.NET到底是什么,它能完成哪些功能,微软对我们的问题作出了精典的回答:.NET Framework 是一种新的计算平台,它简化了在高度分布式 Internet 环境中的应用程序开发,能够快速的创建Windows应用程序、WEB程序以及移动平台Windows Mobile的应用程序,随着.NET2.0版的发布,.NET 的功能越来越强大。
    .NET2.0分为 .NET Framework2.0和.NET Compact Framework2.0(简称.NET CF2.0),其它.NET Framework2.0主要用于Windows系统的编程开发,而.NET CF2.0作为.NET Framework2.0的精装版,主要用于Windows Mobile手机操作系统的编程。
    2. 在.NET环境下,可以使用的编程语言很多,如:C#、C++、VB等,到底使用哪种语言更适合于你,这要看你的爱好了,对我来说,我更热衷于C#语言,因此,本篇将使用C#语言对“文字对战游戏”进行编程及介绍。

二、 C#语言概述
    1. C#是微软新推出的一种编程语言。其功能强大。它的学习和使用象VB一样简单。只要稍有一点编程知识,都非常容易上手学习C#。
    2. 由于.NET框架类库对套接字、TCP、HTTP等协议的支持,C#的网络编程功能也相当强大。
    3. C#可以直接调用GDI+进行绘图、图形和多媒体编程。而GDI+的强大功能绝对让你大吃一惊。
    4. DirectX编程,相信大家并不陌生,自DirectX7.0诞生以来,就有了基于VB编程的DirectX库DxVBLib,同样,DirectX9.0C诞生及DirectX9SDK开发包的推出,在C#中使用DirectX进行多媒体及游戏编程变得简单易行,在本篇中,我们将要学习C#中调用DirectX进行“文字对战游戏”的编程。
    5. ASP.net编程,在.NET还没有出现之前,用ASP进行WEB程序编程,是每个程序员做梦都想精通的,早期在网络上运行的80%的WEB程序是都是用ASP编写,而现在,学了ASP.net,我相信你一定会酷爱ASP.net,而C#,可以直接建立ASP.net,其可视化界面也做得相当不错。
    6. 组件编程:我只能告诉你,我从来没有见过可以向C#一样简单、快速、高效编写组件的开发语言,剩下的你自己猜想。
    7. 数据库编程:在C#中 通过调用ADO.net对数据库进行读、写、查询等操作,C#编写数据库应用程序也一样简单。
    8. 如此强大的功能,难怪微软声称,C#是编写.NET Framework 应用程序最合适的语言,本人也有同感。

三、C# DirectX游戏编程基础
    DirectX技术是微软公司推出一种基于WINDOWS下与硬件设备无关的多媒体及游戏编程技术,DirectX的出现,使WINDOWS下的多媒体及游戏编程变得容易,且性能有较大的提升,而且,DirectX不仅和WINDOWS驱动程序有良好的兼容,还能WINDOWS图形设备接口,即WINDOWS GDI兼容,在DirectX 9.0以前,通常只能在C++和VB中调用DirectX进行编程,但随着DirectX 9.0和DirectX9SDK的推出,在C#中进行DirectX编程非常简单,在本篇中,你将学习如何在C#中调用DirectX进行编程。

    1. DirectX 9.0C和DirectX9SDK开发包的安装
    要调试和编译本篇中的程序,必须先安装DirectX9.0C和 DirectX9SDK开发包,下面告诉你如何安装:
    (1)安装WindowsXp sp2操作系统,安装完毕后DirectX9.0C是默认已经安装,如果你的操作系统是其它版本的操作系统,那么需要单独安装DirectX9.0C。
    (2)安装Microsoft Visual Studio .NET 2005版以上的编程工具。
    (3)安装DirectX9SDK开发包,一共是两个安装程序:dx90asdk.exe和dxsdk_apr2005.exe,安装其中一个就可以了,安装完成后,我们就可以进行DirectX游戏编程了。

    2. C# DirectDraw的使用方法
    DirectDraw是DirectX的一个分枝,专门用于处理2D图形和平面图形,是一项非常好用的技术,Windows下几乎所有2D游戏,包括很多网络游戏都采用DirectDraw技术进行编程,因此,学好DirectDraw技术是游戏编程所必不可少的,下面,让我们一步步的学习如何使用DirectDraw进行编程。
    (1) 对DirectDraw类进行引用:单击菜单“项目”—>“添加引用”—>“.NET”,选择“Microsoft.DirectX.DirectDraw”,单击“确定”。
    (2) 在源代码中添加以下命名空间的引用:
using Microsoft.DirectX;
using Microsoft.DirectX.DirectDraw;
    以上两行代码完成对DirectX和DirectDraw命名空间的引用,只有通过以上命名空间的引用,才能在程序对DirectDraw的编程。
    (3) 定义DirectDraw设备并进行初始化:
Device displayDevice = null;
    此行代码把DirectDraw设备定义为displayDevice,并赋初始值为null,对DirectDraw设备进行定义并初始化后,在程序中就可以使用displayDevice代表DirectDraw进行工作了。
    (4) 定义表面:
Surface front = null; // 主表面.
Surface back = null; // 后表面.
Surface surfaceBmp = null; // 加载位图的离屏表面.
Surface text = null; // 加载文字的离屏表面.
    表面,搞过DirectDraw编程的人都知道,表面实际上是一个内存块,在上述代码中定义了四个表面,front主表面的内容是直接显示在显示器上,可以看得见的内容,back后表面的内容,不直接显示在显示器,后表面的内容只存储在内存中,后表面在内容要在显示器上显示,我们必须把后表面的内容拷贝到前表面中,或者采用翻转的办法将后表面变为主表面,后表面变为主表面后,内容当然显示中显示器中了,所以,在DirectDraw编程中,通常把后表面作为缓冲区,在后表面中进行图形处理,处理结束后再拷贝到主表面,或者向刚刚所说的,把后表面变为主表面,这样就可以避免由于复杂的图形处理过程在显示器上显示造成的闪烁,因为图形处理过程在后表面中进行。
    surfaceBmp表面被定义为加载位图的离屏表面,同样也是一个内存块,text表面被定义为加载文字的离屏表面,也是一个内存块,如果我们要把surfaceBmp表面和text表面的内容同时在显示器中显示,我们应该怎么做呢,就是把surfaceBmp表面的内容先拷贝到后表面back中,再把text表面的内容拷贝到后表面back中,然后,把后表面的back内容拷贝到主表面front中,假设surfaceBmp表面是一幅位图,text表面中是文字,经过上述操作后,我们就可以在显示器看到图形和文字了。
    (5) 创建DirectDraw设备:
    displayDevice = new Device();在我们把displayDevice定义为DirectDraw设备后,我们需要创建它,在C#中,创建DirectDraw设备就一行代码,非常的简单。


    (6) 设置显示模式:
displayDevice.SetCooperativeLevel(this, CooperativeLevelFlags.FullscreenExclusive);
// 设置显示设备为当前窗口,且为全屏独占方式
. displayDevice.SetDisplayMode(800,600,16,0,true); // 设置显示模式为800x600分辩率,颜色为16M色。
玩过游戏的人都清楚,好多游戏都支持多种显示模式,现在大都数游戏都支持1024x768显示模式,而且可以切换窗口和全屏模式,那么,在实际编程中是如何实现的呢?上述代码应该给了你很好的回答。
    (7) 设置透明色:
ColorKey key = new ColorKey();
key.ColorSpaceHighValue = key.ColorSpaceLowValue =0; //设置颜色为黑色
flags = ColorKeyFlags.SourceDraw;
surfaceBmp.SetColorKey(flags, key); // 设置页面透明色.
    在这里,有人肯定会问,何为透明色? 设置透明色有什么作用?别忙,让我告诉你,先请看下图:



    这张图是一张90x90的位图,如果我们在图形处理时,不设置透明色,我们看到的跟上图一模一样,但如果在图形处理时,我设置透明色为此图的背景色(本图背景色为黑色),在图形处理时对这种颜色不进行处理,我们看到的图形就为下图

    这样来举例,应该明白透明色的意义了吧,在游戏编程中,谁愿意看到那周围那些黑呼呼的东西呢,当然得把它处理掉。
    (8) 加载位图:
    surfaceBmp = new Surface(bitmapFileName1, description, displayDevice);
在游戏编程中,需要处理很多图形,如背景图,人物图形,花草树木等很多的图形,那么,这些图形是这样加载到程序中呢,上述代码很简单,那就是通过一个位图文件建立一个位图表面,bitmapFileName1变量为图形文件名,可以包含路径,description为表面,displayDevice为DirectDraw设备。

    (9) 表面拷贝:
    上面讲到,在图形处理中需要进行表面的拷贝操作,DirectDraw的表面拷贝操作非常简单,请看下面的代码。
back.DrawFast(0,0,surfaceBmp, DrawFastFlags.DoNotWait | DrawFastFlags.SourceColorKey);
//全图拷贝
back.DrawFast(0,0,surfaceBmp,surRect,DrawFastFlags.DoNotWait | DrawFastFlags.SourceColorKey);
//矩形拷贝
back.DrawFast(30,40,text,DrawFastFlags.DoNotWait| DrawFastFlags.SourceColorKey);//不带透明色的拷贝
    表面拷贝使用DrawFast方法,第一个参数为目标表面的X座标,第二个参数为目标表面的Y座标,第三个参数为要进行拷贝的表面,第四个参数决定拷贝整个表面或者是表面的一部分,第五个参数决定拷贝方式。
    第一行代码作用是把surfaceBmp表面的全部内容拷贝到back表面座标0,0的地方,并对透明色进行处理。
    第二行代码作用是把surfaceBmp表面的surRect矩形框指定的内容拷贝到back表面座标0,0的地方,并对透明色进行处理。
    第三行代码作用是把text表面的全部内容拷贝到back表面座标30,40的地方,并对透明色进行处理。
    (10) 表面翻转:
    前面讲到,要将后表面的内容在显示器进行显示,需要将后表面翻转为主表面,表面翻转使用以下方法:
front.Flip(back, FlipFlags.DoNotWait);
    第一个参数为要翻转为主表面为表面,第二个参数为翻转模式。
    上述代码把back表面翻转为主表面。
    好了,说了这么多,让我们看一个DirectDraw的实例吧。

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.DirectDraw;

namespace dxDraw
{
/// <summary>
/// Form1 的摘要说明。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.IContainer components;

Device displayDevice = null; // Draw显示设备.
Surface front = null; // 主表面.
Surface back = null; // 后表面.
Surface surfaceBmp = null; // 加载位图的离屏表面.
Surface text = null; // 加载文字的离屏表面.
private System.Windows.Forms.Timer timer1; // Palette entry array.
string bitmapFileName1="black.bmp";

int nx;
//int ny;

public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();

//
// TODO: 在InitializeComponent 调用后添加任何构造函数代码
//
}

/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
displayDevice.RestoreDisplayMode(); // 恢复设置
displayDevice.SetCooperativeLevel(this,CooperativeLevelFlags.Normal);
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows 窗体设计器生成的代码
/// <summary>
/// 设计器支持所需的方法- 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
//
// timer1
//
this.timer1.Interval = 10;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(21, 46);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Font = new System.Drawing.Font("宋体", 30F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(134)));
this.Name = "Form1";
this.Text = "Form1";
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown);
this.Load += new System.EventHandler(this.Form1_Load);

}
#endregion

/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

private void Form1_Load(object sender, System.EventArgs e)
{
InitializeDirectDraw();
}

private void InitializeDirectDraw()
{
SurfaceDescription description=new SurfaceDescription(); //目标表面
ColorKeyFlags flags = new ColorKeyFlags();

displayDevice = new Device(); // 创建DirectDraw设备.
displayDevice.SetCooperativeLevel(this, CooperativeLevelFlags.FullscreenExclusive); // 设置显示设备为当前窗口,且为全屏独占方式
try
{
displayDevice.SetDisplayMode(800,600,16,0,true); // 设置显示模式
}
catch(UnsupportedException)
{
MessageBox.Show("不支持当前的模式,请重新设置", "警告");
Close();
return;
}

description.SurfaceCaps.PrimarySurface = description.SurfaceCaps.Flip = description.SurfaceCaps.Complex = true;
description.BackBufferCount = 1; // 为目标表面建立一个后表面

front = new Surface(description, displayDevice); // 创建基于description的主表面.

SurfaceCaps caps = new SurfaceCaps();
caps.BackBuffer = true;
back = front.GetAttachedSurface(caps); // 从主表面获取后表面.


description.Clear(); // 清除.
surfaceBmp = new Surface(bitmapFileName1, description, displayDevice); // 通过位图创建离屏表面.

ColorKey key = new ColorKey();
key.ColorSpaceHighValue = key.ColorSpaceLowValue =0; //设置颜色为黑色
flags = ColorKeyFlags.SourceDraw;
surfaceBmp.SetColorKey(flags, key); // 设置页面透明色.

description.Clear();
description.SurfaceCaps.OffScreenPlain = true;
description.Width = 500; // 设置目标表面的宽度和高度
description.Height = 40;


text = new Surface(description, displayDevice); // 通过目标表面创建离屏表面
text.ForeColor = Color.White;
text.ColorFill(0);
text.FontHandle=this.Font.ToHfont(); //设置字体为当前窗口的字体
text.DrawText(0, 0,"按Esc键退出,A键动画演示", false);
text.SetColorKey(flags,key); // 设置页面透明色.

Rectangle surRect=new Rectangle(0,0,600,400);

back.ColorFill(144);
//back.DrawFast(0,0,surfaceBmp, DrawFastFlags.DoNotWait | DrawFastFlags.SourceColorKey);//全图拷贝
back.DrawFast(0,0,surfaceBmp,surRect,DrawFastFlags.DoNotWait | DrawFastFlags.SourceColorKey);//矩形拷贝
back.DrawFast(30,40,text,DrawFastFlags.DoNotWait| DrawFastFlags.SourceColorKey);//不带透明色的拷贝

back.FontHandle=this.Font.ToHfont(); //设置字体为当前窗口的字体
back.ForeColor=Color.Red;
back.DrawText(0,0,"CLS",false);
front.Flip(back, FlipFlags.DoNotWait);

}

private void Form1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode==Keys.Escape)
{
Close();
}
if(e.KeyCode==Keys.A)
timer1.Enabled=true;
}

private void timer1_Tick(object sender, System.EventArgs e)
{
nx=nx+1;
back.ColorFill(144);
back.DrawFast(0,0,surfaceBmp, DrawFastFlags.DoNotWait | DrawFastFlags.SourceColorKey);
//back.DrawFast(30,nx,text,DrawFastFlags.DoNotWait| DrawFastFlags.SourceColorKey);
back.DrawText(nx,0,"CLS",false);
front.Flip(back, FlipFlags.DoNotWait);
}
}
}


    3. C# DirectX.AudioVideoPlayback的使用方法
    看到这个类,是不是感到很熟悉,对了,这个类是DirectX分支中用于播放多媒体文件的类,功能非常的强大,不过,功能强大,使用却不难啊。
    (1) 对DirectX.AudioVideoPlayback类进行引用:单击菜单“项目”—>“添加引用”
—>“.NET”,选择“Microsoft.DirectX.AudioVideoPlayback”,单击“确定”。
    (2) 在源代码中添加以下命名空间的引用:
using Microsoft.DirectX;
using Microsoft.DirectX.AudioVideoPlayback;
    以上两行代码完成对DirectX和 Microsoft.DirectX.AudioVideoPlayback命名空间的引用,只有通过以上命名空间的引用,才能在程序中对Microsoft.DirectX.AudioVideoPlayback的编程。

    (3) 定义Audio设备并进行初始化:
private Audio ourAudio = null;
此行代码把Audio设备定义为ourAudio,并赋初始值为null,对Audio设备进行定义并初始化后,在程序中就可以使用ourAudio代表Audio进行工作了,接下来的过程,让你想象不到的简单。

    (4) 以带路径的媒体文件名创建一个新的ourAudio对象,请看下面的代码:
openFileDialog1.ShowDialog();
ourAudio = new Audio(openFileDialog1.FileName);
    这样的代码相信你并不会感到惊奇,是不是再熟悉不过了。

    (5) 接下来,看看下面的代码,你应该什么都明白了吧:
ourAudio.Volume=0; //设置音量,最大值为0;
ourAudio.Play(); //播放
ourAudio.Stop(); //停止

    (6) 当然,看一下完整的代码更能理解,下面是播放媒体文件的完整代码,对了,别忘
    了前面所说的对DirectX.AudioVideoPlayback类进行引用啊。

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.AudioVideoPlayback;

namespace AudioPlay
{
/// <summary>
/// Form1 的摘要说明。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;
private System.Windows.Forms.OpenFileDialog openFileDialog1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;

private Audio ourAudio = null;
private string filterText = "Audio files (*.wav; *.mpa; *.mp2; *.mp3; *.au; *.aif; *.aiff; *.snd; *.wma)|*.wav; *.mpa; *.mp2; *.mp3; *.au; *.aif; *.aiff; *.snd; *.wma|" +
"MIDI Files (*.mid, *.midi, *.rmi)|*.mid; *.midi; *.rmi|" +
"All Files (*.*)|*.*";
public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();

//
// TODO: 在InitializeComponent 调用后添加任何构造函数代码
//
}

/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows 窗体设计器生成的代码
/// <summary>
/// 设计器支持所需的方法- 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(16, 72);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(80, 24);
this.button1.TabIndex = 0;
this.button1.Text = "播放";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(136, 72);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(96, 24);
this.button2.TabIndex = 1;
this.button2.Text = "停止";
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

private void button1_Click(object sender, System.EventArgs e)
{
openFileDialog1.ShowDialog();
ourAudio = new Audio(openFileDialog1.FileName);
ourAudio.Volume=0; //设置音量,最大值为0;
ourAudio.Play(); //播放
}

private void button2_Click(object sender, System.EventArgs e)
{
ourAudio.Stop(); //停止
}

private void Form1_Load(object sender, System.EventArgs e)
{
openFileDialog1.Filter=filterText;
}
}
}

    上面这个实例可以播放WAV、MIDI、MP3等很多音乐文件,因此,还怕在程序中播放背景音乐吗?

    4. C# Microsoft.DirectX.DirectSound的使用方法
    DirectSound又为何物,熟悉DirectX编程的人应该很明白,对了,这个类是DirectX 分支中专门用于播放WAV文件的类,使用同样的简单,对了,有人要问了,AudioVideoPlayback类不是可以播放WAV文件吗,那么,要DirectSound干什么呢,在此,我只能说,萝卜青菜,各有所爱,能说的,还是说一点吧,下面还是来看一看DirectSound的使用。
    (1) 对DirectX.DirectSound类进行引用:单击菜单“项目”—>“添加引用”—>“.NET”,选择“Microsoft.DirectX.DirectSound”,单击“确定”。
    (2) 在源代码中添加以下命名空间的引用:
using Microsoft.DirectX;
using Microsoft.DirectX.DirectSound;
    以上两行代码完成对DirectX和 Microsoft.DirectX. DirectSound命名空间的引用,只有通过以上命名空间的引用,才能在程序中对Microsoft.DirectX.DirectSound的编程。

    (3) 定义DirectSound设备并进行设置:
Device dv=new Device();
dv.SetCooperativeLevel(this,CooperativeLevel.Priority);
    第一行代码把DirectSound设备定义为dv,第二行代码对定义好的DirectSound设备进行设置,接下来的过程,同样的简单。

    (4) 定义DirectSound设备的文件对象
SecondaryBuffer buf;
    (5) 以带路径的媒体文件名创建一个新的DirectSound对象,请看下面的代码:
buf=new SecondaryBuffer("Blip.wav",dv);


    (6) 接下来,看看下面的代码,是不是和AudioVideoPlayback有点不同。
buf.Volume=0; //设置音量,最大值为0
buf.Pan=10000;//设置左、右声道音量平衡,负值为左声道,正值为右声道
buf.Play(0,BufferPlayFlags.Looping); //循环播放
buf.Play(0,BufferPlayFlags.Default); //播放一次
buf.Stop();//停止

    (7) 还是来看一看这个实例的完整代码吧。

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.DirectSound;

namespace dxSndplay
{
/// <summary>
/// Form1 的摘要说明。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;
private System.Windows.Forms.Label label1;

SecondaryBuffer buf;
public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();

//
// TODO: 在InitializeComponent 调用后添加任何构造函数代码
//
}

/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows 窗体设计器生成的代码
/// <summary>
/// 设计器支持所需的方法- 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(40, 72);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(72, 24);
this.button1.TabIndex = 0;
this.button1.Text = "播放";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(40, 128);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(80, 24);
this.button2.TabIndex = 1;
this.button2.Text = "停止";
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// label1
//
this.label1.Location = new System.Drawing.Point(160, 80);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(96, 24);
this.label1.TabIndex = 2;
this.label1.Text = "label1";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.label1);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

private void button1_Click(object sender, System.EventArgs e)
{
Device dv=new Device();
dv.SetCooperativeLevel(this,CooperativeLevel.Priority);
buf=new SecondaryBuffer("Blip.wav",dv);
label1.Text=buf.Volume.ToString();
buf.Volume=0; //设置音量,最大值为
buf.Pan=10000;//设置左、右声道音量平衡,负值为左声道,正值为右声道
buf.Play(0,BufferPlayFlags.Looping); //循环播放
//buf.Play(0,BufferPlayFlags.Default); //播放一次
}

private void button2_Click(object sender, System.EventArgs e)
{
buf.Stop();
}

}
}

四、 微软“XNA”技术
    微软“XNA”是什么?让我们来看看相关资料对“XNA”的介绍:XNA是微软推出的所谓“通用软件开发平台”,它的目标是让游戏开发过程更加轻松简单。XNA中,X代表微软掌握的技术资源,DirectX和Xbox;N代表次世代(NextGenera?tion);A代表架构(Architecture)。以DirectX为原型,微软希望把XNA发展为所有游戏开发平台的通用标准。如此一来将实现游戏开发工具的无缝嵌入和平滑过渡。在“大一统”尚未成形时,微软将透过XNA整合自家门下的Windows、Xbox与WindowsCE系统,在个人电脑、家用游戏机、移动设备这三种游戏平台上将开发工作融会贯通。在微软网站XNA的页面上,象征DNA链的螺旋图形贯穿始终,DNA是建构生命体的核心,微软显然希望将自己的XNA也塑造成游戏生命力的支柱。
    以上是相关资料对微软“XNA”的介绍,从这一段简要的文字,可以看出,“XNA”功能相当强大,但使用简单,而且,“XNA”是基于DirectX基础之上的编程技术,所以,从DirectX过渡到“XNA”相信不是很难的事,和DirectX一样,“XNA”也可以使用C#语言进行开发,这是本篇中为什么要使用C#语言进行DirextX游戏开发的原因, 只要掌握了C# DirectX游戏开发技术,掌握C# “XNA”游戏开发技术是不难的。

五、 “文字对战游戏”开发实例讲解
    从本节开始,我们将对“文字对战游戏”编程技术及实例进行讲解,相信大家都玩过游戏或者编写过游戏,知道一个好游戏需要很好元素,需要有很多技术的支持,其中一项很重要的技术,就是程序编写,我们通常看到的游戏中的动画、音乐、文字都是通过程序编写来调用图形文件、声音文件来实现的,一个好的游戏,除了漂亮的画面、动听的音乐,还要有耐人寻味的中心思想,这好比一篇好的文章或优秀的电影、电视剧,在游戏中,体现中心思想的就是游戏的创意,在90年代,《仙剑奇侠传》这部精典的游戏给我留下的难忘的回忆,在那个年代,玩电脑游戏的人没有人不对它感慨万千的,就是我自己,用那台一直没有舍得丢弃的486电脑,玩了三遍。

    1. 游戏的创意
    说了半天,游戏的创意是什么呢,所谓创意,也就是创作一部作品的意图,通过这部作品,你想表达的思想是什么, 同时, 要给玩家一种什么样的感受,比如说曾轰动一时的优秀作品<<仙剑奇侠传>>,它的成功之处在什么地方呢? 也就是说,这部作品最吸引人的地方,我认为它的成功之处在于初期对感情的抒情和描写, 后期对感情的深化和体现,总之,电脑游戏不仅给人们一种美的享受,还能开发人的智力,比如说:作品中的迷宫,就需要我们用脑去思索,思路对了,一步就过去了,思路不正确,就要多走弯路,还有战斗,也要多用脑子,合理,灵活的应用各种招式和道具,才能克敌致胜,顺利闯关,否则,将百战百败,关于游戏的创意,在这里我不想再多讲,一是因为本篇的重点是游戏编程技术,二是关于游戏的创意,很多书藉都有介绍,所以,读者如果想多了解一点,可以看其它的游戏创作的书藉.
那么,本篇“文字对战游戏”的创意是什么呢,从这个标题,我们可以理解了,就是
通过玩这个游戏,让我们熟悉键盘的操作,更重要的是,熟悉中文打字,在这个游戏中,你可以用你熟悉的输入法如:五笔、拼音等输入法来完成游戏中出现的汉字,若你输入的汉字与游戏中的汉字相匹配,汉字立即消失,并出现新的汉字,也就是说,此游戏与其它打字游戏相比,就是实现了汉字对战,而且不限输入法,只要你玩通了这个游戏,你就学会了英文及中文打字,换一种说法,这个游戏把枯燥乏味的打字学习放到了游戏中,通过玩游戏来练习打字,这是本篇创新编程技术之一;本篇创新编程技术之二在于,此游戏中用到的字符及图形都可以在程序外部改变,不懂编程技术的人都可以把自己喜欢的图片和需要对战的字符在游戏中实现,这也可以说成是游戏DIY吧;本篇创新编程技术之三在于完全应用.NET技术,在.NET框架下通过C#语言调用DirectX进行编程,结束了只有C++才能进行游戏编程的历史,看过本篇文章后,你会感到,C#编写游戏的时代已经来临。

    2. 游戏的策划:开发一个游戏,策划这一环节不能少,“文字对战游戏”策划如下:
(1) 游戏使用的显示模式:窗口模式。
(2) 游戏使用的屏幕分辨率:800x600。
(3) 游戏使用菜单控制游戏:使用。
(4) 游戏的关卡设计:17关。
(5) 游戏的图形编程技术:使用DirectDraw技术。
(6) 游戏的音效编程技术:使用DirectSound技术。
(7) 游戏中使用的图形文件:
Gou1.bmp:180x20的位图文件,动画帧图形文件。
Level1.bmp和Level2.bmp:800x600的位图文件,游戏中的背景图形文件。
(8) 游戏中使用的音效文件:
Three.wav、Two.wav、One.wav用于在游戏每关开始时的音效播放。
YES.wav、NO.wav用于游戏中过程的音效播放。
(9) 游戏中使用的字符文件:
Level1.dat至Level17.dat:共17个数据文件,内容为每一关中出现的字符。
(10) 游戏中使用的关卡控制文件:
state.dat:控制在每一关中出现的字符文件名、字符文件中的字符总数,以及背景图形文件名,要在程序外部对游戏进行DIY时,就需要对这个文件进行编辑。
(11) 其它文件:
readme.txt:游戏的说明文件。
注意:游戏中用到的文件必须和程序编译生成的可执行文件放在同一目录。

    3. 程序中关键技术介绍:
    (1) DirectDraw的窗口显示技术:前面DirectDraw实例介绍的是全屏显示技术,因为在
    游戏中用到的是窗口显示技术,因此,有必要讲解一下。
    DirectDraw的窗口显示技术:就是在一个普通的Windows窗口中使用DirectDraw进行图形处理有技术,和全屏显示技术并太大的区别,只是要用到DirectDraw的一个类Clipper,这个类什么功能呢,让我们来看下面的代码:

/// <summary>
/// 创建directDraw
///
/// </summary>
private void CreateSurfaces()
{
DrawDevice = new Microsoft.DirectX.DirectDraw.Device(); //创建directDraw设备
DrawDevice.SetCooperativeLevel(this, CooperativeLevelFlags.Normal); //设置显示模式为正常显示模式
description = new SurfaceDescription(); //创建目标表面

ColorKeyFlags flags = new ColorKeyFlags();
ColorKey key = new ColorKey();
key.ColorSpaceHighValue = key.ColorSpaceLowValue =0; //设置颜色为黑色
flags = ColorKeyFlags.SourceDraw;

description.SurfaceCaps.PrimarySurface = true; //目标表面的主表面属性设置真

surPrimary = new Surface(description, DrawDevice); //创建基于目标表面和DirectDraw设备上的主表面
clip = new Clipper(DrawDevice); //创建基于DirectDraw设备上的裁剪区域
clip.Window = this; //设置clip的裁剪区域为本程序的窗口
surPrimary.Clipper = clip; //设置主表面的裁剪区域为clip

description.Clear(); // 清除目标表面之前的设置
description.Width = 800; //设置目标表面的宽
description.Height = 600; //设置目标表面的高
surBack = new Surface(description, DrawDevice); //创建基于目标表面和DirectDraw设备上的后表面
surBack.FontHandle=font1.ToHfont(); //设置后表面的字体

description.Clear(); // 清除目标表面之前的设置
surBmp1 = new Surface(StateBmpfilename,description, DrawDevice); // 建立位图表面

description.Clear(); // 清除目标表面之前的设置
surBmp2 = new Surface("Gou1.bmp", description, DrawDevice); // 建立位图表面
surBmp2.SetColorKey(flags, key); // 设置页面透明色.

}

    现在,让我们来看一看上面的代码实现了什么功能,上面的代码是程序中用到的一个方法,实现了以下功能:
    1) 创建了基于本程序窗口的DirectDraw设备,并设置了显示模式为800x600,创建
了主表面、后表面、及两个位图页面。
    2) 设置了黑色为透明色,黑色背景的图形黑色将不再游戏中显示出来,也就是说,
将黑色屏蔽了。
    3) 设置了程序中使用的字体。
从以上的代码和注释中可以看到,Clipper类的作用就是把DirectDraw设备显示区域
指定到本程序的窗口,实现DirectDraw的窗口显示模式。

    (2) 游戏开始时的声音及动画控制技术
作为一个好的游戏,在游戏开始时都有动画及音乐,在“文字对战游戏”编程实例中,讲解了简单的游戏开始时的动画及音乐控制技术,请看下面的代码:

//游戏开始时动画及声音
void GameStartmove()
{
if (null == surPrimary) //如果主表面不存在,则返回
return;

if (FormWindowState.Minimized == WindowState) //如果主窗口被最小化,则返回
return;


destRect = new Rectangle(PointToScreen(new Point(0,0)), ClientSize); //创建并初始化矩形区域
copyRect1=new Rectangle(0,0,800,600);

surBack.DrawFast(0, 0, surBmp1, copyRect1, DrawFastFlags.DoNotWait);//把surBmp1位图表面的copyRect1矩形区域拷贝到后表面
if(Backtime>0)
{
surBack.FontHandle = font2.ToHfont(); //对surBack字体句柄进行赋值
surBack.ForeColor=Color.Blue; //选择字体前景色
surBack.DrawText(350,300,Backtime.ToString(),false); //在后表面上绘字符
}

switch(Backtime)
{
case 3:
bufWaveThree.Play(0,BufferPlayFlags.Default); //播放"Three"的Wav一次
break;
case 2:
bufWaveTwo.Play(0, BufferPlayFlags.Default); //播放"Two"的Wav一次
break;
case 1:
bufWaveOne.Play(0, BufferPlayFlags.Default); //播放"One"的Wav一次
break;

}

Backtime--; //实现游戏开始时的倒计时
if(Backtime<0)
{
surBack.DrawText(300,300,"Start",false); //游戏开始
}
if(Backtime<-1)
{

timer2.Enabled=false;
timer1.Enabled=true;
Backtime=3;
surBack.FontHandle=font1.ToHfont();
}
surPrimary.Draw(destRect,surBack, DrawFlags.Wait); //把后表面的内容绘制到主表面
score=0; //将总分变量赋被初始值
}

    上面的代码是程序中的一个方法,在游戏开始时实现倒计时的动画显示及声音提示,通过每一行代码后的注释,相信理解代码的作用也是一样的简单,毕竟,注释是详细而通俗易懂的,即便这样,我还是想把几行代码再说明一下:

destRect = new Rectangle(PointToScreen(new Point(0,0)), ClientSize); //创建并初始化矩形区域
这是一行创建并初始化矩形区域的代码,很容易理解,PointToScreen(new Point(0,0)的作用是把本应用程序的窗口的左上角在位置转换为屏幕座标,ClientSize的作用是获取本应用程序的窗口工作区的大小,通过创建基于应用程序工作区的矩形区域,为程序实现DirectDraw窗口显示是不缺少的。

surBack.DrawFast(0, 0, surBmp1, copyRect1, DrawFastFlags.DoNotWait);//把surBmp1位图表面的copyRect1矩形区域拷贝到后表面
此行代码实现将surBmp1位图表面中的copyRect1矩形区域部分绘制到surBack后表面中,在本例中,surBmp1存储的是800x600背景图,而copyRect1的区域是(0,0,800,600),因此surBmp1的背景图被全部的绘制到的surBack上。

surBack.DrawText(350,300,Backtime.ToString(),false); //在后表面上绘字符
在后表面中绘制了背景图后,上面的代码又把字符绘到了后表面上,Backtime.ToString()的作用Backtime整型变量转换为字符串。
surPrimary.Draw(destRect,surBack, DrawFlags.Wait); //把后表面的内容绘制到主表面
在图形和字符都绘制到后表面后,为了将后表面的内容在屏幕上显示出来,最后,我们将后表面的内容绘制到了主表面。

    (3) Level字符文件读取技术
Level字符文件存储的是游戏中每一关需要对练的字符数据,从Level1.dat至Level17.dat:共17个数据文件,现在,让我们来看一下Level1.dat和Level2.dat数据格式:
Level1.dat中的数据
A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,.,/,;,',\,],1,2,3,4,5,6,7,8,9,0
Level2.dat中的数据
我,人,有,的,和,主,产,不,为,这,工,要,在,地,一,上,是,中,国,经,以,发,了,民,同,啊,阿

    以上,是用记事本程序打开数据文件,所看到的数据,可以很清楚看到,每一个字符之间是用“,”号进行分隔的,编码采用Unicode进行编码,清楚了数据格式后,读取文件数据对于.NET来说,可以说是轻而易举,不相信,请看下面的代码:

//读Level字符文件
void ReadlevelFile()
{
FileStream myFile1=new FileStream(StateStrfilename,FileMode.Open,FileAccess.Read);
StreamReader rmyFile1=new StreamReader(myFile1);
rmyFile1.BaseStream.Seek(0,SeekOrigin.Begin);
char[] c = null;
string cstr=null;
for(int i=0;i<Statestrcount;i++)
{
while (rmyFile1.Peek() >= 0)//判断是否已到文件末尾
{
c = new char[1];
rmyFile1.Read(c,0,1);
if(c[0].ToString()==",")//读字符直到读到逗号
goto finish;
cstr=cstr+c[0].ToString();
}
finish:Levelstr[i]=cstr;
cstr="";
}
rmyFile1.Close();
myFile1.Close();
}
    上面的代码实现了将字符文件中的字符一个、一个的读到了字符数组Levelstr中存储起来,文件中“,”被过滤出去了,当然,这种方法不是最简单的方法,.NET的字符串操作及文件操作有很多的方法和技巧,如果你有其它更好的方法,请使用你的方法,也请你把你的方法告诉我。

    (4) State数据文件读取技术
    前面讲过,要对游戏进行DIY,要对State.dat文件进行编辑,那么,State.dat文件存储的是什么内容呢?在程序中,这个文件起的又是什么作用?带着这两个问题,还是先来看一看文件内容再说吧:
48
42
Level1.dat
Level1.bmp
27
Level2.dat
Level2.bmp
24
Level3.dat
Level1.bmp

    以上是用记事本程序打开State.dat数据文件,所看到的前面的部分数据,让我们来对这些数据的意义进行一点解释,第一行数据为48,先不管,第二行数据为42,这个数据所表示的是游戏第一关的字符文件Level1.dat中的字符为42个,第三行数据Level.dat则为游戏第一关的字符文件名,第二行数据42和第三行数据Level.dat是互相关联的,如果Level.dat文件的内容发生改变,如Level.dat文件的字符总数由由42个减到41或其它数量时,第二行的42这个数字必须随着改变,第四行数据Level1.bmp则为游戏第一关的背景图形文件名,而下面27、Level2.dat、Level2.bmp则为游戏第二关的数据,后面的依此类推。
    现在,要对游戏进行DIY是不是可以了,不用再举例了吧,如果你还不清楚,请看Readme.txt文件。
    下面,一起来看一看程序中如何读取state.dat文件的数据。

//读State文件
void ReadstateFile()
{
FileStream myFile=new FileStream("state.dat",FileMode.Open,FileAccess.Read);
StreamReader rmyFile=new StreamReader(myFile);
rmyFile.BaseStream.Seek(0,SeekOrigin.Begin);
Totalstate=Int32.Parse(rmyFile.ReadLine()); //总关卡数
Statestr=new string[Totalstate]; //关卡数据数组
int i=0;
while (rmyFile.Peek() >= 0)//判断是否已到文件末尾
{
Statestr[i]=rmyFile.ReadLine();//储存state文件数据
i++;
}
rmyFile.Close();
myFile.Close();

}

    上述代码将state.dat数据的内容一行一行的读入Statestr字符数组中,以便在程序中对每一关的数据进行控制,从代码中可以看到,state.dat文件的第一行数据被读入到Totalstate变量中,我想,关于这个数据的作用,你已经十分的清楚。
    state.dat文件已经读到了Statestr字符数组中,那么,在程序中如何对每一关的数据进行控制呢,请看下面的代码:

//获取关卡数据
void GetstateData()
{
Statestrcount=Int32.Parse(Statestr[Statenum]); //关卡字符数目
Levelstr=new string[Statestrcount]; //根据关卡字符数目创建数组
Statenum++;
StateStrfilename=Statestr[Statenum]; //获取关卡字符文件名
Statenum++;
StateBmpfilename=Statestr[Statenum]; //获取关卡位图文件名
Statenum++;
if(Statenum>=Totalstate) //超过总关卡数
Statenum=0;
}
    (5) 字符比较技术
    对了,本篇名为“文字对战游戏”开发实例,字符比较当然也算本篇中的关键技术,只不过是因为.NET的功能太强大了,所以,字符比较也同样的简单。

int cpr=String.Compare(Textstr,textInput.Text,true);//忽略大小写比较
if(cpr==0) //若比较结果相同
{
ny=0; //字符座标数据为零
score=score+SCORE_STEP; //总分累计
Readstring(0); //读字符
bufWaveYes.Play(0,BufferPlayFlags.Default); //播放“Yes”的Wav文件一次
goto finish; //跳转到最后
}

String.Compare方法,.NET字符串类String类重要方法,有很多重载方法,在这里的作用是对Textstr和textInput.Text的字符比较,true表示比较忽略大小写,若字符相等,则返回零。
Textstr:程序中出现需要对战的字符
textInput.Text:.NET中的重要组件,在本例中的作用是按收玩家输入的中、英文字符。

    (6) 目录定位技术
    在“文字对战游戏”开发实例中用到了好多资源文件,这些文件都和编译生成的可执行文件放在了同一个目录,之所以这么做,主要还是考虑到应用程序的移植,那么,怎样让程序找到这些资源文件呢,来看.NET的目录定位技术:
string cutdirectory=Directory.GetCurrentDirectory();//获取应用程序工作目录
Directory.SetCurrentDirectory(cutdirectory);//设置当前工作目录
以上两行代码实现了应用程序的工作目录定位,这样,就不必担心程序移植到其它计算机出现找不到资源文件的情况了,当然,你的资源文件必须和应用程序放在同一目录下。

    4. “文字对战游戏”工程项目创建
    (1) 首先保证你的计算机操作系统为WindowsXP版本,装有DirectX 9.0C。
    (2) 按前面所讲的安装.NET2.0或以上版本,安装Microsoft Visual Studio .NET 2005版本以上的开发工具和DirectX9SDK开发包,在安装Microsoft Visual Studio .NET 2005时,如果你没有安装.NET2.0或以上版本,Microsoft Visual Studio .NET 2005将无法安装。
    (3) 启动Microsoft Visual Studio .NET,新建一个Windows应用程序项目,将项目名称命名为TypeGame。
    (4) 单击菜单“项目”->“添加引用”->“.NET”选项卡,依次添加Microsoft.DirectX、Microsoft.DirectX.DirectDraw、Microsoft.DirectX.DirectSound等项目的引用。
    (5) 在Form1.cs文件的using部分添加以下命名空间引用:
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.DirectDraw;
using Microsoft.DirectX.DirectSound;
    (6) 单击菜单“项目”->“添加Windows窗体”,在弹出的对话框中添加一个Windows
窗体,名称命名为“NextForm”。
    (7) 在Form1窗体添加一个TextBox组件,设置TextBox组件的Name属性为:textInput。
    (8) 鉴于篇幅的关系,具体的源代码和资源文件不再书写,请参看作者提供的源程序文件,作者提供的源程序在WindowsXP操作系统、Microsoft Visual Studio .NET 2005、安装有DirectX9SDK开发包的环境下编译通过。

    5. 程序运行效果:最后,大家来看一看如何玩“文字对战游戏”
    (1) 运行“文字对战游戏”,出现游戏主界面,单击菜单“游戏”->“开始”,游戏开
始“倒计时”,“倒计时”结束后,游戏开始,此时玩家需要根据游戏中出现的字符来输入相关字符进行对战。


“文字对战游戏”倒计时画面


“文字对战游戏”第一关画面

    (2) 如何通过第一关,当然游戏左上角的“小狗”跑到右边时,第一关的计时结束,此时,会弹出一个窗口,提示是否进入第二关。

六、 “文字对战游戏”程序移植
    在本篇的最后,让我们来看看如何将我们的“文字对战游戏”移植到其它的计算机上,
在此,就讲一个手工移植的方法,不讲如何制作安装包了。
    1. 在需要移植的计算机上安装.NET2.0。
    2. 在需要移植的计算机上安装DirectX Runtime,DirectX Runtime在DirectXSDK的开发包中,具体安装方法如下:运行dxsdk_apr2005.exe安装程序,在Custom Setup界面中选择“Install DirectX Runtime”,其它选项可以不选择,进行安装即可,当然,你也可以找到单独的DirectX Runtime安装包进行安装,微软的网站就可以下载到。
    3. 上述两项安装完成后,将“文字对战游戏”的可执行文件及所用到的资源文件拷贝到需要移植的计算机上即可。


参考文献:
1 Managed DirectX9编程技术