TL; DR
この答えは少しおかしいです。しかし、それは、あなたがあなたの能力を「コマンド」として実装することについて話しているのを見ているからです。そのアプローチは有効ですが、もっと良い方法があります。多分あなたはすでに他の方法をやっています。もしそうなら、まあ。うまくいけば、他の人がそれが役に立つと思うでしょう。
以下のデータドリブンアプローチを参照して、追跡に進んでください。Jacob PennockのCustomAssetUilityをここで取得し、彼に関する投稿を読んでください。
Unityでの作業
他の人が述べたように、100-300のアイテムのリストをトラバースすることは、あなたが考えるほど大したことではありません。したがって、それが直感的なアプローチである場合は、それを実行してください。脳の効率を最適化します。しかし、@ Norguardが彼の回答で示したように、ディクショナリは、一定の時間の挿入と取得ができるため、その問題を簡単に解決するための簡単な方法です。あなたはおそらくそれを使うべきです。
Unity内でこれをうまく機能させるという点で、私の腸は、能力ごとに1つのMonoBehaviourは危険な道を進むことを教えてくれます。いずれかの能力が実行中に状態を維持する場合、その状態をリセットする方法を提供するために、その能力を管理する必要があります。コルーチンはこの問題を軽減しますが、そのスクリプトのすべての更新フレームでIEnumerator参照を管理しているため、不完全でスタックした状態ループを防ぐために、確実にアビリティをリセットする確実な方法があることを確認する必要があります。アビリティは、気づかれないうちに静かにゲームの安定性を台無しにし始めます。「もちろん私がやります!」あなたは「私は「良いプログラマ」です!」と言います。しかし、実際には、私たちはみな客観的にひどいプログラマーであり、最高のAI研究者やコンパイラーライターでさえも、常に物事を台無しにしています。
Unityでコマンドのインスタンス化と取得を実装するすべての方法のうち、2つを考えることができます。1つは問題なく、動脈瘤をもたらさず、もう1つはUNBOUNDED MAGICAL CREATIVITYを可能にします。ちょっと。
コード中心のアプローチ
1つは、ほぼコード内のアプローチです。私がお勧めするのは、各コマンドを、BaseCommand抽象クラスから継承するか、ICommandインターフェースを実装する単純なクラスにすることです(簡潔にするために、これらのコマンドは文字の能力のみであり、組み込むのは難しくありません)その他の用途)。このシステムは、各コマンドがICommandであり、パラメーターを取らないパブリックコンストラクターがあり、アクティブなときに各フレームを更新する必要があると想定しています。
抽象基本クラスを使用すればより簡単ですが、私のバージョンではインターフェースを使用しています。
MonoBehavioursが1つの特定の動作、または密接に関連する動作のシステムをカプセル化することが重要です。単純なC#クラスに効果的にプロキシする多くのMonoBehaviourがあっても問題ありませんが、自分自身もさまざまな種類のオブジェクトへの呼び出しを更新して、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);
}
}
これは完全に問題なく機能しますが、あなたはより良いことができます(また、a List<T>
は時限能力を格納するための最適なデータ構造ではないため、a LinkedList<T>
またはa が必要になる場合がありますSortedDictionary<float, T>
)。
データ駆動型アプローチ
能力の効果を、パラメーター化できる論理的な動作にまで減らすことができる可能性があります。これはUnityが本当に構築された目的です。プログラマーとして、システムを設計します。このシステムを使用すると、ユーザーまたはデザイナーは、エディターで移動して操作し、さまざまな効果を生み出すことができます。これにより、コードの「リギング」が大幅に簡略化され、アビリティの実行のみに集中できます。ここでは、基本クラスまたはインターフェイスとジェネリックスを調整する必要はありません。すべて純粋にデータ駆動型になります(コマンドインスタンスの初期化も簡単になります)。
最初に必要なのは、自分の能力を説明できるScriptableObjectです。ScriptableObjectsは素晴らしいです。Unityのインスペクターでパブリックフィールドを設定できるという点でMonoBehavioursのように機能するように設計されており、それらの変更はディスクにシリアル化されます。ただし、これらはオブジェクトにアタッチされておらず、シーン内のゲームオブジェクトにアタッチしたり、インスタンス化したりする必要はありません。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();
}
ダメージセクションをSerializableクラスにさらに抽象化して、ダメージを与える、または回復する能力を定義し、1つの能力に複数のダメージタイプを含めることができます。複数のスクリプト可能なオブジェクトを使用し、ディスク上のさまざまな複雑な損傷構成ファイルを参照しない限り、唯一のルールは継承ではありません。
あなたはまだ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などから取得したデザイナーやアーティストのためのすべてのデータ入力機能も備えています。al。
先月、私たちのアーティストは、さまざまな種類のホーミングミサイルの動作を定義するはずのPowerable ScriptableObjectタイプを採用し、AnimationCurveと組み合わせて、ミサイルを地面にホバリングさせる動作と、このクレイジーな新しい回転ホッケーパックを作成しました死の武器。
まだ戻ってこの動作の特定のサポートを追加して、効率的に実行されるようにする必要があります。しかし、この一般的なデータ記述インターフェイスを作成したので、プログラマーがやって来て、「こんにちは、見てください。このクールなことで!」そして、それは明らかに素晴らしいものだったので、より強力なサポートを追加できることに興奮しています。