XNA Game Engine教程系列2- Engine, Content和Services

在这一章中,我们要创建游戏引擎的主类。首先新添一个文件“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);

}

}

}

好了,就这么多了!不要失望,现在我们已经完成了游戏引擎的框架!在接下来的教程中,我们将真正开始一些游戏组件编写!


发布时间:2008/12/25 下午1:19:43  阅读次数:7218

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号