"bir yazılımcının not defteri.."

20 Şubat 2017 Pazartesi

Unreal Engine C++ Kodlama Standartları

tekrar merhabalar,

bi sürü teorik bilgiden ben de sıkılmaya başladım :) artık ufaktan ufaktan Unreal Engine deki C++ kullanımına bir giriş yapalım;



kodlama standartlarından bahsetmek istiyorum bugün.

bu standartların anlaşılması, başta motorun C++ kodlarını daha rahat okumamızı sağladığı gibi, bu kodlarla uyumlu yeni kodları yazmamızı da kolaylaştıracaktır; elbette dökümantasyonu da daha rahat okuyabileceğiz :)

eskiden C++ zor bir dil olarak bilinirdi;
günümüzde artık öyle değil dememi bekliyorsanız daha çok beklersiniz :)

giderek daha az programcı tarafından tercih edilen ve C# gibi modern dillerin karşısında giderek daha az sayıda uzmanı bulunabilen C++ sadece daha zor değil, bilinirliği de giderek düşüyor; bu da öğrenmek için bilgisini paylaşacak daha az sayıda insan demektir; ve bu da nihayetinde dökümantasyonların o soğuk aptal diline katlanma zorunluluğu olarak bize dönecek demektir..

lakin endişelenmeyin!  yani en azından Epic Games endişlenmeyin diyor :)  (çok rahatladım...) çünkü Unreal Engine de C++ kodlaması eylenceli, en azından başlangıç için zor değilmiş diyor kendileri; ben onların yalancısıyım; en doğrusu zamanla kendimiz yaşayıp görmemiz diyelim.

bu iddalarn sebebi Epic Games in Unreal Engine deki C++ ı daha kolay hale getirmek için çok sayıda özellik / kütüphane eklemiş olmasıdır. Aslında hala klasik tarz C++ kod biçimi yazmak da mümkündür; fakat Unreal Engine deki önerilen programlama modelini, yani adamların olayı kolaylaştırıcı kütüphanelerini kullanmak tabiki çok daha mantıklı. Ancak klasik C++  desteği de tabiki Boost gibi güçlü  C++ kütüphanlerini kodlarımıza dahil etmek için söz konusu olabilir.

hiç programlama dili bilmeyenler için ise bildiğiniz gibi Blueprint geliştirilmiştir; bunu bir tür görsel script gibi düşünebilsek de, son zamanlarda bunun da çıktı esnasında C++ a derlenerek hız kazandırdırığını duydum.) Unreal Engine hem C++ kodlaması hem de Blueprint görsel script ini destekleyen her iki yola da sahiptir; hatta ikisi aynı anda da kullanılabilir; ve hatta önerilen de budur.

C++ kullanarak programcılar temel gameplay sistemini elde ederler ki, designer'lar Blueprint ile onları özelleştirerek (özelliklerini değiştirerek) tasarladıkları level ortamlarında kullanabilsinler. Her iki programlama sistemini de (C++ ve Blueprint) birleştirerek kullanmak sistemin gücüne güç ve kullanışlılık katmaktadır; bu motor ile en verimli çalışma şekli olarak önerilmektedir.

şimdi hemen bununla ilgili bir örnek yapalım,
bir  C++  sınıfı yaratalım ve sınıftaki bazı özellikleri designer ların sonradan editörden değiştirebileceği ve bu yeni değerleri kullanabileceği bir ortamı simüle edelim:

ilk olarak Unreal Engine içindeki Class Wizard ı kullanarak temel bir C++ sınıfı yaratacağız ki bu sınıf daha sonra Blueprint taraından genişletilecek (extend edilecek):



ikinci adımda yaratacağımız sınıfın adını giriyoruz:



yaratacağımız sınıfı seçtikten sonra wizard dosyaları belirtilen yerde oluşturacak ve development ortamını açacaktır (Visual Studio veya Xcode), böylece yaratılan dosyaları edit edebilelim ve geliştirelim. Aşağıda sınıfın bizim için ilk yaratıldıkları halleri var:

#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public: 
    // Sets default values for this actor's properties
    AMyActor();

    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
};

class wizard sınıfımızın  BeginPlay() ve Tick() fonksyonlarını kendisi overloads ederek yarattı; bunun anlamı bu iki fonksyon zaten parent class'da mevcut idi, burada onları override ettik bazı programcıların tabiri ile "ezdik".

bunlardan BeginPlay() fonksyonu aslında bir event tir;  Actor Spawn olduğunda tetiklenir; sözkonusu Actor ün oyuna aktif dahil olduğunu ve Actor ün tüm ön hazırlıklarının tamamlanmış ve Playable durumda olduğunu anlamamızı sağlar; çünkü tam o an bu fonksyon tetiklenir; burası sınıfımızın (yaratılan actor ün) oyuna başlangıcını sağlamak ve ön hazırlıkları yapmak için iyi bir yerdir; yapıcı fonksyon gibi BeginPlay() de sadece bir kez çağrılır.

Tick() fonksyonu ilerleyen zaman içinde geçen her bir frame de takrar tekrar çağrılır. Bu özel kod bloğunda sürekli yinelenmesi, güncellenmesi gereken işlemler yapılır; eğer bu fonksyonaliteye ihtiyacımız yoksa bu bloğu kaldırmak bir miktar da olsa performansı artırır; eğer kaldırırsanız yapıcı fonksyon içindeki tick fonksyonunun işlemesi gerektiğini söyleyen satırı da kaldırdığımıza emin olmalıyız; yani aşağıdaki gibi,

AMyActor::AMyActor()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you do not need it.
    PrimaryActorTick.bCanEverTick = true;
}
Tick() fonksyonu float tipinde, adına genellikle DeltaSeconds veya DeltaTime denen bir parametre ile geçen birim zaman miktarını her bir frame de yollar; böylece oyunun oynandığı ortamlar arasıdna frame rate hızı değişken olsa bile (ki olacaktır) bu farklılıktan etkilenmeden her birinde eşzamanlılık sağlanabilir;

örneğin oyun bir saniyede 30 FPS kare hızında tazelenerek oynanıyorsa,
DeltaSeonds = 1 / 30 = 0.0333... değerini alır,

60 FPS ise ,
1 / 60 = 0.01666... gibi bir değer alır.

yazdığımız kodların bazı kısımları, özellikle property leri editörden görünebilmesi lazım demiştik; sınıfın bir propertisini Unreal Editor içinde görünür yapmak için değişken adının üzerine UPROPERTY()  makrosunu getiririz; makronun parametresi olarak da ne yapmak istediğimizi belirtiriz, örneğin: UPROPERTY(EditAnywhere)  makrosu da onu editör içinde editlenebilir yani değerini değiştirileiblir yapar;

örneğin:
UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere)
    int32 TotalDamage;

    ...
};
bu değerlerin editörde nasıl ve nerede editlendiğini kontrol etmenin bir çok yolu vardır; bu tür detaylı şeyler UPROPERTY() makrosuna daha fazla parametre girerek yapılır; örneğin TotalDamage diye bir değişkenimiz olsun ve onun ilgili diğer özellikler ile bir arada farklı bir kategori altında görünmesini istiyoruz:

UPROPERTY(EditAnywhere, Category="Damage")
int32 TotalDamage;
şimdi kullanıcı bu property i editlemek için editörden baktığında onun Damage kategorisi başlığı altında varsa o kategorideki diğer propertiler ile birlikte görünecektir; bu ayarların designer ların kolay editleyebileceği şekilde editöre entegre emenin harika bir yoludur.

Blueprint ortamında görüntülenebilmesi için de:

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
int32 TotalDamage;


BlueprintReadOnly parametresi ile Blueprint ortamında artık okunabilir durumda olur; ancak artık bir tür sabit (const) gibidir; değiştirilemez.

BlueprintReadWrite
ile ise hem okunabilir hem de yazılabilir (değeri değiştirilebilir) duruma gelir.

Değişkenlerin motor içinde farklı şekillerde ortaya çıkmalarına neden olan oldukça iyi başka parametreler de vardır:

Aşağıdaki örnekte bir kaç farklı kullanım var. kimisi tasarımcılar tarafından görünür ve değiştirilebilir, kimisi ise görünür ama değiştirilebilir değildir.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
    int32 TotalDamage;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
    float DamageTimeInSeconds;

    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Transient, Category="Damage")
    float DamagePerSecond;

    ...
};


"DamageTimeInSeconds"  değişkeni editörde şimdi değiştirilebilir durumdadır.

"DamagePerSecond"  tasarımcının kullanacağı değişkendir. Ancka salt okunabilir; değiştirilemez.

VisibleAnywhere niteleyicisi ile Unreal Editör içinde söz konusu değişken sadece görünebilir (viewable) olur ama editlenebilir değildir.

Transient niteleyicisi (flag) inin anlamı bu değerin diskten yüklenmeyeceği veya diske kaydedilmeyeceği, tamamen geçici bir bellek değer tutucu olacağı anlamındadır.

Bu değişkenler (variable) resimde görüldüğü gibi artık editör içinde görünebilir durumda:




Yapıcı (constructor) içinde ilk değerleri atama :
yapıcı fonksyon içinde ilk değerleri atama tipik C++ kodları içindeki gibi yapılır; örneğin:

AMyActor::AMyActor()
{
    TotalDamage = 200;
    DamageTimeInSeconds = 1.f;
}

AMyActor::AMyActor() :
    TotalDamage(200),
    DamageTimeInSeconds(1.f)
{
}
yukarıdaki her iki kullanımı da Unreal Engine yapabilir; elbette biz daha çok üsttekine aşinayız; ancak diğer kullanımı da bilmemizde fayda vardır.

şimdi aşağıda yapıcı fonksyon içinde ilk değerler atandıktan sonra editördeki halleri görünüyor:



* ilk önce yapıcı fonksyonun başlangıç değerleri atanır; sonrasında designer in değer değişiklikleri desteklenir.

* bir özellik için default değer sağlamazsak engine otomatik olarak onlara sıfır değer veya pointer tiplerinde null değer atar.

* PostInitProperties()
çağrısı ile tasarımcının atadığı değerlere bağlı olmayan default değerler yaratabiliriz; aşağıda bununla ilgili bir örnek var. TotalDamage ve DamageTimeInSeconds değerleri tasarımcının belirttiği değerlerdir; buna rağmen halen onlara değerler sağlayabiliriz.


void AMyActor::PostInitProperties()
{
    Super::PostInitProperties();
    DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}
burada PostInitProperties() fonksyonunu devreye soktuktan sonraki değerler görünüyor.




Anlık yeniden yükleme (hot reload):
Unreal Engine in şık bir özelliği de editörü kapamadan C++ değişikliklerini (editör ortamından) derleyip elde edebiliyor olmamızdır.

Ya da tam tersi,, editör halen çalışıyorken, Visual Studio ya geçip derleme yaptığımızda editör bunu otomatik olarak algılayacaktır, ve yeni derlenmiş DLL leri anlık olarak yükleyecektir.



ikincisi debugger i attach etmiş isek, ayırmamız (detach etmemiz) lazım önce, sonra da Visual Studio bize build etme izni verecektir; veya daha basit bir şekilde doğrudan editör tool bardaki Compile buttonuna basarak da build edebiliriz (derleyebiliriz).



aşağıda daha detaylı anlatımını bulacaksınız.

Bir C++ sınıfını Blueprint ortamında genişletme
C++ class wizard ile basit bir gameplay sınıfı yarattık ve designer da ayarlanmak üzere içine bazı özellikler (properties) ekledik. şimdi bir designer in yeni bir sınıf yaratmasına göz atalım:

ilk olarak, bizim AMyActor class ından yeni bir Blueprint class ı yaratacağız. aşağıdaki resimde AMyActor yerine sadece MyActor yazdığını fark edin; bu kasıtlıdır; araçlarımız tarafından kullanılan özel isimlendirmeler tasarımcılardan gizlenir; bu isimleri onlar için daha tanıdık hale getirir.



yarattığımız yeni sınıfın adına CustomActor1 dedik göreceğiniz gibi.



bu üzerinde tasarımcılarımızla değişiklik yapacağımız ilk sınıfımız; ilk olarak default value leri değiştireceğiz, Damage kategorisindeki özelliklerden ilkine 300 verelim, ikinci kutunun değeri de 2.0 olsun,



ancak ilk hesaplanmış değerimiz beklediğimiz değer ile uyuşmuyor. 150 olmalıydı fakat halen default değeri 200 görünüyor. bunun nedeni, bu properti ler sadece yüklendikten sonra Damage Per Second hesaplanıyor olmasıdır. Yani kutulara yeni dğerler girmemiz ile birlikte anında C++ kodları koşmadı.

Unreal editör içinde çalışma zamanı (runtime) değişiklikleri hesaplanmaz; çünkü ilgili objeler için editörde olmuş değişikliklerden engine in haberi yoktur. Bu sorun için basit bir çözüm vardır: aşağıdaki kod örneğinde editör içinde değişen değerler için hesaplama yapan bir çapa atılması örneği var:
void AMyActor::PostInitProperties()
{
    Super::PostInitProperties();

    CalculateValues();
}

void AMyActor::CalculateValues()
{
    DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}

#if WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
    CalculateValues();

    Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif

PostEditChangeProperty() metodunun editör içinde specific #ifdef ifadesi ile kaydolduğunu görebilirsiniz. bu, oyununuzu sadece kod ile yapacağınız zaman ihtiyaç duyacağınız şeydir; bu aynı zamanda gereksiz kodları kaldırabilmenizi de sağlar; şimdi derledik, DamagePerSecond değeri beklediğimiz değere uyuyor; resimde görüldüğü gibi.




Fonksyonları C++ ve Blueprint Ortamlarından Çağırma
propertilerin Blueprint ortamında görünmesini sağladık ama bir konu daha var;

designerlar oyun sistemi yaratırken C++ tarafından yaratılmış fonksyonları çağırmaya ihtiyaç duyacaklar; dahası gameplay programcısı da bazen Blueprint tarafından yaratılmış fonksyonlara C++ ortamından erişmek ister. Şimdi yukarıdaki örnekteki CalculateValues() fonksyonunu ilk olarak kullanmaya başlayalım; bu fonksyon Bluerint ortamından çağrılabilirdir. Bir fonksyonu Blueprint ortamından görülebilir kılmak basit bir makro yardımı ile bu kolayca mümkündür; ve bu makro fonksyon deklereasyonunun (tanımının) hemen başına yazılır.

UFUNCTION(BlueprintCallable, Category="Damage")
void CalculateValues();

buradaki UFUNCTION() macrosu C++ fonksyonunu sistem takibi (reflection) için görünür duruma getirir. BlueprintCallable opsiyonu onun Blueprint sanal makinesi için görünür duruma gelmesi içindir. Blueprint için görünür duruma gelen her bir fonksyon ilişkili bir category bilgisine de ihtiyaç duyar. Blueprint editöründe sağ mouse tıklanarak açılan context menüde görünmeye başlar. Aşağıdaki resim kategorilerin context menü içinde nasıl görüntülendiğini gösteriyor:



gördüğünüz gibi fonksyonumuz Damage kategorisi altında görünür durumda. Aşağıdaki Blueprint kodu dataya bağlı olarak tekrar eden çağrılar tarafından Totaldamage değerindeki bir değer değişikliğini gösteriyor.



bu, dataya bağlı olan özelliğimizi hesaplamak için daha evvelden eklediğimiz aynı C++ fonksyonunu kullanır.

Unreal motorunundaki fonksyonların çoğu UFUNCTION() maksrosu ile Blueprint ortamında kullanılabilir; böylece insanlar C++ kodu yazmadan da programlama yapabilir; fakat en iyi yaklaşım gameplay sistem tabanı yaratmak için C++ kullanmak ve Blueprint ile de C++ kod bloklarındaki özellikleri değiştirerek, davranışları özelleştirerek, veya kombine davranışlar elde ederek performans kazanmaktır.  

Böylece designer lar alt düzey kodlarla boğuşmadan level katmanına yakın olan C++ işlevlerini kolayca çağırabilir; bu da Blueprint / C++ arasındaki engeli aşmanın güçlü bir yoludur; bu yaklaşımla C++ kodları Blueprint ler içinde tanımlanan fonksyonlar tarafından çağrılır; bunlar genellikle effecktlerin spawn oluşu, veya diğer görsel etkileri içeren parametrik olarak çağırılabilecek fonksyonlar olacaktır;  örneğin bir actor un kaybolması ve ortaya çıkışı gibi; aşağıda Blueprint tarafından implemente edilen kod kısmı görünüyor.

UFUNCTION(BlueprintImplementableEvent, Category="Damage")
void CalledFromCpp();

bu fonksyon normalde başka herhangi bir C++ fonksyonu tarafından çağrılır; ancak gizli kapakların altında, Blueprint sanal makinesi (Blueprint Virtual Machine) tarafından nasıl çağrılmasını sağlayan bir temel C++ fonksyon implamentasyonu yaratır. Şayet söz konusu Blueprint bu metod için bir fonksyonu gövdesi (function body) sağlamaz ise, fonksyon tıpkı gövdesi olmayan bir C++ fonksyonu gibi davranır; yani hiç birşey yapmaz.

Peki, bir yandan Blueprint e override edebilmesine izin vermeye devam ederek standart bir C++ implemantyasyonu sağlamak istersek ? UFUNCTION() macrosu bunun için bir seçenek içerir; aşağıdaki kod örneğinde buna ulaşmak için header makrosu içinde neyi değiştirdiğimizi gösteriliyor:

UFUNCTION(BlueprintNativeEvent, Category="Damage")
void CalledFromCpp();

bu versiyon halen Blueprint VM içinde çağırmak için thunking method üretir. Peki default implamentasyon nasıl sağlanacak? bunun için de otomatik olarak yeni bir fonksyon deklarasyonu üretilir; bu, fonksyon adının sonuna  _Implementation()  eklenerek sağlanır; ki aksi durumda zaten projemiz bağlamda başarısız olacaktır; aşağıdaki gibi :

void AMyActor::CalledFromCpp_Implementation()
{
    // Do something cool here
}

Şimdi bu fonksyon versiyonu, söz konusu Blueprint onu override etmediği zaman çağrılır.

Önceki Unreal Engine versiyonlarında _Implementation() deklerasyonu otomatik olarak üretilirdi; fakat 4.8 den sonraki versiyonlarda açıkça bizim header a eklememiz beklenmektedir.


Evet,
Şu ana kadar programcı - tasarımcı ortak çalışmasına yönelik özellikleri inceledik; şimdi biraz daha kendi işimize bakarak C++ özelliklerine odaklanalım ve biraz da gameplay class hiyerarşisi etrafında dönelim.

Bu kısımda temel yapı elementlerine başlayacak ve birbirleri ile ilişkisini inceleyeceğiz. Burası Unreal Engine in özel gameplay özelliklerini üretmek için kalıtım ve bir araya getirmeleri (kompozisyon) nasıl kullandığını göreceğimiz yerdir.



Gameplay Classes: Objects, Actors, and Components
Unreal Engine programlaması yaparken kalıtım ile devralabileceğimiz 4 temel majör class tipi vardır:

  • UObject,
  • AActor,
  • UActorComponent,
  • UStruct,

Bunlardan her birinin yapı blokları devam eden alanları tanımlar. Elbette bu sınıflardan birini devralmadan da tipler yaratabiliriz, fakat bu engine içindeki özelliklerden pay sağlamayacaktır. UObject dışındaki class ların tipik kullanımında 3. parti kütüphanelerin entegrasyonu vardır; işletim sistemine özel özellikler gibi..


Unreal Objects (UObject)
Unreal Engine içindeki temel yapı objeleri UObject olarak adlandırılır. Bu sınıf UClass ile özdeşleşmiştir ve motor içindeki bir dizi çok önemli servisleri sağlar:
  • özellik ve method ların yansıması (reflection),
  • özelliklerin serileştirilmesi (Serialization),
  • çöp toplama (Garbage collection),
  • bir UObje sini isim ile bulma,
  • network desteği, özellik ve methodları.

UObject den türetilen her bir sınıfın bir tekil UClass ı vardır ve o instance hakkındaki tüm metadatayı tutar. UObject ve Uclass ikisi birlikte gameplay objelerinin hayat döngüsün temelini oluşturur. Bu aslında her şeyin temelidir.

UClass ve UObject arasındaki farkı düşünmenin en iyi yolu, UClass ın, UObject nin instace inin neye benzediği, hangi özelliklerinin (properties) serialization, ve networking için aktif olduğu nu vs. açıkladığı şeklindedir.

Çoğu gameplay geliştirmesi AActor veya UActorComponent yerine doğrudan UObjects dan doğrudan miras alınarak yapılmaz. Gameplay kodu yazmak için UClass / UObject lerinin nasıl çalıştığının detayını bilmemize gerek yoktur, ama bu sistem varlıklarını bilmek iyidir.



AActor
bir AActor objesi gameplay deneyiminin bir parçasıdır. AActor ler level designer lar tarafından level içine konumlandırılır veya runtime anında da gameplay sistemleri tarafından create edilebilir.

Level içine konumlandırılabilen tüm objeler bu class tan extend edilirler; örneğin AStaticMeshActor, ACameraActor, ve APointLight actorleri gibi.

AActor,  UObject den miras alınarak genişletilmiştir; ve onun tüm özelliklerini destekler.

AActor ler gameplay kodu tarafından (C++ veya Blueprint) yokedilebilir, veyahutta standart garbage collection mekanizması zamanı gelince onu yokeder (hafızadan unload eder).

AActor ler gameplay objelerinin üst düzey davranışlarından sorumludur.

AActor ler aynı zamanda networkleme esnasında çoğaltılabilen (replikasyon) temel tiplerdir.

Network replikasyonu esnasında, AActor ler kendilerine ait olan ve network desteğine ihtiyaç duyan herhangi bir UActorComponent için bilgi paylaşabilir, dağıtabilir.

AActor ler miras ile özelleşmiş kendi davranışlarına sahiptirler, ama aynı zamanda UActorComponent leri hiyerarşisi için bir container işlevi de görürler; bu, AActor ün tek bir UActorComponent içeren RootComponent üyesi ile yapılır; sonra sırayla diğerlerini içerebilir.

bir AActor level içinde konulanmasından (placed) önce transform, rotation ve scale işlemlerini içeren en az bir USceneComponent içermek zorundadır.


AActor yaşam döngüsü (lifecycle) süresince çağrılacak olan bir dizi olay içerir;
bu yaşam çemberine ait bazı basit olayları listeler isek:

BeginPlay - obje gameplay de ilk varolduğunda çağrılır;

Tick - her bir frame yapılacak işlemler için çağrılır;

EndPlay - obje gameplay uzayını terkettiğinde çağrılır;



Çalışma Zamanı Yaşam Döngüsü (Runtime Lifecycle):
yukarıda AActor temel yaşam döngüsü alt kümesi incelendi. Level içine konumlanan actor ler için, yaşam döngüsünü hayal etmek aslında çok kolaydır: Actor ler yüklenir ve level içinde yer alır, ve nihayetinde levelden unload edilir ve yokedilirler.

peki yaratılma ve yok edilmedeki runtime süreci nasıldır? Unreal Engine bir AActor ün yaratımı runtime spawning olarak adlandırılır. Bir actor ün spawn edilişi normal bir oyun içi objesi yaratmadan bir miktar daha karışıktır... bunun nedeni, bir AActor ün gerekliliklerinin karşılanması için çeşitli runtime sistemlerine kaydolması gerekmesidir; örneğin actor için ilk olarak location, rotation bilgisi set edilmelidir; fizik motorunun bu değerleri bilmesi gerekebilir; ya da yönetici motor, her bir actore tick fonksyonlarına bilmesi gerekenleri söylemekle sorumludur vs..  bu yüzden, bir actor ü spawn etmek için hazırlanmış özel bir methodumuz var: UWorld::SpawnActor();  bir kez actor başarılı bir şekilde spawn olmuş ise, onun BeginPlay() metodu hemen çağrılır; hemen akabinde de her bir frame de Tick() metodu çağrılır.

Actor ün ömrü bittiğinde onun Destroy() metodu çağrılarak yok edilir. Bu süreç esnasında EndPlay() fonksyonu çağrılır; eğer yok edilme esnasında yapmak istediğimiz özel işlemler var ise burada yapılır.

Bir actor ün ne kadar üzün süre var olacağını kontrol etmenin bir başka yolu da Lifespan member, yani zaman sayacı üye değişkeni kullanmaktır. Yani objenin yapıcı fonksyonu (constructor) içinde veya runtime anındaki başka bir kod ile bir timespan değişkenini set edebilirsiniz; sonra zamanı dolunca actor otomatik olarak Destroy() fonksyonunu çağırır.


şimdi bazı temel kavramların üzerinden tekrar kısaca geçelim:


UActorComponent
UActorComponent leri kendi davranışlarına (behaviors) sahiptirler ve pek çok AActor tipininde genellikle ortak paylaşılan fonksyonellikten sorumludurlar; örneğin görsel mesh ler, particle effecktleri, camera perspektifleri, ve fizik etkileşimleri vs. gibi özellikleri sağlarlar.

AActor
lere oyundaki rolleri ile ilgili üst düzey hedefler verilirken, UActorComponent lerin genellikle bu üst hedefleri destekleyen daha bireysel görevleri olur.


Component 
Component ler diğer Component lere ataçlanabilir, veya bir component bir actor için root component olabilir.

Bir component sadece bir parent component e veya actor e atach edilebilir; fakat birden çok child component e sahip olabilir; child component lerin location, rotation ve scaling bilgileri parent component veya actore göre artık görecelidir, yani onlara bağlıdır.

Actor leri ve componentleri kullanmanın pek çok yolu varken, bir yol da actor - component ilişkisini düşünmektir. Actor  “bu düşünce nedir?”  sorusuna cevap verebilirken, componentler ise  “bu neyden yapıldı?”   sorusuna cevap verebilir...


RootComponent 
RootComponent, bir AActor ün Components ağacı içindeki en üst düzey Component ine atanmış olan üyesidir.


Ticking Component 
bunlar üyesi olduğu AActor lerin Tick() leri ile tiklenir.  (ne cümle ama :)) yani bir AActor ün Tick fonksyonu yürütüldüğünde bu aynı zamanda ona bağlı tüm componentlerin de tick fonksyonu olarak iş görür.

Durumu daha iyi kavramak için örnek olarak First Person Character i inceleyelim ve AActor ve onun UActorComponent lerini tanıyalım biraz daha:

First person şablonunda bir oyun yarattığımızda yaratılan FirstPersonCharacter in Blueprint ine bakın; bu actor ün component ağacı aşağıda listeleniyor; buradaki CapsuleComponent actor ün RootComponent itir. CapsuleComponent e yani root componente ArrowComponent , MeshComponent ve de FirstPersonCameraComponent attach edilmiş durumda.

En sonda da Mesh1P bileşeni FirstPersonCameraComponent e ataçlanmış, yani kişiyi simgeleyecek olan mesh burada first person camera ya bağlıdır; yani bu oyun şablonunda (FPS) mesh in değerleri FPS kameraya göre değişir, kameraya göre göreceli olur. 



imajdaki component ağacının 3D uzaydaki görüntüsü aşağıdaki gibidir. (mesh component hariç)



Önemli Not:
Component ler ağacı aslıda tek bir class a bağlıdır; bu örnekten görebileceğinz gibi kalıtım ve kompozisyon ile karmaşık gameplay objeleri yaratabilirsiniz; 


varolan bir AActor veya UActorComponent sınıfını biraz daha özelleştirmek istediğimizde kalıtımı (inheritance) kullanın; eğer çok sayıda fatklı AActor tiplerinin fonksyonlarını paylaşmak istiyorsak kompozisyonlar (bir araya getirip bütünleştirmeler) yapmalıyız.



UStruct
Struct yapısı eski C den sonra C++ dan bu yana yoğun kullanılan bir yapıdır. Unreal Engine kodlarında da yoğun şekilde kullanılır.

bir UStruct kullanmak, belli bir sınıfın genişletilmiş halini kullanmak değildir; sadece USTRUCT() ile işaretlenmiş, tekilleştirilmiş bir yapıya (öğeler topluluğu) tanımlamaktır.

UObject den farklı olarak UStruct ler garbage collecte edilmezler, yani çöp toplama işlemine tabi değildirler. Eğer onları dinamik olarak instance edersek bu objelerin yaşam döngülerini kendimiz yönetmeliyiz. 

UStruct lar Unreal Editor içinde editlenebilen eski düz veri tipleri için UObject yansıma (reflection) desteğine sahiptir, böylelikle Editor, Blueprint, serialization, networking de de görev alabilirler.





Unreal Reflection System
Gameplay sınıflarından özel kompozisyonlar, biçimlendirilmiş daha özel oyun objeleri yaratabileceğimizi artık biliyoruz;  C++ kodlamasına girmeden önce şimdi biraz da Unreal Engine Property Sisteminin temellerinden söz edelim; ki bu programlamada çok kullanacağımız bir konu olacak.

Unreal Engine, garbage collection, serialization, network replication, Blueprint / C++ gibi pek çok işlevin birbirleri ile iletişimi için bazı dinamik özellikler sağlayan kendisistemini (Unreal Reflection System) ini kullanır.

Bunun şartı olarak yarattığımız tiplerimizde işaretlemeyi makrolar ile doğru yapmak zorunda olmamızdır; aksi durumda Unreal Engine onları görmezden gelecek (ignore edecek), ve onlar için data yansıması üretmeyecektir.

temel bazı işaretlemeler şöyledir:

UCLASS() 
motora bir sınıf için yansıma (reflection) bilgisi üretmesini söyler; ancak bu sınıf UObject den türetilmiş olmalıdır.


USTRUCT() 
motora bir struct yapı için bir yansıma bilgisi üretmesini söyler.


GENERATED_BODY() 
motor bu satır ile sınıf içine tüm temel kodları ekler; sınıf tanımının başına eklenir;


UPROPERTY()
bir UCLASS veya USTRUCT için bir değişken üyeyi geçerli hale getirir. Bir UPROPERTY nin pek çok kullanımı vardır; bu bir değişkene replike olma, serileşme, Blueprint e erişme vs. gibi pek çok yetki / özellik verebilir. UPROPERTY aynı zamanda garbage collector yani çöp toplama için de kullanılır.


UFUNCTION()
UCLASS ın veya USTRUCT nin bir metodunu geçerli kılarak artık bir UFUNCTION olarak kullanılmasını sağlar; bu da o metodun Blueprint ortamından çağrılabilmesini ve diğer şeylerin arasında kullanılabilmesini mümkün kılar.


şimdi UCLASS ile ilgili bir örnek yapalım:

#include "MyObject.generated.h"

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

public:
    MyUObject();

    UPROPERTY(BlueprintReadOnly, EditAnywhere)
    float ExampleProperty;

    UFUNCTION(BlueprintCallable)
    void ExampleFunction();
};

burada ilk dikkatimizi çekecek olan "MyClass.generated.h" ifadesidir. Unreal tüm yansıma (reflection) verilerini bu dosya içine yerleştirecektir. Biz de bu header dosyasını class ımızı tanımlası için header (.h) dosyası içindeki en son include ifadesi olarak belirtmek zorundayız.

daha fazla specifier da ekleyebiliriz. Biz burada demo için sık kullanılan birkaçını kullandık. Bu belirteçler (specifier) bize sınıfımızın sahip olacağı belli bir davranışı belirtmemize olanak tanır.


Blueprintable 
bu sınıf bir Blueprint sınıfı tarafından miras alınarak genişletilebilir demektir.


BlueprintReadOnly 
bu değişken (property) Blueprint ortamından sadece okunabilir, ama değeri değiştirilemez.


Category 
bir property nin grubunu tanımlamak için kullanılır; daha sonra bu grup Blueprint graph penceresinde ve editörün Details View penceresi altında burada belirtilen kategori altında görüntülenir; organizasyonel bir amaca sahiptir.


BlueprintCallable 
bu sözkonusu fonksyon Blueprint ortamından çağrılabilir.

çok fazla specifier vardır , tam liste için dökümantasyona bakınız...




Object / Actor Yineleyicileri (Iterators):
Object iterators (obje yineleyiciler) özellikle UObject sınıfının ve onun alt sınıflarının nesnelerini yinelemede çok kullanışlı araçlardır.

// Will find ALL current UObjects instances
for (TObjectIterator<UObject> It; It; ++It)
{
    UObject* CurrentObject = *It;
    UE_LOG(LogTemp, Log, TEXT("Found UObject named: %s"), *CurrentObject.GetName());
}


iteratöre daha fazla komplike tipler sağlayarak arama görünürlüğünü sınırlayabilirsiniz

diyelimki, UObject den türetilmiş UMyClass adında bir sınıfımız olsun. bu sınıftan türemiş olan tüm nesneleri (instances) bulabilirdik, örneğin:

