TL; DR
Этот ответ немного сходит с ума. Но это потому, что я вижу, что вы говорите о реализации своих способностей как «Команд», что подразумевает шаблоны проектирования C ++ / Java / .NET, что подразумевает подход с большим количеством кода. Этот подход действителен, но есть лучший способ. Может быть, вы уже делаете по-другому. Если так, хорошо. Надеюсь, другие найдут это полезным, если это так.
Посмотрите на управляемый данными подход ниже, чтобы перейти к погоне. Получите CustomAssetUility Джейкоба Пеннока здесь и прочитайте его пост об этом .
Работа с Unity
Как уже упоминали другие, обход списка из 100-300 предметов - не такая большая проблема, как вы думаете. Так что, если это интуитивный подход для вас, просто сделайте это. Оптимизировать для эффективности мозга. Но Словарь, как продемонстрировал @Norguard в своем ответе , - это простой, не требующий усилий мозг, способ устранить эту проблему, поскольку вы получаете постоянные вставки и извлечения. Вы должны вероятно использовать это.
Что касается правильной работы в Unity, моя интуиция говорит мне, что одно MonoBehaviour на каждую способность - это опасный путь для снижения. Если какая-либо из ваших способностей поддерживает состояние с течением времени, когда они выполняются, вам нужно управлять этим, чтобы обеспечить способ сбросить это состояние. Сопрограммы снимают эту проблему, но вы по-прежнему управляете ссылкой IEnumerator на каждый кадр обновления этого сценария и должны быть абсолютно уверены, что у вас есть надежный способ сброса способностей, чтобы не завершиться и не застрять в цикле состояния Способности незаметно начинают портить стабильность вашей игры, когда они остаются незамеченными. "Конечно, я сделаю это!" Вы говорите: «Я хороший программист!». Но на самом деле, вы знаете, мы все объективно ужасные программисты и даже величайшие исследователи ИИ и авторы компиляторов все время напортачили.
Из всех способов, которыми вы могли бы реализовать создание и извлечение команд в Unity, я могу подумать о двух: один из них хорош и не даст вам аневризмы, а другой допускает неограниченную магическую креативность . Вроде.
Код-ориентированный подход
Во-первых, это в основном кодовый подход. Я рекомендую вам сделать каждую команду простым классом, который либо наследует от класса абстракции BaseCommand, либо реализует интерфейс ICommand (для краткости я предполагаю, что эти команды будут когда-либо только символьными способностями, их нетрудно включить другое использование). Эта система предполагает, что каждая команда является ICommand, имеет открытый конструктор, который не принимает параметров и требует обновления каждого кадра, пока он активен.
Все проще, если вы используете абстрактный базовый класс, но моя версия использует интерфейсы.
Важно, чтобы ваши MonoBehaviours инкапсулировали одно конкретное поведение или систему тесно связанных поведений. Вполне нормально иметь много MonoBehaviours, которые фактически просто перенаправляют на простые классы C #, но если вы обнаружите, что делаете это тоже, можете обновить вызовы для всех видов различных объектов до такой степени, что это начинает выглядеть как игра XNA, тогда вы ' Вы находитесь в серьезной проблеме и должны изменить свою архитектуру.
// ICommand.cs
public interface ICommand
{
public void Execute(AbilityActivator originator, TargetingInfo targets);
public void Update();
public bool IsActive { get; }
}
// CommandList.cs
// Attach this to a game object in your loading screen
public static class CommandList
{
public static ICommand GetInstance(string key)
{
return commandDict[key].GetRef();
}
static CommandListInitializerScript()
{
commandDict = new Dictionary<string, ICommand>() {
{ "SwordSpin", new CommandRef<SwordSpin>() },
{ "BellyRub", new CommandRef<BellyRub>() },
{ "StickyShield", new CommandRef<StickyShield>() },
// Add more commands here
};
}
private class CommandRef<T> where T : ICommand, new()
{
public ICommand GetNew()
{
return new T();
}
}
private static Dictionary<string, ICommand> commandDict;
}
// AbilityActivator.cs
// Attach this to your character objects
public class AbilityActivator : MonoBehaviour
{
List<ICommand> activeAbilities = new List<ICommand>();
void Update()
{
string activatedAbility = GetActivatedAbilityThisFrame();
if (!string.IsNullOrEmpty(acitvatedAbility))
ICommand command = CommandList.Get(activatedAbility).GetRef();
command.Execute(this, this.GetTargets());
activeAbilities.Add(command);
}
foreach (var ability in activeAbilities) {
ability.Update();
}
activeAbilities.RemoveAll(a => !a.IsActive);
}
}
Это прекрасно работает, но вы можете сделать лучше (кроме того, List<T>
не оптимальная структура данных для хранения временных возможностей, вы можете захотеть LinkedList<T>
илиSortedDictionary<float, T>
).
Управляемый данными подход
Возможно, вы сможете уменьшить эффекты своей способности до логического поведения, которое можно параметризировать. Для этого и был построен Unity. Вы, как программист, проектируете систему, которую затем вы или дизайнер можете использовать и редактировать в редакторе для создания широкого спектра эффектов. Это значительно упростит «подтасовку» кода и сосредоточится исключительно на выполнении способности. Нет необходимости манипулировать базовыми классами или интерфейсами и дженериками здесь. Все это будет зависеть только от данных (что также упрощает инициализацию экземпляров команд).
Первое, что вам нужно, это ScriptableObject, который может описать ваши способности. ScriptableObjects потрясающие. Они предназначены для работы как MonoBehaviours в том, что вы можете установить их открытые поля в Инспекторе Unity, и эти изменения будут сериализованы на диск. Тем не менее, они не привязаны к какому-либо объекту и не должны быть привязаны к игровому объекту на сцене или созданы. Они являются универсальными блоками данных Unity. Они могут сериализовать помеченные базовые типы, перечисления и простые классы (без наследования) [Serializable]
. Структуры не могут быть сериализованы в Unity, и сериализация - это то, что позволяет редактировать поля объектов в инспекторе, поэтому помните об этом.
Вот ScriptableObject, который пытается сделать многое. Вы можете разбить это на более сериализованные классы и ScriptableObjects, но это должно просто дать вам представление о том, как это сделать. Обычно это выглядит некрасиво в хорошем современном объектно-ориентированном языке, таком как C #, так как на самом деле это похоже на какое-то дерьмо C89 со всеми этими перечислениями, но реальная сила здесь в том, что теперь вы можете создавать всевозможные различные возможности, даже не создавая новый код для поддержки. их. И если ваш первый формат не делает то, что вам нужно, просто продолжайте добавлять, пока он не сделает. Пока вы не измените имена полей, все ваши старые сериализованные файлы ресурсов будут работать.
// CommandAbilityDescription.cs
public class CommandAbilityDecription : ScriptableObject
{
// Identification and information
public string displayName; // Name used for display purposes for the GUI
// We don't need an identifier field, because this will actually be stored
// as a file on disk and thus implicitly have its own identifier string.
// Description of damage to targets
// I put this enum inside the class for answer readability, but it really belongs outside, inside a namespace rather than nested inside a class
public enum DamageType
{
None,
SingleTarget,
SingleTargetOverTime,
Area,
AreaOverTime,
}
public DamageType damageType;
public float damage; // Can represent either insta-hit damage, or damage rate over time (depend)
public float duration; // Used for over-time type damages, or as a delay for insta-hit damage
// Visual FX
public enum EffectPlacement
{
CenteredOnTargets,
CenteredOnFirstTarget,
CenteredOnCharacter,
}
[Serializable]
public class AbilityVisualEffect
{
public EffectPlacement placement;
public VisualEffectBehavior visualEffect;
}
public AbilityVisualEffect[] visualEffects;
}
// VisualEffectBehavior.cs
public abtract class VisualEffectBehavior : MonoBehaviour
{
// When an artist makes a visual effect, they generally make a GameObject Prefab.
// You can extend this base class to support different kinds of visual effects
// such as particle systems, post-processing screen effects, etc.
public virtual void PlayEffect();
}
Вы можете дополнительно абстрагировать раздел «Урон» в класс «Сериализуемый», чтобы вы могли определять способности, которые наносят урон или исцеляют, и имеют несколько типов повреждений в одной способности. Единственное правило - не наследовать, если вы не используете несколько объектов сценариев и ссылаетесь на различные файлы конфигурации сложных повреждений на диске.
Вам все еще нужен AbilityActivator MonoBehaviour, но теперь он делает немного больше работы.
// AbilityActivator.cs
public class AbilityActivator : MonoBehaviour
{
public void ActivateAbility(string abilityName)
{
var command = (CommandAbilityDescription) Resources.Load(string.Format("Abilities/{0}", abilityName));
ProcessCommand(command);
}
private void ProcessCommand(CommandAbilityDescription command)
{
foreach (var fx in command.visualEffects) {
fx.PlayEffect();
}
switch(command.damageType) {
// yatta yatta yatta
}
// and so forth, whatever your needs require
// You could even make a copy of the CommandAbilityDescription
var myCopy = Object.Instantiate(command);
// So you can keep track of state changes (ie: damage duration)
}
}
Самая холодная часть
Так что интерфейс и общие хитрости в первом подходе будут работать нормально. Но для того, чтобы действительно получить максимальную отдачу от Unity, ScriptableObjects доставит вас туда, куда вы хотите. Unity хорош тем, что он обеспечивает очень согласованную и логичную среду для программистов, но также имеет все тонкости ввода данных для дизайнеров и художников, которые вы получаете от GameMaker, UDK и других. и др.
В прошлом месяце наш художник взял powerup-тип ScriptableObject, который должен был определять поведение для различных типов самонаводящихся ракет, сочетал его с AnimationCurve и поведением, которое заставляло ракеты парить над землей, и делал эту новую безумную вращающуюся хоккейную шайбу. орудия смерти.
Мне все еще нужно вернуться и добавить определенную поддержку для этого поведения, чтобы убедиться, что оно работает эффективно. Но поскольку мы создали этот общий интерфейс описания данных, он смог вытащить эту идею из воздуха и воплотить ее в игру без нас, программистов, даже не подозревая, что он пытается это сделать, пока он не подошел и не сказал: «Эй, ребята, посмотрите на эту классную вещь! И так как это было просто потрясающе, я рад добавить более надежную поддержку.