首页 > 代码库 > Axiom3D写游戏:用Overlay实现Mesh浏览.
Axiom3D写游戏:用Overlay实现Mesh浏览.
从网上找了些资源,大多搜Ogre,Mesh资源,然后为了方便查看各个Mesh,以及对应骨骼动画.为了实用性,考虑放在原游戏窗口里实现.最开始打算窗口新建viewport来实现,后发现这种方式的局限性,不新键窗口与场景管理,所有的viewport都是对应的同一场景,不同的viewport能改变的是摄像机,这种比较容易实现如赛车游戏右下角添加一个viewport,里面显示从车的后视镜看到的情景,因为是同一场景,不同的摄像角度.后面考虑新键窗口,不同的窗口使用不同的场景管理,刚开始发现可行,后面发现有个问题,就是出在Overlay上,因为Overlay是独立与场景,视图,窗口这些,因此你新建的窗口也是共享对应的Overlay层,这样窗口显示同样的Overlay,也可以设置独立场景关闭Overlay显示,处理起来应该可行,但是会麻烦些.
综合之上,我决定在原窗口之上加一个Overlay来实现.下面是效果图.
图片缩小与压缩后,显示的有点不清楚.主要功能有查看当前程序内所有Mesh,在Mesh View内能够缩放,旋转对应Mesh,自动检测是否包含骨骼动画,能够显示对应各种动画,总的来说,我们是用了熟悉Ogre里相应的Overlay的实现.下面给出上面这个效果用到的Overlay文件,其中相应代码如下:
template container BorderPanel(meshView/main){ metrics_mode relative material SdkTrays/MiniTray left 0.1 width 0.8 top 0.1 height 0.8 uv_coords 0.4 0.4 0.6 0.6 border_material SdkTrays/MiniTextBox border_size 0.008 0.008 0.01 0.01 border_topleft_uv 0.0 0.0 0.4 0.4 border_top_uv 0.4 0.0 0.6 0.4 border_topright_uv 0.6 0.0 1.0 0.4 border_left_uv 0.0 0.4 0.4 0.6 border_right_uv 0.6 0.4 1.0 0.6 border_bottomleft_uv 0.0 0.6 0.4 1.0 border_bottom_uv 0.4 0.6 0.6 1.0 border_bottomright_uv 0.6 0.6 1.0 1.0 container BorderPanel(viewMesh) { #SdkTrays/Tray material SdkTrays/Bands metrics_mode relative uv_coords 0.1 0.1 0.9 0.9 left 0.18 width 0.6 top 0.02 height 0.76 border_material SdkTrays/Tray border_size 0.01 0.01 0.01 0.01 border_topleft_uv 0.000 0.000 0.375 0.375 border_top_uv 0.375 0.000 0.625 0.375 border_topright_uv 0.625 0.000 1.000 0.375 border_left_uv 0.000 0.375 0.375 0.625 border_right_uv 0.625 0.375 1.000 0.625 border_bottomleft_uv 0.000 0.625 0.375 1.000 border_bottom_uv 0.375 0.625 0.625 1.000 border_bottomright_uv 0.625 0.625 1.000 1.000 } }template container BorderPanel(meshView/listBox){ material SdkTrays/Frame metrics_mode relative uv_coords 0.375 0.375 0.625 0.625 left 0.02 width 0.15 top 0.02 height 0.4 border_material SdkTrays/Frame border_size 0.01 0.01 0.01 0.01 border_topleft_uv 0.000 0.000 0.375 0.375 border_top_uv 0.375 0.000 0.625 0.375 border_topright_uv 0.625 0.000 1.000 0.375 border_left_uv 0.000 0.375 0.375 0.625 border_right_uv 0.625 0.375 1.000 0.625 border_bottomleft_uv 0.000 0.625 0.375 1.000 border_bottom_uv 0.375 0.625 0.625 1.000 border_bottomright_uv 0.625 0.625 1.000 1.000 container BorderPanel(meshScroll) { metrics_mode relative material SdkTrays/ScrollTrack width 0.01 horz_align right left -0.018 top 0.01 height 0.38 uv_coords 0.0 0.31 1.0 0.69 border_material SdkTrays/ScrollTrack border_size 0 0 0.01 0.01 border_top_uv 0.0 0.0 1.0 0.31 border_bottom_uv 0.0 0.69 1.0 1.0 element Panel(meshDrag) { metrics_mode relative material SdkTrays/Handle horz_align center top 0.01 left -0.005 width 0.01 height 0.02 } }}# text item template container BorderPanel(meshTextItem){ material SdkTrays/MiniTextBox metrics_mode relative height 0.04 width 0.12 top 0.01 left 0.007 uv_coords 0.4 0.4 0.6 0.6 border_material SdkTrays/MiniTextBox border_size 0.005 0.005 0.01 0.01 border_topleft_uv 0.0 0.0 0.4 0.4 border_top_uv 0.4 0.0 0.6 0.4 border_topright_uv 0.6 0.0 1.0 0.4 border_left_uv 0.0 0.4 0.4 0.6 border_right_uv 0.6 0.4 1.0 0.6 border_bottomleft_uv 0.0 0.6 0.4 1.0 border_bottom_uv 0.4 0.6 0.6 1.0 border_bottomright_uv 0.6 0.6 1.0 1.0 element TextArea(text) { metrics_mode relative left 0.006 top 0.01 font_name SdkTrays/Caption char_height 0.02 space_width 0.005 colour 0 0 0 caption Special Delivery }}
里面的东东不多也不复杂,在说明这个文件作用,我们先看下Overlay相应类的各个方法与属性.
OveralyElement比较重要,相应的属性是子类都用的比较多的.对应的left,top,width,height对应的是0-1的小数(如left=0.1 top=0.1,整个视图宽高为1000*800,则left与top对应的像素位置是100,80这里).而前缀带pixel(pixelLeft,pixelTop,pixelWidth,pixHeight)是对应的像素点,这个好理解.带前缀derived只有left与top,这二个属性是在整个视图里的位置,前面的left,top是针对父元素的位置.对应头字母大写(Left,Top,Width,Height)的是根据MatricsMode不同给出对应的像素长度或是0-1的对应位置.默认的MetricsMode是Relative模式(0-1).而Pixel就是像素点模式.
OveralyElementContainer在OveralyElement基础上,增加功能可以添加OveralyElement元素.
OveralyElementManager用来管理OveralyElement与OveralyElementContainer所有对象,以及对应OveralyElement的创建,对应OveralyElement的销毁需要用这个类来DestroyElement来处理,不然在这个里面还会保留,当你新增加同名元素时,就会出错.
Overaly这个类组合当前所需要的OveralyElement以及子类对象,处理统一缩放,旋转,大小,FindVisibleObjects负责处理相应对象是否加到渲染列表,也就是说,只有当OveralyElement元素及子类对象放入Overaly,才能渲染到窗口.
OveralyManager实现了接口IScriptLoader,则有分析一种脚本的能力,对应的就是.overlay脚本.相关分析代码也在这个类里.对应Overlay的创建与删除也在这个类里.
OveralyElement实现了IRenderable接口,意思可渲染的,但是和OveralyElementContainer对象一样,相应的渲染没有任何数据,对应的有三个子类,分别是TextAre,Panel,BorderPanel,这三个类里会实现填充接口里的RenderOperation.(注意这里只说可渲染,如果要加到渲染列表中,需要通过Overaly才能实现,前面介绍Overaly时有提到.)
在介绍下面类之前,说下Overaly里相关元素都是二D的,声明一个长方形只需要指定对角二个点就行,那么同理声明下面各独立元素的纹理坐标时,只需要指定左上角与右下角二点,在对应文件里的顺序分别是u1,v1,u2,v2.UV的指只能指定0到1的浮点数.
TextAre我开始还以为最容易,那想不是,通过Initialize里,我们知道对应每个输出元素分别是P3T2C1,就是顶点三个float,纹理坐标二个float,一个float表示颜色,因为颜色不容易变化,故顶点与纹理放一个buffer,颜色单独放一个buffer.输出时填充根据字符串长度确定输出顶点个数.填充每个顶点位置与纹理位置.
Panel比较简单,数据BUFF里每个元素就三个顶点,只有在不透明及设置了纹理,才会添加到渲染列表里,在有纹理的情况下,添加纹理坐标声明及buffer,可以添加多层纹理.
BorderPanel在Panel的基础上添加了边框,在这里,边框不是指一个,也不是四个,而是八个,大家想下,把一个立方体横竖各二刀,一共是九块,除开中间那个由Panel输出,余下8块全属于边框,就是BorderPanel负责渲染的.各个边框再当一个Panel来看,剩下问题不大,余下都是针对各个边框大小,位置的更改.
好了,Overlay相关类介绍完了,回到最开始的需求,我们需要一个显示的展示mesh的位置,一个存放所有mesh名字的地方,一个对应mesh所有骨骼动画名字的地方.我想这个界面能够和像素无关,所以选择的是MetricMode模式是relative.
对应我们的需求与上面的实现的Overlay代码,第一部分是BorderPanel模式meshView/main,主要包含主界面以及里面的mesh展示窗口,BorderPanel的border_size控制边框大小,对应的八个窗口的uv控制着对应纹理的截取面积,使用BorderPanel一般有二种情况比较明显,一种是material与border_material不同,那么就会有一个border_material样式的边框,更常见的是material与border_material使用相同的材质,这种材质边角有透明元素使得边角圆滑,控制border_size与对应八个窗口的UV,能改变圆滑形状.注意border_size的大小不影响子元素在其中的位置.
而存放mesh与动画我们模拟winForm里的listBox,一个边框,以及可能显示在右边的拖动条,拖动条上有个元素表示当前位置.相应元素分别对应BorderPanel(meshView/listBox),BorderPanel(meshScroll),Panel(meshDrag).
listBox里存放的item用模板BorderPanel(meshTextItem)表示,主要包含一个边框与一个文本.
下面给出相关ViewListBox的相关代码.
namespace BS.Game{ public delegate void SelectItemChangedHandler(string value); public class ViewListBox : IMouseEventListener { public BorderPanel ListBox { get; set; } public event SelectItemChangedHandler SelectItemChanged; private BorderPanel menuScroll; private Panel meshDrag; private int meshCount; private int maxIndex; private int currentIndex; private string selectItem; private List<string> texts; private Dictionary<string, BorderPanel> items = new Dictionary<string, BorderPanel>(); public ViewListBox(string name, float left, float top) { OverlayManager om = OverlayManager.Instance; this.ListBox = om.Elements.CreateElementFromTemplate("meshView/listBox", "BorderPanel", name) as BorderPanel; this.menuScroll = this.ListBox.Children[this.ListBox.Name + "/meshScroll"] as BorderPanel; this.meshDrag = this.menuScroll.Children[this.menuScroll.Name + "/meshDrag"] as Panel; this.ListBox.Top = top; this.ListBox.Left = left; this.menuScroll.Height = this.ListBox.Height - 0.07f; this.MouseOrder = 101; InputManger.Instance.AddMouseListener(this); } public void Load(List<string> names) { if (items != null && items.Count > 0) { foreach (var kv in items) { kv.Value.NukeOverlayElement(); } items.Clear(); } OverlayManager om = OverlayManager.Instance; float top = 0.01f; texts = names; meshCount = texts.Count; maxIndex = (int)((this.ListBox.Height - 0.03) / 0.04); string name = this.ListBox.Name + "/meshItem"; bool bScroll = meshCount > maxIndex; for (int index = 0; index < meshCount; index++) { string meshName = names[index]; var meshItem = om.Elements.CreateElementFromTemplate("meshTextItem", "BorderPanel", name + index) as BorderPanel; var textAre = meshItem.Children[meshItem.Name + "/text"] as TextArea; textAre.Text = meshName; items.Add(meshName, meshItem); if (index <= maxIndex) { meshItem.Top = top; meshItem.IsVisible = true; meshItem.Width = bScroll ? this.ListBox.Width - 0.03f : this.ListBox.Width - 0.014f; this.ListBox.AddChild(meshItem); top = top + meshItem.Height; } else { meshItem.IsVisible = false; this.ListBox.AddChild(meshItem); } } menuScroll.IsVisible = bScroll; } public void onm ouseDown(object sender, Axiom.Input.MouseEventArgs args) { foreach (var kv in items) { if (kv.Value.IsCursorOver(args.X, args.Y)) { //slectMeshLoad(kv.Key); if (SelectItemChanged != null) { selectItem = kv.Key; kv.Value.BorderMaterialName = "SdkTrays/MiniTextBox/Over"; kv.Value.MaterialName = "SdkTrays/MiniTextBox/Over"; SelectItemChanged(kv.Key); } break; } } } public void onm ouseUp(object sender, Axiom.Input.MouseEventArgs args) { } public void onm ouseMove(object sender, MouseMoveArgs args) { foreach (var kv in items) { if (kv.Key == selectItem) continue; if (kv.Value.IsCursorOver(args.X, args.Y)) { kv.Value.BorderMaterialName = "SdkTrays/MiniTextBox/Over"; kv.Value.MaterialName = "SdkTrays/MiniTextBox/Over"; } else { kv.Value.BorderMaterialName = "SdkTrays/MiniTextBox"; kv.Value.MaterialName = "SdkTrays/MiniTextBox"; } } } public void onm ouseWheelChanged(object sender, MouseWheelArgs args) { if (!this.ListBox.IsCursorOver(InputManger.Instance.X, InputManger.Instance.Y)) return; if (meshCount > maxIndex) { if (args.Delta > 0) currentIndex--; else currentIndex++; } ScrollShow(); } public void ScrollShow() { if (maxIndex >= meshCount) return; currentIndex = Math.Min(currentIndex, meshCount - maxIndex - 1); currentIndex = Math.Max(0, currentIndex); float top = 0.01f; for (int i = 0; i < meshCount; i++) { string meshName = texts[i]; var meshItem = items[meshName]; if (i >= currentIndex && i <= currentIndex + maxIndex) { meshItem.IsVisible = true; meshItem.Width = this.ListBox.Width - 0.03f; meshItem.Top = top; top = top + meshItem.Height; } else { meshItem.IsVisible = false; } } meshDrag.Top = 0.01f + currentIndex * this.menuScroll.Height / (meshCount - maxIndex); } public int MouseOrder { get; set; } }}
在ViewListBox里,初始化我们只需要生成的名字,左上角的位置,Load根据传进来的集合生成对应的TextItem项,点击则引发事件,鼠标滑动引发判断应该显示那些项.整个过程还是比较简单的.
下面是MeshView的相关代码.
public class MeshView : IMouseEventListener { public const string name = "meshView"; public const string entityName = "MeshViewEntity"; private Overlay mainLayer; private OverlayElementContainer meshMain; private BorderPanel view; private ViewListBox selectListBox; private ViewListBox animationListBox; private SceneNode meshNode; private AnimationState animationState; public MeshView() { OverlayManager om = OverlayManager.Instance; string nameBase = name + "/"; this.mainLayer = om.Create(nameBase + "main"); this.meshMain = om.Elements.CreateElementFromTemplate("meshView/main", "BorderPanel", name) as OverlayElementContainer; this.view = this.meshMain.Children[nameBase + "viewMesh"] as BorderPanel; this.selectListBox = new ViewListBox("meshListBox", 0.02f, 0.02f); this.selectListBox.SelectItemChanged += slectMeshLoad; this.meshMain.AddChild(selectListBox.ListBox); LoadMeshNames(); this.mainLayer.ZOrder = 310; this.mainLayer.AddElement(this.meshMain); this.MouseOrder = 100; InputManger.Instance.AddMouseListener(this); } public bool IsVisible { get { return mainLayer.IsVisible; } } public void Show() { if (mainLayer.IsVisible) mainLayer.Hide(); else mainLayer.Show(); } public void LoadMeshNames() { var meshNames = ResourceGroupManager.Instance.GetResourceNames(".mesh") .Where(p => p.EndsWith(".mesh")).ToList(); OverlayManager om = OverlayManager.Instance; this.selectListBox.Load(meshNames); } private void slectMeshLoad(string meshName) { string entityName = "MeshViewEntity"; var scene = Root.Instance.SceneManager; if (meshNode != null) { scene.DestroySceneNode(meshNode); scene.RemoveEntity(entityName); } meshNode = scene.RootSceneNode.CreateChildSceneNode(new Vector3(0, 0, 0), Quaternion.Identity); var entity = scene.CreateEntity(entityName, meshName); float lenght = entity.BoundingBox.Size.Length * 2; //scene meshNode.Translate(new Vector3(lenght / 10.0f, 0f, -1.0f * lenght)); //在所有物体显示之上 针对overlay entity.RenderQueueGroup = RenderQueueGroupID.Count; //关掉深度检测 针对场景里的entity foreach (Material sub in entity.SubEntityMaterials) { // sub.DepthCheck = false; } meshNode.AttachObject(entity); if (entity.HasSkeleton) { List<string> animationNames = new List<string>(); foreach (var animat in entity.Skeleton.Animations) { animationNames.Add(animat.Name); } if (animationListBox == null) { animationListBox = new ViewListBox("animationListBox", 0.02f, 0.43f); animationListBox.ListBox.Height = 0.35f; animationListBox.SelectItemChanged += seletAnimation; this.meshMain.AddChild(animationListBox.ListBox); } animationListBox.ListBox.IsVisible = true; animationListBox.Load(animationNames); } else { if (animationListBox != null) { animationListBox.ListBox.IsVisible = false; } } this.mainLayer.AddElement(meshNode); } private void seletAnimation(string name) { if (meshNode != null) { Entity entity = meshNode.GetObject(entityName) as Entity; if (entity != null) { animationState = entity.GetAnimationState(name); animationState.IsEnabled = true; animationState.Loop = true; } } } public void onm ouseDown(object sender, Axiom.Input.MouseEventArgs args) { } public void onm ouseUp(object sender, Axiom.Input.MouseEventArgs args) { } public void onm ouseMove(object sender, MouseMoveArgs args) { if (InputManger.Instance.IsMousePressed(MouseButtons.Left)) { if (meshNode != null) { this.meshNode.Yaw((Real)(new Degree((Real)(args.RelativeX * 0.15f)))); this.meshNode.Pitch((Real)(new Degree((Real)(args.RelativeY * 0.15f)))); } } } public int MouseOrder { get; set; } public void onm ouseWheelChanged(object sender, MouseWheelArgs args) { if (meshNode != null && this.view.IsCursorOver(InputManger.Instance.X, InputManger.Instance.Y)) { var offest = args.Delta * Math.Abs(args.Value) / 2; meshNode.Translate(new Vector3(offest / 10, 0, offest)); } } public void Update(float timeSinceLastFrame) { //deltaTime += timeSinceLastFrame; if (animationState != null) { animationState.Time += timeSinceLastFrame; } } }
几个扩展方法.
namespace BS.Game{ public static class Helper { public static bool IsCursorOver(this OverlayElement element, Vector2 pos) { return IsCursorOver(element, pos.x, pos.y); } public static bool IsCursorOver(this OverlayElement element, float x, float y) { if (element.MetricsMode == MetricsMode.Relative) { var oMgr = OverlayManager.Instance; x = x / oMgr.ViewportWidth; y = y / oMgr.ViewportHeight; } if (element.IsVisible) { if (x >= element.DerivedLeft && x <= element.DerivedLeft + element.Width && y >= element.DerivedTop && y <= element.DerivedTop + element.Height) { return true; } } return false; } public static void NukeOverlayElement(this OverlayElement element) { var container = element as OverlayElementContainer; if (container != null) { var toDelete = new List<OverlayElement>(); foreach (OverlayElement child in container.Children.Values) { toDelete.Add(child); } for (int i = 0; i < toDelete.Count; i++) { NukeOverlayElement(toDelete[i]); } } if (element != null) { OverlayElementContainer parent = element.Parent; if (parent != null) { parent.RemoveChild(element.Name); } OverlayManager.Instance.Elements.DestroyElement(element.Name); } } }}
需要注意的是selectMeshLoad这个方法,原本scene.DestroySceneNode这个方法一直不得行,我后面查找对应的Ogre源码,发现freeTempVertexBufferMap 是multimap类型,因为我修改HardwareBufferManagerBase里freeTempVertexBufferMap从字典修改为可以重复键值的,然后修改一些引用的地方,因调用scene.DestroySceneNode而引发的健值重复问题才消失,运行一段时间在任务管理里查看了下,内存没有增加,这个问题暂时就这样放着,相关问题我提到http://axiom3d.net/forums/viewtopic.php?f=1&t=1523&start=0.
在selectMeshLoad里加载对应的mesh时,需要注意mesh有大有小,我们需要根据相应Entity的大小来改变node的Z值,注意我们显示的时候,左边一块是用来放ViewListBox的,所以我们需要移动相应Node的X值.如代码里,我们根据视角,当前窗口确定大致的XZ位置,后面查看各Mesh时可以发现这个方法一般能正常显示在正确的位置.
注意selectMeshLoad里二句注释的位置,大家可以调整下这二句代码是否启用,查看效果.作用比较大.然后查看是否有骨骼动画,加载骨骼动画.
对应事件都比较简单,鼠标move控制节点旋转,使我们可以各个角度查看Mesh.鼠标滑轮滚动则是调整Mesh的缩放.Update更新骨骼动画.
附件就不放了,因为主要代码都在上面,并且相应的Axiom里源代码我改动了一些,这些附件太多,上传太要时间.
Axiom3D写游戏:用Overlay实现Mesh浏览.