TL; DR
Ta odpowiedź jest trochę szalona. Ale to dlatego, że widzę, że mówisz o implementowaniu swoich umiejętności jako „Poleceń”, co implikuje wzorce projektowe C ++ / Java / .NET, co implikuje duże obciążenie kodu. To podejście jest poprawne, ale istnieje lepszy sposób. Może już robisz w drugą stronę. Jeśli tak, to dobrze. Mam nadzieję, że inni uznają to za przydatne, jeśli tak jest.
Spójrz na podejście oparte na danych poniżej, aby przejść do sedna. Uzyskaj CustomAssetUility Jacoba Pennock jest tutaj i przeczytać jego wpis o niej .
Praca z jednością
Jak inni wspominali, przeglądanie listy 100-300 przedmiotów nie jest tak wielką sprawą, jak mogłoby się wydawać. Więc jeśli jest to intuicyjne podejście, po prostu zrób to. Zoptymalizuj wydajność mózgu. Ale Słownik, jak pokazał @Norguard w swojej odpowiedzi , jest łatwym , niewymagającym siły mózgowej sposobem na wyeliminowanie tego problemu, ponieważ dostajesz ciągłe wstawianie i pobieranie. Prawdopodobnie powinieneś go użyć.
Jeśli chodzi o sprawienie, by działało dobrze w Unity, moje wnętrzności mówią mi, że jeden MonoBehaviour na umiejętność jest niebezpieczną ścieżką zejścia. Jeśli którakolwiek z twoich umiejętności utrzymuje stan w czasie, w którym się wykonuje, musisz zarządzać, aby zapewnić sposób na zresetowanie tego stanu. Korytarze łagodzą ten problem, ale nadal zarządzasz referencją IEnumerator w każdej ramce aktualizacji tego skryptu i musisz absolutnie upewnić się, że masz pewny sposób na zresetowanie umiejętności, aby nie była niekompletna i utknęła w pętli stanu umiejętności cicho zaczynają popsuć stabilność gry, gdy pozostają niezauważone. „Oczywiście, że to zrobię!” mówicie: „Jestem„ dobrym programistą ”!”. Ale tak naprawdę, wszyscy jesteśmy obiektywnie okropnymi programistami, a nawet najwięksi badacze sztucznej inteligencji i autorzy kompilatorów cały czas coś psują.
Spośród wszystkich sposobów, w jakie możesz wdrożyć tworzenie instancji i wyszukiwanie poleceń w Unity, mogę myśleć o dwóch: jednym jest w porządku i nie da ci tętniaka, a drugim pozwala na NIEOGRANICZONĄ MAGICZNĄ TWÓRCZOŚĆ . Raczej.
Podejście zorientowane na kod
Pierwszy to podejście oparte głównie na kodzie. Zalecam, aby uczynić każdą komendę prostą klasą, która albo dziedziczy z absencji klasy BaseCommand, albo implementuje interfejs ICommand (dla zwięzłości zakładam, że te komendy będą tylko zdolnościami postaci, nie jest trudno włączyć Inne zastosowania). Ten system zakłada, że każde polecenie jest komendą IC, ma konstruktor publiczny, który nie przyjmuje parametrów i wymaga aktualizacji każdej ramki, gdy jest ona aktywna.
Sprawa jest prostsza, jeśli używasz abstrakcyjnej klasy bazowej, ale moja wersja używa interfejsów.
Ważne jest, aby Twoje MonoBehaviours obejmowały jedno konkretne zachowanie lub system zachowań ściśle powiązanych. W porządku jest mieć wiele MonoBehaviours, które skutecznie po prostu pośredniczą w zwykłych klasach C #, ale jeśli sam też to zrobisz, może aktualizować wywołania do różnego rodzaju obiektów do tego stopnia, że zaczyna wyglądać jak gra XNA, wtedy „ masz poważne kłopoty i musisz zmienić architekturę.
// 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);
}
}
Działa to całkowicie dobrze, ale możesz to zrobić lepiej (również List<T>
nie jest to optymalna struktura danych do przechowywania zdolności czasowych, możesz chcieć a LinkedList<T>
lub a SortedDictionary<float, T>
).
Podejście oparte na danych
Prawdopodobnie możliwe jest zredukowanie efektów umiejętności do logicznych zachowań, które można sparametryzować. Po to naprawdę zbudowano Unity. Jako programista projektujesz system, w którym Ty lub projektant możecie manipulować w edytorze, aby uzyskać różnorodne efekty. To znacznie uprości „fałszowanie” kodu i skupi się wyłącznie na wykonaniu umiejętności. Nie ma potrzeby żonglowania klasami podstawowymi ani interfejsami i rodzajami tutaj. Wszystko będzie oparte wyłącznie na danych (co również uprości inicjowanie instancji poleceń).
Pierwszą rzeczą, której potrzebujesz, jest obiekt skryptowy, który może opisywać twoje umiejętności. Obiekty ScriptableObjects są niesamowite. Są zaprojektowane tak, aby działały jak MonoBehaviours, ponieważ możesz ustawić ich publiczne pola w inspektorze Unity, a zmiany te zostaną serializowane na dysk. Nie są one jednak przyłączone do żadnego obiektu i nie muszą być dołączone do obiektu gry w scenie ani utworzone w instancji. Są to zbiorcze pakiety danych Unity. Mogą serializować podstawowe typy, wyliczenia i proste klasy (bez dziedziczenia) oznaczone [Serializable]
. Struktury nie mogą być serializowane w Unity, a serializacja umożliwia edycję pól obiektów w inspektorze, więc pamiętaj o tym.
Oto ScriptableObject, który próbuje wiele zrobić. Możesz podzielić to na bardziej szeregowe klasy i obiekty ScriptableObjects, ale ma to po prostu dać ci wyobrażenie, jak to zrobić. Zwykle wygląda to brzydko w ładnym nowoczesnym języku obiektowym, takim jak C #, ponieważ naprawdę wydaje się, że to trochę gówna C89 z tymi wszystkimi wyliczeniami, ale prawdziwa moc polega na tym, że teraz możesz tworzyć różnego rodzaju umiejętności bez pisania nowego kodu do obsługi im. A jeśli twój pierwszy format nie robi tego, czego potrzebujesz, po prostu dodawaj go, aż to zrobi. Dopóki nie zmienisz nazw pól, wszystkie stare serializowane pliki zasobów będą nadal działać.
// 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();
}
Możesz dodatkowo wyodrębnić sekcję Obrażenia do klasy możliwej do serializacji, abyś mógł zdefiniować umiejętności zadające obrażenia lub leczące się i posiadające wiele rodzajów obrażeń w jednej umiejętności. Jedyną regułą jest brak dziedziczenia, chyba że użyjesz wielu obiektów skryptowalnych i odniesiesz się do różnych plików konfiguracji szkód złożonych na dysku.
Nadal potrzebujesz MonoBehaviour Aktywatora Umiejętności, ale teraz on wykonuje trochę więcej pracy.
// 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)
}
}
NAJBARDZIEJ CZĘŚĆ
Interfejs i ogólne oszustwo w pierwszym podejściu będą działać dobrze. Ale aby naprawdę wyciągnąć jak najwięcej z Unity, ScriptableObjects zabierze Cię tam, gdzie chcesz być. Jedność jest świetna pod tym względem, że zapewnia bardzo spójne i logiczne środowisko dla programistów, ale ma także wszystkie funkcje wprowadzania danych dla projektantów i artystów, które otrzymujesz od GameMaker, UDK, i in. glin.
W zeszłym miesiącu nasz artysta wziął ulepszony typ ScriptableObject, który miał określać zachowanie dla różnych rodzajów pocisków naprowadzających, połączył go z AnimationCurve i zachowaniem, które spowodowało, że pociski zawisły nad ziemią i sprawiły, że ten nowy szalony krążek hokejowy broń śmierci.
Nadal muszę wrócić i dodać konkretne wsparcie dla tego zachowania, aby upewnić się, że działa ono skutecznie. Ale ponieważ stworzyliśmy ten ogólny interfejs opisu danych, był on w stanie wyciągnąć ten pomysł z powietrza i wprowadzić go do gry bez nas, programistów, nawet wiedzących, że próbował to zrobić, dopóki nie przyszedł i powiedział: „Cześć, patrzcie na tej fajnej rzeczy! ” A ponieważ było wyraźnie niesamowite, cieszę się, że mogę dodać bardziej solidne wsparcie dla tego.