在这一章中,我们要创建游戏引擎的主类。首先新添一个文件“Engine.cs”到文件夹“Components”中,然后加入以下代码以创建一个静态类“Engine”。这个类与前面我们创建的其他类的区别是它不需要实例化(使用“new ObjectType ();”),这里我们使用“Engine.MemberName”获取其成员。以下是是开始代码:
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Innovation
{
public static class Engine { }
}
接着我们要添加一些成员变量到Engine类中。
// The GraphicsDevice the engine is using
public static GraphicsDevice GraphicsDevice;
// The Engine's SpriteBatch
public static SpriteBatch SpriteBatch;
// The collection of GameScreens we are managinng
public static GameScreenCollection GameScreens = new GameScreenCollection();
// The current GameTime
public static GameTime GameTime;
// Whether the Engine has been initialized yet
public static bool IsInitialized = false;
下一步我们要创建一个方法来初始化引擎。这个方法接受IgraphicsDeviceService对象以帮助我们建立引擎的graphics。如果是从Game类初始化的话,这个graphics就是在默认游戏模板中创建的GraphicsDeviceManager graphics。下面是initialize方法:
// Initializes the engine
public static void SetupEngine(IGraphicsDeviceService GraphicsDeviceService)
{
// Setup the GraphicsDevice and SpriteBatch
Engine.GraphicsDevice = GraphicsDeviceService.GraphicsDevice;
Engine.SpriteBatch = new SpriteBatch(GraphicsDeviceService.GraphicsDevice);
Engine.IsInitialized = true;
}
下一个我们需要的方法是update方法。这将更新所有的GameScreens,而GameScreens更新包含其中的组件。更新和绘制的逻辑有点复杂,因为screen可以互相阻止绘制和更新。update接受GameTime用来更新内部的GameTime,Draw方法也一样。
// Update the engine, screens, and components
public static void Update(GameTime gameTime)
{
// Update the game time
Engine.GameTime = gameTime;
// Create a temporary list
List updating = new List();
// Populate the temp list
foreach (GameScreen screen in GameScreens)
updating.Add(screen);
// BlocksUpdate and OverrideUpdateBlocked login
for (int i = GameScreens.Count - 1; i >= 0; i--)
if (GameScreens[i].BlocksUpdate)
{
if (i > 0)
for (int j = i - 1; j >= 0; j--)
if (!GameScreens[j].OverrideUpdateBlocked)
updating.Remove(GameScreens[j]);
break;
} // Update remaining components
foreach (GameScreen screen in updating)
if (screen.Initialized)
screen.Update();
// Clear list
updating.Clear();
// Repopulate list
foreach (GameScreen screen in GameScreens)
updating.Add(screen);
// BlocksInput and OverrideInputBlocked login
for (int i = GameScreens.Count - 1; i >= 0; i--)
if (GameScreens[i].BlocksInput) { if (i > 0)
for (int j = i - 1; j >= 0; j--)
if (!GameScreens[j].OverrideInputBlocked)
updating.Remove(GameScreens[j]);
break;
}
// Set IsInputAllowed for all GameScreens
foreach (GameScreen screen in GameScreens)
if (!screen.InputDisabled)
screen.IsInputAllowed = updating.Contains(screen);
else
screen.IsInputAllowed = false;
}
draw方法非常相似,我们为BlocksDraw和组件做了一些逻辑,然后我们绘制它们。
// Draws the current collection of screens and components. Accepts a
// ComponentType to render
public static void Draw(GameTime gameTime, ComponentType RenderType)
{
// Update the time, create a temp list
Engine.GameTime = gameTime;
List drawing = new List();
// Clear the back buffer
GraphicsDevice.Clear(Color.CornflowerBlue);
// Populate the temp list if the screen is visible
foreach (GameScreen screen in GameScreens)
if (screen.Visible)
drawing.Add(screen);
// BlocksDraw and OverrideDrawBlocked logic
for (int i = GameScreens.Count - 1; i >= 0; i--)
if (GameScreens[i].BlocksDraw)
{
if (i > 0)
for (int j = i - 1; j >= 0; j--)
{
if (!GameScreens[j].OverrideDrawBlocked)
drawing.Remove(GameScreens[j]);
}
break;
}
// Draw the remaining screens
foreach (GameScreen screen in drawing)
if (screen.Initialized)
screen.Draw(RenderType);
}
下一步我们将添加一个IServiceContainer对象。这个对象将追踪叫做Service Providers的对象。我们可以根据类型检索和存储Service Providers。例如,为了获得GraphicsDeviceService,我们将使用“Engine.Services.GetService(typeof (IGraphicsDeviceService));”。或者,为了存储叫做“content”的ContentManager对象,我们使用“Engine.Services.AddService(typeof(ContentManager),content);”。我们将创建一个继承自IserviceContainer的类,其中包含了添加,删除并获得服务的方法。以下是代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
namespace Innovation
{
public class IEServiceContainer : IServiceProvider
{
// Contains the service types and services Dictionary services = new Dictionary(); // Add a new service public void AddService(Type Service, object Provider)
{ // If we already have this type of service provider, throw an
// exception if (services.ContainsKey(Service)) throw new Exception("The service container already has a " + "service provider of type " + Service.Name); // Otherwise, add it to the list this.services.Add(Service, Provider); }
// Get a service from the service container public object GetService(Type Service) {
// If we have this type of service, return it foreach (Type type in services.Keys) if (type == Service) return services[type];
// Otherwise, throw an exception throw new Exception("The service container does not contain " + "a service provider of type " + Service.Name); } // A shortcut way to get a service. The benefit here is that we
// can specify the type in the brackets and also return the // service of that type. For example, instead of
// "Camera cam = (Camera)Services.GetService(typeof(Camera));", // we can use "Camera cam = Services.GetService()" public T GetService() { object result = GetService(typeof(T)); if (result != null) return (T)result; return default(T); } // Removes a service provider from the container public void RemoveService(Type Service) { if (services.ContainsKey(Service)) services.Remove(Service); } // Gets whether or not the container has a provider of this type public bool ContainsService(Type Service) { return services.ContainsKey(Service); } } }
现在,我们需要在Engine类中建立一个实例。在Engine类顶部添加以下代码:
// The engine's service container public static IEServiceContainer Services;
和在SetupEngine()方法中添加以下代码:
// Setup the service container and add the IGraphicsDeviceService to it Engine.Services = new IEServiceContainer(); Engine.Services.AddService(typeof(IGraphicsDeviceService), GraphicsDeviceService);
现在我们要创建一个自定义ContentManager并将其添加到Engine类中。我们ContentManager将扩展XNA的ContentManager,能让我们选择是否使用content缓存(缓存能存储加载的对象,这样我们可以无需多次加载同一内容),并让我们可以卸载特定的内容而不是卸载所有的内容。以下是代码:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Content;
namespace Innovation
{
public class IEContentManager : ContentManager
{
// Do nothing in the constructor except inherit from ContentManager
public IEContentManager(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
// Whether or not we should keep objects that have been loaded. This
// way we can avoid loading assets multiple times. However, this may
// lead to problems with multiple objects changing loaded data, such
// as effects on a model
public bool PreserveAssets = true;
// Keep a list of disposable assets and loaded assets
List disposable = new List();
Dictionary loaded = new Dictionary();
// Override loading of assets so we can use our own functionality
public override T Load(string assetName) {
// Create a new instance of the requested asset
T r = this.ReadAsset(assetName, RecordIDisposable);
// If we are holding on to loaded assets, add it to the list of
// loaded assets
if (PreserveAssets && !loaded.ContainsKey(assetName)) l
oaded.Add(assetName, r);
// Return the loaded asset
return r;
}
// Internal method to record disposable assets
void RecordIDisposable(IDisposable asset) {
// If we are monitoring loaded assets, add it to the list of
// disposable assets
if (PreserveAssets)
disposable.Add(asset);
}
// Unload all content
public override void Unload()
{
// Dispose all disposable assets
foreach (IDisposable disp in disposable)
disp.Dispose();
// Clear all loaded assets loaded.Clear();
disposable.Clear();
}
// Unload a specific piece of content
public void Unload(string assetName)
{
// If the asset has been loaded
if (loaded.ContainsKey(assetName))
{
// If it is disposable, dispose it and take it off the
// list of disposable content
if (loaded[assetName] is IDisposable && disposable.Contains((IDisposable)loaded[assetName]))
{
IDisposable obj = disposable[ disposable.IndexOf((IDisposable)loaded[assetName])];
obj.Dispose();
disposable.Remove(obj); }
// Take it off the list of loaded content
loaded.Remove(assetName);
}
}
}
}
现在,我们需要添加一个content manager实例到Engine类中。在其他成员声明附近添加以下代码:
// The engine's content manager
public static IEContentManager Content;
现在,将这个Content添加到SetupEngine()最后并初始化:
// Setup the content manager using the service container
Engine.Content = new IEContentManager(Services);
现在我们要做的是添加一些GameScreens到Engine类。第一个GameScreen被称为BackgroundScreen,并重写了BlocksDraw,BlocksUpdate和BlocksInput。这样,我们可以拥有一些在后台运行的组件,这些组件需要更新或接受输入,如Input类。我们还将创建一个叫做DefaultScreen的GameScreen,并将它设置到BackgroundScreen。组件将加入到这个screen中。我们可以在运行时改变DefaultScreen,这样新的组件将建立在代替的screen上。如果现在的DefaultScreen被禁用,这将返回到BackgroundScreen。所以,加入以下代码到Engine类:
// GameScreen provided by the engine.
public static GameScreen BackgroundScreen;
// The GameScreen to set to new GameScreens when a screen is not specified
public static GameScreen DefaultScreen;
并添加以下代码到SetupEngine()中:
// Setup the background screen
BackgroundScreen = new GameScreen("Engine.BackgroundScreen");
BackgroundScreen.OverrideUpdateBlocked = true;
BackgroundScreen.OverrideDrawBlocked = true;
BackgroundScreen.OverrideInputBlocked = true;
// Set the default screen to the background screen so new screens will
// use it automatically unless told otherwise
DefaultScreen = BackgroundScreen;
最后我们需要做的是创建GameScreenCollection类。这个类跟踪GameScreens。它继承自KeyedCollection,因此它可以根据GameScreen的名称返回一个GameScreen,例如: “Engine.GameScreens [”GameScreenName“]”。这样做比使用列表好,因为使用列表的话我们必须跟踪screen的IDs。还有一些逻辑是处理重置Engine.DefaultScreen(如果DefaultScreen被移除的时候)。代码如下:
using System.Collections.ObjectModel;
namespace Innovation
{
public class GameScreenCollection : KeyedCollection
{ // Allow us to get a screen by name like so:
// Engine.GameScreens["ScreenName"]
protected override string GetKeyForItem(GameScreen item)
{ return item.Name; }
protected override void RemoveItem(int index)
{
// Get the screen to be removed
GameScreen screen = Items[index];
// If this screen is the current default screen, set the
// default to the background screen
if (
Engine.DefaultScreen == screen)
Engine.DefaultScreen = Engine.BackgroundScreen;
base.RemoveItem(index);
}
}
}
好了,就这么多了!不要失望,现在我们已经完成了游戏引擎的框架!在接下来的教程中,我们将真正开始一些游戏组件编写!