【译】针对初学者的XNA示例游戏
发表于2016-03-06
原文地址:http://www.codeproject.com/Articles/1072080/XNA-Sample-Game-for-Beginners
版权:作者版权声明为http://www.codeproject.com/info/cpol10.aspx,可以翻译
一、简介
C#.NET/XNA 是一项使用日渐减少但是至少在XBOX 360平台上依旧支持的游戏
文中说到的示例游戏Annie's Laundry是由我用XNA在几个月前开发的,仅仅是为了让我加深对游戏开发基础的认识,并且这种认识也对大家有用。
二、背景
在这篇文章里,我提供了游戏的源代码并且详细描述了物体运动、碰撞、计分、用户控制以及开始结束游戏。这篇文章提供了代码以及上述目标的详细描述。
Annie'sLaundry 是一个用XNA开发的单人2D游戏。游戏的目的是通过捕获掉落的衣服来尽可能的多得分。掉落衣服的种类是手帕和衬衫。玩家需要提防沿着绳子掉落的粪便以及敌人扔过来的炸弹。所以,玩家在捕获衣服的时候要小心避开粪便以及炸弹。篮子越重就会也难移动,任务衣服掉落下来没有抓住都会损害安妮的健康。玩家需要保持安妮的健康,同时尽量避免粪便和炸弹以及得尽可能高的分数。
三、代码解释
XNA游戏是从main方法的Program.cs which开始执行的。Run()方法用来初始化游戏。
using System;
namespace WindowsGame2
{
#if WINDOWS || XBOX
static class Program
{
static void Main(string[] args)
{
using (AnnieLaundary game = new AnnieLaundary())
{
game.Run();
}
}
}
#endif
}
AnnieLaundary这个类继承自Microsoft.Xna.Framework.Gam,必要的资源,比如声音、声音对象实例、精灵字体、2D纹理以及对象的初始化,都是在这个类里面。
public class AnnieLaundary : Microsoft.Xna.Framework.Game
{
SoundEffect background_sound;
SoundEffectInstanceintro_sound_instance;
SpriteFont font;
Bucket bucketObj;
Rectangle screenRectangle;
Texture2D shirt; // shortened code as for the example
}
LoadContent()方法把WindowsGame2Content目录下的声音和纹理资源加载进来。这是一段非常重要的代码,你需要小心不要把资源的名字拼错。
protected override void LoadContent()
{
bomb_sound =Content.Load("Audio\Waves\bomb");
bomb_sound_instance =bomb_sound.CreateInstance();
mainMenu =Content.Load("mainbkg");
help =Content.Load("selectamapwithbckgrnd");
shirt =Content.Load("shirt");
poo =Content.Load("poo");
Mybombimg =Content.Load("bomb");
monkey =Content.Load("monkey");
shirtObj = new Shirt(shirt,screenRectangle);
bombObj = new Bomb(Mybombimg, screenRectangle);
pooObj = new Poo(poo, screenRectangle);
monkeyObj = new Monkey(monkey, screenRectangle, this.Content);
pheObj = new PlayHelpExitBtns
(playNorm, playNeon, helpNorm,helpNeon,
exitNorm, exitNeon, help,mainMenu, screenRectangle, this.Content);
spriteBatch = new SpriteBatch(GraphicsDevice);
font =Content.Load("SpriteFont1");
serviet =Content.Load("serviet"); //shortened code as for example
}
主类里面的Update方法是非常重要的,因为它在持续地更新游戏的逻辑。上次update到现在的时间是作为参数传递进来的。
屏幕状态枚举有两个常量分别是controllerDetect和title。初始是controllerDetect。 updateControllerDetectScreen函数在检测到玩家输入Enter以后会把状态从controllerDetect切为title。
enum screenState
{
controllerDetect,
title
}
screenState currentScreenState;
private void updateControllerDetectScreen()
{
if (Keyboard.GetState().IsKeyDown(Keys.Enter) == true)
currentScreenState =screenState.title;
}
protected override void Update(GameTimegameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
{
this.Exit();
}
switch (currentScreenState)
{
case screenState.controllerDetect:
{
updateControllerDetectScreen();
break;
}
case screenState.title:
{
if (PlayHelpExitBtns.selectedKeyState == "play" && playState == false)
{
StartGame();
playState = true;
intro_sound_instance.Stop();
/* stops the intro sound play when theplaye begins */
}
if (PlayHelpExitBtns.selectedKeyState == "play" && !Serviet.gameover)
{
intro_sound_instance.Stop();
/* stops the intro sound play when theplaye begins */
background_sound_instance.Play();
/* starts background music */
if (Serviet.level >= 2) /* level static variable is declared inserviet */
{
shirtObj.Updateshirt();
}
if (Serviet.level >= 3)
{
secondbucketObj.UpdateSecondBucket();
}
if (Serviet.level >= 4)
{
pooObj.UpdatePoo();
}
if (Serviet.level >= 5)
{
bombObj.UpdateBomb();
}
servietObj.UpdateServiet();
monkeyObj.UpdateMonkey();
bucketObj.UpdateBucket();
}
else
{
pheObj.update();
if (pheObj.exitGame())
{
this.Exit();
}
}
break;
}
}
/* serviet collision with bucket */
if (servietObj.Collision(bucketObj.GetBounds()))
{
if (clothfall_sound_instance.State == SoundState.Stopped)
{
clothfall_sound_instance.Volume = 0.75f; /* Play sounds */
clothfall_sound_instance.Play();
}
}
/* second bucket comes after level 3 */
if (Serviet.level >= 3)
{
if (servietObj.Collision(secondbucketObj.GetBounds()))
{
if (clothfall_sound_instance.State == SoundState.Stopped)
{
clothfall_sound_instance.Volume = 0.75f /* Play Sounds */
clothfall_sound_instance.Play();
}
}
}
if (shirtObj.Collision(bucketObj.GetBounds()))
{
if (clothfall_sound_instance.State == SoundState.Stopped)
{
clothfall_sound_instance.Volume = 0.75f; /* Play Sounds */
clothfall_sound_instance.Play();
}
}
if (Serviet.level >= 3) // second bucket comes after level 3
{
if (shirtObj.Collision(secondbucketObj.GetBounds()))
{
if (clothfall_sound_instance.State == SoundState.Stopped)
{
clothfall_sound_instance.Volume = 0.75f;
clothfall_sound_instance.Play();
}
}
}
/* second bucket comes after level 3 but poop comes after 4 */
if (Serviet.level >= 4)
{
if (pooObj.Collision(bucketObj.GetBounds())) /* collision with first bucket*/
{
if (poop_sound_instance.State == SoundState.Stopped)
{
poop_sound_instance.Volume = 0.75f;
poop_sound_instance.Play();
}
}
if (pooObj.Collision(secondbucketObj.GetBounds())) /* collision with second */
{
if (poop_sound_instance.State == SoundState.Stopped)
{
poop_sound_instance.Volume = 0.75f;
poop_sound_instance.Play();
}
}
}
if (Serviet.level >= 5) // second bucket comes after level 3 but bomb comes after 5
{
/* checks the collision of first bucket withbomb */
if (bombObj.Collision(bucketObj.GetBounds()))
{
if (bomb_sound_instance.State == SoundState.Stopped)
{
bomb_sound_instance.Volume = 0.75f;
bomb_sound_instance.Play();
crying_sound_instance.Play();
}
}
/* checks the collision of second bucket withbomb */
if (bombObj.Collision(secondbucketObj.GetBounds()))
{
if (bomb_sound_instance.State == SoundState.Stopped)
{
bomb_sound_instance.Volume = 0.50f;
bomb_sound_instance.Play();
crying_sound_instance.Play();
}
}
}
base.Update(gameTime);
}
关卡在Serviet里被声明为静态变量,并且会用在update()函数里。
/* second bucket comes after level 3 but bomb comes after 5 */
if (Serviet.level>= 5)
{
// collision conditions
}
Collision() 方法用来检测物体之间是否发生碰撞。
/* checks the collision of second bucket with bomb */
if (bombObj.Collision(secondbucketObj.GetBounds()))
{
// sounds files
}
Draw()方法把所有的精灵作为spritebatches渲染出来。精灵是渲染物体的通用表达方式。
PlayHelpExitBtns类提供了游戏里的帮助菜单,玩家可以选择继续玩、帮助或者退出。
protected override void Draw(GameTimegameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
if (currentScreenState == screenState.controllerDetect)
{
spriteBatch.Draw(splashScreen, Vector2.Zero, Color.White);
}
if (PlayHelpExitBtns.selectedKeyState == "play")
{
// if the game overs with the annies lowhealth the condition is passed here
if (Serviet.gameover && Serviet.anniesmiles == 0)
{
spriteBatch.Draw(Gameover, gameoverposition, Color.White);
spriteBatch.Draw(levelimage, leveluppossition, Color.White);
spriteBatch.DrawString
(font, "Score : " + Serviet.final_score,
new Vector2(1050, 20),Color.Chocolate);
crying_sound_instance.Play();
bomb_sound_instance.Stop();
background_sound_instance.Stop();
clothfall_sound_instance.Stop();
poop_sound_instance.Stop();
}
if (Serviet.gameover)
{
// if the game overs with the hitting of abomb then it is passed here
spriteBatch.Draw(Gameover, gameoverposition, Color.White);
spriteBatch.Draw(levelimage, leveluppossition, Color.White);
spriteBatch.DrawString
(font, "Score : " + Serviet.final_score,
new Vector2(1050, 20),Color.Chocolate);
poop_sound_instance.Stop();
clothfall_sound_instance.Stop();
background_sound_instance.Stop();
levelup_sound_instance.Stop();
}
else
{
spriteBatch.Draw(background, backgrounposition, Color.White);
bucketObj.Draw(spriteBatch);
servietObj.Draw(spriteBatch);
monkeyObj.DrawMonkey(spriteBatch);
DrawText();
spriteBatch.Draw(myTexture, spritePosition, Color.White);
spriteBatch.Draw
(annhealth_icon, new Rectangle
(530 , 15, annhealth_icon.Width, 44),
new Rectangle
(0, 0,annhealth_icon.Width, 44),
Color.White); // healthbag icon
if(Serviet.anniesmiles < 7)
{
healthbar_color =Color.Yellow;
}
if (Serviet.anniesmiles < 4)
{
healthbar_color =Color.Red;
}
spriteBatch.Draw(healthbar,
new Rectangle(600, 25,
(int)(healthbar.Width * ((double)Serviet.anniesmiles/ 10)),
44), new Rectangle
(0, 45, healthbar.Width, 44), healthbar_color);
// draw red line indicating health
spriteBatch.Draw(healthbar,
new Rectangle(600, 10, healthbar.Width, 44),
new Rectangle(0, 0, healthbar.Width, 44), Color.White);
if (Serviet.level >= 2) // shirt on and after the level 2
{
shirtObj.DrawShirt(spriteBatch);
}
if (Serviet.level >= 3)
{
secondbucketObj.DrawSecondBucket(spriteBatch);
}
if (Serviet.level >= 4)
{
pooObj.DrawPoo(spriteBatch);
}
if (Serviet.level >= 5)
{
bombObj.DrawBomb(spriteBatch);
}
}
if ((Serviet.total >= 100 &&Serviet.total <= 120)
|| (Serviet.total >= 200 && Serviet.total <= 220)
|| (Serviet.total >= 300 && Serviet.total <= 320)
|| (Serviet.total >= 400 && Serviet.total <= 420)
|| (Serviet.total >= 500 && Serviet.total <= 520))
{
spriteBatch.Draw(Levelup,leveluppossition, Color.White);
if (Serviet.total == 100
|| Serviet.total == 200
|| Serviet.total == 300
|| Serviet.total == 400
|| Serviet.total == 500)
{
levelup_sound_instance.Play();
}
}
}
else if (currentScreenState == screenState.title)
{
pheObj.draw(spriteBatch);
}
spriteBatch.End();
base.Draw(gameTime);
}
selectedKeyState是静态字符串,由Draw() 调用判断玩家在帮助菜单的输入。
if (PlayHelpExitBtns.selectedKeyState == "play")
{
// code
}
Serviet类里anniesmiles是个静态整数,一开始被初始化为10用来计算安妮的健康值,每有一个衣服掉落到地上,安妮的健康值就会减1
下面是游戏方面,你可以看到得分、关卡和健康值等信息。
游戏结束画面会显示得分
从这里下载代码:https://dl.dropboxusercontent.com/u/83417157/Profiles/Annie%27s%20Laundry.zip