for (TObjectIterator<UMyClass> It; It; ++It)
{
    // ...
}

gibi….

Obje yineleyicilerini editör oyunu içinde kullanmak bize beklenmedik sonuçlara öncülük edebilir. Editör yüklendikten itibaren geçen sürede yineleyici oyun dünyamız için yaratılmış olan tüm UObjects leri döndürür.

Actor yineleyiciler de tıpkı obje yineleyiciler gibi çalışır; ama sadece AActor den miras alınan sınıflar için kullanılır. Actor yineleyicilerin aşağıda belirtilene benze bir sorunları yoktur, ve sadece o anki oyun dünyası (game world instance) tarafından kullanılan objeler döndürürler.

bir Actor yineleyici yaratacağımız zaman ona bir UWorld nesnesinin adresini pointer tipinde vermemiz gerekir. Çoğu UObject sınıfı (daha doğrusu
UObject den türetilmiş çoğu sınıf) örneğin APlayerController, yardımcı olması için GetWorld adında bir metod sağlar. Eğer emin değilseniz, bir UObject üzerinde ImplementsGetWorld metodunu kontrol edip GetWorld metodunun nasıl yapılandırıldığını (implement edildiğini) görebilirsiniz.
APlayerController* MyPC = GetMyPlayerControllerFromSomewhere();
UWorld* World = MyPC->GetWorld();

// Like object iterators, you can provide a specific class to get only objects that are
// or derive from that class
for (TActorIterator<AEnemy> It(World); It; ++It)
{
    // ...
}


bir AActor UObject ten miras alınmasından itibaren AActor lerin örneklerine ulaşmak için TObjectIterator ü de kullanabiliriz; sadece editör içindeki oyunda (PIE) (play in editor) dikkatli olun.



Memory Management and Garbage Collection (hafıza yönetimi ve çöp toplama):
şimdi biraz Unreal Engine deki temel hafıza yönetimi ve çöp toplama konusunda eğilelim,

Garbage Collection yapılandırması için reflection sistemi kullanır. Garbage Collection ile UObject lerini manuel silmek zorunda kalmayız; sadece onların temel referanslarına ihtiyaç duyarız. garbage collection in geçerli olması için sınıflarımız UObject ten miras alınmalıdır. şimdi basit bir kullanım örneği yalalım:

UCLASS()
class MyGCType : public UObject
{
    GENERATED_BODY()
};

Garbage Collection içinde root set olarak adlandılan bir kavram vardır. Root Set temel olarak hiçbir zaman çöp kutusu listesine alınmayacak (hiçbir zaman silinmeyecek) olan nesnelerin bir listesidir. 


Root Set deki bir objeden söz konusu objeye bir referans yolu olduğu sürece o obje hiçbir zaman garbage collecting edilmez (silinmez).

bir nesne için Root Set ine böyle bir yol YOKSA buna unreachable (ulaşılmaz) durumu denir, ve çöp toplayıcısının bir sonraki çalıştırılmasında garbage collector edilir, yani silinir.

Unreal Engine belli aralıklarla garbage collector (çöp toplayıcısı) çalıştırır. 

Peki sayılan şeyin referansı nedir? 
bir UPROPERTY de depolanan tüm UObject pointer larıdır.

bir örnek yapalım,



void CreateDoomedObject()
{
    MyGCType* DoomedObject = NewObject<MyGCType>();
}

yukarıdaki fonksyonu çağırdığımızda yeni bir UObject yaratmış oluyoruz; ama UPROPERTY e herhangi bir pointe depolamıyoruz, ve dolayısı ile root set in bir parçası değildir.

sonunda garbage collector onun unreachable (ulaşılmaz) olduğunu anlayacak ve yok edecektir.



Actor ler ve Garbage Collection
Actor ler genellikle garbage collecte edilmez, bir kez spawn edilir, onları yok etmek için manuel olarak Destroy() fonksyonlarını çağırmamız gerekir. Onlar anında silinmezler; bir sonraki garbage collection fazında silinirler;  bu, UObject özellikleri taşıyan bir actor ün olduğu durumlarda daha yaygındır.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY()
    MyGCType* SafeObject;

    MyGCType* DoomedObject;

    AMyActor(const FObjectInitializer& ObjectInitializer)
        : Super(ObjectInitializer)
    {
        SafeObject = NewObject<MyGCType>();
        DoomedObject = NewObject<MyGCType>();
    }
};

void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
    World->SpawnActor<AMyActor>(Location, Rotation);
}

yukarıdaki fonksyonu çağırdığımızda dünya da bir actor ü spawn etmiş oluyoruz. (oluşturuyoruz). Actor ün constructor fonksyonu iki obje create ediyor, biri bir UPROPERTY e atanıyor, diğeri yalın pointer.

Actor ler Root Set in bir parçası olduğu sürece SafeObject garbage collecte edilmeyecek (silinmeyecek), çünkü Root Set objesinden erişilebilir.

DoomedObject yi ise UPROPERTY ile işaretlemedik; böylece collector onun başvurusunu (reference) bilmiyor, ve sonunda yok edilecek.

"her ne zaman bir UObject garbage collecte edilirse, onu referans eden tüm UPROPERTY ler sizin için otomatikman null pointer (nullptr) olarak set edilir."

bu onu bizim için daha güvenli yapar, objenin garbage collecte edilip eidlmediğinin kontrol edilebilmesi için ise,

if (MyActor->SafeObject != nullptr)
{
    // Use SafeObject
}

daha önce sözü edildiği gibi bu kontrol önemlidir; çünkü Destroy() fonksyonu çağrılmış Actor ler garbage collector tekrar çalıştırılana kadar kaldırılmazlar.

bir UObject nin silinme sırasında bekleyip beklemediği kontrol etmek için IsPendingKill() metodunu kullanabiliriz. eğer true dönerse o objenin artık öldüğünü ve kullanılmaz olduğunu varsaymalıyız.



UStructs
daha önce sözü edilen UStruct lar, UObject lerin daha hafif versiyonlarıdır; örneğin UStruct lar garbage collecte edilemezler. Eğer UStruct ların dinamik versiyonlarını kullanmak zorunda isek, akıllı pointer ları kullanmak isteyebiliriz. bu konuya sonra bakarız.



Non-UObject References
non-UObject ler başka bir objenin referansını ekleme yeteneğine sahip olabilir ve garbage collection a engel olurlar. Bunun olması için objenizin FGCObject dan miras alınması ve kendi AddReferencedObjects class ını override etmesi gerekir.

class FMyNormalClass : public FGCObject
{
public:
    UObject* SafeObject;

    FMyNormalClass(UObject* Object)
        : SafeObject(Object)
    {
    }

    void AddReferencedObjects(FReferenceCollector& Collector) override
    {
        Collector.AddReferencedObject(SafeObject);
    }
};

biz FReferenceCollector u UObject ye manuel olarak ihtiyacımız olan ve garbage collecte edilmesini istemediğimiz sağlam bir referans eklemek için kullanıyoruz.

bir obje silindiğinde ve onun yokedici fonksyonu destructor çalıştığında, obje otomatik olarak onu eklemiş olan tüm referansları da temizler.




Class İsimleri Ön Ekleri (Prefixes):
Unreal Engine derleme süreci esnasında otomatik olarak bizim için kod üreten araçlar sağlar; bu araçlar bazı sınıf isimlendirmelerine ihtiyaç duyar ve bu isimler bu beklenti ile uyuşmaz ise bazı uyarı ve hataları tektiklerler. Bu class isimlendirme önekleri (prefixes) liste olarak aşağıda verilmiştir:


* Actor den devralınan sınıflar her zaman A ön eki ile başlarlar, örneğin AController.


* Object den miras alınan clas lar he rzaman U ön ekini alır, örneğin UComponent.


* Enums lar her zaman E öneki ile başlar, örneğin EFortificationType.


* Interface ler her zaman I öneki ile başlar.


* Template sınıflar her zaman T öneki ile başlar, örneğin TArray.


* SWidget (Slate UI) dan miras alınan sınıflar her zaman S öneki ile başlar, örneğin SButton.


* geri kalan herşey F ön ekini alır, örneğin FVector. 





Numeric Types
farklı platformlar farklı büyüklükte temel tiplere sahiptir, örneğin short, int, , long, UE4 aşağıdaki tipleri destekler:

int8/uint8 : 8-bit signed/unsigned integer.


int16/uint16 : 16-bit signed/unsigned integer.


int32/uint32 : 32-bit signed/unsigned integer.


int64/uint64 : 64-bit signed/unsigned integer.

ondalıklı değerler (akışkan noktalı tipler) (Floating point numbers ) için standart float (32-bit) ve double (64-bit) tipleri desteklenir.

Unreal Engine nin TNumericLimits templeti vardır, miimum ve maksimum aralıkları bulmak için değerler tiplerini tutar. 




Strings
Unreal Engine de string lerle çalışmak için ihtiyacımıza göre birkaç farklı tipte class sağlar:

FString
FString değişebilir bir string tipidir, bu yönüyle tıpkı std::string e benzer. FString bu string işlerini kolaylaştırmak için geniş bir yöntem paketine sahiptir. 


Yeni bir FString yaratmak için TEXT() macrosunu kullanırız:

FString MyStr = TEXT("Hello, Unreal 4!");



FText
FText de FString gibidir, ancak lokalize metin anlamına gelir. Yeni bir FText yaratmak için NSLOCTEXT macrosu kullanılır; bu makro bir namespace, bir KEY, ve bir de değer alır:

FText MyText = NSLOCTEXT("Game UI", "Health Warning Message", "Low Health!")

eğer her bir dosyada bir namespace tanımlamak zorunda isek LOCTEXT macrosu da kullanabiliriz. tabi onu dosyanın bitiminde tanımsız hale getirdiğinize emin olun.

// In GameUI.cpp
#define LOCTEXT_NAMESPACE "Game UI"

//...
FText MyText = LOCTEXT("Health Warning Message", "Low Health!")
//...

#undef LOCTEXT_NAMESPACE
// End of file



FName
bir FName hafıza ve CPU kaynaklarını korumak için ortak kullanılan yinelenen string leri bir identifier (tanımlayıcı) olarak depolar.

String in tamamını onu referans etmiş bi çok objenin karşısında, onu bi çok kez depolamak yerine bir FName, verilen string i haritalayan daha küçük bir depolama izi indeksi kullanır; bu bir kez string içeriğini depolar, ve bu string karşılık gelen çok sayıda objede kullanıldığında hafıza korunmuş olur; iki string    if NameA.Index equals NameB.Index   gibi bi ifade ile her bir karakterin eşitliğinin karşılaştırılmasının önüne geçilerek hızlıca karşılaştırılabilir,



TCHAR
TCHAR lar, farklı platformlar arasında karakter setinden bağımsız bir biçimde karakter depolamanın bir yolu olarak kullanılır; kaputun altında, Unreal Engine stringleri UTF-16 encoding tipinde depolamak için TCHAR dizilerini kullanır; ham dataya ulaşmak için TCHAR tipinde veri döndüren aşırı yüklenmiş operatörler kullanılabilir.

bu ‘%s’ string format specifier kullanan FString::Printf fonksyonu gibi bazı fonksyonlarda gereklidir; onlar FString yerine TCHAR ile iş görürler.

FString Str1 = TEXT("World");
int32 Val1 = 123;
FString Str2 = FString::Printf(TEXT("Hello, %s! You have %i points."), *Str1, Val1);

FChar tipi bireysel TCHAR lar ile çalışmak için bir dizi statik yararlı fonksyon sağlar.

TCHAR Upper('A');
TCHAR Lower = FChar::ToLower(Upper); // 'a'


Containers
Containers data koleksiyonlarını tutan öncelikli fonksyonların sınıflarıdırlar. En yaygın olanları TArray, TMap, TSet tir. Her biri dinamik büyüklükte ihtiyacımız olduğunda, olduğu kadar genişleyebilen yapıdadır.


TArray
bu Unreal Engine içindeki 3 container sınıf içindeki en öncelikli diyebileceğimiz sınıftır; fonksyonları std::vector ün yaptığı gibidir; ama çok daha fazla fonksyonellik sunmaktadır. 


bazı kullanımları şöyledir:
TArray<AActor*> ActorArray = GetActorArrayFromSomewhere();

// Tells how many elements (AActors) are currently stored in ActorArray.
int32 ArraySize = ActorArray.Num();

// TArrays are 0-based (the first element will be at index 0)
int32 Index = 0;
// Attempts to retrieve an element at the given index
TArray* FirstActor = ActorArray[Index];

// Adds a new element to the end of the array
AActor* NewActor = GetNewActor();
ActorArray.Add(NewActor);

// Adds an element to the end of the array only if it is not already in the array
ActorArray.AddUnique(NewActor); // Won't change the array because NewActor was already added

// Removes all instances of 'NewActor' from the array
ActorArray.Remove(NewActor);

// Removes the element at the specified index
// Elements above the index will be shifted down by one to fill the empty space
ActorArray.RemoveAt(Index);

// More efficient version of 'RemoveAt', but does not maintain order of the elements
ActorArray.RemoveAtSwap(Index);

// Removes all elements in the array
ActorArray.Empty();

TArrays e diğer elementlerin garbage collected özelliklerinin faydaları eklenmiştir; bu da onu bir UPROPERTY olarak varsayar ve pointer lerden devralınan UObject leri depolar.

UCLASS()
class UMyClass : UObject
{
    GENERATED_BODY();

    // ...

    UPROPERTY()
    TArray<AActor*> GarbageCollectedArray;
};


TMap
TMap tıpkı std::map gibi key-value çiftlerinden oluşur. TMAP, kendi keyleri üzerinde arama bulma, ekleme ve çıkarma işlemlerini hızlıca yapacak metodlara sahiptir; onu tanımlayan bir GetTypeHash fonksyonuna sahip olduğu sürece Key için herhangi bir tip kullanabiliriz.

Diyelim ki grid tabanlı bir board oyunu yapıyoruz ve he bir kareyi depolama ve sorgulama ya ihtiyacımız var; bir TMap bunu yapmanın kolay yolunu sağlayacaktır; eğer bord küçük ise ve her zaman aynı büyüklükte ise elbette bunu yapmanın daha verimli yolları vardır, ama bir örnek hatrı için yapalım:

enum class EPieceType
{
    King,
    Queen,
    Rook,
    Bishop,
    Knight,
    Pawn
};

struct FPiece
{
    int32 PlayerId;
    EPieceType Type;
    FIntPoint Position;

    FPiece(int32 InPlayerId, EPieceType InType, FIntVector InPosition) :
        PlayerId(InPlayerId),
        Type(InType),
        Position(InPosition)
    {
    }
};

class FBoard
{
private:

    // Using a TMap, we can refer to each piece by its position
    TMap<FIntPoint, FPiece> Data;

public:
    bool HasPieceAtPosition(FIntPoint Position)
    {
        return Data.Contains(Position);
    }
    FPiece GetPieceAtPosition(FIntPoint Position)
    {
        return Data[Position];
    }

    void AddNewPiece(int32 PlayerId, EPieceType Type, FIntPoint Position)
    {
        FPiece NewPiece(PlayerId, Type, Position);
        Data.Add(Position, NewPiece);
    }

    void MovePiece(FIntPoint OldPosition, FIntPoint NewPosition)
    {
        FPiece Piece = Data[OldPosition];
        Piece.Position = NewPosition;
        Data.Remove(OldPosition);
        Data.Add(NewPosition, Piece);
    }

    void RemovePieceAtPosition(FIntPoint Position)
    {
        Data.Remove(Position);
    }

    void ClearBoard()
    {
        Data.Empty();
    }
};

TSet
bir TSet unique değerlerin koleksiyonudur, std::set e benzer. AddUnique ve Contains metodları ile TArrays zaten setler olarak kullanılabilir; fakat 
TArrays gibi onları UPROPERTY olarak kullanamama pahasına da olsa TSet bu operasyonlarda daha hızlı işlem yapabilir; TSet ler aynı zamanda TArray in yaptığı gibi elemanlarına index leme de yapmaz.
TSet<AActor*> ActorSet = GetActorSetFromSomewhere();

int32 Size = ActorSet.Num();

// Adds an element to the set, if the set does not already contain it
AActor* NewActor = GetNewActor();
ActorSet.Add(NewActor);

// Check if an element is already contained by the set
if (ActorSet.Contains(NewActor))
{
    // ...
}

// Remove an element from the set
ActorSet.Remove(NewActor);

// Removes all elements from the set
ActorSet.Empty();

// Creates a TArray that contains the elements of your TSet
TArray<AActor*> ActorArrayFromSet = ActorSet.Array();
şunu unutmayın ki UPROPERTY olarak işaretlenebilen tek continer sınıfı TArray sınıfıdır; bunun anlamı diğer container sınıfları replike edilemez, saklanamaz veya sahip oldukları elemanlar sizin için garbage collecte edilemez. 

Container Iterators (container yineleme) yineleyicileri kullanarak container in her bir elementi üzerinde dönebiliriz; burada bu iterasyonun kod syntaksının neye benzediğine ilişkin bi örnek var , örnekte TSet kullanılıyor:

void RemoveDeadEnemies(TSet<AEnemy*>& EnemySet)
{
    // Start at the beginning of the set, and iterate to the end of the set
    for (auto EnemyIterator = EnemySet.CreateIterator(); EnemyIterator; ++EnemyIterator)
    {
        // The * operator gets the current element
        AEnemy* Enemy = *EnemyIterator;
        if (Enemy.Health == 0)
        {
            // 'RemoveCurrent' is supported by TSets and TMaps
            EnemyIterator.RemoveCurrent();
        }
    }
}
yineleyicilerle kullanabileceğimiz desteklenen başka operasyon:
// Moves the iterator back one element
--EnemyIterator;

// Moves the iterator forward/backward by some offset, where Offset is an integer
EnemyIterator += Offset;
EnemyIterator -= Offset;

// Gets the index of the current element
int32 Index = EnemyIterator.GetIndex();

// Resets the iterator to the first element
EnemyIterator.Reset();

For-each Loop
yineleyiciler iyi güzel ama biraz hantaldırlar; eğer sadece elemenler üzerinden bir kez yapmak istiyorsak biraz ağır kaçıyorlar. Her container sınıfı elemanları üzerinden dönmek için for each tipi döngüyü de destekler. TArray ve TSet her elemanını return ile döndürür, buna karşılık TMap key-value çifti döndürür.

// TArray
TArray<AActor*> ActorArray = GetArrayFromSomewhere();
for (AActor* OneActor : ActorArray)
{
    // ...
}

// TSet - Same as TArray
TSet<AActor*> ActorSet = GetSetFromSomewhere();
for (AActor* UniqueActor : ActorSet)
{
    // ...
}

// TMap - Iterator returns a key-value pair
TMap<FName, AActor*> NameToActorMap = GetMapFromSomewhere();
for (auto& KVP : NameToActorMap)
{
    FName Name = KVP.Key;
    AActor* Actor = KVP.Value;

    // ...
}
bu arada auto keyword ünü hatırlayın bizim için otomatik olarak bir pointer referansı belirtmez, bizim kendimiz eklmemiz gerekir.


TSet/TMap ile kendi tiplerimizi kullanma (Hash Functions):
TSet ve TMap dahili HASH (eşleştirme) fonksyonların kullanımına ihtiyaç duyar. eğer bir TSet içinde veya TMap içinde bir key olarak kullanmak istediğimiz kendi sınıfımızı yaratıyor isek, öncelikle kendi HASH (eşleştirme) fonksyonlarımızı yaratmalıyız. bu tiplerin içine koyacağımız çoğu çoğu Unreal Engine tipleri zaten kendi hash fonksyonlarını tanımlar.

bir hash fonksyon tipimize ait sabit bir pointer referans alır uint64 döndürür. bu dönen değer bir objenin hash kodudur. ve bu objeye istinaden bir rakam içermelidir.

birbirine eşit olan iki obje he zaman aynı hash kodu döndürmelidir.

class FMyClass
{
    uint32 ExampleProperty1;
    uint32 ExampleProperty2;

    // Hash Function
    friend uint32 GetTypeHash(const FMyClass& MyClass)
    {
        // HashCombine is a utility function for combining two hash values
        uint32 HashCode = HashCombine(MyClass.ExampleProperty1, MyClass.ExampleProperty2);
        return HashCode;
    }

    // For demonstration purposes, two objects that are equal
    // should always return the same hash code.
    bool operator==(const FMyClass& LHS, const FMyClass& RHS)
    {
        return LHS.ExampleProperty1 == RHS.ExampleProperty1
            && LHS.ExampleProperty2 == RHS.ExampleProperty2;
    }
};
şimdi, TSet ve TMap anahtarları eşleştirirken (hashing) uygun hash (eşleştirme) foknsyonunu kullanacak;  eğer keyler olarak pointerler kullanıyor isek (TSet gibi)
 uint32 GetTypeHash(const FMyClass* MyClass) i de işteletebiliriz.



Epic Kodlama Standardı:
Epik in bir kaç basit kod standardı ve düzeni mevcuttur. biraz bu standartlardan bahsedelim


kod düzeni programcılar için bir kaç nedenden ötürü önemlidir:
  • yazılım maliyetinin 80% i bakım işlemleridir.
  • tüm yazılımlar bakıma alınır, hayatı boyunca bakımdan geçer.
  • kod alışkanlıkları kodun okunaklılığını artırır, mühendislere yeni kodun anlaşılaiblirliğini artrırır.
  • bir çok yazım düzeni platformlar arası derleyici uyumluluğu için gereklidir.
  • sınıf değişklen ve foknsyon isimleri anımsatıcı olmalıdır, ki başka programcı onu okuyabilsin, biz de hatırlayabilelim.

İsimlendirme Kuralları:
tip veya değişkenlerin her kelimesinin ilk kelimesi mutlaka büyük harf ile başlar.
genellikle kelimeler arası underscore  "_"  kakrakteri almaz. 

örneğin UPrimitiveComponent doğrudur ama lastMouseCoordinates veya delta_coordinates kullanımı yanlıştır.

C# taki önekler gözardı edilmiştir; elbette C# ta durum farklıdır.

UnrealHeaderTool doğru öneklerine ihtiyaç duyar, bu önemlidir.

T
ip isimleri öneki alır, bu bir büyük harftir, böylece onları değişken isimlerinden ayırırız. örneğin FSkin bir tiptir, Skin ise FSkin in bir instance idir.

Template sınıfları mutlaka T ile başlar.

UObject den katıtım ile alınan tüm sınıflar U ile başlar.

AActor den kalıtım ile alınan tüm sınıflar A ile başlar.

SWidget den kalıtım ile alınan tüm sınıflar S ile başlar.

abstract interfaces olan sınıflar da I ile başlar.

Enums lar E ile başlar.

Boolean değişkenler mutlaka küçük b harfi ile başlar; örneğin: "bPendingDestruction", veya "bHasFadedIn" gibi.

çoğu diğer sınıflar da F harfi ile başlar, bazıları başka hafler de kullanabilir.

typedef ler de tanımladığı tipe göre öneki alırlar: 

eğer tanımladığı bir struct ise F, yada tanımladığı bir UObject ise U vs…


özel bir şablonun typedef i artık bir template değildir ve tanımladığı tipe göre ön ek almalıdır (prefix), örneğin:

typedef TArray<FMyType> FArrayOfMyTypes;
Tip (Type) ve değişken (variable) isimleri birer noun (isim) dir. Method isimleri ise birer fiil (verb) dir. bir etkiye sahiptirler. veya bir etki yaratmasalar bile dönecek olan bir değerleri olur.

değişken, metod, ve sınıf isimleri açık, temiz, ve kesin olmalıdır. İsim görünürlüğünden daha iyi olan şey, açıklayıcı bir ismin öneminin büyük olmasıdır; aşırı kısaltmaların önüne geçin.

bool döndüren tüm fonksonlar bir true / false sorusu sormalıdır örneğin IsVisible() veya ShouldClearBuffer() gibi…


bunlar şart olmasa da Epic bu yazım şeklini öneriyor.


giriş veya çıkış parametresi de bool ise fonksyonun adını da da "b" ön eki ile tanımlayabiliriz, örneğin: "bOutResult".

değer döndüren bir fonksyon döndürdüğü değeri tanımlamalıdır; ve ismi de döndürdüğü değeri açıklayıcı olmalıdır; bu özellikle de bool fonksyonlarda gerçekten önemlidir; iki örneğe bakalım:

    bool CheckTea(FTea Tea) {...} // ne demek istediği tam anlaşılmıyor
    bool IsTeaFresh(FTea Tea) {...} // isimden oldukça açık, dönüş true ise çay tazedir

