打造轻量级Windows Phone7 游戏引擎-Samurai 第四话 Button(上)

来源:互联网 发布:单片机和fpga的区别 编辑:程序博客网 时间:2024/05/17 22:06

Button模块我打算分两部分来介绍,第一部分是介绍绘制精灵,第二部分是正式的Button。

首先什么是Button呢?或者说Button应该是什么样子的呢?

当然了,就我们平常的经验来说,Button不就是一个按钮吗,可以被点击然后实现相应的功能。它往往是一个矩形的区域,里面写上相关的功能,点击一下好像被按下去了一样。大致如此。

Samurai中的Button本质上是多了一些与SAInput交互的接口的精灵类。

什么是精灵类?就是我们用来绘制游戏角色的一系列动作的类。

我们先来定义精灵类的基类SASprite(当然还是抽象类)

 public abstract class SASprite    {        public Texture2D texture;        public Color color;        public Vector2 position;        public virtual Vector2 Size { get { return new Vector2(rectangle.Width, rectangle.Height); } } //获取Sprite的大小        public virtual Rectangle rectangle { get { return new Rectangle((int)position.X, (int)position.Y, (int)Size.X, (int)Size.Y); } }//获取Sprite的碰撞矩形        public virtual Rectangle sourceRectangle { get; set; } //截取绘制图像位置        public SASprite() { }        public SASprite(Texture2D texture) : this(texture, Vector2.Zero) { }        public SASprite(Texture2D texture, Vector2 positon) : this(texture, (int)positon.X, (int)positon.Y) { }        public SASprite(Texture2D texture, int pos_x, int pos_y)        {            this.texture = texture;            this.position = new Vector2(pos_x, pos_y);            this.color = Color.White;        }        //判断是否有相交部分        public virtual bool IfCollide(Rectangle rect)        {            return rectangle.Intersects(rect);        }        //外部调用的Draw接口        public void Draw()        {            Draw(SAGlobal.spriteBatch);        }        public virtual void Draw(SpriteBatch spriteBatch)        {            spriteBatch.Draw(texture, position, color);        }    }
这个类是最简单最基本的一个精灵类,它只有一个纹理(图像),有相关的颜色、位置的信息(这里为什么用属性,而且还是virtual的?看了后面你就明白了)

它包含了最基本的“碰撞检测”以及“绘制”的方法。
SASprite有两个子类,一个是只能绘制一帧图像的SASimpleSprite,一个是可以绘制多帧动画的SAActionSprite(好吧,他是SASprite的孙子类):

先来看看SASimpleSprite:

 public class SASimpleSprite:SASprite    {        public SASimpleSprite() { }        /// <summary>        /// 在指定位置绘制整张纹理        /// </summary>        /// <param name="texture">纹理</param>        /// <param name="postion">位置</param>        public SASimpleSprite(Texture2D texture, Vector2 postion)        {            this.texture = texture;            this.sourceRectangle = new Rectangle(0,0,texture.Bounds.Width,texture.Bounds.Height);            this.position = position;            this.color = Color.White;        }        /// <summary>        /// 直接绘制整张纹理        /// </summary>        /// <param name="texture">纹理</param>        public SASimpleSprite(Texture2D texture)            : this(texture, Vector2.Zero)        { }        /// <summary>        /// 在指定position绘制指定的sourceRectangle范围内的纹理        /// </summary>        /// <param name="texture">纹理</param>        /// <param name="sourceRectangle">纹理矩形</param>        /// <param name="position">位置</param>        /// <param name="color">指定颜色</param>        public SASimpleSprite(Texture2D texture,Rectangle sourceRectangle,Vector2 position,Color color)        {            this.texture = texture;            this.sourceRectangle = sourceRectangle;            this.position = position;            this.color = color;        }        public SASimpleSprite(string resource, Vector2 position)            : this(SAGraphicUtil.GetImage(resource), position) { }        public SASimpleSprite(string resource)            : this(SAGraphicUtil.GetImage(resource)) { }        public SASimpleSprite(string resource, Rectangle sourceRectangle, Vector2 position, Color color)            : this(SAGraphicUtil.GetImage(resource), sourceRectangle, position, color) { }        public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)        {            spriteBatch.Draw(texture, position, sourceRectangle, color);        }    }

再来看看SAActionSprite:

    public class SAActionSprite:SASimpleSprite    {        /// <summary>        /// 推荐的做法是:        /// 在子类中的静态方法中将这些数据进行初始化(当然要另外设置相应的静态变量)        /// 在构造方法中直接就为_sourceRectangles & _sizes赋值        /// </summary>        public Rectangle[] _sourceRectangles;        public Vector2[] _sizes;        public int index;        public int Count { get { return _sourceRectangles.Length; } }        /// <summary>        /// 直到此时,我才真正清楚原来Prop是如此的方便,还可以像方法一样被重写。        /// </summary>        public override Vector2 Size { get { return _sizes[index]; } } //获取index帧的Sprite的大小        public override Rectangle rectangle { get { return new Rectangle((int)position.X, (int)position.Y, (int)Size.X, (int)Size.Y); } }//获取index帧的Sprite的碰撞矩形        public override Rectangle sourceRectangle { get { return _sourceRectangles[index]; } } //截取绘制图像位置        /// <summary>        /// 不怕麻烦就用默认构造函数        /// </summary>        public SAActionSprite() { }        /// <summary>        /// 默认动作第零帧的构造函数        /// </summary>        /// <param name="texture"></param>        /// <param name="sourceRectangles"></param>        /// <param name="sizes"></param>        /// <param name="position"></param>        /// <param name="color"></param>        public SAActionSprite(Texture2D texture, Rectangle[] sourceRectangles, Vector2[] sizes, Vector2 position, Color color)             : this(texture, sourceRectangles, sizes, position, color, 0) { }        /// <summary>        /// 最全的构造函数了        /// </summary>        /// <param name="texture">纹理</param>        /// <param name="sourceRectangles">纹理矩形数组</param>        /// <param name="sizes">大小数组</param>        /// <param name="position">绘制位置</param>        /// <param name="color">绘制颜色</param>        /// <param name="index">动作的第几帧</param>        public SAActionSprite(Texture2D texture, Rectangle[] sourceRectangles, Vector2[] sizes, Vector2 position, Color color, int index)        {            this.texture = texture;            this._sourceRectangles = sourceRectangles;            this._sizes = sizes;            this.position = position;            this.color = color;            this.index = index;        }        public SAActionSprite(string resource, Rectangle[] sourceRectangles, Vector2[] sizes, Vector2 position, Color color, int index)            : this(SAGraphicUtil.GetImage(resource), sourceRectangles, sizes, position, color, index) { }        public SAActionSprite(string resource, Rectangle[] sourceRectangles, Vector2[] sizes, Vector2 position, Color color)            : this(SAGraphicUtil.GetImage(resource), sourceRectangles, sizes, position, color, 0) { }        //因为属性被Override,Draw方法反而不用重写了    }

其实就是多了个索引,然后原本的几个属性被Override了,不得不赞一句Prop用起来还是爽歪歪的。

细心看会发现,纳尼,怎么只有一个Texture而有一堆的sourceRectangle!!!请勿激动,因为作者我平时比较爱一张一张Texture地加载,而这样效率远不及在一张大Texture上将全部图像加载效率高,所以这里提醒自己,即使是加载多个Texture,至少在一个Action里还是不要加载多个Texture了吧~


那么回到正题上来,Button其实就是对SAActionSprite的扩展:

(因为Button其实有纯的图像Button,也有图像+文字Button,甚至还有纯的文字Button,所以先定义一个Button的基类,称之为SAControl)

public enum ControlStatus    {        Normal,        Touch,        Release    }    /// <summary>    /// 控件基类,继承自ActionSprite    /// </summary>    public abstract class  SAControl:SAActionSprite    {        //控件的状态,初始为Normal        protected ControlStatus controlStatus;        //关于响应点击的委托        public delegate void ClickDelegate();        protected ClickDelegate  ClickHandler;        //控件是否开启,默认开启        public bool Enable { get; set; }        #region 初始化        public void Init()        {            this.index = 0;            this.controlStatus = ControlStatus.Normal;            this.Enable = true;            this.color = Color.White;        }        #endregion        #region 注册和注销(注意不能在响应方法中调用,因为Foreach中不可以增减)        public void Add()        {            SAInput.AddButton(this);        }        public void Remove()        {            SAInput.RemoveButton(this);        }        #endregion                #region 与SAInput之间的接口        /// <summary>        /// 更新时,先重置状态为Normal,不过子类要重写(index的修改)        /// </summary>        public virtual void Update()        {            this.controlStatus = ControlStatus.Normal;        }        /// <summary>        /// 更新时发现被触碰了        /// </summary>        public virtual void OnTouch(Vector2 touchPos)        {            if (IfOnTouch(touchPos))            {                this.controlStatus = ControlStatus.Touch;            }        }        /// <summary>        /// 辅助判断是否被触碰到        /// </summary>        /// <param name="touchPosition"></param>        /// <returns></returns>        public bool IfOnTouch(Vector2 touchPosition)        {            if (touchPosition.X >= position.X && touchPosition.X <= position.X + Size.X                && touchPosition.Y >= position.Y && touchPosition.Y <= position.Y + Size.Y)            {                return true;            }            return false;        }        public bool IfOnTouch(GestureSample gesture)        {            return IfOnTouch(gesture.Position);        }        #endregion        #region 响应点击事件        public virtual void OnClick()        {            if (this.Enable)            {                if (ClickHandler != null)                {                    this.controlStatus = ControlStatus.Release;                    ClickHandler.Invoke();                }            }        }        #endregion    }
那么SAButton就好说了:

    public class SAButton:SAControl    {        /// <summary>        /// 最简单,一张图片搞定所有        /// </summary>        public SAButton(string resource,Rectangle sourceRect,Vector2 pos,ClickDelegate clickDel)        {            Init();            //TODO            this.texture = SAGraphicUtil.GetImage(resource);            this._sourceRectangles = new Rectangle[3];            this._sourceRectangles[2] = this._sourceRectangles[1] = this._sourceRectangles[0] = sourceRectangle;            this._sizes = new Vector2[3];            this._sizes[2] = this._sizes[1] = this._sizes[0] = new Vector2(rectangle.Width, rectangle.Height);            this.position = pos;            this.ClickHandler = clickDel;            Add();        }        /// <summary>        /// 最全面        /// </summary>        public SAButton(string resource, Rectangle[] sourceRects, Vector2 pos, ClickDelegate clickDel)        {            Init();            //TODO            this.texture = SAGraphicUtil.GetImage(resource);            this._sourceRectangles = new Rectangle[3];            this._sizes = new Vector2[3];            this.position = pos;            this.ClickHandler = clickDel;            #region 处理不同的数量            if (sourceRects.Length == 3)            {                this._sourceRectangles = sourceRects;                for (int i = 0; i < 3; i++)                {                    this._sizes[i] = new Vector2(_sourceRectangles[i].Width,_sourceRectangles[i].Height);                }            }            else if (sourceRects.Length == 2)            {                this._sourceRectangles[2] = this._sourceRectangles[0] = sourceRects[0];                this._sourceRectangles[1] = sourceRects[1];                for (int i = 0; i < 3; i++)                {                    this._sizes[i] = new Vector2(_sourceRectangles[i].Width, _sourceRectangles[i].Height);                }            }            else            {                for (int i = 0; i < 3; i++)                {                    this._sourceRectangles[i] = sourceRects[0];                    this._sizes[i] = new Vector2(_sourceRectangles[i].Width, _sourceRectangles[i].Height);                }            }            #endregion            Add();        }        #region 流程控制        public override void Update()        {            //TODO            index = 0;            //不能删除,更新状态            base.Update();        }        public override void OnClick()        {            //TODO            if (this.Enable)            {                index = 2;            }            //不能删除            base.OnClick();        }        public override void OnTouch(Vector2 touchPos)        {            //TODO            if (IfOnTouch(touchPos))            {                index = 1;            }            //不能删除            base.OnTouch(touchPos);        }        /// <summary>        /// 由于状态的改变以及index的变化进行了修改,所以没有必要重写Draw了        /// </summary>        public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)         {            base.Draw(spriteBatch);        }        #endregion    }

我其实对那段老长了的构造函数比较反感,但是因为平时我们用Button的时候比较懒,虽然Button有“正常”,“被触碰但是手指未离开”,“触碰后手指离开”三种状态,但是往往我们默认第一种和第三种状态用同样的图像进行显示,所以...

我们也可以看到SAButton里有一些与SAInput进行交互的地方,那么,到底是如何进行交互的呢?

我们在原本的SAInput中加入了如下的内容:

首先我们在注册手势之前就已经为Button们开了后门,已经偷偷注册了Tap手势:

        static SAInput()        {            TouchPanel.EnabledGestures = GestureType.Tap;       //默认初始可以处理Tap,用于Button            enable = true;        }
我们添加了一个Button的list:

        private static List<SAControl> buttonList = new List<SAControl>();
在处理注册了的TouchCollection以及Gesture之前,其实我们已经偷偷地先处理了所有的Button了:

        public static void UpdateGesture()        {            if (enable)            {                while (TouchPanel.IsGestureAvailable)                {                    GestureSample gestureSample = TouchPanel.ReadGesture();                    //先处理预定义Button                    if (gestureSample.GestureType == GestureType.Tap)                    {                        foreach (SAControl b in buttonList)                        {                            if (b.IfOnTouch(gestureSample.Position))                            {                                b.OnClick();                                break;//ATTENTION 可以在此中删除Button                            }                        }                    }                    //处理手势事件                    foreach (GestureType g in inputDictionary.Keys)                    {                        if (gestureSample.GestureType == g)                        {                            inputDictionary[g].Invoke(gestureSample);                            break;  //ATTENTION                        }                    }                }            }        }        public static void UpdateTouchCollection()        {            if (enable)            {                touchCollection = TouchPanel.GetState();                //先处理Button                if (touchCollection.Count == 1)                {                    foreach (TouchLocation t in touchCollection)                    {                        if (t.State == TouchLocationState.Pressed || t.State == TouchLocationState.Moved)                        {                            foreach (SAControl b in buttonList)                            {                                b.OnTouch(t.Position);                            }                        }                    }                }                if (OnTouchCollection != null)                {                    OnTouchCollection.Invoke(touchCollection);                }            }        }
当然了还有一些无比easy的Button与SAInput的接口:

        #region 处理Button        public static void AddButton(SAControl button)        {            buttonList.Add(button);        }        public static void CleanButton()        {            buttonList.Clear();        }        public static void RemoveButton(SAControl button)        {            if (buttonList.Contains(button))            {                buttonList.RemoveAt(buttonList.IndexOf(button));            }        }        #endregion
以上,这就是Button与SAInput的整合。


Button的第一部分就写到这里,另一部分是关于“文字Button”绘制的内容,敬请期待。另外,终于可以开始写SAInput问题解决篇二了~Yes!


原创粉丝点击