Gökhan ZER - Unreal Engine C++

19 Temmuz 2017 Çarşamba

sekizinci mini uygulama - timers (zamanlayıcılar) ve Olaylar (Events)

tekrar Merhabalar,

bu yazıda değişken ve fonksyonların editörde nasıl ortaya çıkarılacağını,  kod çalışmalarını ertelemek / geciktirmek için timers ları kullanmayı, ve de Actor ler arası iletişim için event leri kullanmayı öğreneceğiz.


1. Timer kullanan bir Actor yaratalım:
yeni bir basit code projesi yaratarak başlayalım, starter content seçili olsun ve adı da HowTo_VTE olsun, ve hemen bir Actor class projeye ekleyelim, Actor e de Countdown adını verelim:





oyunda görebileceğimiz basit bir countdown timer yaratarak başlayacağız; 


Countdown.h içinde, class definition ın en sonuna aşağıdaki satırları ekleyelim:

int32 CountdownTime;

UTextRenderComponent* CountdownText;

void UpdateTimerDisplay();

Countdown.cpp
içinde render edilebilir bir Text yaratmak için ilgili component i kullanacağız, ve sayacı / gerisayımı (countdown) 3 saniye olarak ayarlayacağız. Ayrıca ona ihtiyacımız olmadığında Actor ün Tick lemesini off duruma getirebiliriz (durdurabiliriz).

ACountdown::ACountdown artık böyle görünüyor:
PrimaryActorTick.bCanEverTick = false;

CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
CountdownText->SetHorizontalAlignment(EHTA_Center);
CountdownText->SetWorldSize(150.0f);
RootComponent = CountdownText;

CountdownTime = 3;
ACountdown::UpdateTimerDisplay görüntülenen text i ne kadar zaman kaldığını gösterecek şekilde güncellemeli (update). bu kd ACountdown actor ümüz oyunda ilk spawn olduğunda çalışmalı ve CountdownTime sayacı sıfır olana dek her saniye bu güncellemeyi yapmalı.
void ACountdown::UpdateTimerDisplay()
{
    CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
}
her ne zaman ki biz bir fonksyonu koşturması için bir Timer atadığımızda bize bir Timer Handle verildi. sayaç bittiğinde Timer i kapamalıyız dolayısı ile handle i tutmamız gerekir (?)

şimdi sayacı azaltara ksayacak fonklsyonu ekleyelim, ve kontrol etmemiz gereken Timer Handle ini de ekleyelim, bunu da Countdown.h içinde sınıf tanımının (class definition) içine yapalım; hazır orada iken, countdown bitiminde bazı özel işleri yapacak olan fonksyonu da ekleyelim:
void AdvanceTimer();

void CountdownHasFinished();

FTimerHandle CountdownTimerHandle;
şimdi Countdown.cpp içinde ACountdown::AdvanceTimer ve de ACountdown::CountdownHasFinished fonksyonlarını da ekleyelim:
void ACountdown::AdvanceTimer()
{
    --CountdownTime;
    UpdateTimerDisplay();
    if (CountdownTime < 1)
    {
        //We're done counting down, so stop running the timer.
        GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
        CountdownHasFinished();
    }
}

void ACountdown::CountdownHasFinished()
{
    //Change to a special readout
    CountdownText->SetText(TEXT("GO!"));
}
ACountdown::BeginPlay içine metin gösterecek kodu başlatalım, bunu yeni update fonksyonumuz ile yapacağız, ve timer i ilerlemesi için ayarlayalım, ve countdown ı her bir seniyede update edelim:
UpdateTimerDisplay();
GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
ACountdown::ACountdown yerine ACountdown::BeginPlay display i update ediyoruz çünkü değerleri yapıcı fonksyonda atandıktan sonra değişkenleri Unreal Editor içinde set ediyor, ancak BeginPlay den önce...

CountdownTime editör de ortaya çıktığında bu value değerleri önem arz edecek.

şimdi Unreal Editor den Compile diyerek projeyi derleyin.



ardından Content Browser içinde ACountdown objesi güncellenmiş olmalıdır; onu sürükleyerek Level Editor içine bırakalım.





ACountdown::BeginPlay içinde countdown text imizi set ettiğimiz için ve bunu ACountdown::ACountdown da yapmadığımız için editörde default text görünüyor. Play e bastığımızda countdown beklendiği gibi üç den geriye sayan progres i başlatacaktır.

bu noktada, timer kullanan küçük basit bi class yarattık. programcı olmayan kullanıcılar da bu countdown süresini değiştirebilir, veya süre bittiğinde yapılacak olan davranışı (behavior) değiştirebilirler. bu özellikleri editörde ortaya çıkaracağız bir sonraki aşamada:

Countdown.h
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#pragma once

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

