Unity'de sıkı script eşlemesini nasıl önleyebilirim?


24

Bir süre önce Unity ile çalışmaya başladım ve hala sıkı bir şekilde birleştirilmiş senaryolar konusunda mücadele ediyorum. Bu sorunu önlemek için kodumu nasıl yapılandırabilirim?

Örneğin:

Ayrı senaryolarda sağlık ve ölüm sistemlerine sahip olmak istiyorum. Ayrıca oyuncu karakterinin hareket şeklini değiştirmeme izin veren değiştirilebilen farklı yürüyüş senaryolarına sahip olmak istiyorum (Mario gibi süper etli çocuklarda olduğu gibi sıkı, seğirmeli kontroller gibi fizik tabanlı eylemsizlik kontrolleri). Sağlık senaryosunun Ölüm senaryosuna bir referans tutması gerekir, böylece oyuncular sağlığı 0'a ulaştığında Die () yöntemini tetikleyebilir. Ölüm senaryosunda, ölümün üzerinde yürümeyi devre dışı bırakmak için kullanılan yürüme senaryosuna bir referans verilmelidir (I zombilerden bıktım).

Ben ediyorum normalde gibi arayüzler yaratmak IWalking, IHealthve IDeathbenim kod kalanını bozmadan bir kapris de bu öğeleri değiştirebilir böylece. Diyelim ki oyuncu nesnesine ayrı bir komut dosyası koymalarını isterdim PlayerScriptDependancyInjector. Belki senaryo kamu olurdu IWalking, IHealthve IDeathbağımlılıklar sürükleyip uygun komut dosyalarını bırakarak müfettiş gelen düzey tasarımcı tarafından ayarlanabilir böylece, özelliklerini.

Bu, basitçe oyun nesnelerine kolayca davranışlar eklememi ve kodlanmış bağımlılıklar hakkında endişelenmemi sağlayacak.

Birlikteki problem

Sorun Birlik içinde ben denetiminde arabirimleri maruz olmasıdır, ve kendi müfettişleri yazarsanız, referanslar tefrika olsun alışkanlık ve gereksiz bir sürü iş var. Bu yüzden sıkı sıkıya kod yazarak bıraktım. Benim Deathkomut dosyası, bir başvuru ortaya InertiveWalkingsenaryo. Ancak, oynatıcı karakterinin sıkı bir şekilde kontrol edilmesini istediğime karar verirsem, sadece TightWalkingbetiği sürükleyip bırakamıyorum, betiği değiştirmem gerekiyor Death. Bu berbat. Bununla başa çıkabilirim, ama ruhum böyle bir şey yaptığımda her zaman ağlıyor.

Birlik'te arayüzlere tercih edilen alternatif nedir? Bu sorunu nasıl düzeltebilirim? Bulduğum bu , ama ben zaten bildiklerini söylüyor ve nasıl Unity yaptığını bana söylemez! Bu da ne yapılmalı değil, ne yapılması gerektiği ile senaryolar arasında sıkı bağlantı sorununu ele almıyor.

Sonuçta, birliğin kod yazmayı öğrenen bir oyun tasarım geçmişi olan Unity'ye gelenler için yazıldığını ve düzenli geliştiriciler için Unity konusunda çok az kaynak bulunduğunu hissediyorum. Unity'deki kodunuzu yapılandırmanın standart bir yolu var mı, yoksa kendi yöntemimi bulmak zorunda mıyım?


1
The problem is that in Unity I can't expose interfaces in the inspector"Müfettişlikte arayüz ortaya çıkar" derken neyi kastettiğimi sanmıyorum çünkü ilk düşüncem "neden olmasın?" Dı.
saat

@jhocking birlik devs isteyin. Sadece denetçide görmüyorsun. Genel bir özellik olarak ayarlarsanız ve diğer tüm özelliklerin yapması durumunda orada görünmez.
KL

1
ohhh arayüzü sadece değişkenin türü olarak kullanmak, sadece o arayüze sahip bir nesneye gönderme yapmak istemiyorsunuz.
saat

1
jzx

1
@jzx SOLID tasarımını biliyorum ve kullanıyorum ve Robert C. Martins kitaplarının büyük bir hayranıyım ama bu soru Unity'de bunun nasıl yapılacağı ile ilgili. Ve hayır, o makaleyi okumadım, şu anda okudum, teşekkürler :)
KL

Yanıtlar:


14

Sıkı komut dosyası birleşiminden kaçınmak için çalışmanın birkaç yolu vardır. Birliğin İçinde, Monobehaviour'un GameObject'sine hedeflendiğinde bu mesajı oyun nesnesindeki her şeye gönderen bir SendMessage işlevidir. Böylece sağlık nesnenizde böyle bir şey olabilir:

[SerializeField]
private int _currentHealth;
public int currentHealth
{
    get { return _currentHealth;}
    protected set
    {
        _currentHealth = value;
        gameObject.SendMessage("HealthChanged", value, SendMessageOptions.DontRequireReceiver);
    }
}

[SerializeField]
private int _maxHealth;
public int maxHealth
{
    get { return _maxHealth;}
    protected set
    {
        _maxHealth = value;
        if (currentHealth > _maxHealth)
        {
            currentHealth = _maxHealth;
        }
    }
}

public void ChangeHealth(int deltaChange)
{
    currentHealth += deltaChange;
}

Bu gerçekten basitleştirildi, ancak tam olarak ne aldığımı göstermeli. Mülkiyet mesajı atma gibi bir olay olur. Ölüm komut dosyanız daha sonra bu iletiyi keser (ve onu kesecek bir şey yoksa, SendMessageOptions.DontRequireReceiver bir istisna almamanızı garanti eder) ve ayarlandıktan sonra yeni sağlık değerine göre eylemler gerçekleştirir. Gibi:

void HealthChanged(int newHealth)
{
    if (newHealth <= 0)
    {
        Die();
    }
}

void Die ()
{
    Debug.Log ("I am undone!");
    DestroyObject (gameObject);
}

Bununla birlikte, bunun için bir sipariş garantisi yoktur. Tecrübelerime göre, her zaman GameObject üzerindeki en üstteki senaryolardan en üstündeki en senaryolara kadar gider, ancak kendiniz için gözlemleyemediğiniz hiçbir şeyi hesaba katmazdım.

Bileşenleri alan özel bir GameObject işlevi geliştiren ve yalnızca Tür yerine belirli bir arabirimi olan bileşenleri döndüren başka bir çözüm yolu var, biraz daha uzun sürecek, ancak amaçlarınıza iyi hizmet edebilecek.

Ek olarak, atamayı gerçekleştirmeden önce bu Arayüz için editörde bir konuma atanmış bir nesneyi kontrol eden özel bir editör yazabilirsiniz (bu durumda komut dosyasını seri hale getirilmesine izin vererek normal olarak atayarak, bir hata atıyor olacaksınız) doğru arayüzü kullanmadıysa ve ödevi reddederse). Bunların ikisini de daha önce yaptım ve bu kodu oluşturabilir, ancak önce onu bulmak için biraz zamana ihtiyaç duyacak.


5
SendMessage () bu durumu ele alır ve bu komutu birçok durumda kullanırım, ancak bunun gibi hedefsiz bir mesaj sistemi doğrudan alıcıya referans vermekten daha az verimlidir. Nesneler arasındaki tüm rutin iletişim için bu komuta güvenmeye dikkat etmelisiniz.
saat

2
Bileşen nesnelerinin daha fazla iç çekirdek sistem hakkında bildiği ancak çekirdek sistemlerin bileşen parçaları hakkında hiçbir şey bilmediği olayların ve delegelerin kişisel hayranıyım. Fakat yüzde yüz emin olduklarını ve cevaplarını istediklerini belirttiğinden emin değildim. Böylece senaryoların nasıl tamamen çözülmeyeceğine cevap veren bir şeyle gittim.
Brett Satoru

1
Ayrıca, Unity Inspector tarafından desteklenmeyen arayüzleri ve diğer programlama yapılarını ortaya çıkarabilen unity varlık deposunda bulunan bazı eklentilerin bulunduğuna inanıyorum.
Matthew Pigram 13:15

7

Ben şahsen ASLA SendMessage kullanmam. SendMessage ile bileşenleriniz arasında hala bir bağımlılık var, sadece çok kötü bir şekilde gösterilmiş ve kırılması kolaydır. Arabirimleri ve / veya delegeleri kullanmak, SendMessage'ı kullanma gereksinimini gerçekten ortadan kaldırır (bu daha yavaş olmasına rağmen, olması gerektiği kadar endişe olmamasına rağmen).

http://forum.unity3d.com/threads/free-vfw-full-set-of-drawers-savesystem-serialize-interfaces-generics-auto-props-delegates.266165/

Bu adamın eşyalarını kullan. Etkilenen arayüzler gibi bir yığın editör ve AND delegeleri sağlar. Dışarıda bunun gibi birçok şey var, ya da kendi başınıza bile yapabilirsiniz. Ne yaparsanız yapın, Unity'nin eksik senaryo desteği nedeniyle tasarımınızdan ödün vermeyin. Bu şeyler varsayılan olarak Birlik'te olmalıdır.

Bu bir seçenek değilse, mono-davranış sınıflarını sürükleyip bırakın ve bunları dinamik olarak gerekli arayüze aktarın. Daha fazla iş ve daha az zarif, ancak işi halleder.


2
Şahsen ben böyle düşük seviyeli şeyler için eklentilere ve kütüphanelere fazla bağımlı kalmaya can atıyorum. Muhtemelen biraz paranoyak oluyorum, ancak proje mola verme riskim çok yüksek gibi görünüyor çünkü Unity güncellemesinden sonra kodları kırılıyor.
saat

Bu çerçeveyi kullanıyordum ve nihayet durdum çünkü jhocking aklımın arkasında kemiren söz etmişti (ve bir güncellemeden diğerine, bir editör yardımcısından her şeyi içeren bir sisteme gitmesine yardımcı olmadı) fakat geriye doğru uyumluluktan
kurtulurken

6

Başıma gelen Birliğe özgü yaklaşım, başlangıçta GetComponents<MonoBehaviour>()bir komut dosyası listesi almak ve ardından bu komut dosyalarını belirli arabirimler için özel değişkenlere dönüştürmek olacaktır. Bunu bağımlılık enjektör komut dosyasında Başlat () bölümünde yapmak isterdiniz. Gibi bir şey:

MonoBehaviour[] list = GetComponents<MonoBehaviour>();
for (int i = 0; i < list.Length; i++) {
    if (list[i] is IHealth) {
        Debug.Log("Found it!");
    }
}

Aslında, bu yöntemle, bireysel bileşenlerin bağımlılık enjeksiyon komut dosyasına bağımlılıklarının ne olduğunu söylemesini sağlayabilirsiniz, typeof () komutunu ve 'Type' türünü kullanın . Başka bir deyişle, bileşenler bağımlılık enjektörüne bir tür listesi verir ve ardından bağımlılık enjektörü bu tür nesnelerin bir listesini döndürür.


Alternatif olarak, arayüzler yerine sadece soyut sınıfları kullanabilirsiniz. Soyut bir sınıf, bir arayüzle hemen hemen aynı amacı gerçekleştirebilir ve bunu Müfettiş'te referans olarak alabilirsiniz.


Birden fazla arayüz uygulayabildiğim halde, birden fazla sınıftan miras alamıyorum. Bu bir problem olabilir ama sanırım hiç yoktan çok daha iyi :) Cevabınız için teşekkürler!
KL

Düzenleme gelince - bir kez komut dosyası listesi bulunduğumda, kodum hangi komut dosyasının hangi arabirime gidemeyeceğini nasıl bilir?
KL

1
Bunu da açıklamak için düzenlendi
Şubat'ta jhocking

1
Unity 5'te, kullanabilirsiniz GetComponent<IMyInterface>()ve GetComponents<IMyInterface>()doğrudan, whch, onu uygulayan bileşenleri döndürür.
S. Tarık Çetin

5

Kendi deneyimlerime göre, oyun devi geleneksel olarak endüstriyel geliştirmeden daha az pratik bir yaklaşımı içerir (daha az soyutlama katmanları ile). Kısmen, sürdürülebilirlik konusundaki performansı arttırmak istediğiniz ve ayrıca kodunuzu diğer bağlamlarda tekrar kullanma olasılığınızın düşük olması nedeniyle (grafikler ve oyun mantığı arasında genellikle güçlü bir içsel bağlantı vardır).

Endişenizi tamamen anlıyorum, özellikle de performans endişesi en son cihazlarla daha az kritik olduğu için, ancak endüstriyel OOP en iyi uygulamalarını Unity oyununun geliştirilmesine uygulamak istiyorsanız (bağımlılık enjeksiyonu gibi) vb...).


2

