कमांड के रूप में चरित्र के कौशल और क्षमताओं को बनाना, अच्छा अभ्यास?


11

मैं एक ऐसे खेल के लिए डिजाइन कर रहा हूं जिसमें ऐसे पात्र शामिल हैं जिनके पास अद्वितीय आक्रामक कौशल और अन्य क्षमताएं जैसे भवन, मरम्मत, आदि खिलाड़ी ऐसे कई पात्रों को नियंत्रित कर सकते हैं।

मैं ऐसे सभी कौशल और क्षमताओं को व्यक्तिगत आदेशों में डालने की सोच रहा हूं। एक स्थिर नियंत्रक इन सभी आदेशों को एक स्थिर कमांड सूची में दर्ज करेगा। स्थिर सूची में सभी उपलब्ध कौशल और खेल के सभी पात्रों की क्षमता शामिल होगी। इसलिए जब कोई खिलाड़ी पात्रों में से किसी एक का चयन करता है और UI पर एक बटन पर क्लिक करके वर्तनी या क्षमता का प्रदर्शन करता है, तो दृश्य सूची से वांछित कमांड प्राप्त करने और उसे निष्पादित करने के लिए स्थिर नियंत्रक को कॉल करेगा।

हालांकि मुझे यकीन नहीं है कि अगर यह एक अच्छा डिज़ाइन है जो मैं एकता में अपना खेल बना रहा हूं। मुझे लगता है कि मैं सभी कौशल और क्षमताओं को व्यक्तिगत घटकों के रूप में बना सकता था, जो तब गेम में पात्रों का प्रतिनिधित्व करने वाले गेम ऑबजेक्ट से जुड़ा होगा। तब यूआई को चरित्र के गेमऑबजेक्ट को पकड़ना होगा और फिर कमांड निष्पादित करना होगा।

एक गेम जो मैं डिजाइन कर रहा हूं, उसके लिए बेहतर डिजाइन और अभ्यास क्या होगा?


अच्छा दिखता है! इस संबंधित तथ्य को वहां से बाहर फेंकना: कुछ भाषाओं में, आप यहां तक ​​कि प्रत्येक कमांड को अपने लिए एक फंक्शन बना सकते हैं। परीक्षण के लिए इसके कुछ भयानक फायदे हैं, क्योंकि आप आसानी से इनपुट को स्वचालित कर सकते हैं। साथ ही, कॉलबैक फ़ंक्शन चर को अलग-अलग कमांड फ़ंक्शन पर पुन: असाइन करके नियंत्रण रिबंडिंग को आसानी से किया जा सकता है।
अंको

@ एंको, उस हिस्से के बारे में क्या जहां मेरे पास सभी कमांड एक स्थिर सूची में हैं? मुझे चिंता है कि सूची बड़ी हो सकती है और हर बार जब एक कमांड की आवश्यकता होती है, तो उसे कमांड की विशाल सूची को क्वेरी करना पड़ता है।
xenon

1
@xenon आप कोड के इस भाग में प्रदर्शन समस्याओं को देखने की बहुत संभावना नहीं है। जहां तक ​​कुछ केवल उपयोगकर्ता बातचीत के अनुसार एक बार हो सकता है, तो प्रदर्शन में ध्यान देने योग्य दंत बनाने के लिए बहुत कम्प्यूटेशनल गहन होना होगा।
आआआआआआआआआआ आआआआआ

जवाबों:


17

टी एल; डॉ

यह जवाब थोड़ा पागल कर देता है। लेकिन ऐसा इसलिए है क्योंकि मैं देख रहा हूं कि आप अपनी क्षमताओं को "कमांड" के रूप में लागू करने के बारे में बात कर रहे हैं, जिसका अर्थ है C ++ / Java / .NET डिजाइन पैटर्न, जो एक कोड-भारी दृष्टिकोण का अर्थ है। यह लगभग मान्य है, लेकिन एक बेहतर तरीका है। शायद आप पहले से ही दूसरे तरीके से कर रहे हैं। यदि हां, तो ठीक है। उम्मीद है कि दूसरों को यह उपयोगी लगे अगर ऐसा हो।

