Professional XNA Game Programming Chapter2 的例子

作者:Object.飘 又名:kobeair
说明:我也是初学,如果那里有错误,请以源代码为准! 不要做为任何商业用途~



在第1章的例子中,我们学会了怎样绘制2D图片和滚动。那么这一章的内容在进一步深入,我们将制作简单的2D游戏。
在游戏中我们会呈现一个游戏菜单和游戏画面。(其中包含简单的AI)听起来是不是很令人兴奋。那么还等什么,let's go!~~

首先游戏有菜单和游戏画面,那么游戏怎么样才能正确的显示为菜单和游戏画面呢?总不能菜单和游戏画面交替出现吧! @。@~所以必须先创建一个枚举类型的游戏菜单控制变量。
//声明一个枚举,用来判断是否为菜单和游戏模式
enum GameMode {
Menu,
Game,
GameOver,
}


这样的几行代码就可以控制游戏运行中什么时候绘制什么~很Easy吧~

我们还要声明一个类SpriteToRender, 这个类主要进行绘制,例如背景,按钮和游戏中的小球.

大家可能还记得在第1章中绘制2D图片的方法:

Texture2D texture;
SpriteBatch spriteBach;
Rectangle rect;
Rectangle? sourceRect;
Color color=null;


这里有一个小小的变化,使用到了Rectangle结构,在第1章是定位图片位置的Vector2这个类,那么Rectangle就是在第1章提到的第2种定位图片的方法,用于拉伸图片(因为背景只有一张嘛 )。值得注意的是Rectangle是一个结构并且是一个泛型的(泛型是C# 2.0的一个新的特性,如果不清除可以去看看有关资料),那么如果给一个泛型的结构赋空值不能直接这样:
Rectangle sourceRect=null;(这样是不允许的)
只能这样
Rectangle? sourceRect;
rect和sourceRect是有区别的,rect不可以为空而sourceRect是可以为空。

在构造函数中进行设置
public SpriteToRender(Texture setTexture,Rectangle setRect,Rectangle? setSourceRect,Color setColor){

texture=setTexture;
rect=setRect;
sourceRect=setSourceRect;
color=setColor;
}


大家都应该明白这几个变量的意思吧 ~如果不明白,那么就看后面我的跟帖吧 先卖个关子~

昨天写到了控制图片的几个变量,其中除了Rectangle? sourceRect;其他的变量大家大概都明白是做什么的~
Rectangle? sourceRect;这个变量是控制图片上显示指定矩形内的内容,其实就是图片上的坐标(不知道这样说是否准确)。如果为空那么就全部显示。现在应该明白为什么Rectangle rect;不能为空Rectangle? sourceRect;可以为空的原因了吧~


接下来要声明构造函数,为这几个宝贝赋值。

public SpriteToRender(Texture2D setTexture, Rectangle setRect, Rectangle? setSourceRect, Color setColor)
{
texture = setTexture;
rect = setRect;
sourceRect = setSourceRect;
color = setColor;
}


由于在游戏中不可能只单单绘制背景,肯定也有其他的元素,比如说UI,飞船(2D),子弹(2D)那么就复用这个类,把本类声明为一个泛型。(其实泛型就是一个容器,可以容纳很多不同的对象)。不这么声明也可以办到,那就要为不同的元素声明不同的类,如果你觉得那样更简单的话,你可以去做。
//声明SpriteToRender为一个泛型
List<SpriteToRender> sprites = new List<SpriteToRender>();


一旦声明为泛型,那么这个类的对象就具有Add()方法,是向List(也就是容器)中添加元素。
看看我们是这么添加的;
我们为这个类添加一个没有返回值的方法RenderSprite();其代码为
public void RenderSprite(Texture2D texture, Rectangle rect, Rectangle? sourceRect, Color color)
{
sprites.Add(new SpriteToRender(texture, rect, sourceRect, color));
}

为sprites这个容器对象添加元素,也就是说每调用一次RenderSprite()方法,那么就为sprites对象添加了新的元素。

接下来我们要重载这个方法,为什么要重载。因为我们不可能知道所有图片的详细信息,那么我们就列出可能出现的情况。
public void RenderSprite(Texture2D texture, Rectangle rect, Rectangle? sourceRect)
{
RenderSprite(texture, rect, sourceRect, Color.White);
}

public void RenderSprite(Texture2D texture, int x, int y, Rectangle? sourceRect, Color color)
{
RenderSprite(texture, new Rectangle(x, y, sourceRect.Value.Width, sourceRect.Value.Height), sourceRect, color);
}

public void RenderSprite(Texture2D texture,int x,int y,Rectangle? sourceRect)
{
RenderSprite(texture, new Rectangle(x, y, sourceRect.Value.Width, sourceRect.Value.Height), Color.White);
}

public void RenderSprite(Texture2D texture,Rectangle rect,Color color) {
RenderSprite(texture, rect, null, color);
}

public void RenderSprite(Texture2D texture,Rectangle rect) {
RenderSprite(texture, rect, null, Color.White);
}

public void RenderSprite(Texture2D texture) {
RenderSprite(texture, new Rectangle(0, 0, 1024, 768), null, Color.White);
}


我们列出了可能出现的情况,大家发现了吗,这些重载的方法中都是对
public void RenderSprite(Texture2D texture, Rectangle rect, Rectangle? sourceRect, Color color)
{
sprites.Add(new SpriteToRender(texture, rect, sourceRect, color));
}

方法的调用。

剩下该干什么?~所有的元素信息得到了,那还等什么大胆的绘制吧。如果你也想到了,那么恭喜你!~^ ^~
为这个类添加一个DrawSprites()方法用于绘制。
在说明这个方法之前我们先看看SpriteBatch.Draw()的原型,
public void Draw (
Texture2D texture,
Rectangle destinationRectangle,
Nullable<Rectangle> sourceRectangle,
Color color
)

这也就是我们为什么在前面要定义那几个变量的原因。
回到我们的这个方法
public void DrawSprite() {
//说明其中没有成员
if(sprites.Count==0){
return;
}
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.FrontToBack, SaveStateMode.None);

foreach(SpriteToRender sprite in sprites){
//SpriteBatch

}
spriteBatch.End();

sprites.Clear();
}


spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.FrontToBack, SaveStateMode.None);

这句是说明绘制的类型,用Alpha混合模式从sprites列表的末尾开始向前绘制,不保存绘制状态。

还记得我们第1章的例子吗?我们用了X,Y获取到窗口大小。那么这个例子不例外也用到窗口大小,因为我们要确切的知道在图片绘制在什么位置~
回到PongGame这个类中,定义width和height.
int width ,height;
然后在Initialize()方法中初始化
protected override void Initialize()
{

width = graphics.GraphicsDevice.Viewport.Width;
height = graphics.GraphicsDevice.Viewport.Height;

base.Initialize();
}


好了,让我们开始绘制背景吧。估计大家都等的不耐烦了!

先声明图片信息Texture2D和spriteBatch,这对大家来说都是小菜一碟了。

Texture2D backgroundTexture, menuTexture, gameTexture;


protected override void LoadGraphicsContent(bool loadAllContent){}方法中添加实例。
// Load all our content
spriteBatch=new SpriteBatch(graphics.GraphicsDevice);
backgroundTexture = content.Load<Texture2D>("SpaceBackground");
menuTexture = content.Load<Texture2D>("PongMenu");
gameTexture = content.Load<Texture2D>("PongGame");


在Draw方法中进行绘制,还记得我在前面讲的那个控制游戏菜单变量吗?(希望没有忘 )

spriteBatch.Begin();
spriteB.Draw(backgroundTexture, new Rectangle(0, 0, width, height), Color.White)
spriteBatch.End();


这里绘制背景,让他充满整个屏幕~

在这里我们就要判断是不是菜单模式了,先声明一个GameMode gameMode=GameMode.Menu的变量。
if(gameMode==GameMode.Menu){
//在这里调用RenderSpripte();方法
RenderSprite(menuTexture,
512-XnaPongLogoRect.Width/2, 150, XnaPongLogoRect);

RenderSprite(menuTexture,
512 - MenuSingleplayerRect.Width / 2, 300, MenuSingleplayerRect,
currentMenuItem == 0 ? Color.Orange : Color.White);

RenderSprite(menuTexture,
512 - MenuMultiplayerRect.Width / 2, 350, MenuMultiplayerRect,
currentMenuItem == 1 ? Color.Orange : Color.White);

RenderSprite(menuTexture,
512 - MenuExitRect.Width / 2, 400, MenuExitRect,
currentMenuItem == 2 ? Color.Orange : Color.White);
}


这样,在sprties的容器里就存在4个对象信息。可能大家已经注意到了,在上面的方法中用到了MenuMultiplayerRect,MenuExitRect等等的变量。这是一系列用于绘制Texture的坐标~所以在你的类中也要定义

static readonly Rectangle
XnaPongLogoRect = new Rectangle(0, 0, 512, 110),
MenuSingleplayerRect = new Rectangle(0, 110, 512, 38),
MenuMultiplayerRect = new Rectangle(0, 148, 512, 38),
MenuExitRect = new Rectangle(0, 185, 512, 38),
GameLivesRect = new Rectangle(0, 222, 100, 34),
GameRedWonRect = new Rectangle(151, 222, 155, 34),
GameBlueWonRect = new Rectangle(338, 222, 165, 34),
GameRedPaddleRect = new Rectangle(23, 0, 22, 92),
GameBluePaddleRect = new Rectangle(0, 0, 22, 92),
GameBallRect = new Rectangle(1, 94, 33, 33),
GameSmallBallRect = new Rectangle(37, 108, 19, 19);

