StartCoroutine / getiri dönüş modeli Unity'de gerçekten nasıl çalışıyor?


134

Koroutin ilkesini anlıyorum. Standart StartCoroutine/ yield returnörüntüyü Unity'de C # ile nasıl çalıştıracağımı biliyorum , örneğin IEnumeratoraracılığıyla geri dönen bir yöntemi çağırın StartCoroutineve bu yöntemde bir şeyler yapın, yield return new WaitForSeconds(1);bir saniye bekleyin, sonra başka bir şey yapın.

Sorum şu: perde arkasında gerçekten neler oluyor? StartCoroutineGerçekten ne yapar ? Ne IEnumeratorolduğunu WaitForSecondsdönen? StartCoroutineKontrolü, çağrılan yöntemin "başka bir şey" kısmına nasıl döndürür? Tüm bunlar, Unity'nin eşzamanlılık modeliyle nasıl etkileşime giriyor (aynı anda birçok şeyin eş anlamlılar kullanılmadan devam ettiği)?


3
C # derleyicisi IEnumerator/ IEnumerable(veya genel eşdeğerleri) döndüren ve yieldanahtar kelimeyi içeren yöntemleri dönüştürür . Yineleyicileri arayın.
Damien_The_Unbeliever

4
Yineleyici, bir "durum makinesi" için çok uygun bir soyutlamadır. Önce bunu anlayın ve Unity eşgüdümlerini de alacaksınız. en.wikipedia.org/wiki/State_machine
Hans Passant

2
Unity etiketi Microsoft Unity tarafından ayrılmıştır. Lütfen kötüye kullanmayın.
Lex Li

11
Bu makaleyi oldukça aydınlatıcı buldum: Ayrıntılı olarak Unity3D coroutines
Kay

5
@Kay - Keşke sana bir bira ısmarlayabilseydim. Bu makale tam da ihtiyacım olan şeydi. Aklımı sorgulamaya başlıyordum, çünkü sorum mantıklı bile değildi, ama makale doğrudan sorumu hayal edebileceğimden daha iyi yanıtlıyor. Belki de gelecekteki SO kullanıcılarının yararına kabul edebileceğim bu bağlantıya bir cevap ekleyebilirsiniz?
Ghopper21

Yanıtlar:


109

Ayrıntılı bağlantıda sık sık başvurulan Unity3D coroutines öldü. Yorumlarda ve cevaplarda bahsedildiği için yazının içeriğini burada yayınlayacağım. Bu içerik bu aynadan geliyor .


Ayrıntılı Unity3D coroutines

Oyunlarda pek çok işlem, birden çok çerçeve boyunca gerçekleşir. Her karede çok çalışan, ancak kare hızını çok fazla etkilememek için birden çok kareye bölünen yol bulma gibi 'yoğun' süreçleriniz var. Çoğu karede hiçbir şey yapmayan, ancak bazen kritik işler yapması için çağrılan oyun tetikleyicileri gibi 'seyrek' süreçleriniz var. Ve ikisi arasında çeşitli süreçler var.

Çok iş parçacığı olmadan birden çok çerçeve üzerinde gerçekleşecek bir işlem oluşturduğunuzda, işi kare başına bir çalıştırılabilen parçalara bölmenin bir yolunu bulmanız gerekir. Merkezi döngüye sahip herhangi bir algoritma için oldukça açıktır: Örneğin bir A * yol bulucu, denemek yerine her çerçevede açık listeden yalnızca bir avuç düğümü işleyerek, düğüm listelerini yarı kalıcı olarak koruyacak şekilde yapılandırılabilir. tüm işi tek seferde yapmak. Gecikmeyi yönetmek için yapılacak bir miktar dengeleme var - sonuçta, kare hızınızı saniyede 60 veya 30 kare ile kilitliyorsanız, işleminiz saniyede yalnızca 60 veya 30 adım atacaktır ve bu, sürecin sadece devam etmesine neden olabilir. genel olarak çok uzun. Düzgün bir tasarım, mümkün olan en küçük çalışma birimini bir seviyede sunabilir - örneğin tek bir A * düğümünü işleyin - ve üstteki katman, birlikte daha büyük parçalar halinde çalışmanın bir yoludur - örneğin, X milisaniye boyunca A * düğümlerini işlemeye devam edin. (Bazı insanlar buna 'zaman dilimleme' diyorlar, ben istemiyorum).

