Karakterin beceri ve yeteneklerini komut olarak yapmak, iyi uygulama?


11

Eşsiz hücum becerileri ve bina, tamir vb. Gibi diğer yetenekleri olan karakterlerden oluşan bir oyun için tasarlıyorum. Oyuncular bu karakterlerin birden fazlasını kontrol edebilir.

Tüm bu beceri ve yetenekleri bireysel komutlara koymayı düşünüyorum. Statik bir denetleyici bu komutların tümünü statik bir komut listesine kaydeder. Statik liste, oyundaki tüm karakterlerin mevcut tüm beceri ve yeteneklerinden oluşacaktır. Dolayısıyla, bir oyuncu karakterlerden birini seçtiğinde ve bir büyü yapmak veya bir yetenek gerçekleştirmek için kullanıcı arayüzündeki bir düğmeyi tıklattığında, Görünüm listeden istenen komutu almak ve yürütmek için statik denetleyiciyi çağırır.

Bununla birlikte, oyunumu Unity'de oluşturduğum göz önüne alındığında, bu iyi bir tasarımsa emin değilim. Tüm becerileri ve yetenekleri bireysel bileşenler olarak yapabileceğimi düşünüyorum, bu da daha sonra oyundaki karakterleri temsil eden GameObjects'e eklenecek. Daha sonra kullanıcı arayüzünün karakterin GameObject öğesini tutması ve ardından komutu yürütmesi gerekir.

Tasarladığım bir oyun için daha iyi bir tasarım ve uygulama ne olabilir?


İyi görünüyor! Bu konuyla ilgili gerçeği oraya atmak: Bazı dillerde, her komutu kendi başına bir işlev haline getirecek kadar ileri gidebilirsiniz. Bu, giriş için kolayca otomatikleştirebileceğiniz için test için bazı harika avantajlara sahiptir. Ayrıca, geri çağırma işlevi değişkeni farklı bir komut işlevine yeniden atanarak kontrol yeniden bağlama kolayca yapılabilir.
Anko

@Anko, tüm komutları statik bir listeye koyduğum bölüm ne olacak? Listenin çok büyük olabileceğinden endişe duyuyorum ve bir komut gerektiğinde, komutların büyük listesini sorgulamak zorunda.
xenon

1
@xenon Kodun bu bölümünde performans sorunlarını görmeniz pek olası değildir. Bir şey kullanıcı etkileşimi başına yalnızca bir kez olabildiğince, performansta göze çarpan bir çentik yapmak için çok hesaplama yoğunluğu gerekir.
aaaaaaaaaaaa

Yanıtlar:


17

TL; DR

Bu cevap biraz deliriyor. Ancak, yeteneklerinizi "Komutlar" olarak uygulamaktan bahsettiğinizi görüyorum, bu da C ++ / Java / .NET tasarım desenlerini ima eden ve kod ağırlıklı bir yaklaşım anlamına geliyor. Bu onay geçerli, ama daha iyi bir yol var. Belki zaten başka bir şey yapıyorsun. Eğer öyleyse, oh iyi. Umarım diğerleri bu durumda faydalı bulurlar.

Kovalamak için aşağıdaki Veriye Dayalı Yaklaşım'a bakın. Jacob Pennock'un CustomAssetUility'sini buradan edinin ve bu konudaki yayınını okuyun .

Unity ile Çalışma

Diğerlerinin de belirttiği gibi, 100-300 öğelik bir listeyi dolaşmak düşündüğünüz kadar büyük bir anlaşma değildir. Bu sizin için sezgisel bir yaklaşımsa, bunu yapın. Beyin verimliliği için optimize edin. @Norguard gösterildiği Ama Sözlük, onun cevap , sen sabit zamanlı ekleme ve alma almak beri bu sorunu ortadan kaldırmak için kolay no-beyin gücü-Gerekli yoludur. Muhtemelen kullanmalısınız.