पीछा करने के लिए कटौती करने के लिए नीचे डेटा-प्रेरित दृष्टिकोण देखें। यहां याकूब पेनेक के कस्टमएसेटसेटिलिटी प्राप्त करें और इसके बारे में उनकी पोस्ट पढ़ें ।

एकता के साथ काम करना

जैसा कि दूसरों ने उल्लेख किया है, 100-300 वस्तुओं की सूची का पता लगाना उतना बड़ा सौदा नहीं है जितना आप सोच सकते हैं। तो अगर यह आपके लिए एक सहज दृष्टिकोण है, तो बस ऐसा ही करें। मस्तिष्क की दक्षता का अनुकूलन। शब्दकोश, लेकिन @Norguard के रूप में डिक्शनरी ने अपने जवाब में कहा , उस समस्या को खत्म करने के लिए आसान- ब्रेन-पावर -आवश्यक तरीका है क्योंकि आपको निरंतर-समय सम्मिलन और पुनर्प्राप्ति मिलती है। आपको शायद इसका इस्तेमाल करना चाहिए।

एकता के भीतर इस काम को अच्छी तरह से करने के संदर्भ में, मेरी आंत मुझसे कहती है कि प्रति क्षमता एक मोनोहेवियर नीचे जाने के लिए एक खतरनाक रास्ता है। यदि आपकी क्षमताओं में से कोई भी समय पर निष्पादित होता है, तो आपको उस राज्य को रीसेट करने का एक तरीका प्रदान करना होगा। Coroutines इस समस्या को कम करती है, लेकिन आप अभी भी उस स्क्रिप्ट के हर अपडेट फ्रेम पर एक IEnumerator संदर्भ का प्रबंधन कर रहे हैं, और यह सुनिश्चित करने के लिए कि आपके पास निश्चित रूप से आग है जिससे कि क्षमताओं को अधूरा और अटक-ए-राज्य-लूप को रीसेट किया जा सके। क्षमताओं चुपचाप अपने खेल की स्थिरता को पेंच करना शुरू करते हैं जब वे किसी का ध्यान नहीं जाते हैं। "बेशक मैं ऐसा करूँगा!" आप कहते हैं, "मैं एक 'अच्छा प्रोग्रामर' हूँ!"। लेकिन वास्तव में, आप जानते हैं, हम सभी उद्देश्यपूर्ण भयानक प्रोग्रामर हैं और यहां तक ​​कि सबसे बड़े एआई शोधकर्ताओं और संकलक लेखकों ने हर समय सामान बिखेर दिया है।

सभी तरीकों से आप एकता में कमांड इंस्टेंटेशन और रिट्रीवल को लागू कर सकते हैं, मैं दो के बारे में सोच सकता हूं: एक ठीक है और आपको एक एन्यूरिज्म नहीं देगा, और दूसरा UNIBUNDED MAGICAL CREATIVITY के लिए अनुमति देता है । की तरह।

कोड-सेंट्रिक दृष्टिकोण

पहला एक ज्यादातर इन-कोड दृष्टिकोण है। मेरा सुझाव है कि आप प्रत्येक कमांड को एक साधारण वर्ग बनाते हैं, जो या तो बेसकॉम्बैंड एबट्रैक्ट क्लास से विरासत में मिलता है या एक आईसीओएमएंड इंटरफ़ेस लागू करता है (मैं संक्षिप्तता के लिए मान रहा हूं कि ये कमांड केवल चरित्र क्षमता होंगे, यह शामिल करना मुश्किल नहीं है अन्य उपयोग)। यह सिस्टम मानता है कि प्रत्येक कमांड एक ICommand है, एक सार्वजनिक कंस्ट्रक्टर है जो कोई पैरामीटर नहीं लेता है, और सक्रिय होने पर प्रत्येक फ्रेम को अपडेट करने की आवश्यकता होती है।

