您的位置:首页 > 其它

关于.Net框架下3D游戏的设计与实现——2.1,渲染引擎Axiom简介

2008-04-13 15:17 357 查看
在上一篇中我展望了一下.Net游戏设计的美好前景(可是也只限于前景-_-! ,现在用.Net编游戏的公司还是很少),本节我们主要介绍一下在.Net下使用渲染引擎。

在.Net平台下可用的渲染器和渲染引擎还是不少的:

1,CSGL(在.Net下使用OpenGL的库),连接http://csgl.sourceforge.net/index.html

2,MDX(这个就不用我说了吧)

3,XNA & XNA 2.0 (微软次时代游戏开发框架),连接http://www.xna.com/

4,Torque X(功能及其强大的傻瓜型游戏编辑器,基于XNA)(收费)

5,Artificial Engines(一个基于MDX用VB.Net写的很优秀的图形引擎)http://www.3dlevel.com

6,隆重推出我在本系列中使用的引擎:Axiom,它是著名开源游戏引擎Ogre的.Net版,现在支持OpenGL和MDX两种渲染器(小道消息称在以后的版本中将支持XNA和Mono.XNA渲染器,最终实现一个跨平台的游戏渲染引擎!),可以支持原来的Ogre文件结构,而且还有支持3DMax模型导出插件oFusion(^_^)和场景编辑器。使用起来手感也不错。连接http://axiomengine.sourceforge.net/wiki/index.php/Main_Page,我使有的是Axiom 0.7.3.0,在http://sourceforge.net/project/showfiles.php?group_id=84345可以下载到。

先看看Axiom的Demo:



第一个界面给我的印象并不太好(-_-!又是黑白版面,仿佛回到了我六年级的时候),这是一个设置页用它来设置Axiom所需要的参数。一会儿我们会把它变成一个Windows窗口这样就不会太生硬了,顺便了解一下Axiom.Configuration.ConfigOption^_^.
接下来是一个例子选择界面:



看来例子还真不少(^_^),和Ogre差不多。选择25号例子看一下