Bunun Birlik içinde iyi çalışmasını sağlamak için, bağırsaklarım bana yetenek başına bir MonoBehaviour'un aşağı inmek için tehlikeli bir yol olduğunu söylüyor. Yeteneklerinizden biri yürüttükleri zaman içinde durumu koruyorsa, bu durumu sıfırlamak için bir yol sağlamanız gerektiğini yönetmeniz gerekir. Coroutines bu sorunu hafifletir, ancak yine de bu komut dosyasının her güncelleme çerçevesinde bir IEnumerator referansı yönetiyorsunuz ve eksik ve bir döngü halinde sıkışmış yetenekleri sıfırlamak için kesin bir yolunuz olduğundan emin olmalısınız. yetenekler fark edilmeden sessizce oyununuzun dengesini bozmaya başlar. "Elbette bunu yapacağım!" "Ben" İyi Bir Programcıyım "diyorsunuz. Ama gerçekten, biliyorsunuz, hepimiz nesnel olarak korkunç programcılarız ve en büyük AI araştırmacıları ve derleyici yazarları her zaman işleri mahvediyor.

Unity'de komut örnekleme ve erişimini uygulayabileceğiniz tüm yollardan ikisini düşünebilirim: biri iyi ve size bir anevrizma vermeyecek ve diğeri SINIRSIZ BÜYÜ YARATICILIĞINA izin veriyor . Bir çeşit.

Kod Merkezli Yaklaşım

Birincisi, çoğunlukla kod içinde bir yaklaşımdır. Ne tavsiye her bir BaseCommand abtract sınıf devralan ya da bir ICommand arabirimi uygulayan basit bir sınıf yapmak (Bu Komutlar sadece karakter yetenekleri olacak kısalık uğruna varsayıyorum, dahil etmek zor değil) diğer kullanımlar). Bu sistem, her komutun bir ICommand olduğunu, parametre almayan ve etkin durumdayken her karenin güncellenmesini gerektiren bir ortak yapıcıya sahip olduğunu varsayar.

Soyut bir temel sınıf kullanırsanız işler daha kolaydır, ancak sürümüm arabirimler kullanıyor.

MonoBehaviours'larınızın belirli bir davranışı veya yakından ilişkili davranışlar sistemini kapsaması önemlidir. Düzgün C # sınıflarına etkili bir şekilde vekalet eden çok sayıda MonoBehaviour'a sahip olmak iyidir, ancak kendinizi çok iyi bulursanız, XNA oyunu gibi görünmeye başladığı noktaya kadar her türlü farklı nesneye yapılan çağrıları güncelleyebilirsiniz. ciddi bir belada ve mimarinizi değiştirmeniz gerekiyor.

// 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);
    }
}

Bu tamamen iyi çalışıyor, ancak daha iyisini yapabilirsiniz (ayrıca, List<T>zamanlanmış yetenekleri saklamak için en uygun veri yapısı değildir, a LinkedList<T>veya a isteyebilirsiniz SortedDictionary<float, T>).

Veriye Dayalı Yaklaşım

Muhtemelen yeteneğinizin etkilerini parametreleştirilebilen mantıksal davranışlara indirgemeniz mümkündür. Birlik gerçekten bunun için inşa edilmişti. Bir programcı olarak, siz veya bir tasarımcının çok çeşitli efektler üretmek için editörde gidip manipüle edebileceği bir sistem tasarlarsınız. Bu, kodun "donanımını" basitleştirecek ve yalnızca bir yeteneğin yürütülmesine odaklanacaktır. Burada temel sınıfları veya arayüzleri ve jenerikleri karıştırmaya gerek yok. Tüm bunlar tamamen veriye dayalı olacaktır (komut örneklerinin başlatılmasını da basitleştirir).

