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

本文详细介绍了在Samurai游戏引擎中创建Button的过程,从Button的基本概念到其实现方式,包括精灵类SASprite的定义、SASimpleSprite和SAActionSprite的子类结构,以及如何通过SAInput实现交互功能。Button的设计考虑了多种类型,如图像、文字或纯文本Button,并在SAInput中添加了Tap手势支持。

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!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值