Editörde arayüzleri açığa çıkarmanıza izin veren IUnified ( http://u3d.as/content/wounded-wolf/iunified/5H1 ) 'e bakınız. Normal değişken bildirimlerine göre yapacak daha fazla kablo bağlantısı var, hepsi bu.

public interface IPinballValue{
   void Add(int value);
}

[System.Serializable]
public class IPinballValueContainer : IUnifiedContainer<IPinballValue> {}

public class MyClassThatExposesAnInterfaceProperty : MonoBehaviour {
   [SerializeField]
   IPinballValueContainer value;
}

Ek olarak, başka cevapların da belirttiği gibi, SendMessage'ı kullanmak kuplajı kaldırmaz. Bunun yerine derleme zamanında hata yapmaz. Çok kötü - projenizi yükseltirken, sürdürülmesi kabus olabilir.

Düzenleyicide arabirim kullanmadan kodunuzu daha da çözmeyi düşünüyorsanız, güçlü bir şekilde yazılan olaya dayalı bir sistem kullanabilirsiniz (ya da gerçekte gevşek bir şekilde yazılan birini, ama yine de sürdürmesi daha da zor olabilir). Bu yazıyı benim için kullandım , ki bu başlangıç ​​noktası olarak süper uygun. GC'yi tetikleyebilecek bir sürü olay oluştururken dikkat edin. Sorun etrafında bir nesne havuzuyla çalıştım, ancak bunun nedeni çoğunlukla mobil cihazlarla çalışmak.


1

Kaynak: http://www.udellgames.com/posts/ultra-useful-unity-snippet-developers-use-interfaces/

[SerializeField]
private GameObject privateGameObjectName;

public GameObject PublicGameObjectName
{
    get { return privateGameObjectName; }
    set
    {
        //Make sure changes to privateGameObjectName ripple to privateInterfaceName too
        if (privateGameObjectName != value)
        {
            privateInterfaceName = null;
            privateGameObjectName = value;
        }
    }
}

private InterfaceType privateInterfaceName;
public InterfaceType PublicInterfaceName
{
    get
    {
        if (privateInterfaceName == null)
        {
            privateInterfaceName = PublicGameObjectName.GetComponent(typeof(InterfaceType)) as InterfaceType;
        }
        return privateInterfaceName;
    }
    set
    {
        //Make sure changes to PublicInterfaceName ripple to PublicGameObjectName too
        if (privateInterfaceName != value)
        {
            privateGameObjectName = (privateInterfaceName as Component).gameObject;
            privateInterfaceName = value;
        }

    }
}

0

Bahsettiğiniz sınırlamaları aşmak için birkaç farklı strateji kullanıyorum.

Bahsetmediğim şeylerden biri, MonoBehaviorverilen bir arabirimin farklı uygulamalarına erişim sağlayan bir kullanma . Örneğin, Raycasts'ın nasıl uygulandığını tanımlayan bir arayüze sahibim.IRaycastStrategy

public interface IRaycastStrategy 
{
    bool Cast(Ray ray, out RaycastHit raycastHit, float distance, LayerMask layerMask);
}

Sonra sınıflar gibi farklı sınıflarda uygulamak LinecastRaycastStrategyve SphereRaycastStrategybir MonoBehavior uygulamak RaycastStrategySelectorher tür tek bir örneğini ortaya çıkarır. Gibi bir RaycastStrategySelectionBehaviorşey, gibi bir şey uygulanır:

public class RaycastStrategySelectionBehavior : MonoBehavior
{

    private readonly Dictionary<RaycastStrategyType, IRaycastStrategy> _raycastStrategies
        = new Dictionary<RaycastStrategyType, IRaycastStrategy>
        {
            { RaycastStrategyType.Line, new LineRaycastStrategy() },
            { RaycastStrategyType.Sphere, new SphereRaycastStrategy() }
        };

    public RaycastStrategyType StrategyType;


    public IRaycastStrategy RaycastStrategy
    {
        get
        {
            IRaycastStrategy raycastStrategy;
            if (!_raycastStrategies.TryGetValue(StrategyType, out raycastStrategy))
            {
                throw new InvalidOperationException("There is no registered implementation for the selected strategy");
            }
            return raycastStrategy;
        }
    }
}

Uygulamaların yapılarındaki Unity işlevlerine referans göstermesi muhtemelse (veya yalnızca güvenli tarafta olmak için, bu örneği yazarken bunu unuttum) Awake, düzenleyicideki veya ana bilgisayarın dışındaki Unity işlevlerine başvuruda bulunmaktan kaçınmak için kullanın iplik

Bu stratejinin bazı dezavantajları vardır, özellikle de hızla değişen birçok farklı uygulamayı yönetmeniz gerektiğinde. Derleme zamanı denetimi eksikliğiyle ilgili sorunlara, özel bir düzenleyici kullanılarak yardımcı olunabilir, çünkü enum değeri herhangi bir özel çalışma olmadan serileştirilebilir (benim uygulamalarım bu kadar çok olmadığından, genellikle bunu reddetmeme rağmen).

Bu stratejiyi, MonoBehaviors kullanarak sizi ara yüzleri uygulamaya zorlamadan MonoBehaviors'e dayanarak sağladığı esneklik nedeniyle kullanıyorum. Bu, Seçici kullanılarak erişilebildiğinden GetComponent, seri hale getirmenin tümü Birlik tarafından gerçekleştirildiğinden ve Seçici, belirli faktörlere dayalı uygulamaları otomatik olarak değiştirmek için çalışma zamanında mantık uygulayabildiğinden , size "her iki dünyanın da en iyisini" verir .

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.