İhtiyacınız olan ilk şey, yeteneklerinizi tanımlayabilen bir ScriptableObject. ScriptableObjects harika. Genel alanlarını Unity'nin müfettişinde ayarlayabilmeniz için MonoBehaviours gibi çalışmak üzere tasarlanmıştır ve bu değişiklikler diske serileştirilir. Ancak, herhangi bir nesneye bağlı değildirler ve bir sahnedeki bir oyun nesnesine iliştirilmeleri veya örneklenmeleri gerekmez. Bunlar Unity'nin tümünü yakalama veri kovalarıdır. Temel türleri, sıralamaları ve işaretli basit sınıfları (kalıtım yok) serileştirebilirler [Serializable]. Yapılar Unity'de serileştirilemez ve serileştirme, denetçideki nesne alanlarını düzenlemenizi sağlar, bu yüzden unutmayın.

İşte çok yapmaya çalışan bir ScriptableObject. Bunu daha serileştirilmiş sınıflara ve ScriptableObjects'e ayırabilirsiniz, ancak bunun size bunu nasıl yapacağınıza dair bir fikir vermesi gerekir. Normalde bu, C # gibi güzel bir modern nesne yönelimli dilde çirkin görünüyor, çünkü tüm bu numaralarla bazı C89 bokları gibi hissediyor, ancak buradaki gerçek güç, artık desteklemek için yeni kod yazmadan her türlü farklı yetenekleri yaratabiliyor olmanız. onlar. Ve ilk biçiminiz yapmanız gereken şeyi yapmazsa, yapana kadar eklemeye devam edin. Alan adlarını değiştirmediğiniz sürece, tüm eski serileştirilmiş varlık dosyalarınız çalışmaya devam eder.

// 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(); 
}

Hasar bölümünü Serileştirilebilir bir sınıfa daha da soyutlayabilirsiniz, böylece hasar veren veya iyileşen yetenekleri tanımlayabilir ve bir yetenekte birden fazla hasar türüne sahip olabilirsiniz. Birden çok komut dosyası çalıştırılabilir nesne kullanmazsanız ve diskteki farklı karmaşık hasar yapılandırma dosyalarına başvurmazsanız, tek kural devralma olmaz.

Hala AbilityActivator MonoBehaviour'a ihtiyacınız var, ama şimdi biraz daha fazla iş yapıyor.

// 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)
    }
}

EN İYİ kısım

Böylece arayüz ve genel hile ilk yaklaşımda iyi çalışacaktır. Ancak Unity'den gerçekten en iyi şekilde yararlanmak için ScriptableObjects sizi istediğiniz yere götürecektir. Unity, programcılar için çok tutarlı ve mantıklı bir ortam sağlaması açısından harika, aynı zamanda GameMaker, UDK, et. ark.

Geçtiğimiz ay, sanatçımız farklı türlerde güdümlü füzeler için davranış tanımlaması gereken bir powerrip ScriptableObject türü aldı, bunu bir AnimationCurve ve füzeleri yerde gezdiren bir davranışla birleştirdi ve bu çılgın yeni dönen-hokey-puck- ölüm silahı.

Yine de geri dönüp verimli çalıştığından emin olmak için bu davranış için özel destek eklemem gerekiyor. Ancak bu jenerik veri tanımlama arayüzünü yaptığımız için, bu fikri ince havadan çekebildi ve biz gelene kadar programcılar bunu yapmaya çalıştığını bile bilmeden oyuna sokabildi. bu harika şeyde! " Ve açıkça harika olduğu için, daha sağlam destek eklemekten heyecan duyuyorum.


3

TL: DR - yüzlerce veya binlerce yeteneği bir listeye / diziye tekrarlamak istediğinizi düşünüyorsanız, her eylem çağrıldığında, eylemin var olup olmadığını ve yapabilen bir karakter olup olmadığını görmek için gerçekleştirin, sonra aşağıyı okuyun.

Değilse, endişelenmeyin.
6 karakter / karakter türü ve belki 30 yetenek hakkında konuşuyorsanız, o zaman gerçekten ne yaptığınız önemli olmayacaktır, çünkü karmaşıklıkları yönetme yükü aslında her şeyi bir yığın içine dökmekten daha fazla kod ve daha fazla işlem gerektirebilir. sıralama ...

