打造轻量级Windows Phone7 游戏引擎-Samurai 第五话 使用Samurai创建游戏
博客写了有一段时间了,其实目前我自己在制作Samurai的过程中已经写好了一个WP7的游戏了,应该两周后大家就可以在应用商店见到了。还是蛮开心的,今天终于可以带大家一起试着使用Samurai了。
首先呢,如果读者您比较偷懒一些,可以到:http://download.youkuaiyun.com/detail/u011257271/6448941去下载下面要讲到的整个样例程序,直接就可以运行了~
如果您是一位勤劳的读者,那么,请先到:http://download.youkuaiyun.com/detail/u011257271/6448961去下载目前的Samurai项目模板。当然了也请到:http://download.youkuaiyun.com/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》”敬请关注~