Halo semuanya! Nama saya Alexander, saya telah bekerja dengan Unreal Engine selama lebih dari 5 tahun, dan hampir selama ini - dengan proyek jaringan.
Karena proyek jaringan berbeda dalam persyaratan pengembangan dan kinerjanya, seringkali perlu untuk bekerja dengan objek yang lebih sederhana, seperti kelas UObject, tetapi fungsinya pada awalnya terpotong, yang dapat menciptakan kerangka kerja yang kuat. Pada artikel ini, saya akan berbicara tentang cara mengaktifkan berbagai fungsi di kelas dasar UObject di Unreal Engine 4.

Padahal, saya lebih banyak menulis artikel sebagai referensi. Sebagian besar informasi sangat sulit ditemukan dalam dokumentasi atau komunitas, dan di sini Anda dapat dengan cepat membuka tautan dan menyalin kode yang diinginkan. Saya memutuskan pada saat yang sama untuk berbagi dengan Anda! Artikel ini ditujukan untuk mereka yang sudah sedikit terbiasa dengan UE4. Kode C ++ akan dipertimbangkan, meskipun tidak perlu mengetahuinya. Anda cukup mengikuti petunjuk jika Anda perlu sesuatu untuk dibicarakan. Selain itu, tidak perlu menyalin semuanya, Anda dapat menempelkan kode dari bagian dengan properti yang diperlukan dan itu harus bekerja.
Sedikit tentang UObject
UObject adalah kelas dasar untuk hampir semua yang ada di Unreal Engine 4. Mayoritas objek yang dibuat di dunia Anda atau hanya di memori diwarisi darinya: objek di atas panggung (AActor), komponen (UActorComponent), berbagai jenis untuk bekerja dengan data dan lainnya.
Kelas itu sendiri, meskipun lebih mudah daripada turunannya, pada saat yang sama cukup fungsional. Misalnya, ini berisi banyak peristiwa bermanfaat, seperti mengubah nilai variabel dalam editor dan fungsi dasar untuk jaringan, yang tidak aktif secara default.
Objek yang dibuat oleh kelas ini tidak bisa di atas panggung dan ada secara eksklusif di memori. Mereka tidak dapat ditambahkan sebagai komponen ke Aktor, meskipun itu bisa menjadi semacam komponen jika Anda menerapkan sendiri fungsionalitas yang diperlukan.
Mengapa saya perlu UObject jika AActor sudah mendukung semua yang saya butuhkan? Secara umum, ada banyak contoh penggunaan. Paling mudah adalah barang inventaris. Di atas panggung, di suatu tempat di langit, tidak praktis untuk menyimpannya, sehingga Anda dapat menyimpannya di memori tanpa memuat render dan tanpa membuat properti yang tidak perlu. Bagi mereka yang menyukai perbandingan teknis, AActor mengambil satu kilobyte (1016 bytes), dan sebuah UObject kosong hanya 56 byte.
Apa itu masalah UObject?
Tidak ada masalah secara umum, atau saya tidak menemukan mereka. Semua yang mengganggu UObject adalah kurangnya berbagai fitur yang tersedia secara default di AActor atau komponen. Berikut adalah masalah yang telah saya identifikasi untuk praktik saya:
- UObjects tidak direplikasi melalui jaringan;
- karena poin pertama, kami tidak dapat memicu peristiwa RPC;
- Anda tidak dapat menggunakan serangkaian fungsi luas yang membutuhkan tautan ke dunia dalam Cetak Biru;
- mereka tidak memiliki acara standar seperti BeginPlay dan Centang;
- Anda tidak dapat menambahkan komponen dari UObjects ke AActor di Blueprints.
Sebagian besar hal dapat dengan mudah diselesaikan. Tetapi beberapa harus mengotak-atik.
Membuat UObject
Sebelum memperluas kelas kami dengan fitur, kami harus membuatnya. Mari kita gunakan editor sehingga generator secara otomatis menulis semua yang diperlukan untuk bekerja ke header (.h).
Kita dapat membuat kelas baru di editor Browser Konten dengan mengklik tombol
Baru dan memilih
Kelas C ++ Baru .

Selanjutnya, kita perlu memilih kelas itu sendiri. Mungkin tidak ada dalam daftar umum, oleh karena itu, buka dan pilih UObject.

