5.使用UIManager管理2D控件
如果你研究过《ProfessionalXNA》一书,发现前一个教程中的三个控件类实际上就相当于SpriteToRender类,体现了面向对象编程的思想,这个方法在《Learning XNA 3.0》的第4章“应用面向对象设计(http://www.cnblogs.com/eros/archive/2009/04/28/1445638.html)”中也有介绍(林公子翻译到了第六章,不过很久没有更新了,大概是放弃了,是不是知道了台湾已经出了繁体中文版的缘故),即每个类包含各自的属性和绘制方法,而本教程要编写的UIManager类就相当于SpriteHelper类(其实叫做SpriteManager类更合适),SpriteHelper类管理SpriteToRender类的集合,主要功能就是遍历集合中的所有SpriteToRender对象,调用它们的绘制方法,同理UIManager类管理从UISceneNode类继承的三种控件的集合,主要功能就是遍历集合中的所有UISceneNode对象,调用它们的绘制方法。
事实上这已经是第三次使用这个思路了,第一次是ScenManager类管理Scene的集合,第二次是Scene类管理非UISceneNode的SceneNode的集合。下面是UIManager的代码:
namespace StunEngine.SceneManagement
...{
/**//// <summary>
/// 处理用户与UI的交互
/// </summary>
public sealed class UIManager
...{
成员变量和构造函数#region 成员变量和构造函数
![]()
private StunXnaGE engine;
private Scene scene;
![]()
/**//// <summary>
/// 所有控件的集合
/// </summary>
private List<UISceneNode> uiElements = new List<UISceneNode>();
![]()
/**//// <summary>
/// 鼠标左键最后点击的控件索引
/// </summary>
public int lastLMousePressed = -1;
![]()
/**//// <summary>
/// 鼠标右键最后点击的控件索引
/// </summary>
public int lastRMousePressed = -1;
![]()
/**//// <summary>
/// 鼠标进入的最后一个控件的索引
/// </summary>
public int lastMouseElement = -1;
![]()
/**//// <summary>
/// 控件集合的最后一个控件的索引
/// </summary>
public int lastTabindex = -1;
![]()
/**//// <summary>
/// 存储ZOrder的集合
/// </summary>
private IndexList zIndex = new IndexList();
![]()
/**//// <summary>
/// 存储tabIndex的集合
/// </summary>
private IndexList tabIndex = new IndexList();
![]()
/**//// <summary>
/// 当前获取焦点的控件的索引
/// </summary>
private int focusIndex =0;
![]()
/**//// <summary>
/// 集合中可以获取焦点的第一个控件的索引
/// </summary>
private int firstAvailableFocusIndex = -1;
![]()
/**//// <summary>
/// 集合中可以获取焦点的最后一个控件的索引
/// </summary>
private int lastAvailableFocusIndex = -1;
![]()
/**//// <summary>
/// 是否已经设置了集合中可以获取焦点的第一个控件的索引
/// </summary>
private bool firstAvailableFocusIndexHasSet = false;
![]()
/**//// <summary>
/// 创建一个新UIManager对象
/// </summary>
/// <param name="engine"></param>
internal UIManager(StunXnaGE engine,Scene setScene)
...{
this.engine = engine;
this.scene = setScene;
}
![]()
#endregion
![]()
属性#region 属性
![]()
/**//// <summary>
/// 返回获取焦点的控件的索引
/// </summary>
public int FocusIndex
...{
get ...{ return focusIndex; }
![]()
}
![]()
/**//// <summary>
/// 获取所有控件的集合
/// </summary>
public List<UISceneNode> UiElements
...{
get ...{ return uiElements; }
}
![]()
/**//// <summary>
/// 返回激活的控件,获得焦点的控件为激活控件
/// </summary>
public UISceneNode ActiveControl ...{ get ...{ return focusIndex >= 0 ? uiElements[focusIndex] : null; } }
![]()
#endregion
![]()
更新、绘制方法#region 更新、绘制方法
![]()
/**//// <summary>
/// 处理键盘和鼠标的交互
/// </summary>
public void Update(GameTime gameTime)
...{
if (uiElements.Count == 0)
return;
![]()
//如果按下Tab或Shift+Tab则将焦点移至下一个/上一个可以接受焦点的控件
if (Input.KeyboardKeyJustPressed(Keys.Tab))
...{
int direction;
if (Input.Keyboard.IsKeyDown(Keys.RightShift) || Input.Keyboard.IsKeyDown(Keys.LeftShift))
direction = -1;
else
direction = 1;
ChangeFocus(direction);
}
![]()
//-----------------------------------
// 处理键盘Enter的按下
//-----------------------------------
if (Input.KeyboardKeyJustPressed(Keys.Enter))
...{
uiElements[focusIndex].OnMouseClick(new MouseState(Input.MousePos.X, Input.MousePos.Y, Input.MouseWheelDelta, ButtonState.Pressed, ButtonState.Released, ButtonState.Released, ButtonState.Released, ButtonState.Released));
}
![]()
![]()
//-----------------------------------
// 处理鼠标事件
//-----------------------------------
![]()
if (Input.MouseHasMoved())
...{
//--------------------------------
// 1. 检测鼠标离开
// 2. 检测鼠标进入
// 3. 检测鼠标点击
//--------------------------------
![]()
//--------------------------------
// 1. 鼠标离开
//--------------------------------
if (lastMouseElement >= 0)
...{
if (!uiElements[lastMouseElement].IsPointInside(Input.MousePos.X, Input.MousePos.Y))
...{
uiElements[lastMouseElement].OnMouseLeave();
lastMouseElement = -1;
}
}
![]()
//--------------------------------
// 2. 鼠标进入
//--------------------------------
for (int i = 0; i < zIndex.Count; i++)
...{
if (uiElements[zIndex[i]].IsPointInside(Input.MousePos.X, Input.MousePos.Y))
...{
// 如果此时的控件不是上一帧的控件才进行下面的操作
if (lastMouseElement != zIndex[i])
...{
// 引发鼠标离开事件
if (lastMouseElement > -1 && lastMouseElement != zIndex[i])
...{
uiElements[lastMouseElement].OnMouseLeave();
lastMouseElement = -1;
}
uiElements[zIndex[i]].OnMouseEnter();
lastMouseElement = zIndex[i];
if(uiElements[zIndex[i]].TabStop ==true )
Sound.Play(Sound.Sounds.Highlight);
break;
}
else
break; // 鼠标已经在最上面ZOrder的控件内,无需再检查下面的控件了
}
}
}
![]()
//--------------------------------
// 3. 鼠标点击
//--------------------------------
if (lastMouseElement >= 0) // 如果鼠标在控件内部
...{
//----------------------------------------
// 如果鼠标左键处于释放状态
//----------------------------------------
if (Input.MouseLeftButtonReleased)
...{
if (lastLMousePressed == lastMouseElement && lastLMousePressed >= 0)
...{
// 处理Lost/Got Focus事件(只在控件能够接受焦点的情况下)
if (focusIndex != lastLMousePressed && uiElements[lastLMousePressed].TabStop)
...{
uiElements[focusIndex].OnLostFocus();
focusIndex = lastLMousePressed;
uiElements[focusIndex].OnGotFocus();
}
uiElements[lastLMousePressed].OnMouseClick(new MouseState(Input.MousePos.X, Input.MousePos.Y, Input.MouseWheelDelta, ButtonState.Pressed, ButtonState.Released, ButtonState.Released, ButtonState.Released, ButtonState.Released));
lastRMousePressed = -1; // 如果鼠标右键点击则取消左键点击
}
lastLMousePressed = -1; // 重置鼠标左键点击
}
else
...{
![]()
//----------------------------------------
// 如果在控件内部点击
//----------------------------------------
if (lastLMousePressed < 0 && lastMouseElement >= 0)
...{
lastLMousePressed = lastMouseElement;
//播放鼠标点击的声音
Sound.Play(Sound.Sounds.Click );
}
}
![]()
//----------------------------------------
// 检测鼠标右键点击
//----------------------------------------
if (Input.MouseRightButtonReleased )
...{
if (lastRMousePressed == lastMouseElement && lastRMousePressed >= 0)
...{
// 处理Lost/Got Focus事件(只在控件能够接受焦点的情况下)
if (focusIndex != lastRMousePressed && uiElements[lastRMousePressed].TabStop)
...{
uiElements[focusIndex].OnLostFocus();
focusIndex = lastRMousePressed;
uiElements[focusIndex].OnGotFocus();
}
uiElements[lastRMousePressed].OnMouseClick(new MouseState(Input.MousePos.X, Input.MousePos.Y, Input.MouseWheelDelta, ButtonState.Released, ButtonState.Released, ButtonState.Pressed, ButtonState.Released, ButtonState.Released));
lastLMousePressed = -1; // 如果鼠标左键点击则取消右键点击
}
lastRMousePressed = -1; // 重置鼠标右键点击
}
else
...{
//----------------------------------------
// 如果在控件内部点击鼠标右键
//----------------------------------------
if (lastRMousePressed < 0 && lastMouseElement >= 0)
lastRMousePressed = lastMouseElement;
}
}
}
![]()
/**//// <summary>
/// 将焦点移至下一个UI控件
/// </summary>
/// <param name="direction">移动反向,1表示向下移动,-1表示向上移动</param>
public void ChangeFocus(int direction)
...{
for (int i = 1; i <= uiElements.Count; i++)
...{
int index = focusIndex + (i * direction);
if (index >= uiElements.Count)
index = firstAvailableFocusIndex;
else if (index < 0)
index = lastAvailableFocusIndex;
![]()
if (uiElements[index].TabStop)
...{
if (index != focusIndex)
...{
uiElements[focusIndex].OnLostFocus();
focusIndex = index;
uiElements[index].OnGotFocus();
//播放鼠标切换的声音
Sound.Play(Sound.Sounds.Highlight);
}
break;
}
}
}
![]()
/**//// <summary>
/// 使用SpriteBatch绘制所有控件
/// </summary>
public void Draw(GameTime gameTime)
...{
StunXnaGE.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None, engine.GlobalTransformation);
![]()
// 根据ZOrder从后向前绘制控件
for (int i = zIndex.Count; i > 0; i--)
...{
uiElements[zIndex[i - 1]].Draw(gameTime);
}
StunXnaGE.SpriteBatch.End();
}
![]()
#endregion
![]()
公有方法#region 公有方法
![]()
/**//// <summary>
/// 在UISceneNodes集合中添加一个控件
/// </summary>
/// <param name="element"></param>
public void AddElement(UISceneNode element)
...{
zIndex.AddIndex(uiElements.Count); // 添加当前node的索引用于zorder排序
tabIndex.AddIndex(uiElements.Count); // 添加当前node的索引用于tab顺序排序
uiElements.Add(element);
![]()
if (element.TabIndex == -1 && element.TabStop) // 如果此控件的可以接受焦点则添加一个tabindex
...{
element.TabIndex = (++lastTabindex);
}
else if (element.TabIndex > lastTabindex)
...{
lastTabindex = element.TabIndex;
}
![]()
//计算场景中可以接受焦点的第一个控件的索引
if (!firstAvailableFocusIndexHasSet)
...{
if (!element.TabStop)
firstAvailableFocusIndex += 1;
else
...{
firstAvailableFocusIndex += 1;
focusIndex = firstAvailableFocusIndex;
firstAvailableFocusIndexHasSet = true;
}
}
//计算场景中可以接受焦点的最后一个控件的索引
if (element.TabStop)
lastAvailableFocusIndex = uiElements.Count - 1;
![]()
Sort();
}
![]()
/**//// <summary>
/// 从UISceneNodes集合中移除一个UI元素
/// </summary>
/// <param name="element"></param>
public void RemoveElement(UISceneNode element)
...{
uiElements.Remove(element);
Sort();
}
![]()
/**//// <summary>
/// 根据ZOrder对所包含的UI元素进行排序
/// </summary>
public void Sort()
...{
zIndex.Sort(new CompareZOrder(uiElements));
tabIndex.Sort(new CompareTabIndex(uiElements));
}
![]()
进行比较的辅助类#region 进行比较的辅助类
![]()
//比较Zorder顺序
private class CompareZOrder : System.Collections.IComparer
...{
private UISceneNode[] elements;
public CompareZOrder(List<UISceneNode> elements)
...{
this.elements = elements.ToArray();
}
public int Compare(object x, object y)
...{
return elements[(int)x].ZOrder - elements[(int)y].ZOrder;
}
}
![]()
//比较TabIndex顺序
private class CompareTabIndex : System.Collections.IComparer
...{
private UISceneNode[] elements;
public CompareTabIndex(List<UISceneNode> elements)
...{
this.elements = elements.ToArray();
}
public int Compare(object x, object y)
...{
return elements[(int)x].TabIndex - elements[(int)y].TabIndex;
}
}
#endregion
![]()
#endregion
![]()
}
}
与前面的管理类一样,都要实现添加和移除所管理的类对象的功能,UIManager类中对应的方法是AddElement和RemoveElement方法。这个类还需要两个集合分别存储绘制顺序的zorder和可以获得焦点的控件的索引tabindex,这两个集合是由IndexList类定义的,IndexList类的代码不写了,自己去看源代码吧。
按Tab键在可获得焦点的控件间切换是在Update方法中实现的,而在Update方法中实际调用的是ChangeFocus方法,接下来处理鼠标事件,自己看注释吧。
在Draw方法中绘制所有UISceneNode集合,而且是根据zorder顺序绘制的,也就是说场景中最先添加的对象最后才被绘制(其实这里有点别扭,习惯上是先添加的先绘制,比方说先添加背景,然后在添加的图像会覆盖在背景上)。
在鼠标点击或移入时还会发出对应的声音,这是由Sound类进行处理的,这个类参考了《ProfessionalXNA》中的§9.3Sound类,其中的Xact项目来自于《ProfessionalXNA》中的XNAShooter游戏,我把除鼠标点击和鼠标移入的其他声音都删除了,Sound的实际代码不长:
namespace StunEngine.Sounds
...{
/**//// <summary>
/// Sound类
/// </summary>
class Sound
...{
变量#region 变量
![]()
static AudioEngine audioEngine;
![]()
static WaveBank waveBank;
![]()
static SoundBank soundBank;
![]()
#endregion
![]()
声音枚举#region 声音枚举
/**//// <summary>
/// 使用的声音枚举
/// </summary>
public enum Sounds
...{
Click,
Highlight,
}
#endregion
![]()
构造函数#region 构造函数
![]()
static Sound()
...{
audioEngine = new AudioEngine("Content/Sounds/UISounds.xgs");
waveBank = new WaveBank(audioEngine, "Content/Sounds/Wave Bank.xwb");
if (waveBank != null)
soundBank = new SoundBank(audioEngine, "Content/Sounds/Sound Bank.xsb");
}
#endregion
![]()
播放声音#region 播放声音
/**//// <summary>
/// 播放声音
/// </summary>
/// <param name="soundName">名称</param>
public static void Play(string soundName)
...{
if (soundBank == null)
return;
![]()
soundBank.PlayCue(soundName);
}
![]()
/**//// <summary>
/// 播放声音
/// </summary>
/// <param name="sound">Sound</param>
public static void Play(Sounds sound)
...{
Play(sound.ToString());
}
![]()
#endregion
![]()
更新#region 更新
/**//// <summary>
/// 调用audioEngine.Update!
/// </summary>
public static void Update()
...{
if (audioEngine != null)
audioEngine.Update();
}
#endregion
![]()
}
}
注意:在XGS3.1版本中需要使用Microsoft Cross-Platform Audio Creation Tool 3 (XACT3)建立Xact项目,而XGS3.0之前的Xact项目是由Microsoft Cross-Platform Audio Creation Tool 2创建的,你需要使用Microsoft Cross-Platform Audio Creation Tool 3打开Xact项目再保存一次,否则程序会报版本不一致的错误。
别忘了在引擎类的Update()方法中调用Sound.Update()方法。
发布时间:2009/11/27 下午2:31:17 阅读次数:6651