यदि आप एक सार आधार वर्ग का उपयोग करते हैं तो चीजें सरल हैं, लेकिन मेरा संस्करण इंटरफेस का उपयोग करता है।

यह महत्वपूर्ण है कि आपका मोनोबीवियर्स एक विशिष्ट व्यवहार या निकट-संबंधित व्यवहारों की एक प्रणाली को अतिक्रमण करता है। यह बहुत अच्छा है MonoBehaviours है कि प्रभावी रूप से सिर्फ सादे सी # कक्षाओं के लिए बाहर छद्म है, लेकिन अगर आप पाते हैं अपने आप को भी कर रहा है अद्यतन करने के लिए विभिन्न वस्तुओं के सभी प्रकार को इंगित करने के लिए जहां यह एक 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);
    }
}

यह पूरी तरह से ठीक काम करता है, लेकिन आप बेहतर कर सकते हैं (यह भी, List<T>समयबद्ध क्षमताओं को संग्रहीत करने के लिए इष्टतम डेटा संरचना नहीं है, आप एक LinkedList<T>या चाहते हो सकते हैं SortedDictionary<float, T>)।

डेटा-प्रेरित दृष्टिकोण

यह संभवतः संभव है कि आप अपनी क्षमता के प्रभावों को तार्किक व्यवहार में कम कर सकते हैं जो कि इसे मानकीकृत किया जा सकता है। यह वही है जो वास्तव में एकता के लिए बनाया गया था। आप, एक प्रोग्रामर के रूप में, एक ऐसी प्रणाली डिजाइन करते हैं, जिसके बाद या तो आप या एक डिजाइनर विभिन्न प्रकार के प्रभावों का उत्पादन करने के लिए संपादक में जा सकते हैं और हेरफेर कर सकते हैं। यह कोड की "हेराफेरी" को बहुत सरल करेगा, और विशेष रूप से एक क्षमता के निष्पादन पर ध्यान केंद्रित करेगा। यहां आधार कक्षाओं या इंटरफेस और जेनरिक को टटोलने की जरूरत नहीं है। यह सभी विशुद्ध रूप से संचालित डेटा होगा (जो कमांड इंस्टेंसेस को इनिशियलाइज़ करने में भी सरल करता है)।

पहली चीज़ जो आपको चाहिए वह है एक ScriptableObject जो आपकी क्षमताओं का वर्णन कर सकता है। ScriptableObjects कमाल के हैं। वे MonoBehaviours की तरह काम करने के लिए डिज़ाइन किए गए हैं, जिसमें आप एकता के इंस्पेक्टर में उनके सार्वजनिक क्षेत्र सेट कर सकते हैं, और उन परिवर्तनों को डिस्क पर क्रमबद्ध रूप से प्राप्त किया जाएगा। हालांकि, वे किसी भी वस्तु से नहीं जुड़े हैं और किसी दृश्य या तात्कालिक रूप से किसी गेम ऑब्जेक्ट से जुड़े नहीं हैं। वे एकता के कैच-ऑल डेटा बकेट हैं। वे मूल प्रकार, एनम और सरल वर्ग (कोई विरासत) चिह्नित कर सकते हैं [Serializable]। यूनिटी में संरचनाओं को क्रमबद्ध नहीं किया जा सकता है, और क्रमांकन वह है जो आपको निरीक्षक में ऑब्जेक्ट फ़ील्ड को संपादित करने की अनुमति देता है, इसलिए याद रखें।

यहाँ एक ScriptableObject है जो बहुत कुछ करने की कोशिश करता है। आप इसे और अधिक क्रमबद्ध कक्षाओं और ScriptableObjects में तोड़ सकते हैं, लेकिन यह सिर्फ आपको यह अंदाजा देता है कि इसे कैसे करना है। आम तौर पर यह C # जैसी एक अच्छी आधुनिक वस्तु उन्मुख भाषा में बदसूरत दिखता है, क्योंकि यह वास्तव में उन सभी Enums के साथ कुछ 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(); 
}

आप नुकसान की धारा को एक धारावाहिक श्रेणी में आगे बढ़ा सकते हैं ताकि आप उन क्षमताओं को परिभाषित कर सकें जो नुकसान, या चंगा करती हैं, और एक क्षमता में कई नुकसान प्रकार हैं। जब तक आप एक से अधिक स्क्रिप्ट योग्य ऑब्जेक्ट्स का उपयोग नहीं करते हैं और डिस्क पर विभिन्न जटिल क्षति कॉन्फ़िगरेशन फ़ाइलों को संदर्भित करते हैं, तब तक एकमात्र नियम कोई विरासत नहीं है।

आपको अभी भी एबिलिटीएक्टीवेटर मोनोबोहेवियर की ज़रूरत है, लेकिन अब वह थोड़ा और काम करता है।

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

COOLEST भाग

तो पहले दृष्टिकोण में इंटरफ़ेस और सामान्य चालबाजी ठीक काम करेगी। लेकिन वास्तव में एकता से सबसे अधिक पाने के लिए, ScriptableObjects आपको वहीं मिलेगा जहां आप होना चाहते हैं। एकता इस मायने में शानदार है कि यह प्रोग्रामर्स के लिए बहुत सुसंगत और तार्किक वातावरण प्रदान करती है, लेकिन गेममेकर, यूडीके, ईटी से आपको प्राप्त होने वाले डिजाइनरों और कलाकारों के लिए सभी डेटा एंट्री निकेट्स भी हैं। अल।

पिछले महीने, हमारे कलाकार ने एक पावरअप ScriptableObject प्रकार लिया, जो कि विभिन्न प्रकार की होमिंग मिसाइलों के लिए व्यवहार को परिभाषित करने वाला था, इसे एक एनिमेशनक्रूव और एक ऐसे व्यवहार के साथ जोड़ा गया, जिससे मिसाइलें जमीन पर मंडराने लगीं, और इस पागल स्पिनिंग-हॉकी-पुक- मौत का हथियार

मुझे अभी भी वापस जाने की ज़रूरत है और इस व्यवहार के लिए विशिष्ट समर्थन जोड़ने के लिए सुनिश्चित करें कि यह कुशलतापूर्वक चल रहा है। लेकिन क्योंकि हमने इस सामान्य डेटा विवरण इंटरफ़ेस को बनाया था, वह इस विचार को पतली हवा से बाहर निकालने में सक्षम था और इसे हमारे बिना प्रोग्रामर के गेम में डाल दिया, यहां तक ​​कि यह जानते हुए भी कि वह ऐसा करने की कोशिश कर रहा था जब तक वह ऊपर नहीं आया और कहा, "अरे दोस्तों, देखो इस शांत बात पर! " और क्योंकि यह स्पष्ट रूप से भयानक था, मैं इसके लिए और अधिक मजबूत समर्थन जोड़ने के लिए उत्साहित हूं।


3

TL: DR - यदि आप एक सूची / सरणी में सैकड़ों या हजारों क्षमताओं को भरने के बारे में सोच रहे हैं, तो आप इसके माध्यम से पुनरावृति करेंगे, हर बार जब कोई कार्रवाई होती है, तो यह देखने के लिए कि क्या कार्रवाई मौजूद है और यदि कोई चरित्र है तो इसे निष्पादित करें, फिर नीचे पढ़ें।

यदि नहीं, तो इसके बारे में चिंता न करें।
यदि आप 6 वर्णों / चरित्र-प्रकारों और शायद 30 क्षमताओं के बारे में बात कर रहे हैं, तो यह वास्तव में आपके द्वारा क्या करने से कोई फर्क नहीं पड़ता है, क्योंकि प्रबंध जटिलताओं के ओवरहेड को वास्तव में ढेर में सब कुछ डंप करने की तुलना में अधिक कोड और अधिक प्रसंस्करण की आवश्यकता हो सकती है छँटाई ...

