FIC*_*EKK 1 c# unity-game-engine
我正在开发一个游戏,我正在创造像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);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,我应该缓存所有预制件的参考,或者只需使用Resources.Load<GameObject>一次我需要参考?
我还应该提到动物经常产卵.
那么,这里的常用方法是什么?如果游戏中有数百种不同的类型,开发者通常如何产生动物?
AnL*_*Log 10
你肯定应该缓存对Resources的调用,因为除非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.");
}
}
}
Run Code Online (Sandbox Code Playgroud)
优点:
缺点:
SpawnAnimal("Bear", 100.0f, 20.0f),后来你决定改变Bear到GrizzlyBear该方法将完全停止工作,因为它不会找到在词典中的条目.如果拼错名字,它也会失败.第二个骗局是可以接受的.但第一个骗局非常讨厌!基于枚举的字典可以为我们修复这两个缺点.
enum包含动物名称的所有名称.这样一来,如果你使用的是一个enum而不是string拼错它的机会,你就可以在整个项目中安全地更改枚举的名称.我们称之为枚举AnimalType.AnimalTypeHolder使用单个AnimalType公共变量调用它.AnimalTypeHolder组件添加到动物文件夹中的每只动物.AnimalFactory来构建您使用的字典时GetComponent<AnimalTypeHolder>().如果GameObject有一个AnimalTypeHolder组件,我们肯定知道我们应该加载它!这意味着我们摆脱了第二个骗局!在Animal文件夹中放置一个不应该存在的文件不会造成麻烦!在我们获得组件之后,我们检索AnimalType枚举并将枚举及其相应的GameObject存储到字典中.由于我们使用枚举而不是字符串,我们也摆脱了第一个骗局!SpawnAnimal以使用AnimalType枚举而不是字符串.恩欧姆:
public enum AnimalType
{
Cow,
Sheep,
Bear
}
Run Code Online (Sandbox Code Playgroud)
保存我们的枚举的组件,需要添加并设置为Animal文件夹中的每个动物GameObject:
public class AnimalTypeHolder : MonoBehaviour
{
public AnimalType type;
}
Run Code Online (Sandbox Code Playgroud)
我们的修改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.");
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在设置动物需要稍长的时间,因为你需要添加组件,但是通过完成这一切你已经消除了整个类的错误!添加新动物仍然很容易!只是:
AnimalType为这个新动物的枚举添加一个新值.AnimalTypeHolder组件添加到预制件.AnimalType在AnimalTypeHolder组件我们做出了新的枚举值.完成!当然,这只是关于如何做到这一点的一般概念.如果您的所有动物都已经拥有类似动物组件的东西,那么您可以将动物组件的整个功能放在其中AnimalTypeHolder.我只是想传达这个概念.您将了解如何根据自己的要求最好地实施它.
现在进一步解决您对性能的担忧.有一个通用的答案可以提高在Unity中实例化GameObject的性能.对象池,对象池,对象池.
对象池是指您预先存储GameObjects和/或不是销毁GameObjects而是停用它们并重置它们并将它们存放在存储中以便重复使用.如果您要实例化200只绵羊,那可能会导致游戏滞后.因此,您可以在加载关卡时预先实例化200只绵羊,在游戏开始前停用它们,然后通过简单地将它们移动到适当的位置并激活它们的GameObject,您只需生成已经实例化的绵羊而不是实例化绵羊.因此,主要目标是避免在游戏过程中尽可能多地使用Instantiate,或者在游戏过于忙于将CPU用于其他事情时避免使用它.
互联网上有很多关于如何最好地实现对象池的资产和教程,我相信你会发现它很有用.我建议你开始研究这个话题.
但我仍然会给你一个如何使用对象池继续前一个例子的可靠例子!
我们只需在组件中添加一个preallocateCount变量AnimalTypeHolder,告诉AnimalFactory我们要提前实例化这些动物的数量:
public class AnimalTypeHolder : MonoBehaviour
{
public AnimalType type;
public int preallocateCount = 10;
}
Run Code Online (Sandbox Code Playgroud)
现在我们新的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!");
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
288 次 |
| 最近记录: |