Closed. This question is opinion-based。它当前不接受答案。
想改善这个问题吗?更新问题,以便editing this post用事实和引用来回答。
去年关闭。
我正在开发一款游戏,可以在其中生成像Minecraft一样的无限世界。问题是我的游戏将包含数百种不同类型的动物和敌人,但是我不确定引用这些预制件的通常方法是什么。
目前,我的解决方案是拥有一个可以产生动物的动物工厂类。看起来像这样:
但是,我应该缓存对所有预制件的引用,还是在需要引用时仅使用
我还应该提到,动物经常会产卵。
那么,这里常用的方法是什么?如果游戏中有成百上千种不同的类型,开发人员通常如何生成动物?
优点:
整个过程只需要一种生成方法。错误更少,代码更少。
您只需要调用Resources.Load就可以整个加载一次。更好的性能。
轻松允许您添加更多动物,而无需更改代码。只需将它们放在Animal文件夹中即可。
缺点:
依靠动物的名字保持不变。如果您调用
您只需在Animal文件夹中保留动物,因为Resources.LoadAll会加载整个目录。
使用基于枚举的字典
第二个缺点是可以接受的。但是第一个骗局非常讨厌!基于枚举的字典可以为我们解决这两个缺点。
创建一个
创建一个Monobehavior类,我们将使用单个
将
当您运行
重新编码
枚举:
包含我们的枚举的Component需要添加并设置到Animal文件夹中的每个动物GameObject:
我们修改后的
现在,由于需要添加组件,因此设置动物的时间稍长一些,但是通过执行所有这些操作,您消除了整个错误类别!而且添加新动物仍然很容易!只是:
将预制件放入动物文件夹中。
为此新动物的
将
将
当然,这只是关于如何执行此操作的一般想法。如果您所有的动物都已经有一个动物组件,则可以将
提高性能-对象池
现在进一步解决您对性能的担忧。有一个普遍的答案可以提高在Unity中实例化GameObject的性能。对象池,对象池,对象池。
对象池化是指您预先存储游戏对象和/或不销毁游戏对象而是停用它们并将其重置并放入存储区以供重用。如果您要实例化200只羊,那可能会使游戏滞后。因此,您可以在关卡加载时预先实例化200只羊,在游戏开始前将其停用,然后您只需将它们移至适当的位置并激活其GameObject即可生成已实例化的绵羊,而不是实例化绵羊。因此,主要目的是避免在游戏过程中尽可能多地使用Instantinate或在游戏过于忙于使用CPU进行其他事情时避免使用它。
互联网上有很多资产和关于如何最好地实现对象池的教程,我相信您会发现它很有用。我建议您开始研究该主题。
但是,我仍将继续前面的示例,为您提供一个如何使用对象池的可靠示例!
我们将添加一个
现在为我们的新
想改善这个问题吗?更新问题,以便editing this post用事实和引用来回答。
去年关闭。
我正在开发一款游戏,可以在其中生成像Minecraft一样的无限世界。问题是我的游戏将包含数百种不同类型的动物和敌人,但是我不确定引用这些预制件的通常方法是什么。
目前,我的解决方案是拥有一个可以产生动物的动物工厂类。看起来像这样:
public static class AnimalFactory
{
public static GameObject sheep = Resources.Load<GameObject>("Prefabs/Animal/Sheep");
public static GameObject cow = Resources.Load<GameObject>("Prefabs/Animal/Cow");
public static void SpawnSheep(float x, float y)
{
GameObject drop = Object.Instantiate(sheep, new Vector3(x, y, 0f), Quaternion.identity);
}
public static void SpawnCow(float x, float y)
{
GameObject drop = Object.Instantiate(cow, new Vector3(x, y, 0f), Quaternion.identity);
}
}
但是,我应该缓存对所有预制件的引用,还是在需要引用时仅使用
Resources.Load<GameObject>
?我还应该提到,动物经常会产卵。
那么,这里常用的方法是什么?如果游戏中有成百上千种不同的类型,开发人员通常如何生成动物?
最佳答案
您绝对应该缓存对资源的调用,因为除非Unity3D为您做一些缓存,否则它将需要重复地查询资源。但是,代码最紧迫的问题不是性能。相反,您必须为每种动物编写一个方法,这非常令人讨厌,并且如果以后使生成方法变得更复杂,也容易出错。
更好的方法是通过字符串或数字ID或字典中的枚举来引用您的生物。利用字典的力量获得胜利!
使用基于字符串的字典
一种解决方案是使用基于字符串的字典,如下所示:
public static class AnimalFactory
{
// Dictionary to map a string to each animal object.
private static Dictionary<string, GameObject> animalDictionary;
// We'll build our dictionary in the static constructor.
static AnimalFactory()
{
// We can load all the animals from that folder.
var animals = Resources.LoadAll<GameObject>("Prefabs/Animal");
animalDictionary =
new Dictionary<string, GameObject>(animals.Length);
foreach (GameObject animal in animals)
{
animalDictionary.Add(animal.name, animal);
}
}
public static void SpawnAnimal(string animalName, float x, float y)
{
if (animalDictionary.ContainsKey(animalName))
{
GameObject drop = Object.Instantiate(
animalDictionary[animalName],
new Vector3(x, y, 0f), Quaternion.identity);
}
else
{
Debug.LogError("Animal with " + animalName + "could not be " +
"found and spawned.");
}
}
}
优点:
整个过程只需要一种生成方法。错误更少,代码更少。
您只需要调用Resources.Load就可以整个加载一次。更好的性能。
轻松允许您添加更多动物,而无需更改代码。只需将它们放在Animal文件夹中即可。
缺点:
依靠动物的名字保持不变。如果您调用
SpawnAnimal("Bear", 100.0f, 20.0f)
,然后再决定将Bear
更改为GrizzlyBear
,则该方法将完全停止工作,因为在词典中找不到该条目。如果您拼错名称,它也会失败。您只需在Animal文件夹中保留动物,因为Resources.LoadAll会加载整个目录。
使用基于枚举的字典
第二个缺点是可以接受的。但是第一个骗局非常讨厌!基于枚举的字典可以为我们解决这两个缺点。
创建一个
enum
,其中包含您动物的所有名称。这样,如果使用enum
而不是string
,就不会有拼写错误的可能,并且可以安全地在整个项目中更改枚举的名称。我们将此枚举称为AnimalType
。创建一个Monobehavior类,我们将使用单个
AnimalTypeHolder
公共变量来调用AnimalType
。将
AnimalTypeHolder
组件添加到animal文件夹中的每只动物。当您运行
AnimalFactory
的静态构造函数来构建字典时,请使用GetComponent<AnimalTypeHolder>()
。如果GameObject具有AnimalTypeHolder
组件,我们肯定知道应该加载它!这意味着我们摆脱了第二个骗局!将本不应该存在的文件放在Animal文件夹中不会造成麻烦!获取组件后,我们检索AnimalType
枚举并将该枚举及其相应的GameObject存储到字典中。由于我们使用枚举而不是字符串,因此我们也摆脱了第一个缺点!重新编码
SpawnAnimal
以使用AnimalType
枚举而不是字符串。枚举:
public enum AnimalType
{
Cow,
Sheep,
Bear
}
包含我们的枚举的Component需要添加并设置到Animal文件夹中的每个动物GameObject:
public class AnimalTypeHolder : MonoBehaviour
{
public AnimalType type;
}
我们修改后的
AnimalFactory
:public static class AnimalFactory
{
private static Dictionary<AnimalType, GameObject> animalDictionary;
static AnimalFactory()
{
var animals = Resources.LoadAll<GameObject>("Prefabs/Animal");
animalDictionary =
new Dictionary<AnimalType, GameObject>(animals.Length);
foreach (GameObject animal in animals)
{
var typeHolder = animal.GetComponent<AnimalTypeHolder>();
if (typeHolder != null)
{
animalDictionary.Add(typeHolder.type, animal);
}
}
}
public static void SpawnAnimal(AnimalType animalType, float x, float y)
{
if (animalDictionary.ContainsKey(animalType))
{
GameObject drop = Object.Instantiate(
animalDictionary[animalType],
new Vector3(x, y, 0f), Quaternion.identity);
}
else
{
Debug.LogError("Animal with " + animalType + "could not be " +
"found and spawned.");
}
}
}
现在,由于需要添加组件,因此设置动物的时间稍长一些,但是通过执行所有这些操作,您消除了整个错误类别!而且添加新动物仍然很容易!只是:
将预制件放入动物文件夹中。
为此新动物的
AnimalType
枚举添加一个新值。将
AnimalTypeHolder
组件添加到预制中。将
AnimalType
组件中的AnimalTypeHolder
设置为我们创建的新枚举值。做完了!当然,这只是关于如何执行此操作的一般想法。如果您所有的动物都已经有一个动物组件,则可以将
AnimalTypeHolder
的全部功能放入该动物组件中。我只想传达这个概念。您将知道如何根据自己的需求最好地实施它。提高性能-对象池
现在进一步解决您对性能的担忧。有一个普遍的答案可以提高在Unity中实例化GameObject的性能。对象池,对象池,对象池。
对象池化是指您预先存储游戏对象和/或不销毁游戏对象而是停用它们并将其重置并放入存储区以供重用。如果您要实例化200只羊,那可能会使游戏滞后。因此,您可以在关卡加载时预先实例化200只羊,在游戏开始前将其停用,然后您只需将它们移至适当的位置并激活其GameObject即可生成已实例化的绵羊,而不是实例化绵羊。因此,主要目的是避免在游戏过程中尽可能多地使用Instantinate或在游戏过于忙于使用CPU进行其他事情时避免使用它。
互联网上有很多资产和关于如何最好地实现对象池的教程,我相信您会发现它很有用。我建议您开始研究该主题。
但是,我仍将继续前面的示例,为您提供一个如何使用对象池的可靠示例!
我们将添加一个
preallocateCount
变量到我们的AnimalTypeHolder
组件中,该变量告诉我们AnimalFactory
我们想要预先实例化这些动物中的多少:public class AnimalTypeHolder : MonoBehaviour
{
public AnimalType type;
public int preallocateCount = 10;
}
现在为我们的新
AnimalFactory
:public static class AnimalFactory
{
private static Dictionary<AnimalType, GameObject> animalDictionary;
private static Dictionary<AnimalType, List<GameObject>> animalPoolActive;
private static Dictionary<AnimalType, List<GameObject>> animalPoolInActive;
static AnimalFactory()
{
var animals = Resources.LoadAll<GameObject>("Prefabs/Animal");
animalDictionary =
new Dictionary<AnimalType, GameObject>(animals.Length);
animalPoolActive =
new Dictionary<AnimalType, List<GameObject>>();
animalPoolInActive =
new Dictionary<AnimalType, List<GameObject>>(animals.Length);
foreach (GameObject animal in animals)
{
var typeHolder = animal.GetComponent<AnimalTypeHolder>();
if (typeHolder != null)
{
animalDictionary.Add(typeHolder.type, animal);
// Since there are no active animals in the beginning, we'll
// create an empty list.
animalPoolActive.Add(typeHolder.type,
new List<GameObject>());
// Make a list to hold our inactive preallocated animals.
var prellocAnimals
= new List<GameObject>(typeHolder.preallocateCount);
for (int i = 0; i < typeHolder.preallocateCount; i++)
{
var go = Object.Instantiate(animal);
go.SetActive(false);
prellocAnimals.Add(go);
}
animalPoolInActive.Add(typeHolder.type, prellocAnimals);
}
}
}
public static void SpawnAnimal(AnimalType animalType, float x, float y)
{
if (animalDictionary.ContainsKey(animalType))
{
var inactives = animalPoolInActive[animalType];
// Check if we have inactive animals of this type we can use.
if (inactives.Count > 0)
{
// We'll just get the last GameObject in the pool.
int last = inactives.Count - 1;
GameObject drop = inactives[last];
// We have to remove it from the inactive pool now that
// we're using it!
inactives.RemoveAt(last);
// Now we have to add it to the active pool.
var actives = animalPoolActive[animalType];
actives.Add(drop);
drop.SetActive(true);
drop.transform.SetPositionAndRotation(new Vector3(x, y, 0f),
Quaternion.identity);
}
// If we don't have them preallocated, we'll have to instantiate
// normally.
else
{
GameObject drop = Object.Instantiate(
animalDictionary[animalType],
new Vector3(x, y, 0f), Quaternion.identity);
animalPoolActive[animalType].Add(drop);
}
}
else
{
Debug.LogError("Animal with " + animalType + "could not be " +
"found and spawned.");
}
}
public static void UnspawnAnimal(GameObject animal)
{
var typeHolder = animal.GetComponent<AnimalTypeHolder>();
if (typeHolder != null)
{
AnimalType type = typeHolder.type;
var actives = animalPoolActive[type];
var inactives = animalPoolInActive[type];
// Check if we're not accidentally using unspawn more than once.
if (inactives.Contains(animal))
{
Debug.LogWarning("Trying to unspawn an animal that " +
"should already be unspawned!");
return;
}
// First we check if it exists in the active pool.
if (actives.Contains(animal))
{
// If it exists then we have to remove it now.
actives.Remove(animal);
}
// We have to add it to the inactive pool for later use.
inactives.Add(animal);
// WARNING: In most situations in order to be able to reuse
// a GameObject like this you need to reset it! For example if
// your animals have HP then you probably despawned them when
// they got to zero! You need to reset the HP back to the
// starting default if you want to reuse the animal!!
}
else
{
Debug.LogWarning("Attempting to use Unspawn Animal on a " +
"GameObject that is either not an animal or doesn't have " +
"an AnimalTypeHolder component!");
}
}
}
07-24 21:36