您的位置:首页 > 大数据 > 人工智能

打造轻量级Windows Phone7 游戏引擎-Samurai 第五话 使用Samurai创建游戏

2013-10-24 13:23 507 查看
打造轻量级Windows Phone7 游戏引擎-Samurai 第五话 使用Samurai创建游戏

博客写了有一段时间了,其实目前我自己在制作Samurai的过程中已经写好了一个WP7的游戏了,应该两周后大家就可以在应用商店见到了。还是蛮开心的,今天终于可以带大家一起试着使用Samurai了。

首先呢,如果读者您比较偷懒一些,可以到:http://download.csdn.net/detail/u011257271/6448941去下载下面要讲到的整个样例程序,直接就可以运行了~

如果您是一位勤劳的读者,那么,请先到:http://download.csdn.net/detail/u011257271/6448961去下载目前的Samurai项目模板。当然了也请到:http://download.csdn.net/detail/u011257271/6448941去下载下面要讲到的整个样例程序,因为有一些图像资源我们将会用到。

先说下如何导入模板:下载了Samurai模板zip文件后,直接将其放置在“\My Documents\Visual Studio 2010\Templates\ProjectTemplates\语言\”文件夹中。

(比如我本人的是“E:\Users\BC\Documents\Visual Studio 2012\Templates\ProjectTemplates\Visual C#”)

然后打开VS,像往常一样新建“windows phone游戏”项目,当项目生成好后,我们右击解决方案->添加->新建项目->点选Visual C#->找到“SamuraiPublicV0.1”即可



然后我们给游戏主项目“添加引用”



这样,我们就可以开始了~

开始的开始,项目就是下图的样子了:

当前的Samurai只有如下的功能:

Controls(控件),Globals(全局共享的引用),Graphics(图像管理),Inputs(输入检测),Music(音乐音效),Screens(页面管理),Sprites(精灵(图像)绘制)



先说下我们的样例做出来后长啥样,有啥功能吧:

这个样例有5个页面,一个是菜单页面,有标题以及按钮,点击按钮Play跳转到其他页面。另外四个页面很空旷啊~(分别代表了春夏秋冬...)只有一个Menu的按钮,点击会回到菜单页面来,当然了,如果你在这四个页面之间做出滑动的手势的话,页面也会进行相应的跳转。

下面是实现后的截图:(哎呀,看来截图暴露了我当前在做的...)







现在请大家先将下载的资源里的“Images/”中的三张图像(包括文件夹Images)添加到Content项目中:



我们先来重写一个导演类:(会看到创建页面 模块是红的,没关系,因为我们还没有创建页面)

class MyDirector:Samurai.SADirector
{
public MyDirector(Game game, GraphicsDeviceManager graphics, SpriteBatch spriteBatch) : base(game,graphics,spriteBatch)
{
//跳转页面
ChangeScreenTo(ScreenType.Menu);
}

/// <summary>
/// 简化的状态转换方法,实际上为了页面的重用,在基类中实现了一个管理者,
/// 通过页面的ScreenType为Key,Screen的创建方法为Value,如果不存在该页面则创建,
/// 否则ReInit()重新使用即可
/// </summary>
/// <param name="screenType"></param>
public override void ChangeScreenTo(ScreenType screenType)
{
switch (screenType)
{
case  ScreenType.Menu:
ChangeScreenTo(ScreenType.Menu,CreateMenuScreen);
break;
case ScreenType.SpringScreen:
ChangeScreenTo(ScreenType.SpringScreen, CreateSpringScreen);
break;
case ScreenType.SummerScreen:
ChangeScreenTo(ScreenType.SummerScreen, CreateSummerScreen);
break;
case ScreenType.AutumnScreen:
ChangeScreenTo(ScreenType.AutumnScreen, CreateAutumnScreen);
break;
case ScreenType.WinterScreen:
ChangeScreenTo(ScreenType.WinterScreen, CreateWinterScreen);
break;
}
}

#region 创建界面
public SAScreen CreateMenuScreen()
{
return new MenuScreen(ChangeScreenTo);
}
public SAScreen CreateSpringScreen()
{
return new SpringScreen(ChangeScreenTo);
}
public SAScreen CreateSummerScreen()
{
return new SummerScreen(ChangeScreenTo);
}
public SAScreen CreateAutumnScreen()
{
return new AutumnScreen(ChangeScreenTo);
}
public SAScreen CreateWinterScreen()
{
return new WinterScreen(ChangeScreenTo);
}
#endregion
}

然后,我们将阿SA导演注册给Game1老大哥:

public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

//TODO
SADirector director;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
TargetElapsedTime = TimeSpan.FromTicks(333333);
InactiveSleepTime = TimeSpan.FromSeconds(1);

//设置竖屏
SAGraphicUtil.SetVertical(graphics);
//设置全屏
graphics.IsFullScreen = true;
}