UCLASS()
class HOWTO_VTE_API ACountdown : public AActor
{
    GENERATED_BODY()

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

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

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

    //How long, in seconds, the countdown will run
    int32 CountdownTime;

    UTextRenderComponent* CountdownText;

    void UpdateTimerDisplay();

    void AdvanceTimer();

    void CountdownHasFinished();

    FTimerHandle CountdownTimerHandle;
};

Countdown.cpp
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "HowTo_VTE.h"
#include "Countdown.h"

// Sets default values
ACountdown::ACountdown()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = false;

    CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
    CountdownText->SetHorizontalAlignment(EHTA_Center);
    CountdownText->SetWorldSize(150.0f);
    RootComponent = CountdownText;

    CountdownTime = 3;
}

// Called when the game starts or when spawned
void ACountdown::BeginPlay()
{
    Super::BeginPlay();

    UpdateTimerDisplay();
    GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
}

// Called every frame
void ACountdown::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );

}

void ACountdown::UpdateTimerDisplay()
{
    CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
}

void ACountdown::AdvanceTimer()
{
    --CountdownTime;
    UpdateTimerDisplay();
    if (CountdownTime < 1)
    {
        // We're done counting down, so stop running the timer.
        GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
        //Perform any special actions we want to do when the timer ends.
        CountdownHasFinished();
    }
}

void ACountdown::CountdownHasFinished()
{
    //Change to a special readout
    CountdownText->SetText(TEXT("GO!"));
}



2. değişken (Variables) ve Function ları Editor de ortaya çıkarmak:
countdown timer imizn şu anda 3 saniye değerini kullanması için kodlandı; ancak editör de istenen değerin atanabileceği şekilde ayarlansa idi daha kullanışlı olurdu; ve bunu yapmanın Visual Studio içinde kolay bir yolu vardır; Countdown.h i açarız ve int32 CountdownTime; in olduğu satırı buluruz; bu tür işler için yani bir değişkeni editörde görünür kılmak için UPROPERTY makrosunu kullanıyoruz; böylece motorun (engine) oyunu çalıştırma veya kaydedilmiş bir level i yüklemesi (loading a saved level) zamanında bir değişkenin değerini koruyabilmesini mümkün kılıyoruz; UPROPERTY nin içi boş parantezler ile parametresiz verilmesi yukarıda sözü edilen etkiyi yapar:
 

UPROPERTY()
int32 CountdownTime;

ama UPROPERTY değişik argümanlar da alabilir bu da motorun bu değişkeni kullanma biçimini değiştirir;

şayet değeri değiştirilebilir bir değişken istiyor isek EditAnywhere argumanını kullanmalıyız.

UPROPERTY(EditAnywhere)
int32 CountdownTime;



C++ ortamında değişkenin üzerine bir de comment ekleyebiliriz ki bu açıklama değişkenin yaptığı iş olarak Unreal Editor ünde de görünebilsin, aşağıda olduğu gibi.

//How long, in seconds, the countdown will run
UPROPERTY(EditAnywhere)
int32 CountdownTime;



UPROPERTY nin pek çok başka argümanları vardır örneğin BlueprintReadWrite ve Category gibi..

şimdi Unreal Editor e dönüp Compile buttonuna basalım, ve değişkenimiz artık Details Panel de görünür durumda olmalıdır; artık buradan değişik değerler verip play a basıp oynayarak deneyebiliriz;

ek olarak timer değerini değiştirmek için programcı olmayanlara da timer in zamanı dolduğunda olacakları değiştirme yeteneği verelim. Visual Studio da Countdown.h i açın ve aşağıdaki satırı bulun:

void CountdownHasFinished();

aynı şekilde fonksyonu da Unreal Engine de görünür kılmak için UFUNCTION u kullanırız:

UFUNCTION()
void CountdownHasFinished();

tıpkı UPROPERTY macro su gibi,, makroya onunla ne yapılacağına dair biraz daha bilgi sağlamamız lazım ki programcı olamayanlar daha fazla özelliğe erişebilsin. 3 opsiyonu göz önünde bulundurun. BlueprintCallable fonksyonları C++ ile yazılır ve Blueprint Graph içinden çağrılabilir fakat onlar tekrar C++ kod editlemesi olmadan değiştirilemez veya override edilemez. bu şekilde işaretlenmiş fonksyonlar programcı olmayanların kullanabileceği özelliklerdir; fakat değiştirilmesi varsayılmaz, veya değiştirilmesi mantıklı olmaz. buna basit bir örnek bir tür math fonksyonu olurdu.

BlueprintImplementableEvent fonksyonular C++ ın heade (.h) dosyası içinde tanımlanır, fakat fonksyon gövdelerinin tamamı Blueprint Graph da yazılır; C++ da değil. bunlar genellikle programcı olmayanlara özel durumlar için özel tanımlar yapabilmelerine olanak tanımak için yaratılır.