Yine de, çalışmanın bu şekilde bölünmesine izin vermek, durumu bir çerçeveden diğerine aktarmanız gerektiği anlamına gelir. Yinelemeli bir algoritmayı bozuyorsanız, yinelemeler arasında paylaşılan tüm durumu ve daha sonra hangi yinelemenin gerçekleştirileceğini izlemenin bir yolunu korumanız gerekir. Bu genellikle çok kötü değildir - 'A * yol bulucu sınıfının' tasarımı oldukça açıktır - ancak daha az hoş olan başka durumlar da vardır. Bazen çerçeveden çerçeveye farklı türde işler yapan uzun hesaplamalarla karşı karşıya kalırsınız; Durumlarını yakalayan nesne, verileri bir çerçeveden diğerine geçirmek için saklanan, yarı yararlı 'yerellerin' büyük bir karmaşasıyla sonuçlanabilir. Ve seyrek bir süreçle uğraşıyorsanız, genellikle işin ne zaman yapılması gerektiğini izlemek için küçük bir durum makinesi uygulamak zorunda kalırsınız.

Tüm bu durumu birden çok çerçevede açıkça izlemek zorunda kalmak yerine ve senkronizasyon ve kilitlemeyi çoklu iş parçacığı olarak okumak ve yönetmek zorunda kalmak yerine, işlevinizi tek bir kod parçası olarak yazmanız düzgün olmaz mıydı ve işlevin 'duraklaması' ve daha sonra devam etmesi gereken belirli yerleri işaretleyin?

Unity - bir dizi başka ortam ve dil ile birlikte - bunu Coroutines biçiminde sağlar.

Nasıl görünuyorlar? "Unityscript" (Javascript) içinde:

function LongComputation()
{
    while(someCondition)
    {
        /* Do a chunk of work */

        // Pause here and carry on next frame
        yield;
    }
}

C # dilinde:

IEnumerator LongComputation()
{
    while(someCondition)
    {
        /* Do a chunk of work */

        // Pause here and carry on next frame
        yield return null;
    }
}

Nasıl çalışırlar? Unity Technologies için çalışmadığımı hemen söylememe izin verin. Unity kaynak kodunu görmedim. Unity'nin coroutine motorunun cesaretini hiç görmedim. Ancak, tarif edeceğimden tamamen farklı bir şekilde uyguladılarsa, o zaman oldukça şaşıracağım. UT'den biri içeri girip aslında nasıl çalıştığı hakkında konuşmak isterse, bu harika olur.

Büyük ipuçları C # sürümündedir. İlk olarak, işlevin dönüş türünün IEnumerator olduğunu unutmayın. İkinci olarak, ifadelerden birinin getiri getirisi olduğuna dikkat edin. Bu, verim değerinin bir anahtar kelime olması gerektiği ve Unity'nin C # desteği vanilya C # 3.5 olduğu için vanilya C # 3.5 anahtar kelimesi olması gerektiği anlamına gelir. Aslında burada MSDN'de - 'yineleyici bloklar' denen bir şeyden bahsediyoruz. Yani, ne oluyor?

İlk olarak, bu IEnumerator türü var. IEnumerator türü, bir dizi üzerinde imleç gibi davranarak iki önemli üye sağlar: İmlecin o anda üzerinde olduğu öğeyi size veren bir özellik olan Current ve dizideki sonraki öğeye hareket eden bir işlev olan MoveNext (). IEnumerator bir arabirim olduğundan, bu üyelerin tam olarak nasıl uygulandığını belirtmez; MoveNext () yalnızca bir taneCurrent ekleyebilir veya yeni değeri bir dosyadan yükleyebilir veya internetten bir görüntü indirip hashing yapabilir ve yeni hash'i Current olarak depolayabilir ... veya hatta ilk için bir şey yapabilir Sıradaki öğe ve ikincisi için tamamen farklı bir şey. İsterseniz sonsuz bir dizi oluşturmak için bile kullanabilirsiniz. MoveNext (), dizideki sonraki değeri hesaplar (daha fazla değer yoksa yanlış döndürür),

