转载请注明出处!
作者:姜晓东 博客:http://blog.csdn.net/abigfrog QQ:800736 MSN:[email protected]
Microsoft XNA Game Studio 3.1 是一套支持各版本的Microsoft Visual Studio 的游戏开发套件,使用它可以让学生或者爱好者来为Microsoft Windows、Microsoft Xbox 360 视频游戏机以及Microsoft Zune开发游戏。XNA Game Studio 包含XNA Framework,它是一套基于.NET Framework 2.0的托管库,是设计用来支持游戏开发的。
XNA Game Studio 3.1支持Windows7,以及常用的Visual Studio版本,我们这里使用Visual Studio2008。
XNA Game Studio 3.1支持创立3.1和3.0两种版本的项目,我们这里使用3.1。
如果你还没有安装,那么先去下载吧:下载地址
这里我们要做的是一个模拟跳舞机的游戏,先来看下界面:
当然,这只是一个简单的模拟,更多的功能需要你自己去实现,那么我们来看看它是如何做的,先来看看这个游戏包含哪些部分:
1、上下左右四个绿色箭头,代表未正确按键;
2、上下左右四个灰色箭头,代表已正确按键;
3、滚动的星空背景;
4、中间一个小人,代表按键开始,在它之前按键将不被游戏识别,在它之后超过一定区域也将视为未命中;
5、左上角的得分和失分记录;
6、这里听不到的按键声音及背景音乐。
好了,我们来从头开始:
1、首先,新建一个项目:
新建的项目包含如下部分:
这里要注意几点:
1)Content目录是存放游戏资源的目录,声音或者材质贴图都会放到这里
2)Game1.cs是游戏的主要文件,它是Microsoft.Xna.Framework.Game的子类,包含了游戏的主框架,包括资源加载、绘图、游戏逻辑等等
3)这里可以观察到对Xna.Framework下诸类的调用
3、Visual Studio帮我们创建了Game1.cs的主要框架代码,其结构如下:
Initialize() | 执行游戏运行前的一些初始化操作,这里是加载必须的服务和非图片资源的地方。 |
LoadContent() | 仅在游戏每次运行时调用一次,是加载游戏资源的地方。 |
UnloadContent() | 仅在游戏每次运行时调用一次,是卸载游戏资源的地方。 |
Update(GameTime gameTime) | 允许游戏逻辑更新游戏场景,比如碰撞检测、接受输入控制、音频播放等;gameTime参数代表一个时间片。 |
Draw(GameTime gameTime) | 负责绘制游戏场景。 |
graphics字段 | 代表图形设备。 |
spriteBatch字段 | 允许多个的精灵使用同样的设置批量绘制。 |
4、将箭头图像资源通过右键菜单添加到Content目录:
5、添加字体资源方法:
然后:
6、下面说下音频制作,首先从这里启动Microsoft Cross-Platform Audio Creation Tool 3 (XACT3):
界面如下:
首先右击Wave Banks,在菜单中选“Insert wave files”,或者按快捷键,Ctrl+W,选择一个wav格式的音频文件,如果你想用mp3,那么请先使用千千静听将它转换为wav格式,你也可以使用cooledit对wav进行编辑,以满足你的要求。
下一步,将右边Wave Bank窗口中新增的音频文件拖放到Sound Bank窗口,然后从Sound Bank窗口拖放到它的子窗口Cue Name窗口。
如果你想设置音乐的循环特性,那么请单击SoundBank上面的窗口中的一根音乐文件,左下角会出现它的相关属性:
Looping即使循环次数,这里Infinite表示无限!你可以在LoopCount中设置具体的次数,注意,如果你想让它成为背景音乐的话,把这里的Category设为Music即可。这里的Name就是在代码中使用的名称:
// 播放背景音乐
Cue cue = soundBank.GetCue("hml");
cue.Play();
设置完后,使用File-Build进行编译,将编译好的xap文件连同wav文件,通过引用加入到
其他的文件放于:
7、下面放上全部源代码,注释中有详细说明:
Game1.cs:
using System; using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace WindowsGame2 { /// /// This is the main type for your game /// public class Game1 : Game { private const int Metronome = 600; //节拍器间隔 private const int Precision = 32; //允许的误差范围 private const int TopInt = 300; //顶部空白长度 private readonly SortedList arrowsCache = new SortedList();//保存要绘制的箭头精灵,顺序很重要 private readonly GraphicsDeviceManager graphics;//图形设备 private readonly SortedList scriptsCache = new SortedList();//保存读取的舞步脚本指令,每行一条数据,包含舞步文件每行前4位 private int DeadCount; //失误次数 private AudioEngine engine;//音频 private SpriteFont Font1;//字体 private Vector2 FontPos;//字体位置 private int CurrentArrowIndex;//要创建的最新行箭头所指索引 //四个箭头的left值及画布宽度、精灵宽度 private int L1, L2, L3, L4; private Texture2D start;//开始标志 private ScrollingBackground starBackground;//星空滚屏 private int nextTime; //显示下一个箭头的时间 private int spriteIndex = 1;//箭头精灵族arrowsCache中的key private int score;//得分 private SoundBank soundBank; private SoundEffect soundEffect; private SpriteBatch spriteBatch;//批量绘制 private Vector2 spriteSpeed = new Vector2(0, 150.0f);//箭头移动速度 private int startSpriteWidth;//开始标志位的宽度 private int timeOut;//一个精灵显示的过期时间 private int quarterWidth;//游戏画布的四分之一宽 private WaveBank waveBank;//音频相关 public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } /// /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// protected override void Initialize() { base.Initialize(); //初始化音频对象,这里是背景音乐 engine = new AudioEngine("Content//Audio//GA.xgs"); soundBank = new SoundBank(engine, "Content//Audio//Sound Bank.xsb"); waveBank = new WaveBank(engine, "Content//Audio//Wave Bank.xwb"); // 播放背景音乐 Cue cue = soundBank.GetCue("hml"); cue.Play(); //设置速度为反方向 spriteSpeed *= -1; quarterWidth = graphics.GraphicsDevice.Viewport.Width / 4; startSpriteWidth = start.Width; //取得四个箭头各自的位置 L1 = ((quarterWidth / 2) - (startSpriteWidth / 2)); L2 = ((quarterWidth / 2) - (startSpriteWidth / 2)) + quarterWidth; L3 = ((quarterWidth / 2) - (startSpriteWidth / 2)) + quarterWidth + quarterWidth; L4 = ((quarterWidth / 2) - (startSpriteWidth / 2)) + quarterWidth + quarterWidth + quarterWidth; //读取舞步脚本 var fsIn = new FileStream(@"../../../in.dat", FileMode.Open, FileAccess.Read); TextReader reader = new StreamReader(fsIn); string line; int lineNum = 1; while ((line = reader.ReadLine()) != null) { if (lineNum > 1) //从第2行开始读 { scriptsCache.Add(lineNum, line);//添加到缓存 } lineNum++; } reader.Close(); fsIn.Close(); } /// /// 创建箭头精灵 /// /// 速度 /// 开始位置 /// 图片名称 /// 在arrowsCache中的键值 private void CreateArrowSpirte(double speed, int initPoint, string imageName, int keyValue) { var sprite = new ArrowSprite { Direction = imageName.ToUpper(), Speed = speed }; var point = new Vector2(initPoint, graphics.GraphicsDevice.Viewport.Height); sprite.Position = point; switch (imageName) { case "Left": sprite.Image = Content.Load("leftArrow");//未命中时的图片 sprite.SuccessImage = Content.Load("leftArrow2");//已命中时的图片 break; case "Up": sprite.Image = Content.Load("topArrow"); sprite.SuccessImage = Content.Load("topArrow2"); break; case "Down": sprite.Image = Content.Load("downArrow"); sprite.SuccessImage = Content.Load("downArrow2"); break; case "Right": sprite.Image = Content.Load("rightArrow"); sprite.SuccessImage = Content.Load("rightArrow2"); break; } sprite.Width = sprite.Image.Width; sprite.Height = sprite.Image.Height; arrowsCache.Add(keyValue, sprite);//添加到cache待用 } /// /// LoadContent will be called once per game and is the place to load /// all of your content. /// protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice);//创建一个SpriteBatch实例,用于绘制箭头精灵 start = Content.Load("start");//加载开始标志位资源 starBackground = new ScrollingBackground();//初始化星空背景 var background = Content.Load("starfield");//加载星空背景资源 starBackground.Load(GraphicsDevice, background); Font1 = Content.Load("Times New Roman");//加载字体资源 FontPos = new Vector2(100, 20);//初始化字体位置:X,Y } /// /// UnloadContent will be called once per game and is the place to unload /// all content. /// protected override void UnloadContent() { } /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { //更新背景音乐音频引擎 engine.Update(); //精灵 UpdateSprite(gameTime); //键盘控制 UpdateInput(); // 打上次Update被调用以后的时间 var elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; //更新星空背景 starBackground.Update(elapsed * 100); if (Environment.TickCount - timeOut > nextTime) //如果当前时间-上次时间大于当前箭头的速度 { timeOut = Environment.TickCount;//设置下次过期时间 string a, b, c, d; double speed = 2; if (CurrentArrowIndex < scriptsCache.Count) { string line = scriptsCache.Values[CurrentArrowIndex];//读取当前行数据 string[] tmp = line.Split(' '); a = tmp[0].Substring(0, 1); b = tmp[0].Substring(1, 1); c = tmp[0].Substring(2, 1); d = tmp[0].Substring(3, 1); if (line.IndexOf('x') > 0) { nextTime = Metronome; } else { nextTime = Convert.ToInt32(tmp[1]); } CurrentArrowIndex++; if (a.Equals("1")) { CreateArrowSpirte(speed, L1, "Left", spriteIndex++);//创建左箭头 } if (b.Equals("1")) { CreateArrowSpirte(speed, L2, "Up", spriteIndex++);//创建上箭头 } if (c.Equals("1")) { CreateArrowSpirte(speed, L3, "Down", spriteIndex++);//创建下箭头 } if (d.Equals("1")) { CreateArrowSpirte(speed, L4, "Right", spriteIndex++);//创建右箭头 } } } base.Update(gameTime); } /// /// 接受键盘输入控制 /// private void UpdateInput() { KeyboardState keyboardState = Keyboard.GetState(); if(keyboardState.IsKeyDown(Keys.Escape)) { Exit();//Esc键退出 } foreach (var pair in arrowsCache) { ArrowSprite sprite = pair.Value; if (!sprite.IsDead && !sprite.IsRight) //只对在可视范围内并且没有按正确的箭头 { if (keyboardState.IsKeyDown(Keys.Left)) //如果按下左箭头键 { //精灵为左箭头,并且像素差值可接受范围内(精灵位置Y-开始标志位距顶端Y) if (sprite.Direction.Equals("LEFT") && Math.Abs(sprite.Y - TopInt) < Precision) { sprite.IsRight = true;//设置正确标志位 score++;//得分增加 PlaySound("kaboom");//播放声音 return; } } if (keyboardState.IsKeyDown(Keys.Up)) { if (sprite.Direction.Equals("UP") && Math.Abs(sprite.Y - TopInt) < Precision) { sprite.IsRight = true; score++; PlaySound("kaboom"); return; } } if (keyboardState.IsKeyDown(Keys.Down)) { if (sprite.Direction.Equals("DOWN") && Math.Abs(sprite.Y - TopInt) < Precision) { sprite.IsRight = true; score++; PlaySound("kaboom"); return; } } if (keyboardState.IsKeyDown(Keys.Right)) { if (sprite.Direction.Equals("RIGHT") && Math.Abs(sprite.Y - TopInt) < Precision) { sprite.IsRight = true; score++; PlaySound("kaboom"); return; } } if (!sprite.IsRight && sprite.Y < -sprite.Height) { DeadCount++; sprite.Dead(); } } } } private ContentManager contentManager; /// /// 播放音频文件 /// /// 音频文件名 private void PlaySound(string soundName) { if (contentManager == null) contentManager = new ContentManager(Services, @"Content/Audio/"); if (soundEffect == null) soundEffect = contentManager.Load(soundName); soundEffect.Play(); } /// /// 更新箭头精灵状态 /// /// private void UpdateSprite(GameTime gameTime) { foreach (var pair in arrowsCache) { ArrowSprite sprite = pair.Value; sprite.Position += spriteSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;//更新箭头精灵的位置 } } private Vector2 startPosition = new Vector2(150, TopInt);//开始标志的位置 /// /// This is called when the game should draw itself. /// /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend);//开始绘制 starBackground.Draw(spriteBatch);//绘制星空背景 spriteBatch.Draw(start, startPosition, Color.White);//绘制开始标志 foreach (var sprite in arrowsCache) //循环绘制箭头精灵 { ArrowSprite sp = sprite.Value; if (sp.IsRight) //正确 { spriteBatch.Draw(sp.SuccessImage, sp.Position, Color.White); } else //错误 { spriteBatch.Draw(sp.Image, sp.Position, Color.White); } } string output = string.Format("Score:{0} Missing:{1}", score, DeadCount);//结果输出 Vector2 FontOrigin = Font1.MeasureString(output) / 2; // 绘制左上角字符串 spriteBatch.DrawString(Font1, output, FontPos, Color.LightGreen, 0, FontOrigin, 1.0f, SpriteEffects.None, 0.5f); spriteBatch.End();//结束绘制 base.Draw(gameTime); } } }
箭头精灵ArrowSprite.cs:
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace WindowsGame2 { public class ArrowSprite { public bool IsDead { private set; get; } public string Direction { get; set; }//方向 public double Speed { get; set; }//速度 public Texture2D Image { get; set; }//图像 public Texture2D SuccessImage { get; set; }//命中后的图像 public Vector2 Position { get; set; }//位置 //x坐标 public float X { get { return Position.X; } } //y坐标 public float Y { get { return (IsDead) ? 0 : Position.Y; } } public int Width { get; set; }//图像宽度 public int Height { get; set; }//图像高度 public bool IsRight { get; set; }//是否命中 /// /// 当前精灵已不在画布中 /// internal void Dead() { IsDead = true; } } }
星空对象ScrollingBackground.cs
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace WindowsGame2 { internal class ScrollingBackground { private Texture2D mytexture; private Vector2 origin; private int screenheight; private Vector2 screenpos; private Vector2 texturesize; public void Load(GraphicsDevice device, Texture2D backgroundTexture) { mytexture = backgroundTexture; screenheight = device.Viewport.Height; int screenwidth = device.Viewport.Width; origin = new Vector2(mytexture.Width/2, 0); screenpos = new Vector2(screenwidth/2, screenheight/2); texturesize = new Vector2(0, mytexture.Height); } public void Update(float deltaY) { screenpos.Y += deltaY; screenpos.Y = screenpos.Y%mytexture.Height; } public void Draw(SpriteBatch batch) { if (screenpos.Y < screenheight) { batch.Draw(mytexture, screenpos, null, Color.White, 0, origin, 1, SpriteEffects.None, 0f); } batch.Draw(mytexture, screenpos - texturesize, null, Color.White, 0, origin, 1, SpriteEffects.None, 0f); } } }
脚本in.dat
5 0001 300 0001 x 0001 500 1000 600 1010 x 1100 550 0001 x 0101 650 0001 700 0110 730 0101 770 0001 800 0100 840 1001 880 0001 x 0010 600 0100 560 1000 800 0001 1000 0010 700 0100 400 1000 400 0001 500 0010 x 0100 x 1000 x 0001 x 0010 x 0100 x 1000 x 0001 x 0010 x 0100 x 1000 x 0001 x 0010 x 0100 x 1000 x
舞步脚本说明:
第1行:保留
第2行~以后:0001 四位,分别表示左上下右,0为否,1为是,0001即为右箭头,1010为左和下箭头,依次类推。
8、总结,我们学习了:
1、什么是XNA
2、XNA生成的框架代码说明
3、如何通过XACT播放音乐
4、一个跳舞机游戏的源代码
5、如何通过XACT制作音频资源,及设置