Axiom主要的代码大致翻看了下,就想到了自己来模拟一下游戏开发.
这章主要包括创建窗口及3D渲染的一些基本元素,并添加一个第三人称的骨骼动画作主角,加上前文中修改过后的地形组件,能用鼠标和键盘进行漫游.如下图所示.
在Axiom上给出的例子中,可以选择是用OpenGL或是DirectX渲染,现在有问题的地方是如果用OpenGL渲染,而Axiom鼠标与键盘输入采用的是SharpInputSystem,这个在DirectX渲染下是没有问题的,但是在OpenGL渲染的窗口下得不到正确的鼠标位置.而DixectX渲染下,运行一会后,骨骼动画在进行骨骼变化时,调用SoftwareVertexBlend会引起程序挂起,这个问题没找到解决方法,以后在来调.
在上面的情况下,在加上前面我主要学的是OpenGL,我决定在我接下来采用OpenGL渲染以及OpenTK里提供的鼠标与键盘输入接口.
接下来,通过Axiom里提供的例子,结合Samples.Brower.Win32与Samples.Common来看Axiom的渲染流程,结合上面的流程自己写了一半,才发现有现存的,在Axiom.Framework里的Game抽象类里,提供对应流程,因为原始采用SharpInputSystem来处理键盘与鼠标反馈,经过修改,下面是代码.
1 public abstract class Game : IDisposable, IWindowEventListener 2 { 3 protected Root Engine; 4 protected IConfigurationManager ConfigurationManager; 5 protected ResourceGroupManager Content; 6 protected SceneManager SceneManager; 7 protected Camera Camera; 8 protected Viewport Viewport; 9 protected RenderWindow Window; 10 protected Axiom.Graphics.RenderSystem RenderSystem; 11 12 public virtual void Run() 13 { 14 PreInitialize(); 15 LoadConfiguration(); 16 Initialize(); 17 CreateRenderSystem(); 18 CreateRenderWindow(); 19 LoadResource(); 20 CreateSceneManager(); 21 CreateCamera(); 22 CreateViewports(); 23 CreateInput(); 24 CreateScene(); 25 this.Engine.StartRendering(); 26 } 27 28 private void PreInitialize() 29 { 30 this.ConfigurationManager = new DefaultConfigurationManager(); 31 32 // instantiate the Root singleton 33 this.Engine = new Root(this.ConfigurationManager.LogFilename); 34 35 // add event handlers for frame events 36 this.Engine.FrameStarted += Engine_FrameRenderingQueued; 37 } 38 39 public virtual void LoadConfiguration() 40 { 41 this.ConfigurationManager.RestoreConfiguration(this.Engine); 42 } 43 44 45 private void Engine_FrameRenderingQueued(object source, FrameEventArgs e) 46 { 47 Update(e.TimeSinceLastFrame); 48 } 49 50 public virtual void Initialize() 51 { 52 } 53 54 public virtual void CreateRenderSystem() 55 { 56 if (this.Engine.RenderSystem == null) 57 { 58 this.RenderSystem = this.Engine.RenderSystem = this.Engine.RenderSystems.First().Value; 59 } 60 else 61 { 62 this.RenderSystem = this.Engine.RenderSystem; 63 } 64 } 65 66 public virtual void CreateRenderWindow() 67 { 68 this.Window = Root.Instance.Initialize(true, "Xin Game"); 69 70 WindowEventMonitor.Instance.RegisterListener(this.Window, this); 71 } 72 73 public virtual void LoadResource() 74 { 75 ResourceGroupManager.Instance.InitializeAllResourceGroups(); 76 } 77 78 public virtual void CreateSceneManager() 79 { 80 // Get the SceneManager, a generic one by default 81 this.SceneManager = this.Engine.CreateSceneManager("DefaultSceneManager", "GameSMInstance"); 82 this.SceneManager.ClearScene(); 83 } 84 85 public virtual void CreateCamera() 86 { 87 // create a camera and initialize its position 88 this.Camera = this.SceneManager.CreateCamera("MainCamera"); 89 this.Camera.Position = new Vector3(0, 0, 0); 90 this.Camera.LookAt(new Vector3(0, 0, -300)); 91 92 } 93 94 public virtual void CreateViewports() 95 { 96 // create a new viewport and set it's background color 97 this.Viewport = this.Window.AddViewport(this.Camera, 0, 0, 1.0f, 1.0f, 100); 98 this.Viewport.BackgroundColor = ColorEx.SteelBlue; 99 } 100 101 public virtual void CreateInput() 102 { 103 //var window = this.Window["nativewindow"]; 104 } 105 106 public abstract void CreateScene(); 107 108 public virtual void Update(float timeSinceLastFrame) 109 { 110 } 111 112 #region IDisposable Implementation 113 114 #region IsDisposed Property 115 116 /// <summary> 117 /// Determines if this instance has been disposed of already. 118 /// </summary> 119 public bool IsDisposed { get; set; } 120 121 #endregion IsDisposed Property 122 123 /// <summary> 124 /// Class level dispose method 125 /// </summary> 126 /// <remarks> 127 /// When implementing this method in an inherited class the following template should be used; 128 /// protected override void dispose( bool disposeManagedResources ) 129 /// { 130 /// if ( !IsDisposed ) 131 /// { 132 /// if ( disposeManagedResources ) 133 /// { 134 /// // Dispose managed resources. 135 /// } 136 /// 137 /// // If there are unmanaged resources to release, 138 /// // they need to be released here. 139 /// } 140 /// 141 /// // If it is available, make the call to the 142 /// // base class's Dispose(Boolean) method 143 /// base.dispose( disposeManagedResources ); 144 /// } 145 /// </remarks> 146 /// <param name="disposeManagedResources">True if Unmanaged resources should be released.</param> 147 protected virtual void dispose(bool disposeManagedResources) 148 { 149 if (!IsDisposed) 150 { 151 if (disposeManagedResources) 152 { 153 if (this.Engine != null) 154 { 155 // remove event handlers 156 this.Engine.FrameStarted -= Engine_FrameRenderingQueued; 157 } 158 if (this.SceneManager != null) 159 { 160 this.SceneManager.RemoveAllCameras(); 161 } 162 this.Camera = null; 163 if (Root.Instance != null) 164 { 165 Root.Instance.RenderSystem.DetachRenderTarget(this.Window); 166 } 167 if (this.Window != null) 168 { 169 WindowEventMonitor.Instance.UnregisterWindow(this.Window); 170 this.Window.Dispose(); 171 } 172 if (this.Engine != null) 173 { 174 this.Engine.Dispose(); 175 } 176 } 177 178 // There are no unmanaged resources to release, but 179 // if we add them, they need to be released here. 180 } 181 IsDisposed = true; 182 } 183 184 /// <summary> 185 /// Call to when class is no longer needed 186 /// </summary> 187 public void Dispose() 188 { 189 dispose(true); 190 GC.SuppressFinalize(this); 191 } 192 193 ~Game() 194 { 195 dispose(false); 196 } 197 198 #endregion IDisposable Implementation 199 200 #region IWindowEventListener Implementation 201 202 /// <summary> 203 /// Window has moved position 204 /// </summary> 205 /// <param name="rw">The RenderWindow which created this event</param> 206 public void WindowMoved(RenderWindow rw) 207 { 208 } 209 210 /// <summary> 211 /// Window has resized 212 /// </summary> 213 /// <param name="rw">The RenderWindow which created this event</param> 214 public void WindowResized(RenderWindow rw) 215 { 216 } 217 218 /// <summary> 219 /// Window has closed 220 /// </summary> 221 /// <param name="rw">The RenderWindow which created this event</param> 222 public void WindowClosed(RenderWindow rw) 223 { 224 // Only do this for the Main Window 225 if (rw == this.Window) 226 { 227 Root.Instance.QueueEndRendering(); 228 } 229 } 230 231 /// <summary> 232 /// Window lost/regained the focus 233 /// </summary> 234 /// <param name="rw">The RenderWindow which created this event</param> 235 public void WindowFocusChange(RenderWindow rw) 236 { 237 } 238 239 #endregion 240 }
在原来的基础上去掉相关SharpInputSystem的代码,相关初始化过程都在Run这个方法内,主要过程生成Root对象,此过程会预先调用大部分Manager类,以达到相关静态构造函数能按顺序进行,不然可能会引发一些问题,插件的加载也在这个方法内。在加载插件时,会查找是否包含OpenGL与DirectX渲染插件,在这里我们只选择添加了RenderSystems.OpenGL的插件,那么在接下创建RenderSystem时,选择这个渲染插件作为Root对象的渲染系统。根据对应的RenderSystem我们创建对应的窗口,跟踪相关代码,我们知道我们创建的是RenderWindow的子类OpenTKWindow.在创建窗口之后。我们会选择加载资源进来。然后是创建场景管理SceneManager,场景管理具体可以查看Ogre中SceneManager分析,Ogre本身给我们提供了二个场景管理插件,分别是BSP与Octree,这二个方案主要区别是针对物体怎么确定是否需要渲染的逻辑有所不同,最新Axiom因为直接用的是Ogre1.7之后的地形插件,所以不需要固定使用Octree的场景管理方案。在已经创建管理基础上,我们创建摄像机,已经对应窗口的Viewport对象。方法Update就是Root对象的消息循环,我们每桢的逻辑都可以放入这个方法内,当我们调用Root的StartRendering方法后,正常情况下就不断更新Update方法。
在原来的Game里我们放弃了原来的SharpInputSystem,我们需要一个能正常接收对应OpenTKWindow里键盘与鼠标的输入,并且还有前面所说的一个骨骼动画加上地形组件所产生的地形,以及一些基本的UI组件显示。
1 public class OpenTKGame : Game 2 { 3 public OpenTKGame() 4 { 5 6 } 7 8 9 #region property 10 public KeyboardState KeyboardState 11 { 12 get 13 { 14 return Keyboard.GetState(); 15 } 16 } 17 18 public MouseState MouseState 19 { 20 get 21 { 22 return Mouse.GetState(); 23 } 24 } 25 26 #endregion 27 28 protected SinbadCharacterController chara; 29 protected CameraManager cameraManager; 30 protected UIManager uiManager; 31 protected TerrainManager terrainManager; 32 33 private bool bLoadTerrain = true; 34 private bool bDown = false; 35 public override void CreateRenderWindow() 36 { 37 base.CreateRenderWindow(); 38 } 39 40 public override void LoadResource() 41 { 42 //ResourceGroupManager.Instance.InitializeResourceGroup("Popular"); 43 ResourceGroupManager.Instance.InitializeResourceGroup("Essential"); 44 uiManager = new UIManager(this.Window); 45 } 46 47 public override void CreateCamera() 48 { 49 //uiManager.ShowBackdrop("SdkTrays/Bands"); 50 base.CreateCamera(); 51 this.cameraManager = new CameraManager(this.Camera); 52 this.terrainManager = new TerrainManager(this.SceneManager); 53 } 54 55 public override void CreateInput() 56 { 57 base.CreateInput(); 58 var window = this.Window["nativewindow"] as OpenTK.NativeWindow; 59 if (window == null) 60 { 61 throw new NotImplementedException(); 62 } 63 var mouse = window.InputDriver.Mouse[0]; 64 var keyboard = window.InputDriver.Keyboard[0]; 65 mouse.ButtonDown += mouse_ButtonDown; 66 mouse.ButtonUp += mouse_ButtonUp; 67 mouse.Move += mouse_Move; 68 69 keyboard.KeyDown += keyboard_KeyDown; 70 keyboard.KeyUp += keyboard_KeyUp; 71 } 72 73 public override void CreateScene() 74 { 75 // set background and some fog 76 Viewport.BackgroundColor = new ColorEx(1.0f, 1.0f, 0.8f); 77 SceneManager.SetFog(FogMode.Linear, new ColorEx(1.0f, 1.0f, 1.0f), 0, 15, 100); 78 // set shadow properties 79 //SceneManager.ShadowTechnique = ShadowTechnique.TextureModulative; 80 SceneManager.ShadowColor = new ColorEx(0.5f, 0.5f, 0.5f); 81 SceneManager.ShadowTextureSize = 1024; 82 SceneManager.ShadowTextureCount = 1; 83 // disable default camera control so the character can do its own 84 cameraManager.setStyle(CameraStyle.Manual); 85 // use a small amount of ambient lighting 86 SceneManager.AmbientLight = new ColorEx(0.3f, 0.3f, 0.3f); 87 88 // add a bright light above the scene 89 Light light = SceneManager.CreateLight("CharacterLight"); 90 light.Type = LightType.Point; 91 light.Position = new Vector3(-10, 40, 20); 92 light.Specular = ColorEx.White; 93 94 this.uiManager.ShowBackdrop("SdkTrays/Bands"); 95 this.uiManager.ShowLoadingBar(6, 1, 0.7f); 96 ResourceGroupManager.Instance.InitializeResourceGroup("Popular"); 97 if (this.bLoadTerrain) 98 { 99 terrainManager.SetupContent(); 100 } 101 this.uiManager.HideLoadingBar(); 102 this.uiManager.HideBackdrop(); 103 104 ShowCursor(0); 105 uiManager.ShowCursor(); 106 107 MeshManager.Instance.CreatePlane("floor", ResourceGroupManager.DefaultResourceGroupName, 108 new Plane(Vector3.UnitY, 0), 100, 100, 10, 10, true, 1, 10, 10, Vector3.UnitZ); 109 110 // create a floor entity, give it a material, and place it at the origin 111 Entity floor = SceneManager.CreateEntity("Floor", "floor"); 112 floor.MaterialName = "Examples/Rockwall"; 113 floor.CastShadows = false; 114 SceneManager.RootSceneNode.AttachObject(floor); 115 116 // create our character controller 117 this.chara = new SinbadCharacterController(Camera); 118 this.chara.bodyNode.Position = new Vector3(1900, 5, 2000); 119 } 120 121 public override void Update(float timeSinceLastFrame) 122 { 123 base.Update(timeSinceLastFrame); 124 this.cameraManager.frameRenderingQueued(timeSinceLastFrame); 125 this.chara.AddTime(timeSinceLastFrame); 126 if (bLoadTerrain) 127 { 128 var position = this.chara.bodyNode.Position; 129 terrainManager.GetTerrainReyHighter(ref position, timeSinceLastFrame); 130 this.chara.bodyNode.Position = position; 131 } 132 } 133 134 [DllImport("user32.dll", EntryPoint = "ShowCursor", CharSet = CharSet.Auto)] 135 public extern static void ShowCursor(int status); 136 137 #region event 138 void keyboard_KeyUp(object sender, KeyboardKeyEventArgs e) 139 { 140 this.cameraManager.injectKeyUp(e); 141 this.chara.InjectKeyUp(e); 142 } 143 144 void keyboard_KeyDown(object sender, KeyboardKeyEventArgs e) 145 { 146 switch (e.Key) 147 { 148 case Key.R: 149 switch (this.Camera.PolygonMode) 150 { 151 case PolygonMode.Points: 152 this.Viewport.Camera.PolygonMode = PolygonMode.Solid; 153 break; 154 case PolygonMode.Solid: 155 this.Viewport.Camera.PolygonMode = PolygonMode.Wireframe; 156 break; 157 case PolygonMode.Wireframe: 158 this.Viewport.Camera.PolygonMode = PolygonMode.Points; 159 break; 160 } 161 break; 162 default: 163 break; 164 } 165 166 this.cameraManager.injectKeyDown(e); 167 this.chara.InjectKeyDown(e); 168 } 169 170 void mouse_Move(object sender, MouseMoveEventArgs e) 171 { 172 this.cameraManager.injectMouseMove(e); 173 if (bDown) 174 { 175 this.chara.InjectMouseMove(e); 176 } 177 uiManager.RefreshCursor(e.X, e.Y); 178 } 179 180 void mouse_ButtonUp(object sender, MouseButtonEventArgs e) 181 { 182 this.cameraManager.injectMouseUp(e); 183 bDown = false; 184 } 185 186 void mouse_ButtonDown(object sender, MouseButtonEventArgs e) 187 { 188 this.cameraManager.injectMouseDown(e); 189 this.chara.InjectMouseDown(e); 190 bDown = true; 191 } 192 #endregion 193 }
OpenTKGame继续Game,有一些方法做了相关改动,在LoadResource里,并没有全部加载所有资源,只是加载了UI所需要的资源。在CreateInput里,我们获取OpenTKWindow里的nativewindow,以此对象来检测鼠标与键盘的输入。然后在CreateScene就是我们相关元素的初始化的一些过程,场景管理的一些基本定义,一个点光源,界面上的一些UI元素,这里有二个位置,一个是加载游戏资源对应的进度条,一个是光标。然后是地形加载,最后就是主角,骨骼动画的加载。
关于地形,我在上文Axiom3D:Ogre地形组件代码解析大致解析了下,原Axiom里的地形组件有些问题还没修正,需要自己修改相关位置的BUG才能得到正确的地形,这里就不仔细说了,需要注意的一点就是,我们的角色要正确的与地面交互.下面是主要源代码。
1 public class TerrainManager 2 { 3 private const string TerrainFilePrefix = "TestTerrain"; 4 private const string TerrainFileSuffix = "dat"; 5 private const float TerrainWorldSize = 5120; 6 private const int TerrainSize = 513; 7 8 private const int TerrainPageMinX = 0; 9 private const int TerrainPageMinY = 0; 10 private const int TerrainPageMaxX = 0; 11 private const int TerrainPageMaxY = 0; 12 13 protected bool terrainsImported = false; 14 15 protected Real fallVelocity; 16 protected Real heightUpdateCountDown; 17 protected Real heightUpdateRate; 18 protected Vector3 terrainPos = new Vector3(0, 0, 0); 19 protected Real brushSizeTerrainSpace = 0.02f; 20 21 protected TerrainGlobalOptions terrainGlobals; 22 protected TerrainGroup terrainGroup; 23 protected List<Entity> houseList = new List<Entity>(); 24 25 private SceneManager SceneManager; 26 27 public TerrainManager(SceneManager sceneManager) 28 { 29 this.heightUpdateRate = 1.0f / 2.0f; 30 this.SceneManager = sceneManager; 31 } 32 33 private ImportData _configureTerrainDefaults(Light l) 34 { 35 // Configure global 36 TerrainGlobalOptions.MaxPixelError = 8; 37 // testing composite map 38 TerrainGlobalOptions.CompositeMapDistance = 3000; 39 TerrainGlobalOptions.LightMapDirection = l.DerivedDirection; 40 //TerrainGlobalOptions.CompositeMapAmbient = SceneManager.AmbientLight; 41 TerrainGlobalOptions.CompositeMapDiffuse = l.Diffuse; 42 43 // Configure default import settings for if we use imported image 44 var defaultImp = this.terrainGroup.DefaultImportSettings; 45 defaultImp.TerrainSize = TerrainSize; 46 defaultImp.WorldSize = TerrainWorldSize; 47 defaultImp.InputScale = 600.0f;// 600.0f; 48 defaultImp.MinBatchSize = 33; 49 defaultImp.MaxBatchSize = 65; 50 51 // textures 52 defaultImp.LayerList = new List<LayerInstance>(); 53 var inst = new LayerInstance(); 54 inst.WorldSize = 30;// 100; 55 inst.TextureNames = new List<string>(); 56 inst.TextureNames.Add("dirt_grayrocky_diffusespecular.dds"); 57 inst.TextureNames.Add("dirt_grayrocky_normalheight.dds"); 58 defaultImp.LayerList.Add(inst); 59 60 inst = new LayerInstance(); 61 inst.WorldSize = 30;//30; 62 inst.TextureNames = new List<string>(); 63 inst.TextureNames.Add("grass_green-01_diffusespecular.dds"); 64 inst.TextureNames.Add("grass_green-01_normalheight.dds"); 65 defaultImp.LayerList.Add(inst); 66 67 inst = new LayerInstance(); 68 inst.WorldSize = 30;// 200; 69 inst.TextureNames = new List<string>(); 70 inst.TextureNames.Add("growth_weirdfungus-03_diffusespecular.dds"); 71 inst.TextureNames.Add("growth_weirdfungus-03_normalheight.dds"); 72 defaultImp.LayerList.Add(inst); 73 74 return defaultImp; 75 } 76 77 public void SetupContent() 78 { 79 var blankTerrain = false; 80 81 MaterialManager.Instance.SetDefaultTextureFiltering(TextureFiltering.Anisotropic); 82 MaterialManager.Instance.DefaultAnisotropy = 7; 83 84 SceneManager.SetFog(FogMode.Linear, new ColorEx(0.07f, 0.07f, 0.08f), 0, 10000, 25000); 85 86 var lightDir = new Vector3(0.55f, 0.3f, 0.75f); 87 lightDir.Normalize(); 88 89 var l = SceneManager.CreateLight("tsLight"); 90 l.Type = LightType.Directional; 91 l.Direction = lightDir; 92 l.Diffuse = ColorEx.White; 93 l.Specular = new ColorEx(0.8f, 0.8f, 0.8f); 94 95 SceneManager.AmbientLight = new ColorEx(0.2f, 0.2f, 0.2f); 96 97 this.terrainGroup = new TerrainGroup(SceneManager, Alignment.Align_X_Z, (ushort)TerrainSize, TerrainWorldSize); 98 this.terrainGroup.SetFilenameConvention(TerrainFilePrefix, TerrainFileSuffix); 99 this.terrainGroup.Origin = this.terrainPos; 100 101 _configureTerrainDefaults(l); 102 103 for (long x = TerrainPageMinX; x <= TerrainPageMaxX; ++x) 104 { 105 for (long y = TerrainPageMinY; y <= TerrainPageMaxY; ++y) 106 { 107 _defineTerrain(x, y, blankTerrain); 108 } 109 } 110 // sync load since we want everything in place when we start 111 this.terrainGroup.LoadAllTerrains(true); 112 if (this.terrainsImported) 113 { 114 foreach (var ts in this.terrainGroup.TerrainSlots) 115 { 116 _initBlendMaps(ts.Instance); 117 } 118 } 119 this.terrainGroup.FreeTemporaryResources(); 120 121 var e = SceneManager.CreateEntity("TudoMesh", "tudorhouse.mesh"); 122 var entPos = new Vector3(this.terrainPos.x + 2043, 0, this.terrainPos.z + 1715); 123 var rot = new Quaternion(); 124 entPos.y = this.terrainGroup.GetHeightAtWorldPosition(entPos) + 65.5 + this.terrainPos.y; 125 rot = Quaternion.FromAngleAxis(Utility.RangeRandom(-180, 180), Vector3.UnitY); 126 var sn = SceneManager.RootSceneNode.CreateChildSceneNode(entPos, rot); 127 sn.Scale = new Vector3(0.12, 0.12, 0.12); 128 sn.AttachObject(e); 129 this.houseList.Add(e); 130 131 e = SceneManager.CreateEntity("TudoMesh1", "tudorhouse.mesh"); 132 entPos = new Vector3(this.terrainPos.x + 1850, 0, this.terrainPos.z + 1478); 133 entPos.y = this.terrainGroup.GetHeightAtWorldPosition(entPos) + 65.5 + this.terrainPos.y; 134 rot = Quaternion.FromAngleAxis(Utility.RangeRandom(-180, 180), Vector3.UnitY); 135 sn = SceneManager.RootSceneNode.CreateChildSceneNode(entPos, rot); 136 sn.Scale = new Vector3(0.12, 0.12, 0.12); 137 sn.AttachObject(e); 138 this.houseList.Add(e); 139 140 e = SceneManager.CreateEntity("TudoMesh2", "tudorhouse.mesh"); 141 entPos = new Vector3(this.terrainPos.x + 1970, 0, this.terrainPos.z + 2180); 142 entPos.y = this.terrainGroup.GetHeightAtWorldPosition(entPos) + 65.5 + this.terrainPos.y; 143 rot = Quaternion.FromAngleAxis(Utility.RangeRandom(-180, 180), Vector3.UnitY); 144 sn = SceneManager.RootSceneNode.CreateChildSceneNode(entPos, rot); 145 sn.Scale = new Vector3(0.12, 0.12, 0.12); 146 sn.AttachObject(e); 147 this.houseList.Add(e); 148 } 149 150 private void _defineTerrain(long x, long y, bool flat) 151 { 152 if (flat) 153 { 154 this.terrainGroup.DefineTerrain(x, y, 0); 155 } 156 else 157 { 158 var filename = this.terrainGroup.GenerateFilename(x, y); 159 if (ResourceGroupManager.Instance.ResourceExists(this.terrainGroup.ResourceGroup, filename)) 160 { 161 this.terrainGroup.DefineTerrain(x, y); 162 } 163 else 164 { 165 var img = _getTerrainImage(x % 2 != 0, y % 2 != 0); 166 this.terrainGroup.DefineTerrain(x, y, img); 167 this.terrainsImported = true; 168 } 169 } 170 } 171 172 private Image _getTerrainImage(bool flipX, bool flipY) 173 { 174 var img = Image.FromFile("terrain.png", ResourceGroupManager.DefaultResourceGroupName); 175 176 if (flipX) 177 { 178 img.FlipAroundY(); 179 } 180 181 if (flipY) 182 { 183 img.FlipAroundX(); 184 } 185 186 return img; 187 } 188 189 private void _initBlendMaps(Axiom.Components.Terrain.Terrain terrain) 190 { 191 var blendMap0 = terrain.GetLayerBlendMap(1); 192 var blendMap1 = terrain.GetLayerBlendMap(2); 193 Real minHeight0 = 70; 194 Real fadeDist0 = 40; 195 Real minHeight1 = 70; 196 Real fadeDist1 = 15; 197 198 var pBlend1 = blendMap1.BlendPointer; 199 var blendIdx = 0; 200 for (var y = 0; y < terrain.LayerBlendMapSize; y++) 201 { 202 for (var x = 0; x < terrain.LayerBlendMapSize; x++) 203 { 204 Real tx = 0; 205 Real ty = 0; 206 blendMap0.ConvertImageToTerrainSpace(x, y, ref tx, ref ty); 207 Real height = terrain.GetHeightAtTerrainPosition(tx, ty); 208 Real val = (height - minHeight0) / fadeDist0; 209 val = Utility.Clamp(val, 0, 1); 210 211 val = (height - minHeight1) / fadeDist1; 212 val = Utility.Clamp(val, 0, 1); 213 pBlend1[blendIdx++] = val; 214 } 215 } 216 217 blendMap0.Dirty(); 218 blendMap1.Dirty(); 219 blendMap0.Update(); 220 blendMap1.Update(); 221 } 222 223 public void GetTerrainReyHighter(ref Vector3 chaPos, float timeSinceLastFrame) 224 { 225 var ray = new Ray(new Vector3(chaPos.x, this.terrainPos.y + 10000, chaPos.z), Vector3.NegativeUnitY); 226 227 TerrainGroup.RayResult rayResult = this.terrainGroup.RayIntersects(ray); 228 Real distanceAboveTerrain = 5; 229 Real fallSpeed = 300; 230 Real newy = chaPos.y; 231 if (rayResult.Hit) 232 { 233 if (chaPos.y > rayResult.Position.y + distanceAboveTerrain) 234 { 235 this.fallVelocity += timeSinceLastFrame * 20; 236 this.fallVelocity = Utility.Min(this.fallVelocity, fallSpeed); 237 newy = chaPos.y - this.fallVelocity * timeSinceLastFrame; 238 } 239 newy = Utility.Max(rayResult.Position.y + distanceAboveTerrain, newy); 240 // Camera.Position = new Vector3(chaPos.x, newy, chaPos.z); 241 chaPos = new Vector3(chaPos.x, newy, chaPos.z); 242 } 243 if (this.heightUpdateCountDown > 0) 244 { 245 this.heightUpdateCountDown -= timeSinceLastFrame; 246 if (this.heightUpdateCountDown <= 0) 247 { 248 this.terrainGroup.Update(); 249 this.heightUpdateCountDown = 0; 250 } 251 } 252 } 253 }
在方法GetTerrainReyHighter里,我们在角色上面取一点与角色下面取一点组成Ray,然后计算与地形的交互点,得到正确的高度,这个具体过程有点像我原来用PyOpengl里的相关过程初试PyOpenGL二 (Python+OpenGL)基本地形生成与高度检测。地形的LOD等级主要与二方面有关,一个是当前块与摄像机位置,一个是当前块的高度。一般来说,与摄像机与近,当前块越高,lod数值越低,精度越高。
关于角色的控制SinbadCharacterController用的就是例子程序的,只做了一点小变动,就不说了。
UI我主要用到一个进度条与光标,提取例子里这些位置组合成下面这个。
1 public class UIManager : IResourceGroupListener 2 { 3 private string mName = "xinGame"; 4 5 protected RenderWindow mWindow; 6 protected ProgressBar LoadBar; 7 protected Real groupInitProportion; // proportion of load job assigned to initialising one resource group 8 protected Real groupLoadProportion; // proportion of load job assigned to loading one resource group 9 protected Real loadInc; 10 11 protected Overlay backdropLayer; // backdrop layer 12 protected OverlayElementContainer backdrop; // backdrop 13 14 protected Overlay cursorLayer; // cursor layer 15 protected OverlayElementContainer cursor; // cursor 16 17 protected Overlay mPriorityLayer; // top priority layer 18 protected OverlayElementContainer mDialogShade; // top priority dialog shade 19 20 public Overlay BackdropLayer 21 { 22 get 23 { 24 return this.backdropLayer; 25 } 26 protected set 27 { 28 this.backdropLayer = value; 29 } 30 } 31 public OverlayElement CursorImage 32 { 33 get 34 { 35 return this.cursor.Children[this.cursor.Name + "/CursorImage"]; 36 } 37 } 38 39 public UIManager(RenderWindow window) 40 { 41 this.mWindow = window; 42 OverlayManager om = OverlayManager.Instance; 43 44 String nameBase = this.mName + "/"; 45 nameBase.Replace(' ', '_'); 46 //背景 47 BackdropLayer = om.Create(nameBase + "BackdropLayer"); 48 BackdropLayer.ZOrder = 100; 49 this.backdrop = (OverlayElementContainer)om.Elements.CreateElement("Panel", nameBase + "Backdrop"); 50 BackdropLayer.AddElement(this.backdrop); 51 //光标 52 this.cursorLayer = om.Create(nameBase + "CursorLayer"); 53 this.cursorLayer.ZOrder = 400; 54 this.cursor = (OverlayElementContainer)om.Elements.CreateElementFromTemplate("SdkTrays/Cursor", "Panel", nameBase + "Cursor"); 55 this.cursorLayer.AddElement(this.cursor); 56 57 //进度条 58 this.mPriorityLayer = om.Create(nameBase + "PriorityLayer"); 59 this.mPriorityLayer.ZOrder = 300; 60 this.mDialogShade = (OverlayElementContainer)om.Elements.CreateElement("Panel", nameBase + "DialogShade"); 61 this.mDialogShade.MaterialName = "SdkTrays/Shade"; 62 this.mDialogShade.Hide(); 63 this.mPriorityLayer.AddElement(this.mDialogShade); 64 this.mPriorityLayer.Show(); 65 } 66 67 public void ShowBackdrop(String materialName) 68 { 69 if (materialName != String.Empty) 70 { 71 this.backdrop.MaterialName = materialName; 72 } 73 BackdropLayer.Show(); 74 } 75 76 public void HideBackdrop() 77 { 78 BackdropLayer.Hide(); 79 } 80 81 public void ShowLoadingBar(int numGroupsInit, int numGroupsLoad, Real initProportion) 82 { 83 if (this.LoadBar != null) 84 { 85 HideLoadingBar(); 86 return; 87 } 88 this.LoadBar = new ProgressBar(this.mName + "/LoadingBar", "Loading...", 400, 308); 89 OverlayElement e = this.LoadBar.OverlayElement; 90 this.mDialogShade.AddChild(e); 91 e.VerticalAlignment = VerticalAlignment.Center; 92 e.Left = (-(e.Width / 2)); 93 e.Top = (-(e.Height / 2)); 94 ResourceGroupManager.Instance.AddResourceGroupListener(this); 95 this.mDialogShade.Show(); 96 if (numGroupsInit == 0 && numGroupsLoad != 0) 97 { 98 this.groupInitProportion = 0; 99 this.groupLoadProportion = 1; 100 } 101 else if (numGroupsLoad == 0 && numGroupsInit != 0) 102 { 103 this.groupLoadProportion = 0; 104 if (numGroupsInit != 0) 105 { 106 this.groupInitProportion = 1; 107 } 108 } 109 else if (numGroupsInit == 0 && numGroupsLoad == 0) 110 { 111 this.groupInitProportion = 0; 112 this.groupLoadProportion = 0; 113 } 114 else 115 { 116 this.groupInitProportion = initProportion / numGroupsInit; 117 this.groupLoadProportion = (1 - initProportion) / numGroupsLoad; 118 } 119 } 120 121 public void HideLoadingBar() 122 { 123 if (this.LoadBar != null) 124 { 125 this.LoadBar.Cleanup(); 126 this.LoadBar = null; 127 ResourceGroupManager.Instance.RemoveResourceGroupListener(this); 128 this.mDialogShade.Hide(); 129 } 130 } 131 132 public void ShowCursor() 133 { 134 ShowCursor(String.Empty); 135 } 136 137 public void ShowCursor(String materialName) 138 { 139 if (materialName != String.Empty) 140 { 141 CursorImage.MaterialName = materialName; 142 } 143 144 if (!this.cursorLayer.IsVisible) 145 { 146 this.cursorLayer.Show(); 147 RefreshCursor(); 148 } 149 } 150 151 public void HideCursor() 152 { 153 this.cursorLayer.Hide(); 154 } 155 156 public void RefreshCursor() 157 { 158 var mouseStatus = Mouse.GetState(); 159 this.cursor.SetPosition(mouseStatus.X, mouseStatus.Y); 160 } 161 162 public void RefreshCursor(int x, int y) 163 { 164 this.cursor.SetPosition(x, y); 165 } 166 167 #region IResourceGroupListener 168 169 public void ResourceGroupScriptingStarted(string groupName, int scriptCount) 170 { 171 this.loadInc = this.groupInitProportion / scriptCount; 172 this.LoadBar.Caption = "Parsing..."; 173 this.mWindow.Update(); 174 } 175 176 public void ScriptParseStarted(string scriptName, ref bool skipThisScript) 177 { 178 this.LoadBar.Comment = System.IO.Path.GetFileName(scriptName); 179 this.mWindow.Update(); 180 } 181 182 public void ScriptParseEnded(string scriptName, bool skipped) 183 { 184 this.LoadBar.Progress = this.LoadBar.Progress + this.loadInc; 185 this.mWindow.Update(); 186 } 187 188 public void ResourceGroupScriptingEnded(string groupName) 189 { 190 } 191 192 public void ResourceGroupPrepareStarted(string groupName, int resourceCount) 193 { 194 } 195 196 public void ResourcePrepareStarted(Resource resource) 197 { 198 } 199 200 public void ResourcePrepareEnded() 201 { 202 } 203 204 public void ResourceGroupPrepareEnded(string groupName) 205 { 206 } 207 208 public void ResourceGroupLoadStarted(string groupName, int resourceCount) 209 { 210 this.loadInc = this.groupLoadProportion / resourceCount; 211 this.LoadBar.Caption = "Loading..."; 212 this.mWindow.Update(); 213 } 214 215 public void ResourceLoadStarted(Resource resource) 216 { 217 this.LoadBar.Comment = resource.Name; 218 this.mWindow.Update(); 219 } 220 221 public void ResourceLoadEnded() 222 { 223 this.LoadBar.Progress = this.LoadBar.Progress + this.loadInc; 224 this.mWindow.Update(); 225 } 226 227 public void WorldGeometryStageStarted(string description) 228 { 229 this.LoadBar.Comment = description; 230 this.mWindow.Update(); 231 } 232 233 public void WorldGeometryStageEnded() 234 { 235 this.LoadBar.Progress = this.LoadBar.Progress + this.loadInc; 236 this.mWindow.Update(); 237 } 238 239 public void ResourceGroupLoadEnded(string groupName) 240 { 241 } 242 #endregion 243 }
效果和例子里的一样。
UIManager这个主要是熟悉下相应的Overlay里相关类的用法,相关label,button,checkbox等这些控件都可以由Overlay,OverlayElementContainer,OverlayElement这些元素组成,这些元素相关属性都可以从对应的overlay文本文件反序列化得到,这样我们可以直接修改相关属性产生不同的效果,不需要改动代码,后面应该会详细介绍Overlay相关元素的代码与方法。需要说明的是,这个项目是控制台应用程序,建winform项目在这没用,上下文环境不一样,相关窗口,UI是不能混用的。
CameraManager这个类在这列出来,这里没怎么用,相关的鼠标与键盘处理都在TerrainManager中,在这也不列出来了。
附件XinGame.zip.需要大家自己下载Axiom项目,相关bin目录里引用的DLL有些多,上传限制就不发了。里面因地图比较大,我移动速度调的很快,大家可以自己修改。wsad方向,鼠标按下移动调整方向。