关于.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了!
在此只是一个黑屏,在下一节中我们将给她装饰一下^_^
在.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了!
在此只是一个黑屏,在下一节中我们将给她装饰一下^_^
相关文章推荐
- 关于.Net框架下3D游戏的设计与实现——2.2,使用我们的Axiom
- 关于.Net框架下3D游戏的设计与实现——1,引言
- 基于opengl的地图渲染引擎设计与实现
- 小型三维引擎设计实现-地球的渲染方法
- 关于 Vulkan 简介 —— Android N 引入新的 3D 渲染引擎
- 关于API的设计与实现
- EasyJWeb Tools业务引擎中分页的设计及实现
- 关于客户端Launcher的设计和实现的一些思路
- 【二】关于界面设计和监听实现
- EasyJWeb Tools业务引擎中分页的设计及实现
- CNN结构:MXNet设计和实现简介
- EasyJWeb Tools业务引擎中分页的设计及实现
- Linux内核设计与实现----Linux内核简介
- 关于最小化设计(尽早实现)的一些想法
- 游戏引擎/GUI的设计与实现-主题
- 游戏引擎/GUI的设计与实现-序
- 李沐等:MXNet设计和实现简介
- 关于工作流引擎取回审批的设计方案与实现过程
- 前端动画渲染引擎pixi.js系列(4)如何实现鼠标交互事件
- 微内核流程引擎(IVR导航)的设计与实现(一)——开发背景