protected override void Initialize()
{
// TODO

base.Initialize();
}

protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO
director = new MyDirector(this, graphics, spriteBatch);
}

protected override void UnloadContent()
{
// TODO
director.UnloadContent();
}

protected override void Update(GameTime gameTime)
{
//if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
//    this.Exit();

// TODO
director.Update(gameTime);
base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
// TODO
director.Draw(gameTime);
base.Draw(gameTime);
}
}

通过这两行语句我们设置了竖屏和全屏:

//设置竖屏
SAGraphicUtil.SetVertical(graphics);
//设置全屏
graphics.IsFullScreen = true;

然后原本点击Back键的检测也注释掉了。

当前已经完成了Director和Game的对接,接下来我们实现各个页面:

先来实现MenuScreen: 类中定义了四个简单的图像以及六个Button

class MenuScreen:SAScreen
{
SASimpleSprite backSprite;
SASimpleSprite title1Sprite;
SASimpleSprite title2Sprite;
SASimpleSprite logoSprite;
SAButton musicButton;
SAButton soundButton;
SAButton helpButton;
SAButton aboutButton;
SAButton scoreButton;
SAButton playButton;

public MenuScreen(ChangeScreenDelegate changeScreen):
base(changeScreen){}

//特别提醒,不需要Reinit的变量在此初始化(所有与Input有关的变量必须在Init中初始化)
public override void LoadContent()
{
backSprite = new SASimpleSprite("Images/BeginBack");
title1Sprite = new SASimpleSprite("Images/Resource", new Rectangle(0, 0, 238, 72),
new Vector2(30, 130),Color.White);
title2Sprite = new SASimpleSprite("Images/Resource", new Rectangle(240, 0, 198, 72),
new Vector2(256, 217),Color.White);
logoSprite = new SASimpleSprite("Images/Resource",new Rectangle(440, 0, 386, 174),
new Vector2(290, 84),Color.White);
}
public override void Init()
{
//最全面的
musicButton = new SAButton("Images/Resource", new Rectangle[] { new Rectangle(0, 220, 148, 35), new Rectangle(200, 220, 148, 35), new Rectangle(200, 220, 148, 35) }, new Vector2(180, 667), OnMusicButtonClick);
//较全面的
playButton = new SAButton("Images/Resource", new Rectangle[] { new Rectangle(0, 80, 194, 53), new Rectangle(200, 80, 194, 53) }, new Vector2(143, 385), BeginToPlay);
soundButton = new SAButton("Images/Resource", new Rectangle[] { new Rectangle(0, 260, 148, 35), new Rectangle(200, 260, 148, 35) }, new Vector2(32, 587), OnMusicButtonClick);
helpButton = new SAButton("Images/Resource", new Rectangle[] { new Rectangle(0, 300, 120, 35) }, new Vector2(328, 747), OnClick);
//最偷懒的
aboutButton = new SAButton("Images/Resource", new Rectangle(0, 180, 148, 35), new Vector2(280, 35), OnClick);
scoreButton = new SAButton("Images/Resource", new Rectangle(0, 140, 148, 35), new Vector2(308, 507), OnClick);
}

public override void SetupInput()
{
SAInput.EnableBackButton(OnBackButton);
}
private void OnBackButton()
{
SAGlobal.game.Exit();
}
public override void Draw(GameTime gameTime)
{
backSprite.Draw();
logoSprite.Draw();
title1Sprite.Draw();
title2Sprite.Draw();
musicButton.Draw();
soundButton.Draw();
helpButton.Draw();
aboutButton.Draw();
scoreButton.Draw();
playButton.Draw();
}

#region 响应事件
public void OnMusicButtonClick()
{

}

public void OnClick()
{ }

public void BeginToPlay()
{
ChangeScreenTo(ScreenType.SpringScreen);
}
#endregion
}