果然和Ogre中一样而且速度也不慢,在我机器上跑了755 FPS(^_^)。其他例子大家可以把引擎下载下来自己看,都是Ogre的经典例子。
下面我来介绍一下我的第一个Axiom Demo(我使用的是Microsoft Visual C# 2005 Express sp1编写的例子,VS2005也可用):
第一步,在建立一个控制台工程。



第二步,把相应的应用程序集引入工程



需要把原来包中Bin目录下的DLL文件拷贝到你的工程的执行目录,并把所有Axiom打头的文件引用到你的工程。
第三步,要让Axiom工作需要一个基础程序类,就和DirectX中的EmptyProject 差不多,而且在Axiom的官网上可以找到,代码如下:


// This file was created from ExampleApplication.h (Ogle v1.2.3) by trejs


// for use with Axiom's tutorials.


//


// It still has some flaws. See the "// TODO" lines to begin with.


// But better that than no tutorials at all :)


//


// 2006-12-26: Fixed the mouse button issue, code now compatible with 0.7.1.0-RC2. / trejs


// 2007-08-17: Updated for compatibility with 0.7.2.0 / borrillis






Namespace Declarations#region Namespace Declarations


using System;


using System.Data;


using System.Collections.Generic;




using Axiom;


using Axiom.Core;


using Axiom.Graphics;


using Axiom.Configuration;


using Axiom.Math;


using Axiom.Overlays;


using Axiom.Input;


#endregion Namespace Declarations




namespace ExampleApplication




...{


public class ExampleApplication




...{




ConfigureConsole#region ConfigureConsole


// Code originally from the command-line demo launcher. Mostly rewritten.


//


// If you only want the configuration menu, just copy this class into your application.


// See how it's used in ExampleApplication.Configure().


private class ConfigureConsole




...{




Types#region Types


public enum DialogResult




...{


Continue,


Run,


Exit


}


#endregion






Private fields#region Private fields


// Currently selected system


private RenderSystem m_currentSystem;




// Holds the list of possible rendersystems


private ConfigOption m_renderSystems;




// Menu items


private List<ConfigOption> m_currentMenuItems = new List<ConfigOption>();




// Currently selected item


private ConfigOption m_currentOption;




// Options for the currently selected system


private List<ConfigOption> m_currentSystemOptions = new List<ConfigOption>();


#endregion






Properties#region Properties




/**//// <summary>


/// Gets the selected rendersystem (and all the values of options set for it)


/// </summary>


public RenderSystem RenderSystem




...{


get




...{


return m_currentSystem;


}


}


#endregion






Constructor#region Constructor


public ConfigureConsole()




...{


// Set current RenderSystem to first in list


m_currentSystem = Root.Instance.RenderSystems[0];




// Create rendersystem option and get possible values


m_renderSystems = new ConfigOption("Render System", m_currentSystem.Name, false);


foreach (RenderSystem r in Root.Instance.RenderSystems)


m_renderSystems.PossibleValues.Add(r.ToString());




// Build list of options for the currently selected rendersystem


BuildCurrentSystemOptions();


}


#endregion






Public methods#region Public methods


public DialogResult Show()




...{


while (true)




...{


// Clear menu buffer


m_currentMenuItems.Clear();




// Build menu


if (m_currentOption == null) // Main-menu




...{




// Build menu from m_currentOptions


foreach (ConfigOption c in m_currentSystemOptions)


m_currentMenuItems.Add(c);


}


else // Option menu




...{


// Add possible values for this option


foreach (object value in m_currentOption.PossibleValues)


m_currentMenuItems.Add(new ConfigOption(value.ToString(), "", false));


}




// Display menu


DisplayOptions();




// Handle next keypress (waits for user input)


DialogResult result = HandleNextKeyPress();




// Check if we're exiting the configure console


if (result != DialogResult.Continue)




...{


Console.Clear();


return result;


}


}


}


#endregion






Private methods#region Private methods


private void BuildCurrentSystemOptions()




...{


// Make shure it's empty


m_currentSystemOptions.Clear();




// Add rendersystem options to the list


m_currentSystemOptions.Add(m_renderSystems);




// Browse and add options of current rendersystem


foreach (ConfigOption c in m_currentSystem.ConfigOptions)


m_currentSystemOptions.Add(c);


}




private bool IsDigit(ref int digit, ConsoleKey key)




...{


string str = key.ToString();


if (str.Length == 2 && char.IsDigit(str[1]))




...{


digit = int.Parse(str.Substring(1)); // Numbers are returned like "D5"


return true;


}


else


return false;


}




private DialogResult HandleNextKeyPress()




...{


// Wait for key and then read it without echo to the console


ConsoleKey key = Console.ReadKey(true).Key;




// If the currentOption.Name is null, then we're in the main-menu


if (m_currentOption == null)




...{


// Escape exits, enter runs


if (key == ConsoleKey.Escape)


return DialogResult.Exit;


else if (key == ConsoleKey.Enter)




...{


// Save options for current system


for (int i = 0; i < m_currentSystemOptions.Count; i++)




...{


ConfigOption opt = m_currentSystemOptions[i];


m_currentSystem.ConfigOptions[opt.Name] = opt;


}




return DialogResult.Run;


}


else




...{


// Check for selection


int selection = 0;


if (IsDigit(ref selection, key) && selection < m_currentMenuItems.Count)


m_currentOption = (ConfigOption)m_currentMenuItems[selection];


}


}


else




...{


// Esc: Return to main-menu


if (key == ConsoleKey.Escape)


m_currentOption = null;




// Check if the current key is a digit, and if that digit is within the possible values


int selection = 0;


if (IsDigit(ref selection, key) && selection < m_currentOption.PossibleValues.Count)




...{


// Check if we're about to change render system


if (m_currentOption.Name == "Render System")




...{


m_currentSystem = Root.Instance.RenderSystems[selection];


m_renderSystems = m_currentOption;


BuildCurrentSystemOptions();




m_currentOption = null; // Reset current option (to show main-menu)


}


else




...{


m_currentOption.Value = (string)m_currentOption.PossibleValues[selection];




// Set the selected value


for (int i = 0; i < m_currentSystemOptions.Count; i++)


if (m_currentSystemOptions[i].Name == m_currentOption.Name)


m_currentSystemOptions[i] = m_currentOption;




m_currentOption = null; // Reset current option (to show main-menu)


}


}


}




return DialogResult.Continue;


}




private void DisplayOptions()




...{


Console.Clear();


Console.WriteLine("Axiom Engine Configuration");


Console.WriteLine("==========================");




if (m_currentOption != null && m_currentOption.Name != null)




...{


Console.WriteLine("Available settings for {0}. ", m_currentOption.Name);


}




// Load Render Subsystem Options


int i = 0;


foreach (object o in m_currentMenuItems)




...{


// If this is a possible-value of an option (and not a list of options in the main-menu)


// we need to display it's name but not the current value (it doesn't have any).


if (m_currentOption == null || m_currentOption.Name == null)


Console.WriteLine("{0} | {1}", i++, o);


else


Console.WriteLine("{0} | {1}", i++, ((ConfigOption)o).Name);


}




if (m_currentOption == null || m_currentOption.Name == null)




...{


Console.WriteLine();


Console.WriteLine("Enter | Saves changes.");


Console.WriteLine("ESC | Exits.");


}




Console.Write(" Select option: ");


}


#endregion


}


#endregion






Private fields#region Private fields


private string m_configFile = "EngineConfig.xml";


private string m_logFile = "AxiomExample.log";


private long m_lastOverlayUpdate = -1000;


private float m_moveScale = 0, m_rotScale = 0, m_moveSpeed = 100, m_rotateSpeed = 36;


private Vector2 m_rotateVector = new Vector2(0, 0);


private Vector3 m_translateVector = new Vector3(0, 0, 0);


private Root m_root;


private Camera m_camera;


private SceneManager m_sceneManager;


private RenderWindow m_renderWindow;


private InputReader m_inputReader;


#endregion Private fields






Protected properties#region Protected properties




/**//// <summary>


/// Root (Axiom.Core.Root)


/// </summary>


protected Root Root




...{


get




...{


return m_root;


}


set




...{


m_root = value;


}


}






/**//// <summary>


/// Camera (Axiom.Core.Camera)


/// </summary>


protected Camera Camera




...{


get




...{


return m_camera;


}


set




...{


m_camera = value;


}


}






/**//// <summary>


/// SceneManager (Axiom.Core.SceneManager)


/// </summary>


protected SceneManager SceneManager




...{


get




...{


return m_sceneManager;


}


set




...{


m_sceneManager = value;


}


}








/**//// <summary>


/// RenderWindow (Axiom.Graphics.RenderWindow)


/// </summary>


protected RenderWindow RenderWindow




...{


get




...{


return m_renderWindow;


}


set




...{


m_renderWindow = value;


}


}






/**//// <summary>


/// InputReader (Axiom.Input.InputReader)


/// </summary>


protected InputReader InputReader




...{


get




...{


return m_inputReader;


}


set




...{


m_inputReader = value;


}


}


#endregion Protected fields






Public properties#region Public properties




/**//// <summary>


/// Gets or set the config file name and path


/// </summary>


public string ConfigFile




...{


get




...{


return m_configFile;


}


set




...{


m_configFile = value;


}


}






/**//// <summary>


/// Gets or sets the config file name and path


/// </summary>


public string LogFile




...{


get




...{


return m_logFile;


}


set




...{


m_logFile = value;


}


}


#endregion






Init methods#region Init methods




/**//// <summary>


/// Starts the example


/// </summary>


public virtual void Run()




...{


try




...{


if (Setup())


m_root.StartRendering();


}


catch (System.Reflection.ReflectionTypeLoadException ex)




...{


// This catches directx missing (or too old) to log :)


for (int i = 0; i < ex.LoaderExceptions.Length; i++)


if (LogManager.Instance != null)


LogManager.Instance.Write(ex.LoaderExceptions[i].Message);


}


catch (Exception ex)




...{


if (LogManager.Instance != null)


LogManager.Instance.Write(ex.ToString());


}




// TODO: Memory cleanup here..


}






/**//// <summary>


/// Initalizes the application


/// </summary>


/// <returns>True if successfull, False to exit the application</returns>


protected virtual bool Setup()




...{


m_root = new Root(ConfigFile, LogFile);




SetupResources();




// Run config utility, exit program if it returns false


if (!Configure())


return false;


else


m_renderWindow = Root.Instance.Initialize(true);




// Initalize input


m_inputReader = PlatformManager.Instance.CreateInputReader();


m_inputReader.Initialize(m_renderWindow, true, true, false, true);




ChooseSceneManager();


CreateCamera();


CreateViewports();




// Set default mipmap level (NB some APIs ignore this)


TextureManager.Instance.DefaultNumMipMaps = 5;




// Create any resource listeners (for loading screens)


CreateResourceListener();




// Add some event handlers


RegisterEventHandlers();




// Lastly, create the scene


CreateScene();




return true;


}






/**//// <summary>


/// Adds the searchpaths from "EngineConfig.xml" to resources


/// </summary>


protected virtual void SetupResources()




...{


EngineConfig config = new EngineConfig();




config.ReadXml(ConfigFile);




foreach (EngineConfig.FilePathRow row in config.FilePath)


ResourceManager.AddCommonArchive(row.src, row.type);


}






/**//// <summary>


/// Configures the application


/// </summary>


/// <returns>True if successfull, False to exit the application</returns>


protected virtual bool Configure()




...{


ConfigureConsole cc = new ConfigureConsole();


if (cc.Show() == ConfigureConsole.DialogResult.Exit)


return false;




// Set selected rendersystem


m_root.RenderSystem = cc.RenderSystem;




return true;


}






/**//// <summary>


/// Chooses scene manager (SceneType.Generic)


/// </summary>


protected virtual void ChooseSceneManager()




...{


// Create a generic scene manager


m_sceneManager = m_root.SceneManagers.GetSceneManager(SceneType.Generic);


}






/**//// <summary>


/// Creates a camera at 500 in the Z direction that looks at -300 in the Z direction


/// </summary>


protected virtual void CreateCamera()




...{


// Create the camera


m_camera = m_sceneManager.CreateCamera("PlayerCam");




// Position it at 500 in the Z direction


m_camera.Position = new Vector3(0, 0, 500);




// Look back along -Z


m_camera.LookAt(new Vector3(0, 0, -300));


m_camera.Near = 5;


}






/**//// <summary>


/// Creates a viewport using mCamera


/// </summary>


protected virtual void CreateViewports()




...{


// Create one viewport, entire window


Viewport vp = m_renderWindow.AddViewport(m_camera);


vp.BackgroundColor = ColorEx.Black;




// Alter the camera aspect ratio to match the viewport


m_camera.AspectRatio = vp.ActualWidth / vp.ActualHeight;


}






/**//// <summary>


/// Optional override method where you can create resource listeners (e.g. for loading screens)


/// </summary>


protected virtual void CreateResourceListener()




...{




}






/**//// <summary>


/// Registers event handlers and calls InitOverlay()


/// </summary>


protected virtual void RegisterEventHandlers()




...{


m_root.FrameStarted += UpdateInput;


m_root.FrameStarted += UpdateOverlay;


m_root.FrameStarted += FrameStarted;


m_root.FrameEnded += FrameEnded;




// Create debug overlay


InitOverlay();


}






/**//// <summary>


/// Initalizes the debug overlay (fps, etc..)


/// </summary>


protected virtual void InitOverlay()




...{


Overlay o = OverlayManager.Instance.GetByName("Core/DebugOverlay");


if (o == null)


throw new Exception("Could not find overlay named 'Core/DebugOverlay'.");


o.Show();


}






/**//// <summary>


/// Creates the scene


/// </summary>


protected virtual void CreateScene()




...{




}


#endregion Init methods






Event handlers#region Event handlers




/**//// <summary>


/// This is run before each frame


/// </summary>


/// <param name="source"></param>


/// <param name="e"></param>


protected virtual void FrameStarted(object source, FrameEventArgs e)




...{




}






/**//// <summary>


/// This is run after each frame


/// </summary>


/// <param name="source"></param>


/// <param name="e"></param>


protected virtual void FrameEnded(object source, FrameEventArgs e)




...{




}






/**//// <summary>


/// Checks for input and handles it


/// </summary>


/// <param name="source"></param>


/// <param name="e"></param>


protected virtual void UpdateInput(object source, FrameEventArgs e)




...{


m_inputReader.Capture();






Camera movement#region Camera movement


// Reset vectors


m_rotateVector.x = m_translateVector.x = 0;


m_rotateVector.y = m_translateVector.y = 0;


m_translateVector.z = 0;




// Move


m_moveScale = m_moveSpeed * e.TimeSinceLastFrame;




// Rotate


m_rotScale = m_rotateSpeed * e.TimeSinceLastFrame;




// Move forward and back


if (m_inputReader.IsKeyPressed(KeyCodes.W) || m_inputReader.IsKeyPressed(KeyCodes.Up))


m_translateVector.z = -m_moveScale;


else if (m_inputReader.IsKeyPressed(KeyCodes.S) || m_inputReader.IsKeyPressed(KeyCodes.Down))


m_translateVector.z = m_moveScale;




// Move left and right


if (m_inputReader.IsKeyPressed(KeyCodes.A))


m_translateVector.x = -m_moveScale;


else if (m_inputReader.IsKeyPressed(KeyCodes.D))


m_translateVector.x = m_moveScale;




// Move up and down


if (m_inputReader.IsKeyPressed(KeyCodes.PageUp))


m_translateVector.y = m_moveScale;


else if (m_inputReader.IsKeyPressed(KeyCodes.PageDown))


m_translateVector.y = -m_moveScale;




// Rotate left and right


if (m_inputReader.IsKeyPressed(KeyCodes.Left))


m_rotateVector.x = -m_rotScale;


else if (m_inputReader.IsKeyPressed(KeyCodes.Right))


m_rotateVector.x = m_rotScale;




// Right mouse button pressed


if (m_inputReader.IsMousePressed(MouseButtons.Right))




...{


// Translate


m_translateVector.x += m_inputReader.RelativeMouseX * 0.13f;


m_translateVector.y -= m_inputReader.RelativeMouseY * 0.13f;


}


else




...{


// Apply mouse rotation


m_rotateVector.x += m_inputReader.RelativeMouseX * 0.13f;


m_rotateVector.y += m_inputReader.RelativeMouseY * 0.13f;


}




// Apply changes


m_camera.Yaw(-m_rotateVector.x);


m_camera.Pitch(-m_rotateVector.y);


m_camera.MoveRelative(m_translateVector);


#endregion Camera movement




// TODO: what about window-closing-event?


if (m_inputReader.IsKeyPressed(KeyCodes.Escape))




...{


Root.Instance.QueueEndRendering();




// TODO: Find a better way


if (m_root != null)




...{


// remove event handlers


// engine.FrameStarted -= new FrameEvent( OnFrameStarted );


// engine.FrameEnded -= new FrameEvent( OnFrameEnded );




m_root.Dispose();


}




m_sceneManager.RemoveAllCameras();


m_sceneManager.RemoveCamera(m_camera);


m_camera = null;


Root.Instance.RenderSystem.DetachRenderTarget(m_renderWindow);


m_renderWindow.Dispose();


return;


}




}






/**//// <summary>


/// Updates the debug overlay


/// </summary>


protected virtual void UpdateOverlay(object source, FrameEventArgs e)




...{


if (Root.Instance.Timer.Milliseconds - m_lastOverlayUpdate >= 1000)




...{


m_lastOverlayUpdate = Root.Instance.Timer.Milliseconds;




OverlayElement element =


OverlayElementManager.Instance.GetElement("Core/DebugText");


element.Text = m_renderWindow.DebugText;




element = OverlayElementManager.Instance.GetElement("Core/CurrFps");


element.Text = string.Format("Current FPS: {0}", Root.Instance.CurrentFPS);




element = OverlayElementManager.Instance.GetElement("Core/BestFps");


element.Text = string.Format("Best FPS: {0}", Root.Instance.BestFPS);




element = OverlayElementManager.Instance.GetElement("Core/WorstFps");


element.Text = string.Format("Worst FPS: {0}", Root.Instance.WorstFPS);




element = OverlayElementManager.Instance.GetElement("Core/AverageFps");


element.Text = string.Format("Average FPS: {0}", Root.Instance.AverageFPS);




element = OverlayElementManager.Instance.GetElement("Core/NumTris");


element.Text = string.Format("Triangle Count: {0}", m_sceneManager.TargetRenderSystem.FacesRendered);


}


}


#endregion Events


}


}



以上代码主要包含了两个类:ConfigureConsole和ExampleApplication,ConfigureConsole包含于ExampleApplication 用于显示开始是的那个选项菜单。而ExampleApplication就是我们的应用程序框架,以后的例子都继承与ExampleApplication 。对于ExampleApplication的介绍和改进我们留到后面几个章节,接下来我们开始让程序运行起来。
第四步,创建我们的游戏类
创建一个类(我在这里创建了名为AxiomTest的类)继承与ExampleApplication,在一个适当的地方加入如下代码:


class Program




...{




static void Main(string[] args)




...{


AxiomTest app = new AxiomTest();


app.Run();


}




}

好了!然后按F5(^_^激动中...)。
恩,怎么回事,竟然有警告-_-!。是的,在运行Axiom是需要解除读取锁定警告。否则无法运行。



然后再按F5就可以看到我们的HelloWorld了!
在此只是一个黑屏,在下一节中我们将给她装饰一下^_^
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: