تمديد UObject في محرك غير واقعي 4

مرحبا بالجميع! اسمي ألكساندر ، لقد عملت مع Unreal Engine منذ أكثر من 5 سنوات ، وتقريباً كل هذا الوقت - مع مشاريع الشبكات.

نظرًا لاختلاف مشاريع الشبكة في متطلبات التطوير والأداء الخاصة بها ، فمن الضروري في الغالب العمل مع كائنات أبسط ، مثل فئات UObject ، ولكن يتم اقتطاع وظائفها مبدئيًا ، والتي يمكن أن تخلق إطارًا قويًا. في هذه المقالة ، سأتحدث عن كيفية تنشيط الوظائف المختلفة في الفئة الأساسية UObject في Unreal Engine 4.



في الواقع ، لقد كتبت المقالة أكثر كمرجع. من الصعب للغاية العثور على معظم المعلومات في الوثائق أو في المجتمع ، وهنا يمكنك بسرعة فتح الرابط ونسخ الرمز المطلوب. قررت في نفس الوقت أن أشاطركم! هذه المقالة موجهة إلى أولئك الذين هم بالفعل على دراية UE4. سيتم النظر في رمز C ++ ، على الرغم من أنه ليس من الضروري معرفة ذلك. يمكنك ببساطة اتباع التعليمات إذا كنت بحاجة إلى شيء للحديث عنه. علاوة على ذلك ، ليس من الضروري نسخ كل شيء ، يمكنك لصق الكود من القسم بالخصائص اللازمة ويجب أن يعمل.



قليلا عن UObject


UObject هي الفئة الأساسية لكل شيء تقريبًا في Unreal Engine 4. يتم توريث الغالبية العظمى من الكائنات التي تم إنشاؤها في عالمك أو في الذاكرة فقط: الكائنات على المسرح (AActor) ، والمكونات (UActorComponent) ، وأنواع مختلفة للعمل مع البيانات وغيرها.

الطبقة نفسها ، على الرغم من أنها أسهل من المشتقات ، تعمل في نفس الوقت تمامًا. على سبيل المثال ، يحتوي على العديد من الأحداث المفيدة ، مثل تغيير قيم المتغيرات في المحرر والوظائف الأساسية للشبكة ، والتي ليست نشطة افتراضيًا.

لا يمكن أن تكون الكائنات التي أنشأتها هذه الفئة على خشبة المسرح وتوجد بشكل حصري في الذاكرة. لا يمكن إضافتها كمكونات إلى الفاعلين ، على الرغم من أنه يمكن أن يكون نوعًا من المكونات إذا قمت بتنفيذ الوظيفة اللازمة بنفسك.

لماذا أحتاج إلى UObject إذا كان AActor يدعم كل ما أحتاجه بالفعل؟ بشكل عام ، هناك الكثير من الأمثلة للاستخدام. أسهل العناصر المخزون. على المسرح ، في مكان ما في السماء ، يكون تخزينها غير عملي ، بحيث يمكنك تخزينها في الذاكرة دون تحميل التجسيد ودون إنشاء خصائص غير ضرورية. بالنسبة لأولئك الذين يحبون المقارنات التقنية ، تأخذ AActor كيلوبايت (1016 بايت) ، ويبلغ UObject الفارغ 56 بايت فقط.



ما هي مشكلة UObject؟


لا توجد مشاكل بشكل عام ، حسناً ، أو لم أتطرق إليها. كل ما يزعج UObject هو الافتقار إلى العديد من الميزات المتوفرة افتراضيًا في AActor أو في المكونات. فيما يلي المشكلات التي حددتها لممارستي:

  • لا يتم نسخ UObjects عبر الشبكة؛
  • بسبب النقطة الأولى ، لا يمكننا تشغيل أحداث RPC ؛
  • لا يمكنك استخدام مجموعة واسعة من الوظائف التي تتطلب ارتباطًا بالعالم في المخططات ؛
  • ليس لديهم أحداث قياسية مثل BeginPlay و Tick؛
  • لا يمكنك إضافة مكونات من UObjects إلى AActor في المخططات.

معظم الأشياء يمكن حلها بسهولة. ولكن البعض سوف تضطر إلى العبث.



إنشاء UObject


قبل توسيع فئتنا بالميزات ، نحتاج إلى إنشائها. دعنا نستخدم المحرر بحيث يقوم المولد تلقائيًا بكتابة كل ما هو مطلوب للعمل على الرأس (.h).

يمكننا إنشاء فئة جديدة في محرر Content Browser بالنقر فوق الزر " جديد" واختيار فئة C ++ جديدة .



بعد ذلك ، نحن بحاجة إلى اختيار الفصل نفسه. قد لا يكون في القائمة العامة ، وبالتالي ، فتحه وحدد UObject.



حدد فصلك وحدد المجلد الذي سيتم تخزينه فيه. عندما أنشأنا الفصل ، يمكنك الذهاب إلى الاستوديو والعثور عليه هناك والبدء في تضمين جميع الوظائف اللازمة.

للمبتدئين ، لاحظ أنه يتم إنشاء ملفين: .h و .ccp. في .h ، ستعلن المتغيرات والوظائف ، وفي .cpp ستحدد منطقها. ابحث عن كلا الملفين في مشروعك. إذا لم تقم بتغيير المسار ، فيجب أن يكونوا في مشروع / مصدر / مشروع /.

إلى أن نستمر ، دعنا نكتب المعلمة Blueprintable في ماكرو UCLASS () أعلى تعريف الفئة. يجب أن تحصل على شيء مثل هذا:

.H

UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() } 

بفضل هذا ، يمكنك إنشاء مخططات ترث كل ما نقوم به مع هذا الكائن.



UObject النسخ المتماثل


بشكل افتراضي ، لا يتم نسخ UObjects عبر الشبكة. كما هو موضح أعلاه ، يتم إنشاء عدد من القيود عندما تحتاج إلى مزامنة البيانات أو المنطق بين الأطراف ، ولكن لا تقم بتخزين البيانات المهملة في العالم.

في Unreal Engine 4 ، يحدث النسخ المتماثل بدقة بسبب الكائنات العالمية. يعني ذلك ببساطة إنشاء كائن في الذاكرة وتكراره سيفشل. في أي حال ، سوف تحتاج إلى مالك يقوم بإدارة نقل بيانات الكائن بين الخادم والعملاء. على سبيل المثال ، إذا كان الكائن الخاص بك مهارة شخصية ، فيجب أن تصبح الشخصية نفسها هي المالك. سيكون أيضًا موصلًا لنقل المعلومات عبر الشبكة.

تحضير كائن لدينا للنسخ المتماثل. حتى الآن في الرأس نحتاج إلى تعيين وظيفة واحدة فقط:

.H

 UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() public: virtual bool IsSupportedForNetworking () const override { return true; }; } 

سيحدد IsSupportedForNetworking () أن الكائن يدعم الشبكة ويمكن نسخه.

ومع ذلك ، ليس كل شيء في غاية البساطة. كما كتبت أعلاه ، تحتاج إلى مالك يتحكم في نقل الكائن. من أجل نقاء التجربة ، قم بإنشاء AActor يقوم بتكرارها. يمكن القيام بذلك بنفس طريقة UObject ، فقط الفئة الأصل ، وبطبيعة الحال ، AActor.

بالنسبة للمبتدئين ، إذا كنت بحاجة إلى نسخ كائن في حرف أو وحدة تحكم أو في أي مكان آخر ، قم بإنشاء الفئة الأساسية المناسبة من خلال المحرر وإضافة المنطق اللازم إليها ورثها بالفعل من هذه الفئة في المخططات.

في الداخل ، نحتاج إلى 3 وظائف: مُنشئ ، دالة لتكرار الكائنات الفرعية ، دالة تحدد ما يتم نسخه داخل AActor (المتغيرات ، مراجع الكائن ، إلخ) والمكان الذي ننشئ فيه كائننا.

لا تنس إنشاء متغير يتم به تخزين كائننا:

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

داخل الملف المصدر ، علينا أن نكتب كل شيء:

.CPP

 //  #include "MyActor.h" #include "Net/UnrealNetwork.h" #include "Engine/World.h" #include "Engine/ActorChannel.h" #include "   UObject/MyObject.h" AMyActor::AMyActor() { //  Actor  . bReplicates = true // . NetCullDistanceSquared = 99999; //  (  ). NetUpdateFrequency = 1.f; } void AMyActor::GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) { Super::GetLifetimeReplicatedProps(OutLifetimeProps); //       .           . DOREPLIFETIME(AMyActor, MyObject); } bool AMyActor::ReplicateSubobjects(UActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags) { bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); //   . if (MyObject ) WroteSomething |= Channel->ReplicateSubobject(MyObject , *Bunch, *RepFlags); return WroteSomething; } AMyActor::BeginPlay() { /*       (  )  .    this.        . ,       ,     . */ if(HasAuthority()) { MyObject = NewObject<UMyObject>(this); //       if(MyObject) UE_LOG(LogTemp, Log, TEXT("%s created"), *MyObject->GetName()); } } 

الآن سيتم نسخ الكائن الخاص بك مع هذا الممثل. يمكنك عرض اسمه على القراد ، ولكن بالفعل على العميل. يرجى ملاحظة أنه من غير المرجح أن يصل كائن قبل بدء تشغيله إلى العميل ، لذلك لا فائدة من كتابة سجل عليه.



تكرار المتغيرات في UObject


في معظم الحالات ، لا معنى لتكرار كائن ما إذا كان لا يحتوي على معلومات ستتم مزامنتها أيضًا بين الخادم والعملاء. منذ أن تم نسخ كائننا بالفعل ، فإن تمرير المتغيرات ليس بالأمر الصعب. يتم ذلك بالطريقة نفسها داخل ممثلنا:

.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

 //  #include "MyObject.h" #include "Net/UnrealNetwork.h" UMyObject ::UMyObject () { //  Object  .     . bReplicates = true //       ,     . } void UMyObject ::GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) { Super::GetLifetimeReplicatedProps(OutLifetimeProps); //   Integer  . DOREPLIFETIME(UMyObject, MyInteger); } } 

بإضافة متغير ووضع علامة عليه للنسخ المتماثل ، يمكننا تكراره. كل شيء بسيط والشيء نفسه كما في AActor.

ومع ذلك ، هناك مأزق صغير لا يمكن رؤيته على الفور ، ولكن يمكن أن يكون مضللاً. سيكون هذا ملحوظًا بشكل خاص إذا كنت تقوم بإنشاء UObject الخاص بك ليس للعمل في C ++ ، ولكن إعداده للميراث والعمل في المخططات.

خلاصة القول هي أنه لن يتم نسخ المتغيرات التي تم إنشاؤها في وريث المخططات. لا يقوم المحرك بوضع علامة عليها تلقائيًا وتغيير المعلمة على الخادم في BP لا يغير أي شيء في القيمة على العميل. ولكن هناك علاج لهذا. للنسخ المتماثل الصحيح لمتغيرات BP ، تحتاج إلى وضع علامة عليها مسبقًا. إضافة سطرين إلى GetLifetimeReplicatedProps ():

.CPP

 void UMyObject ::GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) { Super::GetLifetimeReplicatedProps(OutLifetimeProps); //   Integer  . DOREPLIFETIME(UMyObject, MyInteger); //       UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass()); if (BPClass) BPClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps); } 

سيتم الآن نسخ متغيرات في فصول Blueprint التابعة كما هو متوقع.



أحداث RPC في UObject


أحداث RPC (استدعاء الإجراء البعيد) هي وظائف خاصة يتم استدعاؤها على الجانب الآخر من تفاعل شبكة المشروع. باستخدامها ، يمكنك استدعاء الوظيفة من الخادم على العملاء الآخرين ومن العميل على الخادم. مفيدة جدا وغالبا ما تستخدم عند كتابة مشاريع الشبكة.

إذا لم تكن على دراية بهم ، أوصي بقراءة مقال واحد. يصف الاستخدام في C ++ وفي المخططات .

على الرغم من عدم وجود مشاكل في Actor أو في المكونات الخاصة بمكالمتهم ، فإن أحداث UObject تُطلق النار على نفس الجانب الذي تم الاتصال به ، مما يجعل من المستحيل إجراء مكالمة عن بُعد عند الحاجة.

بالنظر إلى رمز المكون (UActorComponent) ، يمكننا العثور على العديد من الوظائف التي تتيح لك نقل المكالمات عبر الشبكة. نظرًا لأن UActorComponent موروث من UObject ، فيمكننا ببساطة نسخ المقاطع الضرورية من التعليمات البرمجية ولصقها في كائننا بحيث يعمل كما يجب:

.H

 //   #include "Engine/EngineTypes.h" UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() public: virtual bool CallRemoteFunction (UFunction * Function, void * Parms, struct FOutParmRec * OutParms, FFrame * Stack) override; virtual int32 GetFunctionCallspace (UFunction* Function, void* Parameters, FFrame* Stack) override; //   } 

.CPP

 //   #include "Engine/NetDriver.h" //       . bool UMyObject::CallRemoteFunction(UFunction * Function, void * Parms, FOutParmRec * OutParms, FFrame * Stack) { if (!GetOuter()) return false; UNetDriver* NetDriver = GetOuter()->GetNetDriver(); if (!NetDriver) return false; NetDriver->ProcessRemoteFunction(GetOuter(), Function, Parms, OutParms, Stack, this); return true; } int32 UMyObject::GetFunctionCallspace(UFunction * Function, void * Parameters, FFrame * Stack) { return (GetOuter() ? GetOuter()->GetFunctionCallspace(Function, Parameters, Stack) : FunctionCallspace::Local); } 

باستخدام هذه الوظائف ، سنكون قادرين على تشغيل أحداث RPC ليس فقط في الكود ، ولكن أيضًا في المخططات.

يرجى ملاحظة أنه من أجل إطلاق أحداث العميل أو الخادم ، فإنك تحتاج إلى مالك يكون مالكه هو لاعبنا. على سبيل المثال ، الكائن مملوك من قِبل شخصية المستخدم أو الكائن الذي يكون فيه المالك هو المشغل الخاص بالمشغل.



الميزات العالمية في المخططات


إذا قمت بإنشاء "مخطط كائن" من أي وقت مضى ، فقد تكون لاحظت أنه لا يمكنك استدعاء وظائف عمومية (ثابتة ، ولكن من أجل الوضوح نسميها) المتوفرة في فئات أخرى ، على سبيل المثال ، GetGamemode (). يبدو أنه لا يمكنك ببساطة القيام بالفصول في فئات الكائنات ، والتي بسببها يجب عليك إما تمرير كل الروابط عند الإنشاء ، أو الانحراف بطريقة أو بأخرى ، وفي بعض الأحيان يقع الاختيار تمامًا في فئة الممثل الذي تم إنشاؤه على المسرح و يدعم كل شيء.

لكن في C ++ ، بالطبع ، لا توجد مثل هذه المشكلات. ومع ذلك ، لا يستطيع مصمم اللعبة ، الذي يلعب بالإعدادات ويضيف أشياء صغيرة مختلفة ، أن يقول إنك بحاجة إلى فتح Visual Studio ، والعثور على الفئة المناسبة والحصول على وضع اللعبة في وظيفة doSomething () عن طريق تغيير النقاط الموجودة فيه. لذلك ، من الضروري أن يتمكن المصمم من تسجيل الدخول إلى Bluprint وبنقرتين ، يقوم بعمله. حفظ كل وقته وخاصتك. ومع ذلك ، تم اختراع المخططات لهذا الغرض.

خلاصة القول هي أنه عندما تبحث عن وظائف أو تستدعيها في قائمة السياق في Bluprint ، فإن نفس الوظائف العامة التي تتطلب إشارة إلى العالم تحاول استدعاء وظيفة داخل كائنك الذي يشير إليها. وإذا رأى المحرر أنه لا توجد وظيفة ، فهو يفهم أنه لا يستطيع استخدامها ولا يعرضها في القائمة.



ومع ذلك ، هناك علاج لهذا. حتى اثنين.

لننظر أولاً في خيار للاستخدام الأكثر ملاءمة في المحرر. سوف نحتاج إلى إعادة تعريف وظيفة تُرجع رابطًا للعالم ، ثم سيتفهم المحرر أنه في اللعبة نفسها يمكنه العمل:

.H

 UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() //  GetWorld()    . virtual UWorld* GetWorld() const override; //   } 

.CPP

 UWorld* UMyObject::GetWorld() const { //       ,    . if (GIsEditor && !GIsPlayInEditorWorld) return nullptr; else if (GetOuter()) return GetOuter()->GetWorld(); else return nullptr; } 

الآن يتم تعريفه وسوف يفهم المحرر أن الكائن قادر عمومًا على الحصول على المؤشر المرغوب (على الرغم من أنه غير صالح) واستخدام الوظائف العامة في BP.

يرجى ملاحظة أن المالك (GetOuter ()) يجب أن يتمتع أيضًا بحق الوصول إلى العالم. قد يكون UObject آخر مع كائن GetWorld () أو مكون أو ممثل معين في المشهد.



هناك طريقة أخرى. يكفي إضافة تسمية إلى ماكرو UCLASS () عند الإعلان للفئة التي ستتم إضافة المعلمة WorldContextObject منها إلى الوظائف الثابتة في BP ، والتي يتم فيها تغذية أي كائن يعمل كموصل إلى "العالم" ويتم تغذية الوظائف العالمية للمحرك. هذا الخيار مناسب لأولئك الذين في المشروع يمكن أن يكون لهم عوالم متعددة في نفس الوقت (على سبيل المثال ، عالم اللعبة والعالم للمتفرج):

.H

 //   WorldContext      UCLASS(Blueprintable, meta=(ShowWorldContextPin)) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() //   } 

إذا أدخلت GetGamemode في البحث في BP ، فستظهر في القائمة ، مثل الوظائف الأخرى المماثلة ، وستكون المعلمة هي WorldContextObject ، التي تحتاج إلى تمرير رابط إلى ممثل.



بالمناسبة ، يمكنك فقط تقديم ملف مالك فنادقنا هناك. أوصي بإنشاء وظيفة على ممثل ، وسوف يكون دائما مفيدا للكائن:

.H

 UCLASS(Blueprintable, meta=(ShowWorldContextPin)) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() //      ,     . public: UFUNCTION(BlueprintPure) AActor* GetOwner() const {return Cast<AActor>(GetOuter());}; //   } 

الآن يمكنك ببساطة استخدام الوظائف العالمية بالاقتران مع وظيفة Pure للحصول على المالك.



إذا قمت أيضًا بتعريف GetWorld () في المتغير الثاني كما في المتغير الأول ، فيمكنك إرسال مرجع إلى نفسك (Self أو This) في معلمة WorldContextObject.



أحداث BeginPlay والقراد


هناك مشكلة أخرى قد يواجهها مطورو Blueprint وهي أنه لا توجد أحداث BeginPlay و Tick في فئة Object. بالطبع ، يمكنك إنشاءها بنفسك والاتصال من فصل آخر. ولكن يجب أن تعترف أنه أكثر ملاءمة عندما يعمل كل شيء خارج الصندوق.

لنبدأ بفهم كيفية بدء اللعب. يمكننا إنشاء وظيفة متاحة لإعادة كتابتها في BP واستدعاؤها في مُنشئ الفصل ، ولكن هناك عددًا من المشكلات ، لأنه في وقت المُنشئ لم يتم تهيئة الكائن بالكامل بعد.

في كل الفئات ، توجد وظيفة PostInitProperties () ، والتي تسمى بعد تهيئة معظم المعلمات وتسجيل الكائن في الأنظمة الداخلية المختلفة ، على سبيل المثال ، لمجمع البيانات المهملة. في ذلك ، يمكنك فقط الاتصال بالحدث الخاص بنا ، والذي سيتم استخدامه في المخططات:

.H

 UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() //      . virtual void PostInitProperties() override; // ,      . UFUNCTION(BlueprintImplementableEvent) void BeginPlay(); //   } 

.CPP

 void UMyObject::PostInitProperties() { Super::PostInitProperties(); //   ,   .   BeginPlay    if(GetOuter() && GetOuter()->GetWorld()) BeginPlay(); } 

بدلاً من if (GetOuter () && GetOuter () -> GetWorld ()) يمكنك ببساطة وضع (GetWorld ()) إذا كنت قد أعدت تعريفه بالفعل.

كن حذرا! بشكل افتراضي ، يتم استدعاء PostInitProperties () أيضًا في المحرر.

الآن يمكننا الانتقال إلى كائن BP الخاص بنا واستدعاء حدث BeginPlay. سيتم استدعاؤه عندما يتم إنشاء الكائن.

دعنا ننتقل إلى حدث الأحداث. لا توجد وظيفة بسيطة بالنسبة لنا. تستدعي الكائنات المحددة في المحرك مديرًا خاصًا ، تحتاج إلى التقاطه بطريقة أو بأخرى. ومع ذلك ، هناك خدعة مريحة للغاية هنا - وراثة إضافية من FTickableGameObject. سيسمح لك ذلك بالقيام تلقائيًا بكل ما تحتاجه ، وبعد ذلك سيكون كافياً فقط لالتقاط الوظائف الضرورية:

.H

 //   #include "Tickable.h" //   c FTickableGameObject UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject, public FTickableGameObject { GENERATED_BODY() public: //   virtual void Tick(float DeltaTime) override; virtual bool IsTickable() const override; virtual TStatId GetStatId() const override; protected: //     UFUNCTION(BlueprintImplementableEvent) void EventTick(float DeltaTime); //   } 

.CPP

 void UMyObject::Tick(float DeltaTime) { //       . EventTick(DeltaTime); //     . } //     bool UMyObject::IsTickable() const { return true; } TStatId UMyObject::GetStatId() const { return TStatId(); } 

إذا كنت ترث من الكائن وقمت بإنشاء فئة BP ، فسيكون حدث EventTick متاحًا ، مما سيتسبب في منطق كل إطار.



إضافة مكونات من UObjects


في مخططات UObject ، لا يمكنك تكوين مكونات للممثلين. نفس المشكلة متأصلة في مخططات ActorComponent. منطق Epic Games غير واضح للغاية ، لأنه في C ++ يمكن القيام بذلك. علاوة على ذلك ، يمكنك إضافة مكون من ممثل إلى كائن ممثل آخر عن طريق تحديد رابط. ولكن هذا لا يمكن القيام به.

لسوء الحظ ، لم أستطع معرفة هذا البند. إذا كان لدى أي شخص تعليمات حول كيفية القيام بذلك ، سأكون سعيدًا لنشرها هنا.

الخيار الوحيد الذي يمكنني تقديمه في الوقت الحالي هو صنع غلاف في فئة UObject ، مما يتيح الوصول إلى إضافة بسيطة من المكونات. وبالتالي ، سيكون من الممكن إضافة مكونات إلى الفاعل ، لكنك لن تكون قد قمت بإنشاء معلمات إدخال ديناميكية للفرخ. في كثير من الأحيان ، يمكن إهمال هذا.



إعداد مثيل من خلال المحرر


في UE4 ، هناك ميزة أخرى ملائمة للعمل مع الكائنات - وهي القدرة على إنشاء مثيل أثناء التهيئة وتغيير المعلمات من خلال المحرر ، وبالتالي تحديد خصائصه ، دون إنشاء فئة فرعية فقط من أجل الإعدادات. مفيدة بشكل خاص لمصممي اللعبة.

افترض أن لديك مدير معدل لأحد الشخصيات وأن المعدلات نفسها ممثلة بفئات تصف التأثيرات المتراكبة. قام مصمم اللعبة بإنشاء زوج من المعدلات ويشير في المدير إلى تلك المستخدمة.

في الوضع الطبيعي ، سيبدو كما يلي:

.H

 class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere) TSubclassOf<class UMyObject> MyObjectClass; } 



ومع ذلك ، هناك مشكلة في أنه لا يمكن تكوين المعدلات وعليك إنشاء فئة إضافية للقيم الأخرى. موافق ، ليس من المريح جدًا وجود عشرات الفئات في متصفح المحتوى تختلف فقط في القيم. إصلاح هذا أمر سهل. يمكنك إضافة حقلين داخل USTRUCT () ، وكذلك الإشارة في كائن الحاوية إلى أن كائناتنا ستكون مثيلات ، وليس مجرد إشارات إلى كائنات أو فئات غير موجودة:

.H

 UCLASS(Blueprintable, DefaultToInstanced, EditInlineNew) //  -        class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() UPROPERTY(EditAnywhere) //       uint8 MyValue; // ,    //   } 

هذا وحده لا يكفي ، والآن من الضروري الإشارة إلى أن نفس المتغير مع الفصل سيكون نسخة. يتم ذلك بالفعل حيث تقوم بتخزين الكائن ، على سبيل المثال ، في مدير معدل الأحرف:

.H

 class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere, Instanced) //   Instanced    class UMyObject* MyObject; //    } 

يرجى ملاحظة أننا نستخدم الإشارة إلى الكائن ، وليس إلى الفصل ، حيث سيتم إنشاء المثيل فور التهيئة. الآن يمكننا الانتقال إلى نافذة المحرر لتحديد فئة وضبط القيم داخل المثيل. هو أكثر ملاءمة وأكثر مرونة.

صورة



معلومات


هناك فئة أخرى مثيرة للاهتمام في Unreal Engine. هذا هو AInfo. فئة موروثة من AActor ليس لها تمثيل مرئي في العالم. تستخدم Info فئات مثل: وضع اللعبة ، و GameState ، و PlayState ، وغيرها. وهذا هو ، الطبقات التي تدعم شرائح مختلفة من AActor ، على سبيل المثال ، النسخ المتماثل ، ولكن لا يتم وضعها على الساحة.

إذا كنت بحاجة إلى إنشاء مدير عالمي إضافي يجب أن يدعم الشبكة وجميع فئات Actor الناتجة ، فيمكنك استخدامها. ليس لديك لمعالجة فئة UObject كما هو موضح أعلاه لإجبارها ، على سبيل المثال ، لتكرار البيانات.

ومع ذلك ، ضع في اعتبارك أنه على الرغم من أن الكائن لا يحتوي على إحداثيات ، ولا يحتوي على عناصر مرئية ولا يتم عرضه على الشاشة ، إلا أنه لا يزال سليل فئة الممثل ، مما يعني أنه ثقيل مثل الأصل. تستخدم بشكل معقول بكميات صغيرة والراحة.



استنتاج


هناك حاجة إلى استخدام UObject في كثير من الأحيان ، وأنا أنصحك باستخدامه عندما لا تكون هناك حاجة إلى الممثل بالفعل. إنه لأمر مؤسف أنه محدود بعض الشيء ، لكنه زائد أيضًا. في بعض الأحيان ، يجب عليك العبث عندما تحتاج إلى استخدام قالب مخصص ، ولكن الأهم من ذلك أنه يمكن إزالة جميع القيود الرئيسية.

, , UObject, , , .

, , Unreal Engine 4. - , . , - , UObject.

Source: https://habr.com/ru/post/ar475622/


All Articles