我们可以看到,Button可以有不同的构造,勤奋固然好,偷懒的做法也是允许的~我们重写了LoadContent、Init以及SetupInput。

LoadContent中主要放的是一次性加载的资源,以后页面回收利用的时候就不会再LoadContent了,而所有页面回收需要重新初始化的变量,我们在Init中进行初始化,特别的是,与触控相关的(比如Button)必须要在Init中进行初始化,因为实际上Button在初始化时对SAInput进行了注册,而每当页面回收时,SAInput会清空,所以Button在页面重用的过程中必须重新注册。

如果您已经等急了,那么先把这个模块:

#region 创建界面
public SAScreen CreateMenuScreen()
{
return new MenuScreen(ChangeScreenTo);
}
public SAScreen CreateSpringScreen()
{
return new SpringScreen(ChangeScreenTo);
}
public SAScreen CreateSummerScreen()
{
return new SummerScreen(ChangeScreenTo);
}
public SAScreen CreateAutumnScreen()
{
return new AutumnScreen(ChangeScreenTo);
}
public SAScreen CreateWinterScreen()
{
return new WinterScreen(ChangeScreenTo);
}
#endregion


替换成:

#region 创建界面
public SAScreen CreateMenuScreen()
{
return new MenuScreen(ChangeScreenTo);
}
public SAScreen CreateSpringScreen()
{
return null;//new SpringScreen(ChangeScreenTo);
}
public SAScreen CreateSummerScreen()
{
return null;//new SummerScreen(ChangeScreenTo);
}
public SAScreen CreateAutumnScreen()
{
return null;// new AutumnScreen(ChangeScreenTo);
}
public SAScreen CreateWinterScreen()
{
return null;//new WinterScreen(ChangeScreenTo);
}
#endregion

好的,我们可以运行一下程序了~大家可以试着点击不同的Button看看他们有什么不同的表现~

接下来是“春天的页面”

class SpringScreen:SAScreen
{
SASimpleSprite backSprite;
SAButton menuButton;
public SpringScreen(ChangeScreenDelegate changeScreenDelegate) : base(changeScreenDelegate) { }

public override void LoadContent()
{
backSprite = new SASimpleSprite("Images/Season");
}
public override void Init()
{
menuButton = new SAButton("Images/Resource", new Rectangle[] { new Rectangle(0, 340, 156, 44), new Rectangle(200, 340, 156, 44) }, new Vector2(143, 385), OnMenuButtonClick);
}
public override void SetupInput()
{
SAInput.EnableBackButton(OnBackButton);
SAInput.EnableGesture(Microsoft.Xna.Framework.Input.Touch.GestureType.FreeDrag, OnFreeDrag);
SAInput.EnableGesture(Microsoft.Xna.Framework.Input.Touch.GestureType.DragComplete, OnDragComplete);
}

private void OnBackButton()
{
ChangeScreenTo(ScreenType.Menu);
}
private void OnFreeDrag(Microsoft.Xna.Framework.Input.Touch.GestureSample gestureSample) { }
private void OnDragComplete(Microsoft.Xna.Framework.Input.Touch.GestureSample gestureSample)
{
if (gestureSample.Delta.X <= 0)
{
ChangeScreenTo(ScreenType.SummerScreen);
}
}
private void OnMenuButtonClick()
{
ChangeScreenTo(ScreenType.Menu);
}

public override void Draw(GameTime gameTime)
{
backSprite.Draw();
menuButton.Draw();
}
}


