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

佴淮晨
2023-12-01

 

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

    }

    }

    }

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

 类似资料: