Bagaimana cara StartCoroutine / pola pengembalian hasil bekerja di Unity?

134

Saya mengerti prinsip coroutine. Saya tahu bagaimana cara mendapatkan standar StartCoroutine/ yield returnpola untuk bekerja di C # di Unity, misalnya memanggil metode yang kembali IEnumeratormelalui StartCoroutinedan dalam metode itu melakukan sesuatu, lakukan yield return new WaitForSeconds(1);untuk menunggu sebentar, kemudian lakukan sesuatu yang lain.

Pertanyaan saya adalah: apa yang sebenarnya terjadi di balik layar? Apa yang StartCoroutinesebenarnya dilakukan? Apa IEnumeratoryang WaitForSecondskembali? Bagaimana StartCoroutinemengembalikan kontrol ke bagian "sesuatu yang lain" dari metode yang dipanggil? Bagaimana semua ini berinteraksi dengan model konkurensi Unity (di mana banyak hal terjadi pada saat yang sama tanpa menggunakan coroutine)?

Ghopper21
sumber
3
Compiler C # mengubah metode yang mengembalikan IEnumerator/ IEnumerable(atau setara dengan generik) dan yang mengandung yieldkata kunci. Cari iterator.
Damien_The_Unbeliever
4
Sebuah iterator adalah abstraksi yang sangat nyaman untuk "mesin negara". Pahami itu dulu dan Anda akan mendapatkan coroutine Unity juga. en.wikipedia.org/wiki/State_machine
Hans Passant
2
Tag unity dicadangkan oleh Microsoft Unity. Tolong jangan menyalahgunakannya.
Lex Li
11
Saya menemukan artikel ini cukup mencerahkan: Unity3D coroutine secara detail
Kay
5
@Kay - Kuharap aku bisa membelikanmu bir. Artikel itulah yang saya butuhkan. Saya mulai mempertanyakan kewarasan saya karena tampaknya pertanyaan saya bahkan tidak masuk akal, tetapi artikel itu langsung menjawab pertanyaan saya lebih baik daripada yang bisa saya bayangkan. Mungkin Anda dapat menambahkan jawaban dengan tautan ini yang dapat saya terima, untuk kepentingan pengguna SO di masa mendatang?
Ghopper21

Jawaban:

109

Coroutine Unity3D yang sering dirujuk dalam tautan detail sudah mati. Karena itu disebutkan dalam komentar dan jawaban saya akan memposting konten artikel di sini. Konten ini berasal dari cermin ini .


Unity3D coroutine secara rinci

Banyak proses dalam game berlangsung selama beberapa frame. Anda punya proses 'padat', seperti merintis jalan, yang bekerja keras setiap frame tetapi terbelah di beberapa frame sehingga tidak terlalu mempengaruhi framerate. Anda memiliki proses 'jarang', seperti pemicu gameplay, yang tidak melakukan apa pun pada sebagian besar frame, tetapi kadang-kadang diminta untuk melakukan pekerjaan penting. Dan Anda punya berbagai macam proses di antara keduanya.

Setiap kali Anda membuat proses yang akan berlangsung di beberapa frame - tanpa multithreading - Anda perlu menemukan beberapa cara untuk memecah pekerjaan menjadi potongan-potongan yang dapat dijalankan satu per frame. Untuk algoritme apa pun dengan loop pusat, cukup jelas: pathfinder A *, misalnya, dapat disusun sedemikian rupa sehingga mempertahankan daftar simpulnya secara semi-permanen, hanya memproses beberapa node dari daftar terbuka setiap frame, alih-alih mencoba untuk melakukan semua pekerjaan sekaligus. Ada beberapa penyeimbangan yang harus dilakukan untuk mengelola latensi - setelah semua, jika Anda mengunci framerate Anda pada 60 atau 30 frame per detik, maka proses Anda hanya akan mengambil 60 atau 30 langkah per detik, dan itu mungkin menyebabkan proses hanya mengambil keseluruhan terlalu lama. Desain yang rapi mungkin menawarkan unit kerja sekecil mungkin di satu tingkat - mis proses satu simpul * A - dan lapisan di atas cara pengelompokan bekerja bersama menjadi potongan yang lebih besar - misalnya tetap memproses simpul A * untuk X milidetik. (Beberapa orang menyebut ini 'timeslicing', walaupun saya tidak).

Namun, membiarkan pekerjaan dibubarkan dengan cara ini berarti Anda harus mentransfer status dari satu frame ke frame berikutnya. Jika Anda memecah algoritma iteratif ke atas, maka Anda harus mempertahankan semua status yang dibagikan di seluruh iterasi, serta cara melacak mana iterasi yang akan dilakukan selanjutnya. Itu biasanya tidak terlalu buruk - desain 'kelas pathfinder A *' cukup jelas - tetapi ada kasus lain juga yang kurang menyenangkan. Terkadang Anda akan menghadapi perhitungan panjang yang melakukan berbagai jenis pekerjaan dari bingkai ke bingkai; objek yang menangkap keadaan mereka dapat berakhir dengan kekacauan besar 'penduduk setempat' yang semi-berguna, yang disimpan untuk meneruskan data dari satu frame ke frame berikutnya. Dan jika Anda berurusan dengan proses yang jarang, Anda seringkali harus menerapkan mesin negara kecil hanya untuk melacak ketika pekerjaan harus dilakukan sama sekali.

Bukankah lebih rapi jika, daripada harus secara eksplisit melacak semua keadaan ini di beberapa frame, dan bukannya harus multithread dan mengelola sinkronisasi dan penguncian dan sebagainya, Anda bisa menulis fungsi Anda sebagai satu potongan kode, dan tandai tempat-tempat tertentu di mana fungsi harus 'berhenti' dan melanjutkan di lain waktu?

Persatuan - bersama dengan sejumlah lingkungan dan bahasa lain - menyediakan ini dalam bentuk Coroutine.

Bagaimana penampilan mereka? Dalam "Unityscript" (Javascript):

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

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

Dalam C #:

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

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

Bagaimana mereka bekerja? Biar saya katakan, cepat, bahwa saya tidak bekerja untuk Unity Technologies. Saya belum melihat kode sumber Unity. Saya belum pernah melihat nyali mesin coroutine Unity. Namun, jika mereka telah mengimplementasikannya dengan cara yang sangat berbeda dari apa yang akan saya jelaskan, maka saya akan cukup terkejut. Jika ada orang dari UT yang ingin berpura-pura dan berbicara tentang cara kerjanya, maka itu akan sangat bagus.

Petunjuk besar ada di versi C #. Pertama, perhatikan bahwa tipe pengembalian untuk fungsi adalah IEnumerator. Dan kedua, perhatikan bahwa salah satu pernyataannya adalah imbal hasil. Ini berarti bahwa hasil harus berupa kata kunci, dan karena dukungan C # Unity adalah vanilla C # 3.5, itu harus kata kunci vanilla C # 3.5. Memang, ini dia di MSDN - berbicara tentang sesuatu yang disebut 'blok iterator.' Jadi apa yang terjadi?

Pertama, ada tipe IEnumerator ini. Tipe IEnumerator bertindak seperti kursor di atas suatu urutan, menyediakan dua anggota signifikan: Arus, yang merupakan properti yang memberi Anda elemen yang kursornya selesai, dan MoveNext (), sebuah fungsi yang bergerak ke elemen berikutnya dalam urutan. Karena IEnumerator adalah antarmuka, itu tidak menentukan secara tepat bagaimana anggota ini diimplementasikan; MoveNext () bisa saja menambahkan satu keCurrent, atau bisa memuat nilai baru dari file, atau bisa mengunduh gambar dari Internet dan hash dan menyimpan hash baru di Current ... atau bahkan dapat melakukan satu hal untuk yang pertama elemen dalam urutan, dan sesuatu yang sama sekali berbeda untuk yang kedua. Anda bahkan dapat menggunakannya untuk menghasilkan urutan yang tidak terbatas jika diinginkan. MoveNext () menghitung nilai selanjutnya dalam urutan (menghasilkan false jika tidak ada lagi nilai),

Biasanya, jika Anda ingin mengimplementasikan antarmuka, Anda harus menulis sebuah kelas, mengimplementasikan anggota, dan sebagainya. Blok Iterator adalah cara yang mudah untuk mengimplementasikan IEnumerator tanpa semua kerumitan - Anda cukup mengikuti beberapa aturan, dan implementasi IEnumerator dihasilkan secara otomatis oleh kompiler.

Blok iterator adalah fungsi reguler yang (a) mengembalikan IEnumerator, dan (b) menggunakan kata kunci hasil. Jadi apa yang sebenarnya dilakukan kata kunci hasil? Ini menyatakan apa nilai selanjutnya dalam urutan itu - atau bahwa tidak ada lagi nilai. Titik di mana kode menemukan imbal hasil X atau hasil istirahat adalah titik di mana IEnumerator.MoveNext () harus berhenti; imbal hasil X menyebabkan MoveNext () mengembalikan true andCurrent untuk diberi nilai X, sedangkan imbal hasil menyebabkan MoveNext () mengembalikan false.

Sekarang, inilah triknya. Tidak masalah apa nilai aktual yang dikembalikan oleh urutan. Anda dapat memanggil MoveNext () berulang-ulang, dan mengabaikan Current; perhitungan akan tetap dilakukan. Setiap kali MoveNext () dipanggil, blok iterator Anda berjalan ke pernyataan 'hasil' berikutnya, terlepas dari ekspresi apa yang sebenarnya dihasilkannya. Jadi, Anda dapat menulis sesuatu seperti:

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

dan apa yang sebenarnya Anda tulis adalah blok iterator yang menghasilkan urutan panjang dari nilai nol, tetapi yang penting adalah efek samping dari pekerjaan yang dilakukan untuk menghitungnya. Anda dapat menjalankan coroutine ini menggunakan loop sederhana seperti ini:

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

Atau, lebih bermanfaat, Anda dapat mencampurnya dengan pekerjaan lain:

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

Semuanya ada dalam pengaturan waktu Seperti yang Anda lihat, setiap pernyataan pengembalian hasil harus memberikan ekspresi (seperti nol) sehingga blok iterator memiliki sesuatu untuk benar-benar ditetapkan ke IEnumerator.Current. Urutan nol yang panjang tidak terlalu berguna, tetapi kami lebih tertarik pada efek sampingnya. Bukan kita?

Sebenarnya ada sesuatu yang berguna yang bisa kita lakukan dengan ungkapan itu. Bagaimana jika, alih-alih hanya menghasilkan nol dan mengabaikannya, kami menghasilkan sesuatu yang ditunjukkan ketika kami berharap perlu melakukan lebih banyak pekerjaan? Seringkali kita perlu membawa langsung pada frame berikutnya, tentu saja, tetapi tidak selalu: akan ada banyak waktu di mana kita ingin melanjutkan setelah animasi atau suara selesai diputar, atau setelah sejumlah waktu tertentu telah berlalu. Sementara itu (playingAnimation) menghasilkan pengembalian nol; konstruksinya agak membosankan, bukan begitu?

Unity mendeklarasikan tipe dasar YieldInstruction, dan menyediakan beberapa tipe turunan konkret yang mengindikasikan jenis tunggu tertentu. Anda mendapatkan WaitForSeconds, yang melanjutkan coroutine setelah jumlah waktu yang ditentukan telah berlalu. Anda punya WaitForEndOfFame, yang melanjutkan coroutine pada titik tertentu di frame yang sama. Anda memiliki tipe Coroutine itu sendiri, yang, ketika coroutine A menghasilkan coroutine B, menjeda coroutine A sampai setelah coroutine B selesai.

Seperti apa tampilan ini dari sudut pandang runtime? Seperti yang saya katakan, saya tidak bekerja untuk Unity, jadi saya belum pernah melihat kode mereka; tapi saya membayangkan itu akan terlihat sedikit seperti ini:

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;

Tidak sulit membayangkan berapa banyak subtipe YieldInstruction yang dapat ditambahkan untuk menangani kasus lain - dukungan level mesin untuk sinyal, misalnya, dapat ditambahkan, dengan Instruksi YieldInstruksi WaitForSignal ("SignalName") yang mendukungnya. Dengan menambahkan lebih banyak YieldInstructions, coroutine itu sendiri dapat menjadi lebih ekspresif - imbal hasil baru WaitForSignal ("GameOver") lebih bagus untuk dibaca daripada itu (! Sinyal. MemilikiFired ("GameOver")) menghasilkan pengembalian nol, jika Anda bertanya kepada saya, terlepas dari fakta bahwa melakukannya di mesin bisa lebih cepat daripada melakukannya di skrip.

Beberapa konsekuensi yang tidak jelas. Ada beberapa hal berguna tentang semua ini yang kadang-kadang orang lewatkan, yang saya pikir harus saya tunjukkan.

Pertama, pengembalian hasil hanya menghasilkan ekspresi - ekspresi apa pun - dan YieldInstruction adalah tipe reguler. Ini berarti Anda dapat melakukan hal-hal seperti:

YieldInstruction y;

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

yield return y;

Baris spesifik menghasilkan return WaitForSeconds baru (), menghasilkan return WaitForEndOfFrame baru (), dll, adalah umum, tetapi mereka sebenarnya bukan bentuk khusus dalam hak mereka sendiri.

Kedua, karena coroutine ini hanya blok iterator, Anda dapat beralih sendiri jika Anda mau - Anda tidak perlu memiliki mesin melakukannya untuk Anda. Saya telah menggunakan ini untuk menambahkan kondisi interupsi ke coroutine sebelumnya:

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

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

Ketiga, fakta bahwa Anda dapat menghasilkan pada coroutine lain dapat semacam memungkinkan Anda untuk menerapkan Instruksi Produksi Anda sendiri, meskipun tidak berkinerja seolah-olah mereka dilaksanakan oleh mesin. Sebagai contoh:

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

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

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

Namun, saya tidak akan benar-benar merekomendasikan hal ini - biaya memulai Coroutine sedikit berat untuk seleraku.

Kesimpulan Saya harap ini menjelaskan sedikit dari apa yang sebenarnya terjadi ketika Anda menggunakan Coroutine in Unity. Blok iterator C # adalah konstruk kecil yang asyik, dan bahkan jika Anda tidak menggunakan Unity, mungkin Anda akan merasa berguna untuk memanfaatkannya dengan cara yang sama.

James McMahon
sumber
2
Terima kasih telah mereproduksi itu di sini. Ini luar biasa, dan sangat membantu saya.
Naikrovek
96

Judul pertama di bawah ini adalah jawaban langsung untuk pertanyaan itu. Dua judul setelah itu lebih bermanfaat bagi programmer sehari-hari.

Kemungkinan Detail Implementasi Coroutine yang Membosankan

Coroutine dijelaskan di Wikipedia dan di tempat lain. Di sini saya hanya akan memberikan beberapa detail dari sudut pandang praktis. IEnumerator,, yielddll. adalah fitur bahasa C # yang digunakan untuk tujuan berbeda di Unity.

Sederhananya, IEnumeratorklaim memiliki koleksi nilai yang dapat Anda minta satu per satu, seperti jenis a List. Di C #, fungsi dengan tanda tangan untuk mengembalikan IEnumeratortidak harus benar-benar membuat dan mengembalikannya, tetapi dapat membiarkan C # memberikan implisit IEnumerator. Fungsi kemudian dapat menyediakan konten yang dikembalikan IEnumeratordi masa depan dengan cara yang malas, melalui yield returnpernyataan. Setiap kali pemanggil meminta nilai lain dari implisit itu IEnumerator, fungsi dieksekusi sampai yield returnpernyataan berikutnya , yang memberikan nilai berikutnya. Sebagai produk sampingan dari ini, fungsi berhenti sampai nilai berikutnya diminta.

Di Unity, kami tidak menggunakan ini untuk memberikan nilai di masa depan, kami mengeksploitasi fakta bahwa fungsi berhenti. Karena eksploitasi ini, banyak hal tentang coroutine di Unity tidak masuk akal (Apa yang IEnumeratorharus dilakukan dengan apa pun? Apa itu yield? Mengapa new WaitForSeconds(3)? Dll.). Apa yang terjadi "di bawah tenda" adalah, nilai yang Anda berikan melalui IEnumerator digunakan oleh StartCoroutine()untuk memutuskan kapan harus meminta nilai berikutnya, yang menentukan kapan coroutine Anda akan dihentikan lagi.

Game Unity Anda adalah Single Threaded (*)

Coroutine bukan utas. Ada satu loop utama Unity dan semua fungsi yang Anda tulis dipanggil oleh utas utama yang sama secara berurutan. Anda dapat memverifikasi ini dengan menempatkan while(true);di salah satu fungsi atau coroutine Anda. Ini akan membekukan semuanya, bahkan editor Unity. Ini adalah bukti bahwa semuanya berjalan dalam satu utas utama. Tautan yang disebutkan Kay dalam komentarnya di atas juga merupakan sumber yang bagus.

(*) Unity memanggil fungsi Anda dari satu utas. Jadi, kecuali Anda membuat utas sendiri, kode yang Anda tulis adalah utas tunggal. Tentu saja Unity menggunakan utas lainnya dan Anda dapat membuat utas sendiri jika Anda mau.

Deskripsi Praktis Coroutine untuk Pemrogram Game

Pada dasarnya, ketika Anda menelepon StartCoroutine(MyCoroutine()), itu persis seperti fungsi panggilan biasa untuk MyCoroutine(), sampai yang pertama yield return X, di mana Xadalah sesuatu seperti null, new WaitForSeconds(3), StartCoroutine(AnotherCoroutine()), break, dll Ini adalah ketika mulai berbeda dari fungsi. Unity "jeda" yang berfungsi tepat di yield return Xgaris itu, berjalan dengan bisnis lain dan beberapa frame berlalu, dan ketika saatnya tiba lagi, Unity melanjutkan fungsi itu tepat setelah garis itu. Itu mengingat nilai-nilai untuk semua variabel lokal dalam fungsi. Dengan cara ini, Anda dapat memiliki forloop yang loop setiap dua detik, misalnya.

Kapan Unity akan melanjutkan coroutine Anda tergantung pada apa yang Xada di Anda yield return X. Misalnya, jika Anda menggunakannya yield return new WaitForSeconds(3);, akan dilanjutkan setelah 3 detik berlalu. Jika Anda menggunakannya yield return StartCoroutine(AnotherCoroutine()), ia kembali setelah AnotherCoroutine()sepenuhnya selesai, yang memungkinkan Anda untuk bersarang perilaku dalam waktu. Jika Anda baru saja menggunakan yield return null;, itu dilanjutkan tepat di bingkai berikutnya.

Gazihan Alankus
sumber
2
Sayang sekali, UnityGem tampaknya sudah turun untuk sementara waktu sekarang. Beberapa orang di Reddit berhasil mendapatkan versi terakhir dari arsip: web.archive.org/web/20140702051454/http
ForceMagic
3
Ini sangat kabur dan berisiko salah. Inilah cara kode sebenarnya mengkompilasi dan mengapa ia bekerja. Juga, ini juga tidak menjawab pertanyaan. stackoverflow.com/questions/3438670/…
Louis Hong
Ya saya kira saya menjelaskan "bagaimana coroutine dalam Unity bekerja" dari perspektif programmer game. Pertanyaan sebenarnya adalah menanyakan apa yang terjadi di bawah tenda. Jika Anda dapat menunjukkan bagian yang salah dari jawaban saya, saya akan dengan senang hati memperbaikinya.
Gazihan Alankus
4
Saya setuju tentang imbal hasil yang salah, saya menambahkannya karena seseorang mengkritik jawaban saya karena tidak memilikinya dan saya sedang terburu-buru untuk meninjau apakah hasilnya bermanfaat, dan hanya menambahkan tautan. Saya menghapusnya sekarang. Namun, saya berpikir bahwa Persatuan menjadi satu utas dan bagaimana coroutine cocok dengan itu tidak jelas bagi semua orang. Banyak programmer Unity pemula yang saya ajak bicara memiliki pemahaman yang sangat kabur tentang semuanya dan mendapat manfaat dari penjelasan seperti itu. Saya mengedit jawaban saya untuk memberikan jawaban aktual untuk pertanyaan itu. Saran diterima.
Gazihan Alankus
2
Unity bukan fwiw berulir tunggal. Ini memiliki utas utama yang dijalankan oleh metode siklus hidup MonoBehaviour - tetapi juga ada utas lainnya. Anda bahkan bebas membuat utas sendiri.
benthehutt
10

Itu tidak bisa lebih sederhana:

Unity (dan semua mesin gim) berbasis bingkai .

Seluruh pokok keseluruhan, raison d'etre dari Unity, adalah bahwa ia berbasis bingkai. Mesin melakukan "setiap frame" untuk Anda. (Animasi, merender objek, melakukan fisika, dan sebagainya.)

Anda mungkin bertanya .. "Oh, bagus sekali. Bagaimana jika saya ingin mesin melakukan sesuatu untuk saya setiap frame? Bagaimana saya memberi tahu mesin untuk melakukan ini-dan-itu dalam bingkai?"

Jawabannya adalah ...

Itulah tepatnya apa yang dimaksud dengan "coroutine".

Sederhana saja.

Dan pertimbangkan ini ....

Anda tahu fungsi "Pembaruan". Cukup sederhana, apa pun yang Anda menempatkan di sana dilakukan setiap frame . Secara harfiah persis sama, tidak ada perbedaan sama sekali, dari sintaksis coroutine-yield.

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

Sama sekali tidak ada perbedaan.

Catatan Kaki: seperti yang ditunjukkan semua orang, Persatuan tidak memiliki utas . "Frame" di Unity atau di mesin permainan apa pun sama sekali tidak memiliki koneksi ke utas.

Coroutine / yield hanyalah cara Anda mengakses frame di Unity. Itu dia. (Dan memang, itu sama dengan fungsi Update () yang disediakan oleh Unity.) Hanya itu yang ada, sesederhana itu.

Fattie
sumber
Terima kasih! Tetapi jawaban Anda menjelaskan cara menggunakan coroutine - bukan bagaimana mereka bekerja di belakang layar.
Ghopper21
1
Dengan senang hati, terima kasih. Saya mengerti apa yang Anda maksud - ini bisa menjadi jawaban yang bagus untuk pemula yang selalu bertanya apa sih coroutine itu. Bersulang!
Fattie
1
Sebenarnya - tidak ada jawaban, bahkan sedikit, menjelaskan apa yang terjadi "di balik layar". (Yaitu bahwa itu adalah IEnumerator yang akan ditumpuk ke penjadwal.)
Fattie
Anda berkata, "Sama sekali tidak ada perbedaan." Lalu mengapa Unity membuat Coroutine ketika mereka sudah memiliki implementasi kerja yang tepat seperti Update()? Maksud saya setidaknya harus ada sedikit perbedaan antara kedua implementasi ini dan kasus penggunaannya yang cukup jelas.
Leandro Gecozo
hey @LeandroGecozo - saya akan mengatakan lebih banyak, bahwa "Pembaruan" hanyalah semacam penyederhanaan ("konyol") yang mereka tambahkan. (Banyak orang tidak pernah menggunakannya, gunakan saja coroutine!) Saya tidak berpikir ada jawaban yang bagus untuk pertanyaan Anda, hanya bagaimana Unity.
Fattie
5

Belilah ini belakangan, tulis sebuah posting di sini - http://eppz.eu/blog/understanding-ienumerator-in-unity-3d/ - yang memberi penerangan pada bagian internal (dengan contoh kode padat), IEnumeratorantarmuka yang mendasarinya , dan bagaimana ini digunakan untuk coroutine.

Menggunakan pencacah koleksi untuk tujuan ini masih agak aneh bagi saya. Ini adalah kebalikan dari apa yang menurut pencacah dirancang untuk. Titik enumerator adalah nilai yang dikembalikan pada setiap akses, tetapi titik Coroutines adalah kode di antara nilai yang dikembalikan. Nilai aktual yang dikembalikan tidak ada gunanya dalam konteks ini.

Geri Borbás
sumber
0

Fungsi dasar di Unity yang Anda dapatkan secara otomatis adalah fungsi Mulai () dan fungsi Pembaruan (), jadi fungsi Coroutine pada dasarnya sama seperti fungsi Mulai () dan Pembaruan (). Setiap fungsi fungsi lama () dapat dipanggil dengan cara yang sama seperti Coroutine dapat dipanggil. Unity jelas telah menetapkan batas-batas tertentu untuk Coroutine yang membuatnya berbeda dari fungsi biasa. Salah satu perbedaannya adalah bukan

  void func()

Anda menulis

  IEnumerator func()

untuk coroutine. Dan dengan cara yang sama Anda dapat mengontrol waktu dalam fungsi normal dengan baris kode seperti

  Time.deltaTime

Coroutine memiliki pegangan khusus tentang cara waktu dapat dikendalikan.

  yield return new WaitForSeconds();

Meskipun ini bukan satu-satunya hal yang dapat dilakukan di dalam IEnumerator / Coroutine, ini adalah salah satu hal berguna yang digunakan Coroutine. Anda harus meneliti API skrip Unity untuk mempelajari penggunaan khusus Coroutine lainnya.

Alexhawkburr
sumber
0

StartCoroutine adalah metode untuk memanggil fungsi IEnumerator. Ini mirip dengan hanya memanggil fungsi void sederhana, bedanya adalah Anda menggunakannya pada fungsi IEnumerator. Jenis fungsi ini unik karena dapat memungkinkan Anda untuk menggunakan fungsi hasil khusus , perhatikan bahwa Anda harus mengembalikan sesuatu. Itu yang saya tahu. Di sini saya menulis permainan flicker sederhana Atas metode teks dalam kesatuan

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

Saya kemudian menyebutnya keluar dari IEnumerator itu sendiri

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

Seperti yang Anda lihat bagaimana saya menggunakan metode StartCoroutine (). Semoga saya bisa membantu. Saya sendiri seorang pengemis, jadi jika Anda mengoreksi saya, atau menghargai saya, umpan balik jenis apa pun akan bagus.

Deepanshu Mishra
sumber