Closed. This question is opinion-based。它当前不接受答案。
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            想改善这个问题吗?更新问题,以便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