TL; DR
Diese Antwort wird ein bisschen verrückt. Aber es liegt daran, dass Sie über die Implementierung Ihrer Fähigkeiten als "Befehle" sprechen, was C ++ / Java / .NET-Entwurfsmuster impliziert, was einen codeintensiven Ansatz impliziert. Dieser Ansatz ist gültig, aber es gibt einen besseren Weg. Vielleicht machst du schon den anderen Weg. Wenn ja, na ja. Hoffentlich finden es andere nützlich, wenn dies der Fall ist.
Schauen Sie sich den datengesteuerten Ansatz unten an, um auf den Punkt zu kommen. Holen Sie sich Jacob Pennocks CustomAssetUility hier und lesen Sie seinen Beitrag darüber .
Mit der Einheit arbeiten
Wie andere bereits erwähnt haben, ist das Durchsuchen einer Liste mit 100 bis 300 Elementen keine so große Sache, wie Sie vielleicht denken. Wenn dies für Sie ein intuitiver Ansatz ist, tun Sie dies einfach. Optimieren Sie die Effizienz Ihres Gehirns. Aber das Wörterbuch ist, wie @Norguard in seiner Antwort gezeigt hat , die einfache Möglichkeit, dieses Problem ohne Gehirnleistung zu beseitigen, da Sie zeitlich konstant einfügen und abrufen können. Sie sollten es wahrscheinlich verwenden.
Um diese Funktion in Unity gut zu machen, sagt mir mein Bauch, dass ein MonoBehaviour pro Fähigkeit ein gefährlicher Weg ist. Wenn eine Ihrer Fähigkeiten den Status im Laufe der Zeit beibehält und ausgeführt wird, müssen Sie dies verwalten und eine Möglichkeit zum Zurücksetzen dieses Status bereitstellen. Coroutinen lindern dieses Problem, aber Sie verwalten immer noch eine IEnumerator-Referenz in jedem Update-Frame dieses Skripts und müssen unbedingt sicherstellen, dass Sie die Fähigkeiten auf sichere Weise zurücksetzen können, damit sie nicht unvollständig sind und in einer Statusschleife stecken bleiben Fähigkeiten beeinträchtigen leise die Stabilität Ihres Spiels, wenn sie unbemerkt bleiben. "Natürlich mache ich das!" Sie sagen: "Ich bin ein guter Programmierer!" Aber wirklich, wissen Sie, wir sind alle objektiv schreckliche Programmierer und selbst die größten KI-Forscher und Compiler-Autoren vermasseln ständig Dinge.
Von allen Möglichkeiten, wie Sie die Instanziierung und den Abruf von Befehlen in Unity implementieren können, kann ich mir zwei vorstellen : Eine ist in Ordnung und gibt Ihnen kein Aneurysma, und die andere ermöglicht UNBOUNDED MAGICAL CREATIVITY . Art von.
Code-zentrierter Ansatz
Erstens ist ein größtenteils im Code enthaltener Ansatz. Ich empfehle, dass Sie jeden Befehl zu einer einfachen Klasse machen, die entweder von einer BaseCommand abtract-Klasse erbt oder eine ICommand-Schnittstelle implementiert (der Kürze halber gehe ich davon aus, dass diese Befehle immer nur Zeichenfähigkeiten sind, es ist nicht schwer zu integrieren andere Verwendungen). Dieses System geht davon aus, dass jeder Befehl ein ICommand ist, einen öffentlichen Konstruktor hat, der keine Parameter akzeptiert, und dass jeder Frame aktualisiert werden muss, während er aktiv ist.
Die Dinge sind einfacher, wenn Sie eine abstrakte Basisklasse verwenden, aber meine Version verwendet Schnittstellen.
Es ist wichtig, dass Ihre MonoBehaviours ein bestimmtes Verhalten oder ein System eng verwandter Verhaltensweisen enthalten. Es ist in Ordnung, viele MonoBehaviours zu haben, die sich effektiv nur auf einfache C # -Klassen übertragen lassen. Wenn Sie dies jedoch auch tun, können Sie Aufrufe an alle möglichen Objekte so weit aktualisieren, dass sie wie ein XNA-Spiel aussehen. Sie sind in ernsthaften Schwierigkeiten und müssen Ihre Architektur ändern.
// 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);
}
}
Dies funktioniert völlig in Ordnung, aber Sie können es besser machen (außerdem ist a List<T>
nicht die optimale Datenstruktur zum Speichern zeitgesteuerter Fähigkeiten, möglicherweise möchten Sie a LinkedList<T>
oder a SortedDictionary<float, T>
).
Datengesteuerter Ansatz
Es ist wahrscheinlich möglich, dass Sie die Auswirkungen Ihrer Fähigkeiten auf logische Verhaltensweisen reduzieren können, die parametrisiert werden können. Dafür wurde Unity wirklich gebaut. Als Programmierer entwerfen Sie ein System, das Sie oder ein Designer im Editor bearbeiten können, um eine Vielzahl von Effekten zu erzielen. Dies wird das "Rigging" des Codes stark vereinfachen und sich ausschließlich auf die Ausführung einer Fähigkeit konzentrieren. Hier müssen keine Basisklassen oder Schnittstellen und Generika jongliert werden. Es wird alles rein datengesteuert sein (was auch das Initialisieren von Befehlsinstanzen vereinfacht).
Das erste, was Sie brauchen, ist ein ScriptableObject, das Ihre Fähigkeiten beschreiben kann. ScriptableObjects sind fantastisch. Sie funktionieren wie MonoBehaviours, da Sie ihre öffentlichen Felder im Unity-Inspektor festlegen können und diese Änderungen auf die Festplatte serialisiert werden. Sie sind jedoch keinem Objekt zugeordnet und müssen nicht an ein Spielobjekt in einer Szene angehängt oder instanziiert werden. Sie sind die Sammeldatenbereiche von Unity. Sie können markierte Basistypen, Aufzählungen und einfache Klassen (keine Vererbung) serialisieren [Serializable]
. Strukturen können in Unity nicht serialisiert werden. Durch Serialisierung können Sie die Objektfelder im Inspektor bearbeiten. Denken Sie also daran.
Hier ist ein ScriptableObject, das viel zu tun versucht. Sie können dies in mehr serialisierte Klassen und ScriptableObjects aufteilen, aber dies soll Ihnen nur eine Vorstellung davon geben, wie Sie vorgehen müssen. Normalerweise sieht dies in einer schönen modernen objektorientierten Sprache wie C # hässlich aus, da es sich wirklich wie C89-Scheiße mit all diesen Aufzählungen anfühlt, aber die wahre Stärke hier ist, dass Sie jetzt alle möglichen Fähigkeiten erstellen können, ohne jemals neuen Code zur Unterstützung zu schreiben Sie. Und wenn Ihr erstes Format nicht das tut, was Sie brauchen, fügen Sie es einfach hinzu, bis es es tut. Solange Sie die Feldnamen nicht ändern, funktionieren alle Ihre alten serialisierten Asset-Dateien weiterhin.
// 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();
}
Sie können den Abschnitt "Schaden" weiter in eine serialisierbare Klasse abstrahieren, um Fähigkeiten zu definieren, die Schaden verursachen oder heilen, und mehrere Schadensarten in einer Fähigkeit zu haben. Die einzige Regel ist keine Vererbung, es sei denn, Sie verwenden mehrere skriptfähige Objekte und verweisen auf die verschiedenen Konfigurationsdateien für komplexe Schäden auf der Festplatte.
Sie benötigen noch den AbilityActivator MonoBehaviour, aber jetzt erledigt er etwas mehr Arbeit.
// 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)
}
}
Der coolste Teil
Die Benutzeroberfläche und die generischen Tricks im ersten Ansatz funktionieren also einwandfrei. Um Unity wirklich optimal nutzen zu können, bringt Sie ScriptableObjects dahin, wo Sie sein möchten. Unity ist insofern großartig, als es Programmierern eine sehr konsistente und logische Umgebung bietet, aber auch alle Feinheiten der Dateneingabe für Designer und Künstler bietet, die Sie von GameMaker, UDK, et. al.
Letzten Monat nahm unser Künstler einen Powerup-ScriptableObject-Typ, der das Verhalten für verschiedene Arten von Zielsuchraketen definieren sollte, kombinierte ihn mit einer AnimationCurve und einem Verhalten, das Raketen über den Boden schweben ließ, und machte diesen verrückten neuen Spinning-Hockey-Puck- Todeswaffe.
Ich muss noch zurückgehen und spezifische Unterstützung für dieses Verhalten hinzufügen, um sicherzustellen, dass es effizient ausgeführt wird. Aber weil wir diese generische Datenbeschreibungsoberfläche erstellt haben, konnte er diese Idee aus dem Nichts herausholen und ins Spiel bringen, ohne dass wir Programmierer wussten, dass er es versuchte, bis er vorbeikam und sagte: "Hey Leute, schaut bei dieser coolen Sache! " Und weil es eindeutig großartig war, freue ich mich darauf, eine robustere Unterstützung dafür hinzuzufügen.