TL; DR
Questa risposta diventa un po 'folle. Ma è perché vedo che stai parlando dell'implementazione delle tue abilità come "Comandi", il che implica modelli di progettazione C ++ / Java / .NET, che implica un approccio pieno di codice. Quell'approvazione è valida, ma c'è un modo migliore. Forse stai già facendo diversamente. Se è così, vabbè. Speriamo che altri lo trovino utile se è così.
Guarda l'approccio basato sui dati di seguito per andare al sodo. Ottieni qui CustomAssetUility di Jacob Pennock e leggi il suo post al riguardo .
Lavorare con l'unità
Come altri hanno già detto, attraversare un elenco di 100-300 articoli non è un grosso problema come potresti pensare. Quindi se questo è un approccio intuitivo per te, allora fallo. Ottimizza per l'efficienza del cervello. Ma il Dizionario, come ha dimostrato @Norguard nella sua risposta , è il modo semplice e non necessario per il cervello di eliminare quel problema poiché si ottiene l'inserimento e il recupero a tempo costante. Probabilmente dovresti usarlo.
Per quanto riguarda il corretto funzionamento di Unity, il mio istinto mi dice che un comportamento MonoBeur per abilità è un percorso pericoloso da percorrere. Se una qualsiasi delle tue abilità mantiene lo stato nel tempo mentre viene eseguita, dovrai gestirla e fornire un modo per ripristinare quello stato. Le coroutine alleviano questo problema, ma stai ancora gestendo un riferimento IEnumerator su ogni frame di aggiornamento di quello script e devi assolutamente assicurarti di avere un modo sicuro per ripristinare le abilità per non essere incompleto e bloccato in un ciclo di stato le abilità iniziano silenziosamente a rovinare la stabilità del gioco quando passano inosservate. "Certo che lo farò!" dici "Sono un buon programmatore"! Ma davvero, sai, siamo tutti programmatori oggettivamente terribili e persino i più grandi ricercatori e scrittori di compilatori di intelligenza artificiale rovinano tutto il tempo.
Di tutti i modi in cui è possibile implementare l'istanza e il recupero dei comandi in Unity, posso pensare a due: uno va bene e non ti darà un aneurisma, e l'altro consente la CREATIVITÀ MAGICA NON RICONDIZIONATA . Una specie di.
Approccio incentrato sul codice
Il primo è un approccio prevalentemente nel codice. Quello che consiglio è di rendere ogni comando una semplice classe che eredita da una classe abtract di BaseCommand o implementa un'interfaccia ICommand (suppongo per brevità che questi comandi siano sempre e solo abilità di carattere, non è difficile da incorporare altri usi). Questo sistema presuppone che ogni comando sia un comando IC, abbia un costruttore pubblico che non accetta parametri e richiede l'aggiornamento di ciascun frame mentre è attivo.
Le cose sono più semplici se usi una classe base astratta, ma la mia versione usa interfacce.
È importante che i tuoi MonoBehaviours incapsulino un comportamento specifico o un sistema di comportamenti strettamente correlati. Va bene avere un sacco di MonoBehaviour che eseguono effettivamente il proxy su semplici classi C #, ma se ti accorgi di fare anche tu potresti aggiornare le chiamate a tutti i tipi di oggetti diversi al punto in cui inizia a sembrare un gioco XNA, allora tu ' sei in gravi difficoltà e devi cambiare la tua architettura.
// 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);
}
}
Funziona perfettamente, ma puoi fare di meglio (inoltre, a List<T>
non è la struttura di dati ottimale per la memorizzazione di abilità a tempo, potresti volere a LinkedList<T>
o a SortedDictionary<float, T>
).
Approccio basato sui dati
È probabile che tu possa ridurre gli effetti della tua abilità in comportamenti logici che possono essere parametrizzati. Questo è ciò per cui Unity è stata davvero costruita. Tu, come programmatore, progetti un sistema che poi tu o un designer potete andare e manipolare nell'editor per produrre un'ampia varietà di effetti. Ciò semplificherà notevolmente il "rigging" del codice e si concentrerà esclusivamente sull'esecuzione di un'abilità. Non c'è bisogno di destreggiarsi tra classi di base o interfacce e generici qui. Sarà tutto basato esclusivamente sui dati (il che semplifica anche l'inizializzazione delle istanze di comando).
La prima cosa di cui hai bisogno è uno ScriptableObject in grado di descrivere le tue abilità. ScriptableObjects sono fantastici. Sono progettati per funzionare come MonoBehaviours in quanto è possibile impostare i loro campi pubblici nella finestra di ispezione di Unity e tali modifiche verranno serializzate su disco. Tuttavia, non sono collegati a nessun oggetto e non devono essere collegati a un oggetto di gioco in una scena o istanziati. Sono i secchi di dati generali di Unity. Possono serializzare tipi di base, enumerazioni e classi semplici (nessuna eredità) contrassegnate [Serializable]
. Le strutture non possono essere serializzate in Unity e la serializzazione è ciò che ti consente di modificare i campi degli oggetti nella finestra di ispezione, quindi ricorda che.
Ecco uno ScriptableObject che cerca di fare molto. Puoi dividerlo in classi più serializzate e ScriptableObjects, ma ciò dovrebbe darti solo un'idea di come procedere. Normalmente sembra brutto in un bel linguaggio moderno orientato agli oggetti come C #, dal momento che sembra davvero una merda C89 con tutti quegli enum, ma il vero potere qui è che ora puoi creare tutti i tipi di abilità diverse senza mai scrivere un nuovo codice per supportare loro. E se il tuo primo formato non fa quello che ti serve, continua ad aggiungerlo fino a quando non lo fa. Finché non cambi i nomi dei campi, tutti i tuoi vecchi file di risorse serializzati continueranno a funzionare.
// 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();
}
Potresti ulteriormente astrarre la sezione Danno in una classe serializzabile in modo da poter definire abilità che infliggono danno o cure e che abbiano più tipi di danno in un'abilità. L'unica regola è l'ereditarietà a meno che non si utilizzino più oggetti scriptabili e si faccia riferimento ai diversi file di configurazione del danno complesso sul disco.
Hai ancora bisogno dell'AbilityActivator MonoBehaviour, ma ora fa un po 'più di lavoro.
// 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)
}
}
La parte più fredda
Quindi l'interfaccia e l'inganno generico nel primo approccio funzioneranno bene. Ma per ottenere davvero il massimo da Unity, ScriptableObjects ti porterà dove vuoi essere. Unity è eccezionale in quanto fornisce un ambiente molto coerente e logico per i programmatori, ma ha anche tutte le caratteristiche di immissione dei dati per designer e artisti che si ottengono da GameMaker, UDK, ecc. al.
Il mese scorso, il nostro artista ha preso un tipo di oggetto ScriptableObject che avrebbe dovuto definire il comportamento per diversi tipi di missili homing, combinato con un AnimationCurve e un comportamento che ha fatto librare i missili lungo il terreno e ha creato questo pazzo nuovo spinning-hockey-puck- arma della morte.
Devo ancora tornare indietro e aggiungere un supporto specifico per questo comportamento per assicurarmi che funzioni in modo efficiente. Ma poiché abbiamo creato questa interfaccia di descrizione dei dati generici, è stato in grado di estrarre questa idea dal nulla e metterla in gioco senza che noi programmatori sapessimo anche che stava cercando di farlo fino a quando non si è avvicinato e ha detto: "Ehi ragazzi, guardate a questa bella cosa! " E poiché è stato chiaramente fantastico, sono entusiasta di aggiungere un supporto più solido per questo.