这个页面,我们的注意力主要放在

1.页面跳转:

其实就是Screen通过构造函数中的委托获得了上下文中的管理页面的权限,可以在自己方便的时候使用跳转到其他页面的这个权利。

大家看“状态模式”的话,往往这里给的是一个上下文(Context)的引用,但是由于C#的语言特性,使用委托实现是一种更为巧妙的方式。

2.触控接口:

关于响应事件,大家应该也看得比较明白吧。多说一句就是——对于像FreeDrag之类的手势,会有DragComplete事件,我们在注册FreeDrag时不要忘记注册DragComplete。那么我们如何判断Drag的方向呢?通过GestureSample的Delta.X的值的正负来判断。但是这个判断只能在FreeDrag注册的响应事件中进行判断,DragComplete中并不给出响应的方向信息。这个页面中对FreeDrag手势的使用其实是一个错误的方式,正确的方式请看下一个页面:

class SummerScreen : SAScreen
{
enum Direction
{
Left,
Right
};
SASimpleSprite backSprite;
SAButton menuButton;
Direction direction;

public SummerScreen(ChangeScreenDelegate changeScreenDelegate) : base(changeScreenDelegate) { }

public override void LoadContent()
{
backSprite = new SASimpleSprite("Images/Season", new Rectangle(480, 0, 480, 800), Vector2.Zero, Color.White);
}
public override void Init()
{
direction = Direction.Right;
menuButton = new SAButton("Images/Resource", new Rectangle[] { new Rectangle(0, 340, 156, 44), new Rectangle(200, 340, 156, 44) }, new Vector2(143, 385), OnMenuButtonClick);
}
public override void SetupInput()
{
SAInput.EnableBackButton(OnBackButton);
SAInput.EnableGesture(Microsoft.Xna.Framework.Input.Touch.GestureType.FreeDrag, OnFreeDrag);
SAInput.EnableGesture(Microsoft.Xna.Framework.Input.Touch.GestureType.DragComplete, OnDragComplete);
}

private void OnBackButton()
{
ChangeScreenTo(ScreenType.SpringScreen);
}
private void OnFreeDrag(Microsoft.Xna.Framework.Input.Touch.GestureSample gestureSample)
{
if (gestureSample.Delta.X > 0)
{
direction = Direction.Right;
}
else if(gestureSample.Delta.X<0)
{
direction = Direction.Left;
}
}
private void OnDragComplete(Microsoft.Xna.Framework.Input.Touch.GestureSample gestureSample)
{
if (direction == Direction.Right)
{
ChangeScreenTo(ScreenType.SpringScreen);
}
else
{
ChangeScreenTo(ScreenType.AutumnScreen);
}
}
private void OnMenuButtonClick()
{
ChangeScreenTo(ScreenType.Menu);
}

public override void Draw(GameTime gameTime)
{
backSprite.Draw();
menuButton.Draw();
}
}

关于这个页面,没啥好多说的了~

不过还是有要说的:春夏秋冬四个页面每个页面都差不多,这种Ctrl C+Ctrl V的方式来写页面也太逊了吧~

那么我们其实只需要修改一下相关的private为protected:(好吧已经到了秋页面了)