Örnekler:

    float TeaWeight;

    int32 TeaCount;

    bool bDoesTeaStink;

    FName TeaName;

    FString TeaFriendlyName;

    UClass* TeaClass;

    USoundCue* TeaSound;

    UTexture* TeaTexture;



Unreal Engine deki Temel C++ tipleri (platformlar arası taşınabilir):
boolean (hiçbir zaman büyüklüğünü saymamıza gerek yoktur.)
TCHAR (karakter tipi içindir; onunda sizeof ile büyüklüğünü hesap etmemize gerek yoktur.)
uint8 (1 byte)
int8 (1 byte)
uint16 (2 bytes)
int16 (2 bytes)
uint32 (4 bytes)
int32 (4 bytes)
uint64 (8 bytes)
int64 (8 bytes)
float (4 bytes)
double (8 bytes)
PTRINT (bir pointer tutabilen integer içindir, hiçbir zaman büyüklüğünü hesap etmemize gere kyoktur)

bu temel Unreal Engine tiplerini platform çeşitliliğini düşünmeden rahatça kullanabiliriz. 




Comments
comment ler kodlarla bizim aramızdaki iletişimdir; bu iletişim de hayati önem taşır. Unreal Engine açıklama satırları için de standartlara sahiptir.


kendi kendini aklayan kod kullanın:
// Kötü:
t = s + l - b;

// İyi:
TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;
faydalı açıklama satırlarını bıllca kullanın:
// Kötü:
// Leaves i arttır
++Leaves;

// İyi:
// başka bir çay yaprağı olduğunu biliyoruz
++Leaves;
Kötü koda yorum yazmayın:
// Kötü:
// yaprakların toplam değeri
// Küçük ve büyük yapraklardan daha az 
// her ikisi de yaprak sayısı
t = s + l - b;

// İyi:
TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;
Kod ile çelişecek açıklamalar yapmayın:
// Kötü:
// Leaves i asla arttırma!
++Leaves;

// İyi:
// başka bir çay yaprağı olduğunu biliyoruz
++Leaves;


Const Doğruluğu
const lar derleyici direktifleri gibi dökümansal nitelik taşırlar; dolayısı ile tüm kodlar sabitler ile uyumlu olmalıdır; örneğin referans tipi alan bir fonksyona const atarsak ve fonksyon onun değerini değiştirmeye kalkışır ise hata oluşur. Bir metodu const olarak tanımamak bu durumu önler.

  void SomeMutatingOperation(FThing& OutResult, const TArray& InArray); // InArray will not be modified by SomeMutatingOperation, but OutResult probably will be

    void FThing::SomeNonMutatingOperation() const
    {
        // This code will not modify the FThing it is invoked on
    }

    TArray<FString> StringArray;
    for (const FString& : StringArray)
    {
        // The body of this loop will not modify StringArray
    }

Const değer (value) fonksyon parametreleri ve lokal değişkenleri tarafından da tercih edilmelidir; bu onuyucuya der ki: bu değişken fonskyon boyunca asla asla değiştirilemez; bu da anlaşılabilirliği artırır; eğer bunu yaparsanız bildirim (declaration) ve tanımlama (definition) nun uymasını sağlama almalısınız:

    void AddSomeThings(const int32 Count);

    void AddSomeThings(const int32 Count)
    {
        const int32 CountPlusOne = Count + 1;

        // Neither Count nor CountPlusOne can be changed during the body of the function
    }

bunun bir istisnası, value (değer) parametrelerini container tiplerinden biri ile aktarmaktır, ancak bu nadir olmalıdır:

  void FBlah::SetMemberArray(TArray<FString> InNewArray)
    {
        MemberArray = MoveTemp(InNewArray);
    }

Pointer tanımlarken const kullanmak, pointerin refere ettiği adrese başka bir adres atanamaz duruma getirir.

    // const pointer e const olmayan objeye atandığında pointer yeni değer alamaz, ama T halen değiştirilebilir durumda
    T* const Ptr = ...;

    // Illegal
    T& const Ref = ...;

yerleşik türler için derleyici uyarıları vereceğinden ve karmaşık tipler içinde const ile taşınmasında engeller olabileceğinden dönüş tiplerinde (return type) asla const kullanmayın. 


bu kural yalnızca dönüş tipinin kendisi için uygulanır; pointer ve referanslar için geçerli değildir; daha iyi anlaşılması için örneklere bakalım:
    // Kötü- bir const dizisi (array) döndürüyor
    const TArray<FString> GetSomeArray();

    // İyi- const dizisinin (array) referansını döndürüyor
    const TArray<FString>& GetSomeArray();

    // İyi- bir const dizisinin pointer ini döndürüyor
    const TArray<FString>* GetSomeArray();

    // Kötü- bir const dizisini işaret eden bir const pointer döndürüyor
    const TArray<FString>* const GetSomeArray();


Bazı Yazım Formatları:
sınıf biçimine gözatalım birazda; 

commentler in aşırı kullanımı kodu çoğaltır; ve kodu değiştirdiğimizde commentleri de değiştirdiğimize emin olmalıyız. 

Fonksyon commentleri tek bir kez olmalıdır; çünkü public metodlara verilen o comment editör ortamında da Blueprint işlemleri esnasında görüntüleniyor; elbette tekrar söyleyelim metod public ise bu oluyor; buradaki comment açıklaması metodun çalışması / çağrılması ile ilgili bilgi içermelidir.

    class IDrinkable
    {
    public:
        /**
         * Called when a player drinks this object.
         * @param OutFocusMultiplier - Upon return, will contain a multiplier to apply to the drinker's focus.
         * @param OutThirstQuenchingFraction - Upon return, will contain the fraction of the drinker's thirst to quench (0-1).
         */
        virtual void Drink(float& OutFocusMultiplier, float& OutThirstQuenchingFraction) = 0;
    };

    class FTea : public IDrinkable
    {
    public:
        /**
         * Calculate a delta-taste value for the tea given the volume and temperature of water used to steep.
         * @param VolumeOfWater - Amount of water used to brew in mL
         * @param TemperatureOfWater - Water temperature in Kelvins
         * @param OutNewPotency - Tea's potency after steeping starts, from 0.97 to 1.04
         * @return the change in intensity of the tea in tea taste units (TTU) per minute
         */
        float Steep(
            const float VolumeOfWater,
            const float TemperatureOfWater,
            float& OutNewPotency
            );

        void Sweeten(const float EquivalentGramsOfSucrose);

        float GetPrice() const
        {
            return Price;
        }

        virtual void Drink(float& OutFocusMultiplier, float& OutThirstQuenchingFraction) override;

    private:

        float Price;

        float Sweetness;
    };

    float FTea::Steep(const float VolumeOfWater, const float TemperatureOfWater, float& OutNewPotency)
    {
        ...
    }

    void FTea::Sweeten(const float EquivalentGramsOfSucrose)
    {
        ...
    }

    void FTea::Drink(float& OutFocusMultiplier, float& OutThirstQuenchingFraction)
    {
        ...
    }

peki sınıf (class) commenti ne içerir?
sınıfın çözdüğü problemi açıklayan bir commenttir; bu sınıf neden yaratıldı ?


tüm o method parçalarını commenti ne anlam ifade eder ?
öncelikle fonksyonun amacı, çözdüğü problem,

fonksyona gelen formal parametreleri ve dönüş parametresi comment lenebilir.

ardından parametre comment leri gelir; her bir parametre commenti ölçü birimini dahil etmelidir, yani beklenen değerlerin aralığını, imkansız değerler, ve status / error kodlarının anlamları vs..

ardından return commenti gelir; o bir çıkış değeri olarak beklenen return değerini dökümante eder.



evet bu kadar,
aslında C++ 11 deki standartlardan da bahsedecektim ama bu makale çok uzadı; 
ve daha açıklayıcı olması için de bi çok defalar güncelleyip üzerinden geçmem gerekecek;
evet bir yayını paylaştıktan sonra yaklaşık bir ay boyunca onu güncellemeye devam ediyorum; 
o yüzden şimdilik burada duralım, önce buraya kadar yazdıklarımızı bir sindirelim;


yine konuşacağız :D

1 yorum:

Kendime Göre dedi ki...

Açıklayıcı yazınız için çok teşekkür ederim.
ue4 öğrenirken kullanılan terimlerin çoğu havada kalmıştı. (UPROPERTY,AACTOR, GENERATED_BODY VS). İhtiyaç oldukça okumam gerekecek, tekrardan paylaşımınız için teşekkürler.