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!