class AutumnScreen:SAScreen
{
protected enum Direction
{
Left,
Right
};
protected SASimpleSprite backSprite;
protected SAButton menuButton;
protected Direction direction;

public AutumnScreen(ChangeScreenDelegate changeScreenDelegate) : base(changeScreenDelegate) { }

public override void LoadContent()
{
backSprite = new SASimpleSprite("Images/Season", new Rectangle(960, 0, 480, 800), Vector2.Zero, Color.White);
}
public override void Init()
{
direction = Direction.Right;
menuButton = new SAButton("Images/Resource", new Rectangle[] { new Rectangle(0, 340, 156, 44), new Rectangle(200, 340, 156, 44) }, new Vector2(143, 385), OnMenuButtonClick);
}
public override void SetupInput()
{
SAInput.EnableBackButton(OnBackButton);
SAInput.EnableGesture(Microsoft.Xna.Framework.Input.Touch.GestureType.FreeDrag, OnFreeDrag);
SAInput.EnableGesture(Microsoft.Xna.Framework.Input.Touch.GestureType.DragComplete, OnDragComplete);
}

protected virtual void OnBackButton()
{
ChangeScreenTo(ScreenType.SummerScreen);
}
protected void OnFreeDrag(Microsoft.Xna.Framework.Input.Touch.GestureSample gestureSample)
{
if (gestureSample.Delta.X > 0)
{
direction = Direction.Right;
}
else if(gestureSample.Delta.X<0)
{
direction = Direction.Left;
}
}
protected virtual void OnDragComplete(Microsoft.Xna.Framework.Input.Touch.GestureSample gestureSample)
{
if (direction == Direction.Right)
{
ChangeScreenTo(ScreenType.SummerScreen);
}
else
{
ChangeScreenTo(ScreenType.WinterScreen);
}
}
protected void OnMenuButtonClick()
{
ChangeScreenTo(ScreenType.Menu);
}

public override void Draw(GameTime gameTime)
{
backSprite.Draw();
menuButton.Draw();
}
}

OK,最后一个页面继承自秋页面:

class WinterScreen:AutumnScreen
{
public WinterScreen(ChangeScreenDelegate changeScreenDelegate) : base(changeScreenDelegate) { }
public override void LoadContent()
{
backSprite = new SASimpleSprite("Images/Season", new Rectangle(1440, 0, 480, 800), Vector2.Zero, Color.White);
}
protected override void OnBackButton()
{
ChangeScreenTo(ScreenType.AutumnScreen);
}
protected override void OnDragComplete(Microsoft.Xna.Framework.Input.Touch.GestureSample gestureSample)
{
if (direction == Direction.Right)
{
ChangeScreenTo(ScreenType.AutumnScreen);
}
}
}

是不是通过继承,冬页面看起来就有点儿专业了?

最后一步,如果刚刚把您把“创建页面”模块替换掉了,那么现在再把它换回来:(在MyDirector中)

       #region 创建界面
public SAScreen CreateMenuScreen()
{
return new MenuScreen(ChangeScreenTo);
}
public SAScreen CreateSpringScreen()
{
return new SpringScreen(ChangeScreenTo);
}
public SAScreen CreateSummerScreen()
{
return new SummerScreen(ChangeScreenTo);
}
public SAScreen CreateAutumnScreen()
{
return new AutumnScreen(ChangeScreenTo);
}
public SAScreen CreateWinterScreen()
{
return new WinterScreen(ChangeScreenTo);
}
#endregion

点击运行吧~

以上~

虽然效果都基本上有了,但是有一个地方很不美气:

就是ScreenType枚举定义的位置,我们可以在

位置见到:

//这样子似乎不美气,但是更美气的方法还在思考中
public enum ScreenType
{
Menu,
SpringScreen,
SummerScreen,
AutumnScreen,
WinterScreen
};

这个枚举是在Samurai项目中定义的,也就是说平时我们写好页面后,还要过来手工地“注册”一下(显得好傻呀),希望大家有更好的解决方法告我一声~

遇见不明白的地方,欢迎提问;遇见我犯下的错误或者做的不好的地方,欢迎指正~

最后呢?我到11月中旬时间就比较宽裕了,在那时想再写两个系类的博客——“3天打造WP版《会说话的汤姆猫》”以及“7天打造WP版《Doodle Jump》”敬请关注~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息