Beri nama kelas Anda dan pilih di folder mana itu akan disimpan. Ketika kami membuat kelas, Anda dapat masuk ke studio, menemukannya di sana dan mulai menanamkan semua fungsi yang diperlukan.
Pemula, perhatikan bahwa dua file dibuat: .h dan .ccp. Di .h, Anda akan mendeklarasikan variabel dan fungsi, dan dalam .cpp Anda akan mendefinisikan logikanya. Temukan kedua file di proyek Anda. Jika Anda tidak mengubah jalur, maka mereka harus di Project / Source / Project /.Sampai kita melanjutkan, mari kita menulis parameter
Blueprintable di makro UCLASS () di atas deklarasi kelas. Anda harus mendapatkan sesuatu seperti ini:
.hUCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() }
Berkat ini, Anda dapat membuat Cetak Biru yang akan mewarisi semua yang kami lakukan dengan objek ini.
Replikasi UObject
Secara default, UObjects tidak direplikasi melalui jaringan. Seperti yang saya jelaskan di atas, sejumlah pembatasan dibuat ketika Anda perlu menyinkronkan data atau logika antara para pihak, tetapi jangan menyimpan sampah di dunia.
Di Unreal Engine 4, replikasi terjadi justru karena benda-benda dunia. Ini berarti bahwa hanya membuat objek dalam memori dan mereplikasi akan gagal. Bagaimanapun, Anda akan memerlukan pemilik yang akan mengelola transfer data objek antara server dan klien. Misalnya, jika objek Anda adalah keterampilan karakter, maka karakter itu sendiri harus menjadi pemilik. Dia juga akan menjadi konduktor untuk mengirimkan informasi melalui jaringan.
Persiapkan objek kita untuk replikasi. Sejauh ini di header kita hanya perlu menetapkan satu fungsi:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() public: virtual bool IsSupportedForNetworking () const override { return true; }; }
IsSupportedForNetworking () akan menentukan bahwa objek mendukung jaringan dan dapat direplikasi.
Namun, tidak semuanya begitu sederhana. Seperti yang saya tulis di atas, Anda memerlukan pemilik yang mengontrol transfer objek. Untuk kemurnian percobaan, buat AActor yang akan mereplikasi itu. Ini dapat dilakukan dengan cara yang persis sama dengan UObject, hanya kelas induk, tentu saja, AActor.
Pemula, jika Anda perlu mereplikasi objek dalam karakter, controller, atau di tempat lain, buat kelas dasar yang sesuai melalui editor, tambahkan logika yang diperlukan untuk itu, dan sudah mewarisi dari kelas ini dalam Cetak Biru.Di dalam kita memerlukan 3 fungsi: konstruktor, fungsi untuk mereplikasi sub-objek, fungsi yang menentukan apa yang direplikasi di dalam AActor ini (variabel, referensi objek, dll.) Dan tempat di mana kita membuat objek kita.
Jangan lupa untuk membuat variabel yang dengannya objek kita akan disimpan:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: AMyActor(); virtual bool ReplicateSubobjects (class UActorChannel *Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; void GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) const override; virtual void BeginPlay (); UPROPERTY(Replicated, BlueprintReadOnly, Category="Object") class UMyObject* MyObject; }
Di dalam file sumber kita harus menulis semuanya:
.cpp
Sekarang objek Anda akan direplikasi dengan Aktor ini. Anda dapat menampilkan namanya pada centang, tetapi sudah pada klien. Harap dicatat bahwa pada Mulai Mainkan suatu objek tidak mungkin tiba sebelum klien, jadi tidak ada gunanya menulis log di atasnya.
Replikasi variabel dalam UObject
Dalam kebanyakan kasus, tidak masuk akal untuk mereplikasi objek jika tidak mengandung informasi yang juga akan disinkronkan antara server dan klien. Karena objek kita sudah direplikasi, meneruskan variabel tidak sulit. Ini dilakukan dengan cara yang sama seperti di dalam Aktor kami:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() public: virtual bool IsSupportedForNetworking () const override { return true; }; void GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) const override; UPROPERTY(Replicated, BlueprintReadWrite, Category="Object") int MyInteger;
.cpp
Dengan menambahkan variabel dan menandai untuk replikasi, kita dapat mereplikasi. Semuanya sederhana dan sama seperti di AActor.
Namun, ada jebakan kecil yang tidak langsung terlihat, tetapi bisa menyesatkan. Ini akan sangat terlihat jika Anda membuat UObject bukan untuk bekerja di C ++, tetapi mempersiapkannya untuk warisan dan bekerja di Cetak Biru.
Intinya adalah bahwa variabel yang dibuat dalam pewaris Blueprints tidak akan direplikasi. Mesin tidak secara otomatis menandai mereka dan mengubah parameter pada server di BP tidak mengubah apa pun dalam nilai pada klien. Tetapi ada obat untuk ini. Untuk replikasi variabel BP yang benar, Anda harus menandainya terlebih dahulu. Tambahkan beberapa baris ke GetLifetimeReplicatedProps ():
.cpp void UMyObject ::GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) { Super::GetLifetimeReplicatedProps(OutLifetimeProps);
Variabel dalam kelas cetak biru anak sekarang akan mereplikasi seperti yang diharapkan.
Peristiwa RPC di UObject
Peristiwa RPC (Remote Procedure Call) adalah fungsi khusus yang dipanggil di sisi lain dari interaksi jaringan proyek. Menggunakannya, Anda dapat memanggil fungsi dari server di klien lain dan dari klien di server. Sangat berguna dan sering digunakan ketika menulis proyek jaringan.
Jika Anda tidak terbiasa dengan mereka, saya sarankan membaca satu artikel. Ini menjelaskan penggunaan dalam C ++ dan dalam Cetak Biru .Meskipun tidak ada masalah dalam Aktor atau dalam komponen dengan panggilan mereka, dalam peristiwa UObject menembak pada sisi yang sama di mana mereka dipanggil, yang membuat tidak mungkin untuk membuat panggilan jarak jauh ketika dibutuhkan.
Melihat kode komponen (UActorComponent), kami dapat menemukan beberapa fungsi yang memungkinkan Anda untuk mentransfer panggilan melalui jaringan. Karena UActorComponent diwarisi dari UObject, kita dapat dengan mudah menyalin bagian kode yang diperlukan dan menempel ke objek kita sehingga berfungsi seperti seharusnya:
.h
.cpp
Dengan fungsi-fungsi ini, kita akan dapat memicu peristiwa RPC tidak hanya dalam kode, tetapi juga dalam cetak biru.
Harap dicatat bahwa untuk memicu peristiwa Klien atau Server, Anda memerlukan pemilik yang Pemiliknya adalah pemain kami. Misalnya, objek dimiliki oleh karakter pengguna atau objek di mana Pemilik adalah Pengontrol Pemain pemain.
Fitur Global dalam Cetak Biru
Jika Anda pernah membuat Cetak Biru Object, Anda mungkin telah memperhatikan bahwa Anda tidak dapat memanggil fungsi global (statis, tetapi demi kejelasan kami menyebutnya) yang tersedia di kelas lain, misalnya, GetGamemode (). Tampaknya Anda tidak bisa melakukan kelas di kelas Object, karena itu Anda harus melewati semua tautan saat membuat, atau entah bagaimana memutarbalikkan, dan kadang-kadang pilihannya sepenuhnya jatuh pada kelas Aktor yang dibuat di atas panggung dan mendukung semuanya.
Tetapi dalam C ++, tentu saja, tidak ada masalah seperti itu. Namun, perancang permainan, yang bermain dengan pengaturan dan menambahkan hal-hal kecil yang berbeda, tidak bisa mengatakan bahwa Anda perlu membuka Visual Studio, menemukan kelas yang sesuai dan mendapatkan mode permainan dalam fungsi doSomething () dengan mengubah titik di dalamnya. Oleh karena itu, sangat penting bahwa perancang dapat masuk ke Bluprint dan dengan dua klik melakukan pekerjaannya. Hemat waktu dan miliknya. Namun, Cetak Biru diciptakan untuk ini.
Intinya adalah bahwa ketika Anda mencari atau memanggil fungsi dalam menu konteks di Bluprint, fungsi-fungsi global yang sama yang memerlukan referensi ke dunia mencoba memanggil fungsi di dalam objek Anda yang merujuk padanya. Dan jika editor melihat bahwa tidak ada fungsi, dia mengerti bahwa dia tidak dapat menggunakannya dan tidak menunjukkannya dalam daftar.

Namun, ada obatnya untuk ini. Bahkan dua.
Pertama-tama mari kita pertimbangkan opsi untuk penggunaan yang lebih nyaman di editor. Kami perlu mendefinisikan kembali fungsi yang mengembalikan tautan ke dunia dan kemudian editor akan memahami bahwa dalam permainan itu sendiri ia dapat bekerja:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
.cpp UWorld* UMyObject::GetWorld() const {
Sekarang didefinisikan dan editor akan mengerti bahwa secara umum objek dapat memperoleh pointer yang diinginkan (meskipun tidak valid) dan menggunakan fungsi global di BP.
Harap perhatikan bahwa pemilik (GetOuter ()) juga harus memiliki akses ke dunia. Ini bisa berupa UObject lain dengan GetWorld (), komponen, atau objek Aktor tertentu di TKP.
Ada cara lain. Cukup menambahkan label ke makro UCLASS () ketika mendeklarasikan kelas bahwa parameter WorldContextObject akan ditambahkan ke fungsi statis di BP, di mana objek apa pun yang berfungsi sebagai konduktor ke "dunia" dan fungsi global engine diumpankan. Opsi ini cocok untuk mereka yang dalam proyek dapat memiliki beberapa dunia pada saat yang sama (misalnya, dunia game dan dunia untuk penonton):
.h
Jika Anda memasukkan GetGamemode ke dalam pencarian di BP, itu akan muncul dalam daftar, seperti fungsi serupa lainnya, dan parameternya adalah WorldContextObject, di mana Anda harus meneruskan tautan ke Aktor.

Ngomong-ngomong, Anda bisa mengajukan pemilik properti kami di sana. Saya merekomendasikan membuat fungsi pada Aktor, itu akan selalu berguna untuk objek:
.h UCLASS(Blueprintable, meta=(ShowWorldContextPin)) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
Sekarang Anda cukup menggunakan fungsi global dalam kombinasi dengan fungsi Murni kami untuk mendapatkan pemiliknya.

Jika Anda juga mendeklarasikan GetWorld () dalam varian kedua seperti pada varian pertama, Anda dapat mengirimkan referensi kepada diri sendiri (Cukup atau Ini) dalam parameter WorldContextObject.

Acara BeginPlay dan Centang
Masalah lain yang mungkin dihadapi pengembang Blueprint adalah tidak ada acara BeginPlay dan Centang di kelas Object. Tentu saja, Anda dapat membuatnya sendiri dan menelepon dari kelas lain. Tetapi Anda harus mengakui bahwa itu jauh lebih nyaman ketika semuanya bekerja di luar kotak.
Mari kita mulai dengan memahami cara membuat Begin Play. Kami dapat membuat fungsi yang tersedia untuk menulis ulang di BP dan menyebutnya di konstruktor kelas, tetapi ada sejumlah masalah, karena pada saat konstruktor objek Anda belum sepenuhnya diinisialisasi.
Di semua kelas, ada fungsi PostInitProperties (), yang dipanggil setelah menginisialisasi sebagian besar parameter dan mendaftarkan objek di berbagai sistem internal, misalnya, untuk pengumpul sampah. Di dalamnya, Anda bisa menghubungi acara kami, yang akan digunakan dalam Cetak Biru:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
.cpp void UMyObject::PostInitProperties() { Super::PostInitProperties();
Alih-alih if (GetOuter () && GetOuter () -> GetWorld ()) Anda dapat dengan mudah meletakkan if (GetWorld ()) jika Anda telah mendefinisikannya kembali.
Berhati-hatilah! Secara default, PostInitProperties () juga dipanggil di editor.
Sekarang kita bisa masuk ke objek BP kita dan memanggil acara BeginPlay. Ini akan dipanggil saat objek dibuat.
Mari kita beralih ke Event Tick. Tidak ada fungsi sederhana untuk kami. Centang objek di engine memanggil manajer khusus, yang Anda perlukan untuk mengambilnya. Namun, ada trik yang sangat nyaman di sini - pewarisan tambahan dari FTickableGameObject. Ini akan memungkinkan Anda untuk secara otomatis melakukan semua yang Anda butuhkan, dan itu sudah cukup hanya untuk mengambil fungsi yang diperlukan:
.h
.cpp void UMyObject::Tick(float DeltaTime) {
Jika Anda mewarisi dari objek Anda dan membuat kelas BP, acara EventTick akan tersedia, yang akan menyebabkan logika untuk setiap frame.
Menambahkan Komponen dari UObjects
Dalam Cetak Biru UObject, Anda tidak dapat menelurkan komponen untuk Aktor. Masalah yang sama melekat pada Cetak Biru ActorComponent. Logika Epic Games tidak terlalu jelas, karena di C ++ ini bisa dilakukan. Selain itu, Anda dapat menambahkan komponen dari Aktor ke objek Aktor lain hanya dengan menentukan tautan. Tetapi ini tidak bisa dilakukan.
Sayangnya, saya tidak dapat menemukan item ini. Jika ada yang punya petunjuk tentang cara melakukan ini, saya akan senang mempostingnya di sini.
Satu-satunya pilihan yang dapat saya tawarkan saat ini adalah membuat pembungkus di kelas UObject, memberikan akses ke penambahan komponen yang sederhana. Dengan demikian, dimungkinkan untuk menambahkan komponen ke Aktor, tetapi Anda tidak akan secara dinamis membuat parameter input dari spawn. Seringkali, ini dapat diabaikan.
Menyiapkan instance melalui editor
Di UE4, ada "fitur" lain yang nyaman untuk bekerja dengan objek - ini adalah kemampuan untuk membuat instance selama inisialisasi dan mengubah parameternya melalui editor, dengan demikian mengatur propertinya, tanpa membuat kelas anak hanya untuk kepentingan pengaturan. Sangat berguna untuk desainer game.
Misalkan Anda memiliki manajer pengubah untuk karakter dan pengubah sendiri diwakili oleh kelas yang menggambarkan efek yang ditumpangkan. Perancang permainan menciptakan sepasang pengubah dan menunjukkan pada manajer yang mana yang digunakan.
Dalam situasi normal, akan terlihat seperti ini:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere) TSubclassOf<class UMyObject> MyObjectClass; }

Namun, ada masalah karena itu tidak dapat mengkonfigurasi pengubah dan Anda harus membuat kelas tambahan untuk nilai-nilai lain. Setuju, tidak mudah untuk memiliki puluhan kelas di Browser Konten yang hanya berbeda dalam nilainya. Memperbaiki ini mudah. Anda dapat menambahkan beberapa bidang di dalam USTRUCT (), dan juga menunjukkan dalam objek kontainer bahwa objek kita akan menjadi instance, dan bukan hanya referensi ke objek atau kelas yang tidak ada:
.h UCLASS(Blueprintable, DefaultToInstanced, EditInlineNew)
Ini saja tidak cukup, sekarang perlu untuk menunjukkan bahwa variabel yang sama dengan kelas akan menjadi instance. Ini sudah dilakukan ketika Anda menyimpan objek, misalnya, di manajer pengubah karakter:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere, Instanced)
Harap dicatat bahwa kami menggunakan referensi ke objek, dan bukan ke kelas, karena instance akan dibuat segera setelah inisialisasi. Sekarang kita bisa masuk ke jendela editor untuk memilih kelas dan menyesuaikan nilai di dalam instance. Itu jauh lebih nyaman dan lebih fleksibel.

Info
Ada kelas lain yang menarik di Unreal Engine. Ini adalah AInfo. Kelas yang diwarisi dari AActor yang tidak memiliki representasi visual di dunia. Info menggunakan kelas-kelas seperti: mode permainan, GameState, PlayerState, dan lainnya. Yaitu, kelas yang mendukung chip yang berbeda dari AActor, misalnya, replikasi, tetapi tidak ditempatkan di tempat kejadian.
Jika Anda perlu membuat manajer global tambahan yang harus mendukung jaringan dan semua kelas Aktor yang dihasilkan, maka Anda dapat menggunakannya. Anda tidak perlu memanipulasi kelas UObject seperti yang dijelaskan di atas untuk memaksanya, misalnya, untuk mereplikasi data.
Namun, perlu diingat bahwa meskipun objek tidak memiliki koordinat, tidak ada komponen visual, dan tidak ditampilkan di layar, objek tersebut masih merupakan turunan dari kelas Aktor, yang berarti ia seberat induknya. Cukup digunakan dalam jumlah kecil dan untuk kenyamanan.
Kesimpulan
UObject sangat dibutuhkan, dan saya menyarankan Anda untuk menggunakannya kapan pun Aktor tidak benar-benar diperlukan. Sangat disayangkan bahwa itu sedikit terbatas, tetapi juga merupakan nilai tambah. Terkadang Anda harus mengotak-atik saat Anda perlu menggunakan templat khusus, tetapi yang paling penting, semua batasan utama dapat dihapus.
Jika Anda sering bekerja dengan objek dari Cetak Biru, tetapi tidak ingin terus-menerus membuat kelas dan menambahkan fitur ini ke mereka, Anda cukup membuat satu kelas UObject, dengan dukungan untuk semua yang mungkin Anda butuhkan dalam proyek, dan kemudian membuat cetakan anak dari itu dan bekerja .
, , Unreal Engine 4. - , . , - , UObject.