इसीलिए @eBusiness ने सुझाव दिया है कि आप इवेंट-प्रेषण के दौरान प्रदर्शन के मुद्दों को देखने की संभावना नहीं रखते हैं, क्योंकि जब तक आप ऐसा करने के लिए वास्तव में कठिन प्रयास नहीं कर रहे हैं, तब तक यहां बहुत अधिक काम नहीं होता है, 3- की स्थिति बदलने की तुलना में स्क्रीन पर मिलियन वर्टिकल आदि ...

इसके अलावा, यह समाधान नहीं है , बल्कि समान मुद्दों के बड़े सेट के प्रबंधन के लिए एक समाधान है ...

परंतु...

यह सब नीचे आता है कि आप कितना बड़ा खेल बना रहे हैं, कितने अक्षर समान कौशल साझा करते हैं, कितने अलग-अलग वर्ण / विभिन्न कौशल हैं, है ना?

कौशल होना चरित्र के घटक होते हैं, लेकिन उन्हें कमांड-इंटरफ़ेस से पंजीकृत / अपंजीकृत करना जैसे कि चरित्र जुड़ते हैं या आपका नियंत्रण छोड़ देते हैं (या बाहर खटखटाया / आदि) अभी भी समझ में आता है, बहुत ही स्टारक्राफ्ट तरह से, हॉटकी के साथ। कमांड कार्ड।

मुझे एकता की पटकथा के साथ बहुत कम अनुभव है, लेकिन मैं भाषा के रूप में जावास्क्रिप्ट के साथ बहुत सहज हूं।
यदि वे इसकी अनुमति देते हैं, तो उस सूची को एक साधारण वस्तु क्यों न बनाया जाए:

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

और इसका उपयोग इस तरह किया जा सकता है:

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

जहाँ डेव (); init फंक्शन लग सकता है:

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

यदि केवल डेव से अधिक लोगों के पास है .Repair(), लेकिन आप गारंटी दे सकते हैं कि केवल एक डेव होने जा रहा है, तो बस इसे बदल देंsystem.notify("register-ability", "dave:repair", this.Repair);

और कौशल का उपयोग करके कॉल करें system.notify("use-action", "dave:repair");

मुझे यकीन नहीं है कि आप जिन सूचियों का उपयोग कर रहे हैं वे पसंद हैं। (यूनिटीस्क्रिप्ट टाइप-सिस्टम के संदर्भ में, और पोस्ट-संकलन पर क्या चल रहा है) के संदर्भ में।

मैं शायद यह कह सकता हूं कि यदि आपको सैकड़ों कौशल मिले हैं जो आप सूची में भरने के बजाय योजना (पंजीकरण और अपंजीकृत करने के आधार पर, जिस पर आपके पास वर्तमान में उपलब्ध हैं) के आधार पर योजना बना रहे हैं, तो यह पूरे जेएस सरणी (फिर, के माध्यम से पुनरावृत्ति करता है) यदि वे ऐसा कर रहे हैं) एक वर्ग / वस्तु की संपत्ति की जांच करने के लिए, जो उस क्रिया के नाम से मेल खाती है जिसे आप प्रदर्शन करना चाहते हैं, तो इससे कम प्रदर्शन होने वाला है।

यदि अधिक-अनुकूलित संरचनाएं हैं, तो वे इससे अधिक प्रदर्शन करने वाले हैं।

लेकिन या तो मामले में, अब आपके पास वर्ण हैं जो अपने स्वयं के कार्यों को नियंत्रित करते हैं (इसे एक कदम आगे ले जाएं और उन्हें घटक / संस्थाएं बनाएं, यदि आप चाहें), और आपके पास एक नियंत्रण प्रणाली है जिसके लिए कम से कम पुनरावृत्ति की आवश्यकता है (जैसा कि आप बस हैं नाम से टेबल-लुकअप करना)।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.