后面3方法中的后面一个参数是用3元运算符的,在这里的作用是处理焦点处的高光~^ ^~ 如果比较难理解,那么运行一下代码,在游戏处于菜单状态时,按上下键就明白了。
那么还要声明currentMenuItem 的变量控制焦点。
int currentMenuItem=0 ;

这时运行一下游戏看看,背景、UI都出来了~但是选择游戏模式的高光不能上下移动,那是因为我们还没有加入键盘的处理方法。先声明键盘对象KeyboardState keyboard

在Update()方法里进行实例化

keyboard = Keyboard.GetState();

同样在Draw()方法中进行判断
if (keyboard.IsKeyDown(Keys.Down))
{
currentMenuItem = (currentMenuItem + 1) % 3;
}
else if (keyboard.IsKeyDown(Keys.Up))
{
currentMenuItem = (currentMenuItem + 2) % 3;
}

运行一下,看看效果。高光可以正常的显示了,但是好像有一点问题。当键盘按下的时候光标移动的非常快。我们还需要一个控制键盘按下的BOOL变量,如果按下我们就设置为真,如果松开我们就设置为假。
bool remUpPressed = false,
remDownPressed = false;


if (keyboard.IsKeyDown(Keys.Down)&&remDownPressed==false)
{
currentMenuItem = (currentMenuItem + 1) % 3;
}
else if (keyboard.IsKeyDown(Keys.Up)&&remUpPressed==false)
{
currentMenuItem = (currentMenuItem + 2) % 3;
}

将其加入到该方法中。这样还是不够的~因为他永远为假。我们还需要在Update()中进行设置。

remDownPressed = keyboard.IsKeyDown(Keys.Down);
remUpPressed = keyboard.IsKeyDown(Keys.Up);
这两行代码不用我解释了吧。
现在运行一下代码,正常了。

================================================================
以上我去掉了Xbox360的手柄控制代码,因为家里没Xbox360,也不好做测试。NND``````
下次我们进入更高级的部分,最终游戏!~很期待吧··
现在游戏菜单终于有了,那么接下来我们就为游戏加入音效吧,毕竟一个没有音乐的游戏会让人郁闷的死掉。

XNA Game Studio express中的声音,本质上是基于Wave的。Wave文件(*.wav)集中在XACT工程里面,并被编译成可以在你的游戏中加载的wave bank.所以必须要制作或找到一些.WAV文件格式的音乐,然后通过XACT XNA自带的音频文件工具进行编译,如果不懂可以去看看XNA的帮助文件。

我们首先要定义3个控制声音的对象
public AudioEngine audioEngine = null;
public WaveBank waveBank = null;
public SoundBank soundBank = null;

AudioEngine对象将一个.xgs格式的文件传入。
WaveBank对象将一个.xwb格式的文件传入。
SoundBank对象将一个.xsb格式的文件传入。

xgs的文件包含了.xwb和.xsb的一些信息。
下来我们在 LoadGraphicsContent()方法实例化这些对象

audioEngine = new AudioEngine("PongSound.xgs");
waveBank = new WaveBank(audioEngine, "Wave Bank.xwb");

if (waveBank != null)
soundBank = new SoundBank(audioEngine, "Sound Bank.xsb");

在XNA中所有的音乐和音效都是通过Cue对象调用GetCue获得或者通过SoundBank.PlayCue()方法播放,我们将采用第2种。如果想要在游戏控制音量,那么就要用到第1种方法。
在Draw方法找到
if (keyboard.IsKeyDown(Keys.Down)&&remDownPressed==false)
{
currentMenuItem = (currentMenuItem + 1) % 3;
soundBank.PlayCue("PongBallHit");
}
else if (keyboard.IsKeyDown(Keys.Up)&&remUpPressed==false)
{
currentMenuItem = (currentMenuItem + 2) % 3;
soundBank.PlayCue("PongBallHit");
}

将播放方法加入,其中soundBank.PlayCue()中的参数是播放.Wave文件名。
现在运行游戏,在菜单状态下按下键盘。是不是和我听到的一样~非常清脆的声音·^ ^~

这个游戏中只有声效没有背景音乐,如果喜欢的话可以自行加入以.wav格式的音乐文件。然后将
public AudioEngine []audioEngine = null;
public WaveBank []waveBank = null;
public SoundBank []soundBank = null;
设置成数组就OK了!~

================================================================
今天看到Clayman的BLOG5月2号发表的帖子,非常的郁闷。为他感到惋惜也同样害怕,怕他提到的人就是我~ 555555555555555~
哎~睡了~太晚了!明天继续~

顺便截张图,看看效果。是不是大家和我显示的一样!嘿嘿~~
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值