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 阅读次数:6466