1.概述和场景管理
一.概述
我研究的比较深入的引擎有《Professional XNA Game Programming》(以后简称《ProfessionalXNA》)中介绍的引擎,http://www.innovativegames.net/blog/2009/10/18/innovation-engine-roadmap-2009-2010/上的引擎教程,这个教程在讲到编辑器时停了好长时间,与2009年10月又开始连载,好像作者重新进行了构思,并给出了路线图,使用Farseer Physics实现2D物理引擎,JigLibX实现3D物理引擎。本引擎主要参考了TXnaEngine,http://txnage.codeplex.com/,截止到2009年11月,最新版本是0.5.3。稍微研究过的是QuickStart Engine (3D Game Engine for XNA),http://quickstartengine.codeplex.com/,Ox Game Engine for XNA,http://oxgameengine.codeplex.com/,XEN,http://xen.codeplex.com/。
对引擎的架构来说,我觉得成熟的开源引擎Ogre,http://ogre3d.cn/wiki/非常值得参考,虽然它并不是用XNA编写的,TxnaEngine引擎的基本框架与Ogre的非常相像。
至于不开源的商业引擎,可以有试用版下载的是visual3d,http://www.visual3d.net/和NeoAxisEngine,http://www.neoaxisgroup.com/,这也是我想实现的最终目标。
NeoAxisEngine场景编辑器
二.场景管理
一个游戏通常有多个屏幕,首先要解决的问题就是如何管理这些屏幕。一个很好的例子就是XNA官方网站上的场景管理,http://creators.xna.com/en-US/samples/gamestatemanagement,具体的教程可见例子的注释或者《XNA 3.0 Game Programming Recipes》(以后简称《XNARecipes》)中的3.6 创建2D菜单界面,我的引擎就是参考这个例子的。
核心思想就是先创建一个代表屏幕的Scene类(在官网的例子中叫做GameScreen类),这个类包含所有场景中绘制的对象等,再创建一个SceneManager类(在官网的例子中叫做ScreenManager类)管理Scene的集合,实现添加、移除屏幕等功能,而SceneManager类从DrawableGameComponent继承,这样在引擎主类中就可以自动调用SceneManager类的Draw(),Update()等方法了,使用DrawableGameComponent的方法可参考《XNARecipes》中的1.7 使用GameComponents。
下面是SceneManagement命名空间中的Scene类的代码:
namespace StunEngine.SceneManagement ...{ /**//// <summary> /// 表示屏幕所处状态的枚举 /// </summary> public enum ScreenState ...{ TransitionOn, Active, TransitionOff, Hidden, } /**//// <summary> /// 这是SceneNode得数据包含容器。一个Scene是根据绘制时间分组的对象的集合 /// Scene对象是自我包含的,这样属于这个场景的SceneNodes不会和其他场景中的nodes发生交互 /// 一个游戏可以包含一个或多个scene,但只有一个是激活的,只绘制属于它的nodes。 /// </summary> public abstract class Scene ...{ 成员变量和构造函数#region 成员变量和构造函数 protected StunXnaGE engine; /**//// <summary> /// 存储这个场景中的SceneNode的集合 /// </summary> private List<SceneNode> nodes; /**//// <summary> /// 是否正在退出 /// </summary> bool isExiting = false; /**//// <summary> /// 其他屏幕是否处在激活状态 /// </summary> bool otherScreenHasFocus; /**//// <summary> /// 场景管理器 /// </summary> SceneManager sceneManager; /**//// <summary> /// 这个场景拥有的UI管理器 /// </summary> UIManager uiManager; /**//// <summary> /// scene的ID /// </summary> internal int sceneId; /**//// <summary> /// scene的名称 /// </summary> private string name; /**//// <summary> /// 是否显示光标 /// </summary> private bool isShowMouse; /**//// <summary> /// 是否弹出窗口 /// </summary> bool isPopup = false; /**//// <summary> /// 淡入持续的时间 /// </summary> TimeSpan transitionOnTime = TimeSpan.Zero; /**//// <summary> /// 淡出持续的时间 /// </summary> TimeSpan transitionOffTime = TimeSpan.Zero; /**//// <summary> /// 淡入淡入过程中所处的位置 /// </summary> float transitionPosition = 1; //屏幕刚创建时处于淡入状态 ScreenState screenState = ScreenState.TransitionOn; /**//// <summary> /// 创建一个新scene对象 /// </summary> /// <param name="engine">引擎</param> /// <param name="name">scene的名称</param> public Scene(StunXnaGE engine, string name) : this(engine, 0, name) ...{ } /**//// <summary> /// 创建一个新scene对象 /// </summary> /// <param name="engine">引擎</param> /// <param name="name">scene的名称</param> /// <param name="aproxNodes">初始化nodes集合</param> public Scene(StunXnaGE engine, string name, int aproxNodes) : this(engine, 0, name, aproxNodes) ...{ } /**//// <summary> /// 给定ID创建一个新Scene对象。 /// 调用者必须保证ID唯一 /// </summary> /// <param name="engine">引擎</param> /// <param name="sceneId">ID</param> /// <param name="name">名称</param> internal Scene(StunXnaGE engine, ushort sceneId, string name) : this(engine, sceneId, name, 255) ...{ } /**//// <summary> /// 使用一个给定ID创建一个新Scene对象。 /// 调用者必须保证ID唯一 /// </summary> /// <param name="engine">引擎</param> /// <param name="sceneId">ID</param> /// <param name="name">名称</param> /// <param name="aproximateNodes">估计的节点数量</param> internal Scene(StunXnaGE engine, ushort sceneId, string name, int aproximateNodes) ...{ this.sceneId = sceneId; this.name = name; this.engine = engine; this.sceneManager = engine.SceneManager; this.isShowMouse = true; uiManager = new UIManager(engine,this); this.nodes = new List<SceneNode>(aproximateNodes); } private Scene() ...{ } #endregion 属性#region 属性 /**//// <summary> /// 获取这个场景拥有的uiManager /// </summary> public UIManager UiManager ...{ get ...{ return uiManager; } } /**//// <summary> /// 获取这个屏幕所属的场景管理器 /// </summary> public SceneManager SceneManager ...{ get ...{ return sceneManager; } internal set ...{ sceneManager = value; } } /**//// <summary> /// 检查当前屏幕是否被激活可以接受用户输入。 /// </summary> public bool IsActive ...{ get ...{ return !otherScreenHasFocus && (screenState == ScreenState.TransitionOn || screenState == ScreenState.Active); } } /**//// <summary> /// 返回scene中的node数量 /// </summary> public int Nodes ...{ get ...{ return nodes.Count; } } /**//// <summary> /// 获取当前屏幕变换的位置,从0(完全激活,没有转换)到1(转换到完全没有)。 /// </summary> public float TransitionPosition ...{ get ...{ return transitionPosition; } protected set ...{ transitionPosition = value; } } /**//// <summary> /// 返回scene的ID. /// </summary> public int ID ...{ get ...{ return sceneId; } } /**//// <summary> /// 当一个屏幕在另一个之上时,第一个屏幕会淡出给新的屏幕留出位置 /// 这个属性表示这个屏幕是否是一个弹出屏幕 /// </summary> public bool IsPopup ...{ get ...{ return isPopup; } protected set ...{ isPopup = value; } } /**//// <summary> /// 获取或设置是否显示光标 /// </summary> public bool IsShowMouse ...{ get ...{ return isShowMouse; } set ...{ isShowMouse = value; } } /**//// <summary> /// 返回scene的名称 /// </summary> public string Name ...{ get ...{ return name; } } /**//// <summary> /// 有两个原因屏幕会淡出。 /// 有可能它暂时离开为另一个屏幕留出位置,或者就是退出。 /// 这个属性表示屏幕是否在退出: /// 如果是,屏幕会在淡出结束后移除自己。 /// </summary> public bool IsExiting ...{ get ...{ return isExiting; } protected internal set ...{ isExiting = value; } } /**//// <summary> /// 获取或设置屏幕淡出持续的时间 /// </summary> public TimeSpan TransitionOffTime ...{ get ...{ return transitionOffTime; } protected set ...{ transitionOffTime = value; } } /**//// <summary> /// 获取当前转换的alpha值,从255 (完全激活,没有转换)到0(转换到完全没有)。 /// </summary> public byte TransitionAlpha ...{ get ...{ return (byte)(255 - TransitionPosition * 255); } } /**//// <summary> /// 获取当前屏幕状态。 /// </summary> public ScreenState ScreenState ...{ get ...{ return screenState; } protected set ...{ screenState = value; } } /**//// <summary> /// 获取或设置屏幕淡入的时间 /// </summary> public TimeSpan TransitionOnTime ...{ get ...{ return transitionOnTime; } set ...{ transitionOnTime = value; } } #endregion 虚拟方法#region 虚拟方法 /**//// <summary> /// 加载素材 /// </summary> public virtual void LoadContent() ...{ } /**//// <summary> /// 清除素材 /// </summary> public virtual void UnloadContent() ...{ } public virtual void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen) ...{ this.otherScreenHasFocus = otherScreenHasFocus; //如果处于Exiting过程中 if (isExiting) ...{ // 如果此屏幕已经被移除,则它的状态应为TransitionOff screenState = ScreenState.TransitionOff; if (!UpdateTransition(gameTime, transitionOffTime, 1)) ...{ // 当变换结束时,移除此屏幕 SceneManager.RemoveScene(this); } } else if (coveredByOtherScreen) ...{ // 如果这个屏幕被其他屏幕覆盖,它应该处于TransitionOff状态 if (UpdateTransition(gameTime, transitionOffTime, 1)) ...{ // 仍处于淡出状态 screenState = ScreenState.TransitionOff; } else ...{ // 否则淡出结束 screenState = ScreenState.Hidden; } } else ...{ // 否则此屏幕仍处于淡入过程,结束后才能激活 if (UpdateTransition(gameTime, transitionOnTime, -1)) ...{ // 仍处于淡入过程 screenState = ScreenState.TransitionOn; } else ...{ // 淡入过程结束! screenState = ScreenState.Active; } } // 更新此场景中所有非UISceneNode类型的SceneNode foreach (SceneNode node in nodes) node.Update(gameTime); // 更新此场景中所有UISceneNode类型的SceneNode // if (this == sceneManager.ActiveScene) ...{ foreach (UISceneNode node in UiManager.UiElements) node.Update(gameTime); } } /**//// <summary> /// 更新屏幕变换位置的辅助类 /// </summary> bool UpdateTransition(GameTime gameTime, TimeSpan time, int direction) ...{ // 淡入淡出过程持续到的位置 float transitionDelta; if (time == TimeSpan.Zero) transitionDelta = 1; else transitionDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / time.TotalMilliseconds); // 更新淡入淡出的位置 transitionPosition += transitionDelta * direction; // 是否已经在淡入淡出过程的最后? if (((direction < 0) && (transitionPosition <= 0)) || ((direction > 0) && (transitionPosition >= 1))) ...{ transitionPosition = MathHelper.Clamp(transitionPosition, 0, 1); return false; } // 否则仍处于淡入淡出过程中 return true; } public virtual void Draw(GameTime gameTime) ...{ //绘制所有非UISceneNode类型的节点 foreach (RenderableSceneNode node in nodes) node.Draw(gameTime); //绘制所有UISceneNode类型的节点 uiManager.Draw(gameTime); } /**//// <summary> /// 让屏幕可以接受用户输入。不同于Update,这个方法只在激活的屏幕中调用。 /// </summary> public virtual void HandleInput() ...{ } #endregion 公有方法#region 公有方法 /**//// <summary> /// 告知屏幕离开,不同于SceneManager.RemoveScene方法,SceneManager.RemoveScene方法会立即移除屏幕 /// 这个方法让屏幕根据TransitionOffTime逐渐透明化的离开 /// </summary> public void ExitScreen() ...{ if (TransitionOffTime == TimeSpan.Zero) ...{ // 如果TransitionOffTime为0,则移除这个屏幕 SceneManager.RemoveScene(this); } else ...{ // 否则仍处于淡出状态,当淡出结束后才能移除这个屏幕 isExiting = true; } } /**//// <summary> /// 在scene添加一个新node /// </summary> /// <param name="newNode"></param> public void AddNode(SceneNode newNode) ...{ if (engine.GraphicsDevice.IsDisposed) return; newNode.Scene = this; //如果是UI控件则添加到UIManager的UIElement集合中 if (newNode is UISceneNode) ...{ UISceneNode element = (UISceneNode)newNode; element.UIManager = this.uiManager; element.Initialize(); uiManager.AddElement(element); } //否则添加到nodes集合中 else ...{ nodes.Insert(0, newNode); newNode.Initialize(); } } /**//// <summary> /// 从场景中移除一个node /// </summary> /// <param name="node"></param> public void RemoveNode(SceneNode node) ...{ nodes.Remove(node); } #endregion } }
其中叫做uiManager的类管理所有2D控件,下面会讲到。其他东西注释中已经写得很清楚了。
然后是SceneManager类的代码:
namespace StunEngine.SceneManagement ...{ /**//// <summary> /// SceneManager负责管理一个scene集合,创建,添加,初始化scene对象。 /// 它还负责更新和绘制当前scene /// </summary> public sealed class SceneManager : DrawableGameComponent ...{ 构造函数和成员变量#region 构造函数和成员变量 private StunXnaGE engine; /**//// <summary> /// 所有scene的集合 /// </summary> List<Scene> scenes = new List<Scene>(); /**//// <summary> /// scene集合的副本 /// </summary> List<Scene> scenesToUpdate = new List<Scene>(); /**//// <summary> /// 当前激活的屏幕 /// </summary> Scene activeScene; /**//// <summary> /// 存储scene名称和ID的Dictionary /// </summary> private Dictionary<string, int> sceneIdForNames = new Dictionary<string, int>(); /**//// <summary> /// 初始的sceneId /// </summary> private int sceneId = 0; /**//// <summary> /// 是否已经初始化 /// </summary> bool isInitialized; private SceneManager() : base(null) ...{ } /**//// <summary> /// 创建一个新SceneManager对象。 /// </summary> /// <param name="engine"></param> internal SceneManager(StunXnaGE engine) : base(engine) ...{ this.engine = engine; } #endregion 属性#region 属性 /**//// <summary> /// 获取当前激活的scene /// </summary> public Scene ActiveScene ...{ get ...{ return activeScene; } set ...{ activeScene = value; } } /**//// <summary> /// Scene索引器 /// </summary> /// <param name="index"></param> /// <returns></returns> public Scene this[int index] ...{ get ...{ return scenes[index]; } } /**//// <summary> /// 返回Scene对象数量 /// </summary> public int Scenes ...{ get ...{ return scenes.Count; } } /**//// <summary> /// 获取是否显示光标 /// </summary> /// <returns>Bool</returns> public bool isShowMouseCursor ...{ get ...{ return ((scenes.Count > 0) && (activeScene.IsShowMouse == true)); } } #endregion DrawableGameComponent重载方法#region DrawableGameComponent重载方法 /**//// <summary> /// 初始化SCM /// </summary> public override void Initialize() ...{ base.Initialize(); isInitialized = true; } protected override void LoadContent() ...{ // 让所有屏幕加载内容 foreach (Scene scene in scenes) ...{ scene.LoadContent(); } } /**//// <summary> /// 移除内容 /// </summary> protected override void UnloadContent() ...{ // // 让所有屏幕移除内容 foreach (Scene scene in scenes) ...{ scene.UnloadContent(); } } /**//// <summary> /// 更新激活scene的节点 /// </summary> /// <param name="gameTime"></param> public override void Update(GameTime gameTime) ...{ // 创建一个屏幕集合的副本以防止添加或移除屏幕时可能产生的冲突 scenesToUpdate.Clear(); foreach (Scene scene in scenes) scenesToUpdate.Add(scene); bool otherScreenHasFocus = !Game.IsActive; bool coveredByOtherScreen = false; // 更新所有的scene while (scenesToUpdate.Count > 0) ...{ // 将集合顶部的scene移除 Scene scene = scenesToUpdate[scenesToUpdate.Count - 1]; scenesToUpdate.RemoveAt(scenesToUpdate.Count - 1); // 更新当前scene scene.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); if (scene.ScreenState == ScreenState.TransitionOn ||scene.ScreenState == ScreenState.Active) ...{ //如果此屏幕是第一个激活屏幕,则处理它的自定义输入 if (!otherScreenHasFocus) ...{ scene.HandleInput(); otherScreenHasFocus = true; } // 如果这不是一个弹出scene,告知其下的屏幕它们被这个屏幕覆盖 if (!scene.IsPopup) coveredByOtherScreen = true; } } //只更新激活scene的控件 activeScene.UiManager.Update(gameTime); } /**//// <summary> /// 绘制scene /// </summary> /// <param name="gameTime"></param> public override void Draw(GameTime gameTime) ...{ engine.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, engine.backGroundColor, 1.0f, 0); //---------------------------------- // 设置常用的RenderState //---------------------------------- Game.GraphicsDevice.RenderState.AlphaBlendEnable = true; Game.GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add; Game.GraphicsDevice.RenderState.AlphaDestinationBlend = Blend.Zero; Game.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha; Game.GraphicsDevice.RenderState.AlphaTestEnable = true; Game.GraphicsDevice.RenderState.AlphaSourceBlend = Blend.One; #if DEBUG if (Input.Keyboard.IsKeyDown(Keys.F9)) ...{ Game.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame; Game.GraphicsDevice.RenderState.CullMode = CullMode.None; } else ...{ Game.GraphicsDevice.RenderState.FillMode = FillMode.Solid; Game.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace; } #endif //绘制scene集合中的所有scene for(int i=0;i<scenes .Count ;i++) ...{ if (scenes[i].ScreenState == ScreenState.Hidden) continue; scenes[i].Draw(gameTime); } } #endregion 公有方法#region 公有方法 /**//// <summary> /// 根据scene的名称返回一个scene /// </summary> /// <param name="name">scene的名称</param> /// <returns></returns> public Scene GetSceneByName(string name) ...{ if (!sceneIdForNames.ContainsKey(name)) throw new KeyNotFoundException("给定的Scene名称不存在"); return scenes[sceneIdForNames[name]]; } /**//// <summary> /// 根据scene的ID返回一个scene /// </summary> /// <param name="sceneId">scene的ID</param> /// <returns></returns> public Scene GetSceneById(ushort sceneId) ...{ if (sceneId > scenes.Count) throw new IndexOutOfRangeException("ID必须小于Scene对象的数量"); return scenes[sceneId - 1]; } /**//// <summary> /// 将一个scene添加到SceneManager中的scene集合 /// </summary> /// <param name="scene">要添加的scene</param> public void AddScene(Scene scene) ...{ if (sceneIdForNames.ContainsKey(scene.Name)) throw new InvalidOperationException("给定的Scene名称已经存在了"); scene.sceneId = sceneId++; sceneIdForNames.Add(scene.Name, scene.sceneId); scene.SceneManager = this; scene.IsExiting = false; // 如果图形设备已经建立,加载secne的内容 if (isInitialized) ...{ scene.LoadContent(); } scenes.Add(scene); //将最后加入的scene作为当前激活的scene activeScene = scenes[scenes.Count - 1]; } /**//// <summary> /// 从集合中移除一个scene。通常需要使用Scene.ExitScreen方法替代直接调用这个方法 /// 这样才能实现淡出效果 /// </summary> public void RemoveScene(Scene scene) ...{ // 如果图形设备已建立,则移除scene的内容 if (isInitialized) ...{ scene.UnloadContent(); } sceneIdForNames.Remove(scene.Name); scenes.Remove(scene); scenesToUpdate.Remove(scene); //将集合中的最后一个scene作为当前激活的scene if(scenes.Count >0) activeScene = scenes[scenes.Count - 1]; } /**//// <summary> /// 返回包含所有scene的数组 /// </summary> public Scene[] GetScenes() ...{ return scenes.ToArray(); } #endregion } }
最后只要在引擎主类中添加SceneManager类就可以了:
namespace StunEngine ...{ /**//// <summary> /// 引擎主类 /// </summary> public class StunXnaGE : Microsoft.Xna.Framework.Game ...{ 构造函数和成员变量#region 构造函数和成员变量 private readonly SceneManager scm; private GraphicsDeviceManager graphics; /**//// <summary> /// 绘制所有图像和文字的spriteBatch /// </summary> protected static SpriteBatch spriteBatch; /**//// <summary> /// 默认字体 /// </summary> protected SpriteFont defaultFont; Texture2D cursor; /**//// <summary> /// 相对于800*600分辨率的缩放因子 /// </summary> private Vector2 screenScalingFactor; /**//// <summary> /// 用于spriteBatch的缩放矩阵,使图像和文字能根据当前分辨率自动调整大小 /// </summary> private Matrix globalTransformation; /**//// <summary> /// 背景颜色 /// </summary> internal Color backGroundColor; /**//// <summary> /// 是否需要改变设备设置 /// </summary> public bool applyDeviceChanges=true; 用于计算fps的变量#region 用于计算fps的变量 /**//// <summary> /// 此帧所用的时间,单位毫秒 /// </summary> private static float elapsedTimeThisFrameInMs = 0.001f; /**//// <summary> /// 总时间,单位毫秒 /// </summary> private static float totalTimeMs = 0; /**//// <summary> /// 这一秒的开始时刻 /// </summary> private static float startTimeThisSecond = 0; /**//// <summary> /// 为了计算更精确,只计算1秒内的帧数,然后更新fpsLastSecond。 /// </summary> private static int frameCountThisSecond = 0, totalFrameCount = 0, fpsLastSecond = 60; /**//// <summary> /// Fps /// </summary> /// <returns>Int</returns> public static int Fps ...{ get ...{ return fpsLastSecond; } } /**//// <summary> /// 经过10秒后的插值fps /// </summary> private static float fpsInterpolated = 100.0f; /**//// <summary> /// 总帧数 /// </summary> /// <returns>Int</returns> public static int TotalFrames ...{ get ...{ return totalFrameCount; } } #endregion /**//// <summary> /// 创建一个引擎对象。 /// 用户无法创建这个类的实例,必须从这个类继承 /// 这个类继承自Microsoft.Xna.Framework.Game,使用方法和Game对象相同 /// </summary> public StunXnaGE() ...{ Content.RootDirectory = "Content"; graphics = new GraphicsDeviceManager(this); scm = new SceneManager(this); this.Components.Add(scm); backGroundColor = Color.Black ; #if DEBUG // 在debug模式中不限制垂直刷新率 graphics.SynchronizeWithVerticalRetrace = false; this.IsFixedTimeStep = false; #endif } #endregion 属性#region 属性 /**//// <summary> /// 返回当前GraphicsDevice.VeiwPort /// </summary> public Viewport ViewPort; /**//// <summary> /// 获取背景颜色 /// </summary> public Color BackGroundColor ...{ get ...{ return backGroundColor; } set ...{ backGroundColor = value; } } /**//// <summary> /// 获取图形设备管理器 /// </summary> public GraphicsDeviceManager Graphics ...{ get ...{ return graphics; } } /**//// <summary> /// 获取场景管理器 /// </summary> public SceneManager SceneManager ...{ get ...{ return scm; } } /**//// <summary> /// 获取绘制所有图像和文字的spriteBatch /// </summary> public static SpriteBatch SpriteBatch ...{ get ...{ return spriteBatch; } } /**//// <summary> /// 获取默认字体 /// </summary> public SpriteFont DefaultFont ...{ get ...{ return defaultFont; } } 发布时间:2009/11/18 下午12:59:47 阅读次数:9005