İşte tam da bu yüzden @eBusiness, olay gönderimi sırasında performans sorunlarını görmenizin mümkün olmadığını öne sürüyor, çünkü bunu yapmaya gerçekten zorlanmadıkça, 3- konumunu değiştirmeye kıyasla burada çok fazla iş yok. Ekrandaki milyon köşe noktası vb.

Ayrıca, bu çözüm değil , benzer sorunların daha büyük setlerini yönetmek için bir çözümdür ...

Fakat...

Her şey oyunu ne kadar büyük yaptığınıza, kaç karakterin aynı becerileri paylaştığına, kaç farklı karakterin / farklı becerinin olduğuna bağlı, değil mi?

Becerilerin karakterin bileşenleri olması, ancak karakterlerin kontrolünüze katılması veya kontrolünüzden ayrılması (veya nakavt vb.) Gibi bir komut arayüzünden kaydolma / kayıtlarını kaldırma, StarCraft türünde kısayol tuşları ve komut kartı.

Unity'nin komut dosyası yazma konusunda çok, çok az deneyimim oldu, ancak JavaScript olarak dil konusunda çok rahatım.
İzin veriyorlarsa, neden bu liste basit bir nesne olmasın:

// Command interface wraps this
var registered_abilities = {},

    register = function (name, callback) {
        registered_abilities[name] = callback;
    },
    unregister = function (name) {
        registered_abilities[name] = null;
    },

    call = function (name,/*arr/undef*/params) {
        var callback = registered_abilities[name];
        if (callback) { callback(params); }
    },

    public_interface = {
        register : register,
        unregister : unregister,
        call : call
    };

return public_interface;

Ve şu şekilde kullanılabilir:

var command_card = new CommandInterface();

// one-time setup
system.listen("register-ability",   command_card.register  );
system.listen("unregister-ability", command_card.unregister);
system.listen("use-action",         command_card.call      );

// init characters
var dave = new PlayerCharacter("Dave"); // Character Factory pulls out Dave + dependencies
dave.init();

Dave (). İnit işlevi aşağıdaki gibi görünebilir:

// Inside of Dave class
init = function () {
    // other instance-level stuff ...

    system.notify("register-ability", "repair",  this.Repair );
    system.notify("register-ability", "science", this.Science);
},

die = function () {
    // other clean-up stuff ...

    system.notify("unregister-ability", "repair" );
    system.notify("unregister-ability", "science");
},

resurrect = function () { /* same idea as init */ };

Sadece Dave'den daha fazla insan varsa .Repair(), ama sadece bir Dave olacağını garanti edebilirsin, o zaman sadecesystem.notify("register-ability", "dave:repair", this.Repair);

Ve becerilerini kullanarak system.notify("use-action", "dave:repair");

Kullandığınız listelerin nasıl olduğundan emin değilim. (UnityScript tip sistemi ve VE derleme sonrası neler olduğu açısından).

Muhtemelen listeye girmeyi planladığınız yüzlerce beceriniz varsa (şu anda hangi karakterlere sahip olduğunuza bağlı olarak kayıt ve kayıttan ziyade), tüm JS dizisi boyunca tekrarlayan (tekrar, eğer yaptıkları bu ise) yapmak istediğiniz eylemin adıyla eşleşen bir sınıfın / nesnenin bir özelliğini kontrol etmek için bundan daha az performans gösterecektir.

Daha optimize edilmiş yapılar varsa, bundan daha performans göstereceklerdir.

Ancak her iki durumda da, şimdi kendi eylemlerini kontrol eden Karakterleriniz var (bunu bir adım daha ileri götürün ve onları bileşen / varlık haline getirin) ve VEYA minimum yineleme gerektiren bir kontrol sisteminiz var (sadece isme göre tablo aramaları yapmak).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.