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

本文介绍如何使用Samurai创建一个轻量级的WindowsPhone7游戏引擎,并提供了详细步骤和示例代码。

打造轻量级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》”敬请关注~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值