Normalde, bir arabirim uygulamak istiyorsanız, bir sınıf yazmanız, üyeleri uygulamanız vb. Gerekir. Yineleyici blokları, IEnumerator'ü tüm bu güçlükler olmadan uygulamanın uygun bir yoludur - yalnızca birkaç kuralı takip edersiniz ve IEnumerator uygulaması derleyici tarafından otomatik olarak oluşturulur.

Yineleyici bloğu, (a) IEnumerator döndüren ve (b) getiri anahtar sözcüğünü kullanan normal bir işlevdir. Öyleyse getiri anahtar kelimesi gerçekte ne yapar? Sıradaki bir sonraki değerin ne olduğunu veya daha fazla değer olmadığını bildirir. Kodun bir getiri getirisi X veya getiri kesintisiyle karşılaştığı nokta, IEnumerator.MoveNext () 'in durması gereken noktadır; bir getiri dönüşü X, MoveNext () işlevinin true döndürmesine veCurrent değerinin X değerine atanmasına neden olurken, getiri kesintisi MoveNext () 'in false döndürmesine neden olur.

Şimdi, işte püf noktası. Dizi tarafından döndürülen gerçek değerlerin ne olduğu önemli olmak zorunda değildir. MoveNext () öğesini tekrar tekrar çağırabilir ve Current'ı yok sayabilirsiniz; hesaplamalar yine de yapılacaktır. MoveNext () her çağrıldığında, yineleyici bloğunuz, gerçekte hangi ifadeyi verdiğine bakılmaksızın bir sonraki 'verim' ifadesine çalışır. Böylece şöyle bir şey yazabilirsiniz:

IEnumerator TellMeASecret()
{
  PlayAnimation("LeanInConspiratorially");
  while(playingAnimation)
    yield return null;

  Say("I stole the cookie from the cookie jar!");
  while(speaking)
    yield return null;

  PlayAnimation("LeanOutRelieved");
  while(playingAnimation)
    yield return null;
}

ve aslında yazdığınız şey, uzun bir boş değerler dizisi oluşturan bir yineleyici bloğudur, ancak önemli olan, onları hesaplamak için yaptığı işin yan etkileridir. Bu coroutini aşağıdaki gibi basit bir döngü kullanarak çalıştırabilirsiniz:

IEnumerator e = TellMeASecret();
while(e.MoveNext()) { }

Veya daha kullanışlı bir şekilde, onu başka bir işle karıştırabilirsiniz:

IEnumerator e = TellMeASecret();
while(e.MoveNext()) 
{ 
  // If they press 'Escape', skip the cutscene
  if(Input.GetKeyDown(KeyCode.Escape)) { break; }
}

Hepsi zamanlamada Gördüğünüz gibi, her bir getiri dönüş ifadesi bir ifade sağlamalıdır (null gibi), böylece yineleyici bloğun aslında IEnumerator.Current'a atayacağı bir şey vardır. Uzun bir sıfır dizisi tam olarak yararlı değildir, ancak yan etkilerle daha çok ilgileniyoruz. Değil miyiz?

Aslında bu ifadeyle yapabileceğimiz kullanışlı bir şey var. Ya sadece boş bırakmak ve onu görmezden gelmek yerine, daha fazla iş yapmamız gerektiğini umduğumuzda gösteren bir şey verdiysek? Çoğunlukla bir sonraki kareye devam etmemiz gerekir, elbette, ancak her zaman değil: Bir animasyon veya ses oynatıldıktan sonra veya belirli bir süre geçtikten sonra devam etmek istediğimiz pek çok zaman olacaktır. While (playingAnimation), null döndürür; yapılar biraz sıkıcı, sence de öyle değil mi?

Unity, YieldInstruction temel türünü bildirir ve belirli bekleme türlerini gösteren birkaç somut türetilmiş tür sağlar. Beklenen süre geçtikten sonra coroutine devam eden WaitForSeconds'a sahipsiniz. Aynı çerçevede daha sonra belirli bir noktada koroutini devam ettiren WaitForEndOfFrame'e sahipsiniz. Coroutine türünün kendisine sahipsiniz, bu, coroutine A coroutine B'yi verdiğinde, coroutine A'yı coroutine B bitene kadar duraklatır.

Çalışma zamanı açısından bu neye benziyor? Dediğim gibi, Unity için çalışmıyorum, bu yüzden kodlarını hiç görmedim; ama biraz şöyle görünebileceğini tahmin ediyorum:

List<IEnumerator> unblockedCoroutines;
List<IEnumerator> shouldRunNextFrame;
List<IEnumerator> shouldRunAtEndOfFrame;
SortedList<float, IEnumerator> shouldRunAfterTimes;

foreach(IEnumerator coroutine in unblockedCoroutines)
{
    if(!coroutine.MoveNext())
        // This coroutine has finished
        continue;

    if(!coroutine.Current is YieldInstruction)
    {
        // This coroutine yielded null, or some other value we don't understand; run it next frame.
        shouldRunNextFrame.Add(coroutine);
        continue;
    }

    if(coroutine.Current is WaitForSeconds)
    {
        WaitForSeconds wait = (WaitForSeconds)coroutine.Current;
        shouldRunAfterTimes.Add(Time.time + wait.duration, coroutine);
    }
    else if(coroutine.Current is WaitForEndOfFrame)
    {
        shouldRunAtEndOfFrame.Add(coroutine);
    }
    else /* similar stuff for other YieldInstruction subtypes */
}

unblockedCoroutines = shouldRunNextFrame;

Diğer durumları ele almak için nasıl daha fazla YieldInstruction alt tipinin eklenebileceğini hayal etmek zor değil - örneğin, sinyaller için motor seviyesinde destek, onu destekleyen bir WaitForSignal ("SignalName") YieldInstruction ile eklenebilir. Daha fazla YieldInstructions ekleyerek, coroutinlerin kendileri daha anlamlı hale gelebilir - get return new WaitForSignal ("GameOver") bir süredir okunması daha güzeldir (! Signals.HasFired ("GameOver")), bana sorarsanız, tamamen farklı olarak boş döndürür. motorda yapmanın komut dosyasında yapmaktan daha hızlı olabileceği gerçeği.

Bariz olmayan birkaç sonuç Tüm bunlarla ilgili, insanların bazen gözden kaçırdığı ve benim de bahsetmem gerektiğini düşündüğüm birkaç yararlı şey var.

İlk olarak, verim dönüşü sadece bir ifade - herhangi bir ifade - ve YieldInstruction normal bir türdür. Bu, aşağıdaki gibi şeyler yapabileceğiniz anlamına gelir:

YieldInstruction y;

if(something)
 y = null;
else if(somethingElse)
 y = new WaitForEndOfFrame();
else
 y = new WaitForSeconds(1.0f);

yield return y;

Belirli satırlar, yeni WaitForSeconds () döndürür, döndürür yeni WaitForEndOfFrame () vb. Döndürür, yaygındır, ancak aslında kendi başlarına özel formlar değildir.

İkincisi, bu eşgüdüm blokları sadece yineleyici bloklar olduğu için, isterseniz bunları kendiniz yineleyebilirsiniz - bunu sizin için motora yaptırmanız gerekmez. Bunu daha önce bir coroutine kesme koşulları eklemek için kullandım:

IEnumerator DoSomething()
{
  /* ... */
}

IEnumerator DoSomethingUnlessInterrupted()
{
  IEnumerator e = DoSomething();
  bool interrupted = false;
  while(!interrupted)
  {
    e.MoveNext();
    yield return e.Current;
    interrupted = HasBeenInterrupted();
  }
}

Üçüncüsü, diğer coroutine'lerde verebildiğiniz gerçeği, kendi YieldInstructions'ınızı, motor tarafından uygulanmış gibi yüksek performanslı olmasa da uygulamanıza izin verebilir. Örneğin:

IEnumerator UntilTrueCoroutine(Func fn)
{
   while(!fn()) yield return null;
}

Coroutine UntilTrue(Func fn)
{
  return StartCoroutine(UntilTrueCoroutine(fn));
}

IEnumerator SomeTask()
{
  /* ... */
  yield return UntilTrue(() => _lives < 3);
  /* ... */
}

ancak bunu gerçekten tavsiye etmem - bir Coroutine başlatmanın maliyeti benim beğenime göre biraz ağır.

Sonuç Umarım bu, Unity'de bir Coroutine kullandığınızda gerçekte neler olduğunu biraz açıklığa kavuşturur. C # 'ın yineleyici blokları harika küçük bir yapıdır ve Unity kullanmıyor olsanız bile, belki onlardan aynı şekilde yararlanmayı faydalı bulabilirsiniz.


2
Bunu burada yeniden ürettiğiniz için teşekkür ederiz. Mükemmel ve bana çok yardımcı oldu.
Naikrovek

96

Aşağıdaki ilk başlık sorunun doğrudan cevabıdır. Sonraki iki başlık, günlük programcı için daha kullanışlıdır.

Coroutine'lerin Muhtemelen Sıkıcı Uygulama Detayları

Coroutinler Wikipedia ve başka yerlerde açıklanmıştır . Burada sadece pratik bir bakış açısıyla bazı ayrıntılar vereceğim. IEnumerator, yieldvb. Unity'de biraz farklı bir amaç için kullanılan C # dil özellikleridir .

Çok basitçe söylemek gerekirse, bir IEnumerator tek tek talep edebileceğiniz bir değerler koleksiyonuna sahip iddiası tür List. C # 'da, an döndüren bir imzaya sahip bir işlev IEnumeratorgerçekten bir tane oluşturup döndürmek zorunda değildir, ancak C #' ın bir örtük sağlamasına izin verebilir IEnumerator. Daha sonra işlev IEnumerator, ileride döndürülen içeriğin tembel bir şekilde yield returnifadeler aracılığıyla sağlayabilir . Arayan kişi bu örtükten başka bir değer istediğinde IEnumerator, işlev bir sonrakine kadar çalışır.yield return sonraki değeri sağlayan bir ifadeye . Bunun bir yan ürünü olarak, işlev bir sonraki değer talep edilene kadar duraklar.

Unity'de bunları gelecekteki değerleri sağlamak için kullanmıyoruz, işlevin durakladığı gerçeğinden yararlanıyoruz. Bu sömürü nedeniyle, Unity'deki coroutine'lerle ilgili birçok şey bir anlam ifade etmiyor ( IEnumeratorHerhangi bir şeyle ne ilgisi var? Ne yield? Neden new WaitForSeconds(3)? Vb.). "Başlık altında" ne olur, IEnumerator aracılığıyla sağladığınız değerler StartCoroutine(), bir sonraki değerin ne zaman sorulacağına karar vermek için kullanılır , bu da coroutininizin yeniden ne zaman devam edeceğini belirler.

Unity Oyununuz Tek İş Parçacıklı (*)

Coroutinler değil iş parçacığı . Unity'nin bir ana döngüsü vardır ve yazdığınız tüm bu işlevler sırayla aynı ana iş parçacığı tarafından çağrılmaktadır. Bunu, while(true);işlevlerinizden veya eşgüdümlerinizden herhangi birine bir yerleştirerek doğrulayabilirsiniz . Unity editörü dahil her şeyi donduracak. Bu, her şeyin tek bir ana iş parçacığında yürüdüğünün kanıtıdır. Kay'ın yukarıdaki yorumunda bahsettiği bu bağlantı da harika bir kaynak.

(*) Unity, işlevlerinizi bir iş parçacığından çağırır. Dolayısıyla, kendiniz bir iş parçacığı oluşturmadığınız sürece, yazdığınız kod tek iş parçacıklıdır. Elbette Unity başka iş parçacıkları kullanır ve isterseniz kendiniz de ileti dizileri oluşturabilirsiniz.

Oyun Programcıları için Coroutine'lerin Pratik Bir Açıklaması

Aradığınızda Temel olarak, StartCoroutine(MyCoroutine())bu tam olarak düzenli bir işlev çağrısı gibi MyCoroutine()ilk kadar yield return Xnerede, Xgibi bir şey null, new WaitForSeconds(3), StartCoroutine(AnotherCoroutine()), breakbir işlevinden farklı başladığında budur vs.. Unity, bu yield return Xsatırdaki işlevi "duraklatır", diğer işlerle devam eder ve bazı çerçeveler geçer ve zamanı geldiğinde, Unity bu satırın hemen ardından o işlevi sürdürür. Fonksiyondaki tüm yerel değişkenlerin değerlerini hatırlar. Bu şekilde, sahip olabilirsinizfor örneğin her iki saniyede döngü yapan döngüye .

Birlik sizin eşyordam bağlıdır devam edecek zaman XGözlerinde farklı oldu yield return X. Örneğin, kullandıysanız yield return new WaitForSeconds(3);, 3 saniye geçtikten sonra devam eder. Kullandıysanız yield return StartCoroutine(AnotherCoroutine()), AnotherCoroutine()tamamen bittikten sonra devam eder , bu da davranışları zamanında iç içe geçirmenizi sağlar. Eğer a kullandıysanız, bir yield return null;sonraki karede devam eder.


2
Bu çok kötü, UnityGems şimdiye kadar bir süredir kapalı görünüyor. Reddit'teki
ForceMagic

3
Bu çok belirsizdir ve yanlış olma riski taşır. İşte kodun gerçekte nasıl derlendiği ve neden çalıştığı. Ayrıca bu soruya da cevap vermiyor. stackoverflow.com/questions/3438670/…
Louis Hong

Evet sanırım bir oyun programcısının bakış açısından "Unity'de eşgüdümlerin nasıl çalıştığını" açıkladım. Asıl soru, kaputun altında neler olduğunu sormaktı. Cevabımın yanlış kısımlarını işaret edebilirseniz, düzeltmekten memnuniyet duyarım.
Gazihan Alankuş

4
Getirinin yanlış dönmesine katılıyorum, ekledim çünkü birisi cevabımı alamadığım için eleştirdi ve yararlı olup olmadığını gözden geçirmek için acelem vardı ve sadece bağlantıyı ekledim. Şimdi kaldırdım. Bununla birlikte, Unity'nin tek iş parçacıklı olduğunu ve coroutinlerin buna nasıl uyduğunu herkes için açık olmadığını düşünüyorum. Pek çok yeni başlayan Unity programcısı, her şeyi çok belirsiz bir şekilde anladı ve böyle bir açıklamadan yararlandı. Soruya gerçek bir yanıt vermek için cevabımı düzenledim. Önerilere açığız.
Gazihan Alankuş

2
Unity, tek iş parçacıklı fwiw değildir . MonoBehaviour yaşam döngüsü yöntemlerinin çalıştığı bir ana iş parçacığı vardır - ancak başka iş parçacıkları da vardır. Hatta kendi konularınızı oluşturmakta özgürsünüz.
benthehutt

10

Daha basit olamazdı:

Unity (ve tüm oyun motorları) çerçeve tabanlıdır .

Bütün mesele, Birliğin bütün varoluş nedeni, çerçeve temelli olmasıdır. Motor sizin için "her karede" bazı şeyler yapar. (Canlandırır, nesneleri oluşturur, fizik yapar vb.)

Şöyle sorabilirsiniz .. "Oh, bu harika. Ya motorun her karede benim için bir şeyler yapmasını istersem? Motora bir çerçevede şu ve benzerlerini yapmasını nasıl söyleyebilirim?"

Cevap ...

Tam olarak bir "koroutin" bunun içindir.

Bu kadar basit.

Ve şunu bir düşünün ...

"Güncelle" işlevini biliyorsunuz. Oldukça basit, oraya koyduğunuz her şey her karede yapılır . Kelimenin tam anlamıyla aynıdır, koroutin-verim sözdiziminden hiçbir farkı yoktur.

void Update()
 {
 this happens every frame,
 you want Unity to do something of "yours" in each of the frame,
 put it in here
 }

...in a coroutine...
 while(true)
 {
 this happens every frame.
 you want Unity to do something of "yours" in each of the frame,
 put it in here
 yield return null;
 }

Kesinlikle hiçbir fark yok.

Dipnot: Herkesin de belirttiği gibi, Unity'de hiç konu yok . Unity'deki veya herhangi bir oyun motorundaki "çerçevelerin" hiçbir şekilde iş parçacıklarıyla hiçbir bağlantısı yoktur.

Coroutines / verim, Unity'deki çerçevelere nasıl eriştiğinizdir. Bu kadar. (Ve aslında, Unity tarafından sağlanan Update () işlevi ile kesinlikle aynıdır.) Hepsi bu kadar, bu kadar basit.


Teşekkürler! Ancak cevabınız koroutinlerin nasıl kullanılacağını açıklıyor - perde arkasında nasıl çalıştıklarını değil.
Ghopper21

1
Benim için zevk, teşekkürler. Ne demek istediğini anlıyorum - bu, her zaman yordamların ne olduğunu soran yeni başlayanlar için iyi bir cevap olabilir. Şerefe!
Fattie

1
Aslında - cevapların hiçbiri, çok az da olsa "perde arkasında" neler olduğunu açıklamıyor. (Bu, bir programlayıcıya istiflenen bir IEnumerator'dır.)
Fattie

"Kesinlikle bir fark yok" dedin. Öyleyse Unity, zaten tam olarak çalışan bir uygulamaya sahipken neden Coroutines'i yarattı Update()? Demek istediğim, bu iki uygulama ile kullanım durumları arasında en azından küçük bir fark olmalı ki bu oldukça açık.
Leandro Gecozo

hey @LeandroGecozo - Daha fazlasını söyleyeyim, "Güncelleme" sadece ekledikleri bir tür ("aptalca") basitleştirme. (Pek çok insan bunu asla kullanmaz, sadece korutinleri kullanın!) Sorunuzun iyi bir cevabı olduğunu sanmıyorum, bu sadece Unity'nin nasıl olduğu.
Şişko

5

Son zamanlarda bu konuyu araştırın, buraya bir gönderi yazın - http://eppz.eu/blog/understanding-ienumerator-in-unity-3d/ - iç kısımlara ışık tutan (yoğun kod örnekleriyle), temel IEnumeratorarayüz, ve coroutinler için nasıl kullanıldığını.

Bu amaçla koleksiyon numaralandırıcıları kullanmak benim için hala biraz tuhaf görünüyor. Numaralandırıcıların tasarlandıklarının tersidir. Numaralandırıcıların noktası, her erişimde döndürülen değerdir, ancak Coroutines'in noktası, değer döndürülen değerlerin arasındaki koddur. Gerçek döndürülen değer bu bağlamda anlamsızdır.


0

Unity'de otomatik olarak aldığınız temel işlevler Start () işlevi ve Update () işlevidir, bu nedenle Coroutine'ler aslında Start () ve Update () işlevi gibi işlevlerdir. Herhangi bir eski işlev func (), bir Coroutine'nin çağrılabileceği şekilde çağrılabilir. Unity, Coroutine'ler için onları normal işlevlerden farklı kılan belli sınırlar koymuştur. Yerine bir fark

  void func()

Sen yaz

  IEnumerator func()

Coroutines için. Aynı şekilde, normal işlevlerde zamanı kod satırları ile kontrol edebilirsiniz.

  Time.deltaTime

Bir koroutin, zamanın kontrol edilebilmesi için belirli bir tutamaca sahiptir.

  yield return new WaitForSeconds();

Bir IEnumerator / Coroutine içinde yapılabilecek tek şey bu olmasa da, Coroutine'lerin kullanıldığı yararlı şeylerden biridir. Coroutines'in diğer belirli kullanımlarını öğrenmek için Unity'nin betik API'sini araştırmanız gerekir.


0

StartCoroutine, bir IEnumerator işlevini çağırmak için bir yöntemdir. Basit bir void işlevini çağırmaya benzer, tek fark, onu IEnumerator işlevlerinde kullanmanızdır. Bu tür bir işlev, özel bir getiri işlevi kullanmanıza izin verebileceğinden benzersizdir, bir şeyi döndürmeniz gerektiğini unutmayın. Bildiğim kadarıyla bu. Burada basit bir titreme oyunu yazdım , birlik içinde metin yöntemi

    public IEnumerator GameOver()
{
    while (true)
    {
        _gameOver.text = "GAME OVER";
        yield return new WaitForSeconds(Random.Range(1.0f, 3.5f));
        _gameOver.text = "";
        yield return new WaitForSeconds(Random.Range(0.1f, 0.8f));
    }
}

Daha sonra onu IEnumerator'ın kendisinden aradım

    public void UpdateLives(int currentlives)
{
    if (currentlives < 1)
    {
        _gameOver.gameObject.SetActive(true);
        StartCoroutine(GameOver());
    }
}

StartCoroutine () yöntemini nasıl kullandığımı görebileceğiniz gibi. Umarım bir şekilde yardım etmişimdir. Ben de bir başlangıç ​​yapıyorum, bu yüzden beni düzeltirseniz veya beni takdir ederseniz, her türlü geri bildirim harika olur.

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.