程序内容生成的第一部分:通用资产工厂(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/procedural-content-generation-part-i-generic-asset-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 17 分钟阅读 - 8046 个词 阅读量 0程序内容生成的第一部分:通用资产工厂(译文)
原文地址:https://www.codeproject.com/Articles/545824/Procedural-Content-Generation-Part-I-Generic-Asset
原文作者:CoderGirl42
译文由本站 robot-v1.0 翻译
前言
An extensible procedural generation framework
可扩展的程序生成框架
介绍(Introduction)
程序生成是游戏中通过算法和数学创建动态内容的一种技术.程序生成的方法很多,在本系列文章的整个过程中,我将带您介绍一些我开发的技术.在第一章中,我们将创建一个通用工厂模式,用于加载和管理游戏资产.我们将把这些资产组合到场景中,然后在随后的章节中研究在运行时以程序方式创建它们的不同方法.(Procedural generation is a technique in gaming to create dynamic content through algorithms and mathematics. There are many approaches to procedural generation, I will take you through a few techniques I have developed throughout the course of this article series. In the first chapter, we will create a generic factory pattern for loading and managing in game assets. We will compose these assets into scenes and then examine different approaches to procedurally create them at runtime in subsequent chapters.)
- 程序内容生成的第一部分:通用资产工厂(Procedural Content Generation Part I: Generic Asset Factory)
- 程序内容生成第二部分:加载块和图块(Procedural Content Generation Part II: Loading Chunks and Tiles)
- 程序内容生成第三部分:噪声算法(Procedural Content Generation Part III: Noise Algorithms)
- 程序内容生成第四部分:基于代理的方法(Procedural Content Generation Part IV: An Agent Based Approach)
- 示例代码:(Example Code:) Azmyth GitHub存储库(Azmyth GitHub Repository)
背景(Background)
多年来,我对过程内容的生成很感兴趣.我已经尝试了几种不同的概念和方法,并取得了不同程度的成功.我这样做是一种业余爱好,我想与社区分享我的一些经验.我的很多工作都是基于(I have been interested in procedural content generation for many years. I have experimented with several different concepts and approaches with varying degrees of success. I do this as a hobby and I want to share some of my experience with the community. A lot of my work is based on techniques by) 肯`佩林(Ken Perlin) . Ken Perlin开发了(. Ken Perlin developed the) 佩林噪声(Perlin Noise) 1982年为电影<特隆>拍摄的算法.后来,他的工作获得了学院奖.我也喜欢Minecraft之类的游戏,并享受程序生成的体素环境.我的目标是将不同的方法结合在一起,以创建一个灵活的工具箱,以用C#生成过程.(algorithm in 1982 for the movie Tron. He later won an academy award for his work. I also like games such as Minecraft and enjoy the procedurally generated voxel environment. My goal is to combine different approaches together to create a flexible toolbox for procedural generation in C#.)
总览(Overview)
我们将首先创建一个通用资产管理系统,该系统可用于创建和存储2D资产.第一步是创建接口的核心,这些接口将成为我们通用类型系统的基础.一旦设置了接口,就可以创建一个特定的实现以产生我们选择的内容.(We are going to start by creating a generic asset management system that can be used to create and store 2D assets. The first step is to create a core of interfaces that will be the base of our generic type system. Once we have the interfaces setup, we can go about creating a specific implementation to produce content of our choice.)
我将首先创建一个2D拼贴系统,该系统可以动态创建地形.要绘制看起来不错的地图,我们可能需要加载和跟踪许多资产.该系统将使我们能够管理程序生成的资产的生命周期.我们将介绍代理商和(I am going to start by creating a 2D tiling system that can create terrain dynamically. To render a decent looking map, we may need to load and track many assets. This system will allow us to manage the lifecycle of the procedurally generated assets. We will cover Agents and the) IAgent
界面在下一章中.为简单起见,我们现在暂时将此工作呈现到控制台.可以很容易地将此设计扩展到其他平台,例如GDI +,XNA,MonoGame或其他游戏平台!(interface in a later chapter. To keep it simple, we are just going to render this work to the console for now. It will be easy to extend this design to other platforms like GDI+, XNA, MonoGame, or other gaming platforms!)
场景(Scenes)
第一步是创建场景界面.场景是在2D或3D空间中空间排列的游戏资产的集合.我们将从2D开始,然后在后续章节中探索将其扩展到3D的方法.的(The first step is to create a scene interface. A scene is a collection of game assets arranged spatially in either 2D or 3D space. We will start with 2D and then explore ways to extend this to 3D in subsequent chapters. The) IScene
接口将负责存储和管理游戏资产的生命周期.(interface will be responsible for storing and managing the lifecycle of in game assets.)
// The Scene interface.
public interface IScene
{
// Registers an Asset of type T with the Abstract Factory.
IScene RegisterAsset<T>()
where T : class, IAsset;
// Registers Loaders of type T2 for type T1. Implements the Abstract Factory for type T1.
IScene RegisterLoader<T1, T2>()
where T1 : class, IAsset
where T2 : class, ILoader;
// Gets a list of IAssets of type T.
List<IAsset> GetAssets<T>() where T : class, IAsset;
// Queries a list of IAssets of type T within a boundary.
List<IAsset> QueryAssets<T>(RectangleF bounds) where T : class, IAsset;
// Loads an Asset of type T at location (X, Y)
T LoadAsset<T>(IAsset parent, int x, int y) where T : class, IAsset;
// Unloads an Asset and calls Dispose.
void UnloadAsset(IAsset asset);
}
该接口创建一个简单的通用工厂模式,以基于(This interface creates a simple generic factory pattern to manage multiple types based on) IAsset
.它还允许您查询场景中存储的资产,并根据需要加载和卸载资产.我们将在另一部分介绍具体的实现.(. It also allows you to query assets stored within the scene and load and unload assets as needed. We’ll cover a concrete implementation in another section.)
资产(Assets)
资产是我们游戏环境的基础.资产基本上是用于存储代表我们游戏世界各个方面的数据的容器.这些可能是地图的图块,项目,npc或什至是音频区域和加载区域等更高级的概念.在本系列中,我不会涵盖所有这些想法.随着我们的进步,您将希望认识到该系统管理多种不同类型内容的灵活性.我们将实施(Assets are the building blocks of our game environment. Assets are basically containers for storing data to represent different aspects our game world. These could be tiles for a map, items, npcs, or even more advanced concepts like audio zones and loading areas. I’m not going to cover all of these ideas in this series. As we progress, you will hopefully recognize the flexibility of this system to manage many different types of content. We will implement the) IAsset
界面如下:(interface as follows:)
// The IAsset interface, requires implementation of IDisposable.
public interface IAsset : IDisposable
{
// Referene to the containing Scene object.
IScene Scene { get; }
// A Referene a Parent Asset if one exists.
IAsset Parent { get; }
// A boolean indicator for disposed status.
bool Disposed { get; }
// A rectangle that represents the Assets location in the Scene.
RectangleF Bounds { get; }
// An Event for the QuadTree Implementation used in this example.
event EventHandler BoundsChanged;
// An Update function updating assets in a game loop.
void Update(TimeSpan elapsed);
}
如您所见,这是一个相当简单的实现,可以在资产和场景之间建立关系.您还将注意到我们有一个对父对象的引用.这允许将资产更高级地合成为简单结构.资产实施(As you can see, it’s a fairly simple implementation that sets up a relationship between the asset and a scene. You’ll also notice we have a reference to a parent object. This allows for more advanced compositing of assets into simple structures. The asset implements) IDisposable
并具有用于指示已处置状态的指示器-与(and has an indicator for disposed status - this works with the) IDisposable
接口.资产的位置是通过(interface. The location of the asset is retrieved through the) Bounds
属性,执行此(property, the implementation of this) QuadTree
用途(uses) float
s,但稍后我们将为切片系统将它们转换为整数.还提供了更新功能,用于游戏循环中的更新.(s but we are just going to convert these to integers later for our tiling system. An update function is also provided for updates in a game loop.)
装载机(Loaders)
加载程序代表每种类型的工厂模式的实现(Loaders represent the implementation of the factory pattern for each type of) IAsset
在现场注册.我们将创建一个接口,以便为每种注册的类型提供自定义实现.稍后将看到,这为过程生成技术提供了很大的灵活性.我们将为示例实现过程加载程序,但可以轻松实现加载程序以加载其他介质,例如(registered with the scene. We’ll create an interface so that we can provide a custom implementation for each type registered. This allows for a lot of flexibility in procedural generation techniques as you’ll see later. We’re going to implement a procedural loader for our example, but loaders can easily be implemented to load for other mediums, like the) filesystem
,或数据库.(, or a database.)
public interface ILoader
{
IAsset LoadAsset(IScene scene, IAsset parent, int x, int y);
}
的(The) ILoader
在这种情况下,接口只有一种方法,(interface in this case only has one method,) LoadAsset
.的(. The) LoadAsset
函数获取包含场景,父资产(如果有),(function takes the containing scene, a parent asset(if any), an) X
和(and) Y
二维坐标系中的位置和可选参数.我们可以实施(position in our 2D coordinate system and an optional parameter. We could implement) LoadAsset
将采用文件名或数据库ID的重载.(overloads that would take a file name or database ID.)
代理商(Agents)
代理将在本系列的后续部分中介绍.我将在这里简短地提及它们,以便您可以更好地了解整体设计.代理将使我们能够执行自定义的程序生成任务,这些任务不必使用诸如Perlin Noise之类的体积填充算法.代理的工作方式更像是有限状态自动机,并且可以通过一组简单的规则来修改资产.(Agents will be addressed in a later part of this series. I will mention them briefly here so you can have a better idea of the overall design. Agents will allow us to do customized procedural generation tasks that don’t necessarily use volumetric filling algorithms like Perlin Noise. Agents work more like a finite state automata and can modify assets with a simple set of rules.)
创建一个场景(Creating a Scene)
现在已经设置了核心接口,我们可以创建一个实现.我创建了一个名为(Now that the core interfaces are setup, we can create an implementation. I created a new class called) Scene
继承(that inherits) IScene
.我选择使用(. I chose to use a) QuadTree
存储该项目的资产,但也可以使用其他数据结构,例如数组,八叉树或任何适合您需要的数据.(to store assets for this project but it is possible to use other data structures like an array, octree, or whatever suits your needs.)
public class Scene : IScene
{
...
}
接下来,我们需要定义接口指定的每个函数.我们将从注册资产类型开始,然后继续进行加载程序.(Next, we’ll need to define each function specified by the interface. We’ll start with registering asset types and then move on to loaders.)
注册资产(Registering Assets)
首先,建立一个(First, create a) List<Type>
要存储在场景中注册的资产类型,我们还需要一个(to store asset types registered with the Scene, we’ll also need a) Dictionary
以资产类型为键来存储(with the asset types as keys to store a) QuadTree
对于已注册的每种资产类型.然后要注册资产类型,我们将创建一个采用类型的通用方法(for each asset type registered. Then to register asset types, we will create a generic method that takes a type) 必须是继承自的类(*which must be a class that inherits from*) `IAsset` .我们将使用由定义的类型(*. We'll use the type defined by*)
在场景管理的资产类型列表中创建一个新条目.我退回(to create a new entry in a list of asset types managed by the scene. I return the) this
创建流畅接口的参考,以后我们调用这些函数时,您将看到它是如何工作的.(reference to create a fluent interface, you’ll see how this works when we call these functions later.)
// List of types registered with Scene.
private List<Type> m_assetTypes = new List<Type>();
// List of QuadTrees to store assets of different types.
private Dictionary<Type, QuadTree<IAsset>> m_assets = new Dictionary<Type, QuadTree<IAsset>>();
...
// Register Asset.
public IScene RegisterAsset<T>() where T : class, IAsset
{
// Convert Generic T to Type.
Type assetType = typeof(T);
// Throw an exception if this type is already registered.
if (m_assetTypes.Contains(assetType))
{
throw new Exception(assetType.Name + " already registered.");
}
// Register Asset Type with the Scene.
m_assetTypes.Add(assetType);
// Create a new QuadTree to store loaded assets of type T.
m_assets.Add(assetType, new QuadTree<IAsset>(new Size(100, 100), 1));
// Return this for fluent interface.
return this;
}
注册装载机(Registering Loaders)
当注册一个加载器时,我们将实例化一个实例并将其存储在按资产类型索引的字典中.稍后,我们将使用该对象创建资产的实例.要创建加载程序的实例,我们将使用反射,我们可以调用(When we register a loader, we’re going to instantiate a single instance and store in a dictionary indexed by the asset type. We’ll later use this object to create instances of our assets. To create an instance of the loader, we’ll use reflection, we can call) Activator.CreateInstance<T2>
创建一个新实例.(to create a new instance.)
...
// Dictionary of Registered Loaders.
private Dictionary<Type, ILoader> m_assetLoaders = new Dictionary<Type, ILoader>();
...
// Register ILoader of type T2 where T1 is an IAsset
public IScene RegisterLoader<T1, T2>() where T1 : class, IAsset where T2 : class, ILoader
{
// Convert generics to types.
Type assetType = typeof(T1);
Type loaderType = typeof(T2);
// Throw an Exception if T1 has not been registered.
if (!m_assetTypes.Contains(assetType))
{
throw new Exception("Unable to register loader without registered asset.");
}
// Ensure a single instance of the Loader is created.
if (!m_assetLoaders.ContainsKey(assetType))
{
// Use Reflection to create an instance of loader T2.
m_assetLoaders.Add(assetType, Activator.CreateInstance<T2>());
}
// Return this for fluent interface.
return this;
}
资产和装载机(Assets & Loaders)
要加载资产,我们需要创建一个资产和一个加载器类,它们继承自(To load an asset, we need to create an asset and a loader class that inherit from) IAsset
和(and) ILoader
分别.在此示例中,我们将创建两个通用资产,每个资产都有一个加载器,以演示抽象工厂模式将如何工作.进入第二章时,我们将在加载块和图块时看到此模式的一些高级用法.(respectively. In this example, we’re just going to create a two generic assets each with a loader to demonstrate how the abstract factory pattern will work. When we get to the second chapter, we will see some more advanced uses of this pattern when we load chunks and tiles.)
资产类型(Asset Types)
我将创建两个新的资产类型.(I’m just going to create two new asset types.) Asset1
和(and) Asset2
,这两个都将继承自(, both of which will inherit from) IAsset
.两种类型将具有相同的实现,但我还将添加一个(. Both types will have the same implementation but I am also going to add a) Name
每个属性都将返回"(property to each which will return “) Asset1
“和”(” and “) Asset2
为我们的演示.(” for our demo.)
// Asset1 concrete implementation of IAsset.
public class Asset1 : IAsset
{
public IScene Scene { get; private set; }
public IAsset Parent { get; private set; }
public RectangleF Bounds { get; private set; }
public bool Disposed { get; private set; }
public event EventHandler BoundsChanged;
public string Name
{
get
{
return "Asset1";
}
}
public Asset1(IScene scene, IAsset parent, int x, int y)
{
Scene = scene;
Parent = parent;
Bounds = new RectangleF(x, y, 1, 1);
}
public void Update(TimeSpan elapsed)
{
// Perform any asset specific logic.
}
public void Dispose()
{
if(!Disposed)
{
// Perform any Cleanup here.
Disposed = true;
}
}
}
接下来,(Next, the) Asset2
该类的实现会稍有不同,只需复制(class will have a slightly different implementation, just copy the) Asset1
阶级与变化(class and change) Asset1
至(to) Asset2
.通常,您的资产类型将具有与名称不同的属性,但是对于此演示,我们将使其保持简单.(. Typically, your asset types will have different properties than just a name, but for this demo, we’re just going to keep it simple.)
装载机类型(Loader Types)
创建一个加载器非常简单,在这个例子中,我们将实例化新的加载器.(Creating a loader is pretty simple, in this example, we’re just going to instantiate our new) Asset
类型.我们每个都需要一个不同的加载器(types. We’ll need a different loader for each) Asset
类型.这将允许我们对每种类型使用自定义加载逻辑.这可能意味着要从文件,数据库中加载资产,或者使用稍后将要介绍的程序方法来加载资产.(type. This will allow us to use custom loading logic for each type. This could mean loading assets from a file, database, or by using a procedural approach as you’ll see later.)
// Asset1 Loader class.
public class Asset1Loader : ILoader
{
public IAsset LoadAsset(IScene scene, IAsset parent, int x, int y)
{
// Create a new asset using a constructor
Asset1 asset = new Asset1(scene, parent, x, y);
// Perform additional loading logic here.
// Return the loaded asset.
return asset;
}
}
// Asset2 Loader class.
public class Asset2Loader : ILoader
{
public IAsset LoadAsset(IScene scene, IAsset parent, int x, int y)
{
// Create a new asset using a constructor
Asset2 asset = new Asset2(scene, parent, x, y);
// Perform additional loading logic here.
// Return the loaded asset.
return asset;
}
}
在示例加载程序中,我们只有几行代码,这里我们只是使用构造函数实例化一个新实例.在必要时,我们将在此处执行其他加载逻辑.(In our example loaders, we just have a few lines of code, here we are just instantiating a new instance using the constructor. We would perform additional loading logic here where necessary.)
注册和加载(Registering & Loading)
现在我们有了一个基本的场景以及一些带有装载器的资产示例,我们准备将它们放在一起,以便我们可以将装载资产移动到场景中.我们将创建一个控制台应用程序,以加载和查询新资产类型.首先创建一个简单的(Now that we have a basic scene and some example assets with loaders, we’re ready to put them together so we can move on the loading assets into the scene. We’re going to create a console application that loads and queries the new asset types. Start by creating a simple) main
如下注册我们新资产的功能.(function that registers our new assets as follows.)
注册(Registering)
static void Main(string[] args)
{
// Create a new Scene instance.
Scene scene = new Scene();
// Use fluent interface to Register Asset1 and it's Loader.
Scene.RegisterAsset<Asset1>()
.RegisterLoader<Asset1, Asset1Loader>();
// Use fluent interface to Register Asset2 and it's Loader.
Scene.RegisterAsset<Asset2>()
.RegisterLoader<Asset2, Asset2Loader();
...
}
在这里,我们使用由Scene中创建的注册函数定义的流畅接口.我们将资产和装载程序的类型作为通用参数传递给我们的注册功能.(Here, we are using the fluent interface defined by the register functions created in the Scene. We pass the types for the assets and loaders as generic parameters to our registration functions.)
载入中(Loading)
接下来,我们需要添加一个函数,该函数将调用新的加载器并存储在场景中创建的资产.我们已经在我们的函数中定义了一个函数(Next, we need to add a function that will call our new loaders and store the assets created in the scene. We already have a function defined in our) IScene
接口称为(interface called) LoadAsset<T>()
.我们将创建(. We will create the) LoadAsset
在功能(function in the) Scene
类如下:(class as follows:)
public Scene : IScene
{
...
// Loads an Asset of type T at location (X, Y)
public T LoadAsset<T>(IAsset parent, int x, int y) where T : class, IAsset
{
IAsset asset = null;
Type assetType = typeof(T);
// Make sure the asset type has been registered with the scene.
if(!m_assetTypes.Contains(assetType))
{
throw new Exception(assetType.Name + " has not been registered.");
}
// Make sure a loader has been registered for the asset type.
if(!m_assetLoaders.ContainsKey(assetType))
{
throw new Exception("No loader registered for " + assetType.Name + ".");
}
// Call LoadAsset with registered asset loader.
asset = m_assetLoaders[assetType].LoadAsset(this, parent, x, y);
// Store the new asset in our scene.
m_assets[assetType].Insert(asset);
return asset;
}
...
}
卸货(Unloading)
我们还可以添加一个(We can also add an) unload
该功能使我们可以从场景中删除资产.我已经在(function that will allow us to remove assets from the scene. I have defined it as follows in the) Scene
类.(class.)
public Scene : IScene
{
...
// Unloads an asset and calls it's dispose method.
public void UnloadAsset(IAsset asset)
{
// Check if asset is contained in the Scene.
if(m_assets.Contains(asset))
{
// Remove the asset from our QuadTree.
m_assets.Remove(asset);
// Call the Dispose function defined for the Asset.
asset.Dispose();
}
}
...
}
现在,我们的场景可以创建通用资产实例并将其存储在内部存储器中,在这种情况下,这是我们的(Our scene can now create instances of generic assets and store them in it’s internal storage, in this case it’s our) QuadTree
.使用我们的新功能非常简单.我们可以更新我们的(. It’s pretty simple to use our new functions. We can update our) main
加载和卸载资产的功能,如下所示:(function to load and unload an asset like below:)
static void Main(string[] args)
{
// Create a new Scene instance.
Scene scene = new Scene();
// Use fluent interface to Register Asset1 and it's Loader.
scene.RegisterAsset<Asset1>()
.RegisterLoader<Asset1, Asset1Loader>();
// Use fluent interface to Register Asset2 and it's Loader.
scene.RegisterAsset<Asset2>()
.RegisterLoader<Asset2, Asset2Loader();
// Create a new asset at (0, 0) with no parent.
Asset1 asset1 = scene.LoadAsset<Asset1>(null, 0, 0);
// Create a new asset at (1, 1) with asset1 as a parent.
Asset2 asset2 = scene.LoadAsset<Asset2>(asset1, 1, 1);
// Perform additional steps with assets
Console.Print("{0} ({1}, {2})", asset1.Name, asset1.Bounds.X, asset1.Bounds.Y);
Console.Print("{0} ({1}, {2})", asset2.Name, asset2.Bounds.X, asset2.Bounds.Y);
// Unload asset2.
scene.UnloadAsset(asset2);
...
}
查询资产(Querying Assets)
我们有一种存储资产的方法,但在演示中,我们仅使用由资产返回的资产.(We have a way to store assets but in the demo, we’re just using the assets returned by the) LoadAsset
直接发挥作用.这种方法对于一次管理许多资产不是很有用.在我们的(function directly. This approach isn’t very useful for managing many assets at once. In our) Scene
实施中,我们设置了一个字典(implementation, we setup a dictionary of) QuadTrees
对于每种注册类型.您还应该记得,我们将加载的资产存储在(for each type registered. You will also recall that we stored the asset loaded in) LoadAsset
在里面(in the) QuadTree
字典.(dictionary.)
四叉树(Quad Trees)
引用:https://en.wikipedia.org/wiki/Quadtree(Quote: https://en.wikipedia.org/wiki/Quadtree) 一种(A)**四叉树(quadtree)**是一个(is a) 树数据结构(tree data structure) 其中每个内部节点正好有四个子节点.四叉树最常用于通过将二维空间递归细分为四个象限或区域来划分二维空间.这些区域可以是正方形或矩形,或者可以具有任意形状.该数据结构被命名为四叉树(in which each internal node has exactly four children. Quadtrees are most often used to partition a two-dimensional space by recursively subdividing it into four quadrants or regions. The regions may be square or rectangular, or may have arbitrary shapes. This data structure was named a quadtree by) 拉斐尔`芬克尔(Raphael Finkel)(Raphael Finkel) 和(and) 宾利(J.L. Bentley) 1974年.类似的分区也称为(in 1974. A similar partitioning is also known as a)Q树(Q-tree).所有形式的四叉树都有一些共同的特征:(. All forms of quadtrees share some common features:)
- 它们将空间分解为适应性细胞(They decompose space into adaptable cells)
- 每个单元(或存储桶)具有最大容量.当达到最大容量时,存储桶分裂(Each cell (or bucket) has a maximum capacity. When maximum capacity is reached, the bucket splits)
- 树目录遵循四叉树的空间分解.(The tree directory follows the spatial decomposition of the quadtree.)
我不会详细介绍(I’m not going to go into details how)
QuadTrees
可以正常工作,但是从上面的引用中可以看到,它们将对象存储在矩形单元格中,这使其非常适合存储2D数据.我使用的是稍微修改的(work but as you can see from the quote above, they store objects in rectangular cells, which makes it perfect for storing 2D data. I’m using a slightly modified)QuadTree
从(from) 这里(here) .可以使用其他数据结构,并可以使用八叉树将其扩展到3D.的(. It is possible to use other data structures and could be extended to 3D using an octree. The)IScene
接口有两种用于查询资产的方法.(interface has two methods defined for querying assets.)GetAssets<T>
返回加载到场景中的每个资产的列表(returns a list of each asset loaded into the scene of type) `` 不考虑位置.而(without regard for location. While)QueryAssets<T>
接受一个矩形参数并返回其中包含的资产的列表.(takes a rectangle parameter and returns a list of assets contained within.)
查询资产(Query Assets)
的(The) QueryAssets
需要类型的通用参数(requires a generic parameter of type) `` 并采取(and takes a) RectangleF
要查询的边界(boundary to query in our) QuadTree
.的(. The) QuadTree
有个(has a) QuadTree.Query
方法,我们只需要验证资产类型已在工厂注册.(method, we just need to verify that the asset type is registered with the factory.) QuadTree.Query
返回一个(returns a) List
包含边界内包含的所有资产.(containing all assets contained within the boundary.)
public List<IAsset> QueryAssets<T>(RectangleF bounds) where T : class, IAsset
{
List<IAsset> assets = new List<IAsset>();
// Verify Asset type is registered.
if (!m_assets.ContainsKey(typeof(T)))
{
throw new Exception("Asset Type not registered.");
}
// Query assets of type T within bounds.
assets = m_assets[typeof(T)].Query(bounds);
return assets;
}
获取资产(Get Assets)
的(The) GetAssets
函数采用类型为泛型的参数(function takes a generic parameter of type) `` ,并返回资产列表.的(, and returns a list of assets. The) QuadTree
我正在使用一种无需使用矩形即可直接在节点中查询数据的方法.我只是遍历每个节点,并将它们添加到函数返回的列表中.(I’m using has a way to query the data in the nodes directly without using a rectangle. I just loop through each node and add them to the list returned by the function.)
public List<IAsset> GetAssets<T>() where T : class, IAsset
{
List<IAsset> assets = new List<IAsset>();
// Loop through each QuadTree node.
foreach(QuadTree<IAsset>.QuadNode node in m_assets[typeof(T)].GetAllNodes())
{
// Add the objects contained in the node to the list.
assets.AddRange(node.Objects);
}
return assets;
}
整理起来(Finishing Up)
在里面(In the) Main
函数,我们可以基于矩形查询资产,而不是使用由返回的引用(function, we can query the assets based on a rectangle instead of using the references returned by) LoadAsset
直.我们可以将多个资产加载到(directly. We can load several assets in different parts of the) QuadTree
然后查询其中的一个子集.在此示例中,我们只是打印返回的资产,然后将其卸载.(and then query a subset of them. In this example, we’re just printing the returned assets and then unloading them.)
static void Main(string[] args)
{
// Create a new Scene instance.
Scene scene = new Scene();
// Use fluent interface to Register Asset1 and it's Loader.
scene.RegisterAsset<Asset1>()
.RegisterLoader<Asset1, Asset1Loader>();
// Load a few assets into the scene.
scene.LoadAsset<Asset1>(null, 0, 0);
scene.LoadAsset<Asset1>(null, 5, 5);
scene.LoadAsset<Asset1>(null, 10, 10);
scene.LoadAsset<Asset1>(null, 15, 15);
// Query a rectangle of 10 by 10
var assets = scene.QueryAssets<Asset1>(new RectangleF(0, 0, 10, 10));
// Print the assets returned and then unload them.
foreach(Asset1 asset in assets)
{
Console.Print("{0} ({1}, {2})", asset.Name, asset.Bounds.X, asset.Bounds.Y);
// Unload the asset.
scene.UnloadAsset(asset);
}
...
}
运行该程序时,我们将看到以下输出:(We will see the following output when running the program:)
Asset1 (0, 0)
Asset1 (50, 50)
Asset1 (100, 100)
加载和选择的数据的直观表示类似于下图:(A visual representation of the data loaded and selected looks like the following image:)
而已!在下一个教程中,我们将研究加载大量资产并将它们组织成可管理的单元.最终结果将是一个2D切片系统,我们将能够使用该系统创建程序生成的地图.请继续关注:程序地形生成第二部分:加载块和图块(That’s it! In the next tutorial, we will look into loading larger amounts of assets and organizing them into manageable units. The final result will be a 2D tiling system that we will be able to create procedurally generated maps with. Stay tuned for: Procedural Terrain Generation Part II: Loading Chunks and Tiles)
历史(History)
- 23(23)rd(rd)2016年8月-文章已发布(August, 2016 - Article posted)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# .NET Dev game 新闻 翻译