BlueprintNativeEvent fonksyonları BlueprintCallable ve BlueprintImplementableEvent in bir combinasyonu gibidir. C++ ile programlanmış default davranışları vardır, fakat Blueprint Graph dan override edilebilir veya değiştirilebilirlier. Bunları programlarken C++ kodu her zaman sonuna _Implementation eklenmesi ile bir virtual fonksyona girer, aşağıda belirtildiği gibi. Bu en esnek çözümdür, ki bir bu makalede bunu kullanacağız:

programcı olmayanlara C++ fonksyonlarımızı çağırma yeteneği vermek ve onları Blueprint ile override edebilmelerini sağlamak için aşağıdaki şekilde header dosyasını (Countdown.h) yazmalıyız:

UFUNCTION(BlueprintNativeEvent)
void CountdownHasFinished();
virtual void CountdownHasFinished_Implementation();
ve de Countdown.cpp in içinde aşağıdaki satırda değişiklik yapmalıyız:
void ACountdown::CountdownHasFinished()
bu satırı şöyle:
void ACountdown::CountdownHasFinished_Implementation()

şimdi bir değişken ve fonksyonu erişilebilir (accessible) hale getirdik, ve C++ daki kendi fonksyonumuz ve default value miz sağlanıyo iken, programcı olmayanlar tarafından da değiştirilebilir oldu. Programcı olmayanların bunu nasıl kullanaibleceğini görmek için, ACountdown sınıfımızın bir Blueprint genişlemesini (extension) yapalım ve onu kendimize göre değiştirelim:
Countdown.h
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#pragma once

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

UCLASS()
class HOWTO_VTE_API ACountdown : public AActor
{
    GENERATED_BODY()

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

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

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

    //How long, in seconds, the countdown will run
    UPROPERTY(EditAnywhere)
    int32 CountdownTime;

    UTextRenderComponent* CountdownText;

    void UpdateTimerDisplay();

    void AdvanceTimer();

    UFUNCTION(BlueprintNativeEvent)
    void CountdownHasFinished();
    virtual void CountdownHasFinished_Implementation();

    FTimerHandle CountdownTimerHandle;
};

Countdown.cpp

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "HowTo_VTE.h"
#include "Countdown.h"

// Sets default values
ACountdown::ACountdown()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = false;

    CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
    CountdownText->SetHorizontalAlignment(EHTA_Center);
    CountdownText->SetWorldSize(150.0f);
    RootComponent = CountdownText;

    CountdownTime = 3;
}

// Called when the game starts or when spawned
void ACountdown::BeginPlay()
{
    Super::BeginPlay();

    UpdateTimerDisplay();
    GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
}

// Called every frame
void ACountdown::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );

}

void ACountdown::UpdateTimerDisplay()
{
    CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
}

void ACountdown::AdvanceTimer()
{
    --CountdownTime;
    UpdateTimerDisplay();
    if (CountdownTime < 1)
    {
        // We're done counting down, so stop running the timer.
        GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
        //Perform any special actions we want to do when the timer ends.
        CountdownHasFinished();
    }
}

void ACountdown::CountdownHasFinished_Implementation()
{
    //Change to a special readout
    CountdownText->SetText(TEXT("GO!"));
}



3. C++ ı Blueprints ile override etme ve genişletme:
ACountdown instance inin davranışını değiştirmek için, editörden Countdown1 çağrılıyor, önce onun editleneiblir bir Blueprint versiyonunu yaratmalıyız. bunu yapmak için World Outliner dan önce onu seçin, ve sonra Details Panel içinden Blueprint/Add Script buttonuna tıklayın.



buradan değiştirilmiş ACountdown sınıfını içerecek Blueprint asset i için bir path, veya isim sağlayabiliriniz.



bu, Countdown1 in Blueprint versiyonunu temsil edecek yeni asset i yaratacak. aynı zamanda Countdown1 i bu yeni Blueprint instance i ile değiştirecek (replace edecek), Blueprint de yaptığımız değişiklik oyun içinde Countdown1 i etkileyecek.

Unreal Editor otomatik olarak bizi Content Browser daki yeni asset e götürecek, ve ona sağ tıklayıp Edit diyerek onu kendi Blueprint graph ının içinde, component hiyerarşisini ve default valuelerini değiştirebiliriz.






fonksyonlar ve event ler Event Graph tabı içinde bulunabilir, ve böylece onları ilk önce seçebiliriz.



ardından Event Graph penceresinde herhangi bir yere sağ tıklayın, burada 
CountdownHasFinished fonksyonumuzu bir davranışını tanımlayacak event nodu olarak ekleyebiliriz



şimdi sol clikleyerek ve beyaz pin i sağ tarafa sürükleyerek istediğimiz her hangi ek fonksyonalite ekleyebiliriz.



mouse i serbest bıraktığımızda hangi olay /event) veya fonksyonaliteyi eklemek istediğimiz bize sorulacaktır. Biz bu yazıda 
countdown bitiminde bir partice sistem spawn edeceğiz. Lokasyon nodunun olduğu yerde bir Spawn Emitter istiyoruz, öyleyse şimdi onu listeden seçelim; kelime olarak adını aratarak bulmak bize zaman kazandıracaktır.





Blueprint Editor den compile buttonuna basın ve kaydedin. 


Eğer şimdi play buttonuna basarsak countdown ımızın gerçekleştiğiniz göreceğiz, ve countdown sıfırlandığında patlama gerçekleşecek.



fakat biz countdown ımızı 
sonda "GO!" demesi için programladık, sıfıncı saniyede değil. Bu artık devam etmez çünkü C++ fonksyonalitemizi Blueprint visual scripti ile tamamen değiştirdik. Bu vakada bu yapmak istediğimiz şey değil. Dolayısı ile bu fonksyonun  C++ a çağrı yapan versiyonunu eklemeliyiz.  Countdown noduna a sağ tıkalrız ve "Add call to parent function" ı seçeriz:



bittiğinde  "Countdown Has Finished"  olarak etiketlenmiş node, Event Graph da konumlanmış olacaktır. Parent noda tipik bağlantı konumu doğrudan event noddan dır. Burada olacak yapılacak şeyler kodlanır. Bu gerekli olmasada, parent-call node diğer herhangi başka bir node gibi istediğimiz herhangi bir yerden çağrılabilir, hatta bir çok kez dahi.



Spawn Emitter At Location a bağlantının değişeceğinin farkında olun, yani Parent: Countdown Has Finished nodumuzun sağ tarafına (çıkışına) execution pin ine onu bağlamamız gerek veya çalışmaz.



şimdi oyunu çalıştırdığımızda, GO! kelimesini ve patlamayı göreceğiz, bu C++ kodumuzdandır ve patlama da BP graph dandır. (sayaç countdown bittikten sonra).



Countdown.h

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#pragma once

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

UCLASS()
class HOWTO_VTE_API ACountdown : public AActor
{
    GENERATED_BODY()

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

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

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

    //How long, in seconds, the countdown will run
    UPROPERTY(EditAnywhere)
    int32 CountdownTime;

    UTextRenderComponent* CountdownText;

    void UpdateTimerDisplay();

    void AdvanceTimer();

    UFUNCTION(BlueprintNativeEvent)
    void CountdownHasFinished();
    virtual void CountdownHasFinished_Implementation();

    FTimerHandle CountdownTimerHandle;
};

Countdown.cpp
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "HowTo_VTE.h"
#include "Countdown.h"

// Sets default values
ACountdown::ACountdown()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = false;

    CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
    CountdownText->SetHorizontalAlignment(EHTA_Center);
    CountdownText->SetWorldSize(150.0f);
    RootComponent = CountdownText;

    CountdownTime = 3;
}

// Called when the game starts or when spawned
void ACountdown::BeginPlay()
{
    Super::BeginPlay();

    UpdateTimerDisplay();
    GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
}

// Called every frame
void ACountdown::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );

}

void ACountdown::UpdateTimerDisplay()
{
    CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
}

void ACountdown::AdvanceTimer()
{
    --CountdownTime;
    UpdateTimerDisplay();
    if (CountdownTime < 1)
    {
        // We're done counting down, so stop running the timer.
        GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
        //Perform any special actions we want to do when the timer ends.
        CountdownHasFinished();
    }
}

void ACountdown::CountdownHasFinished_Implementation()
{
    //Change to a special readout
    CountdownText->SetText(TEXT("GO!"));
}



4. ev ödevi:

  • öğrendiklerimiz ile aşağıdakileri yapın:
  • bir olay (event) ile hedef transforma doğru hareket eden veya dönen bir actor yartın.
  • bu oyun içinde hareketli bir platform veya bir kapı gibi kullanılabilir. 
  • Aktörü orijinal yerine geri getiren ikinci bir Olayı tetikleyecek bir Zamanlayıcı başlatın.
  • UPROPERTY kullanarak değişkenleri editörde expose edin.
  • timer handle ve bir kaç özel Event kullanarak açılır kapanır meşale yapın; bunu bir ateş benzeri PArticle System Componentini active deactive yaparak gerçekleştire bilirsiniz.

bunlar gibi pek çok atraksyon Tick kullanmadan timer ile yapılabilir.



evet bu konuya da tekrar geri dönüp üzerinden geçeceğiz çünkü önemli bi konu; o güne değin şimdilik hoşçakalın.