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

12 Mayıs 2017 Cuma

ikinci minik ugulama (kamera ViewTarget değiştirme)

selam arkadaşlar,

bu yazımızda bir kamerayı aktive etmeyi, ve onu bir başkası ile değiştirme üzerine konuşacağız, ve C++ ile basit ama kontrolü kısmen elimizde olan kamera (game-controlled camera) yapıcaz,

1- önce yeni bir C++ templatinden bir proje yaratalım, projeye istediğiniz ismi verin hiç farketmez, 

2- şimdi level imize extra kamera(lar) yerleştiricez, biz örneğimizde extra iki kamera daha ekliycez, 

bir level e iki farklı şekilde kamera ekleyebiliriz; birincisi klasik bildiğiniz Camera Actor dir, ve sol taraftaki Modes penceresinin All Classes kısmından Camera Actor ü level in içine sürükleyip bırakın, 

bittiğinde Level içinde, yani world ortamında kamera actor ikonu görünecektir:



ikinci kamera da ise durum biraz daha farklı olsun: 

component ler konusundan hatırlayalım ki, her hangi bir actor e ilave özellikler katmak için onlara değişik niteliklere sahip componentleri ataç ediyorduk; ve hatta bir Actor e bir bakış (View) özelliği katmak için ona bir Camera Component ekliyorduk (attach); akabinde o kamera bileşeni de sürekli olarak eklendiği komponente doğru bakıyordu, sürekli olarak onu görüntülüyordu; ve burada bir parent-child ilşikisi olduğundan artık onunla birlikte hareket de ediyordu, yani pozisyonu da ona bağlıydı. 

Tüm ataçlamalarda bir parent-child ilişkisi söz konusu olduğunu hatırlayın; dolayısı ile biz bir Actor e bir camera copmponent atach edersek actorun lokasyonuna bağımlı olacak ve sürekli de actor ü görüntüleyecektir (ViewTarget).

Önce sahneye herhangi bir Actor niyetine bir küp ekleyelim, (yahutta level inizde seçtiğiniz şablon gereği hazır küpler varsa herhangi birini de kullanabilirsiniz)  Modes tabı içindeki Basic kısmına geçelim, ve bir Cube i level penceresi içine sürükleyin,

küpü level imize eklediğimikten sonra, küp seçili iken details panel den +Add Component diyerek CameraComponent deyin; 


CameraComponent  default olarak sürekli attach edildiği Actöre bakmak isteyecektir :) bunu değiştirip istediğimiz bakış açısını ve location (konum) ve de rotation (açı) durumunu verelim:



şimdi ne yaptık bi konuşalım,

level imize iki kamera ekledik ama aslında toplamda üç kamera var; bir kamera da nesnesinin içinde ona ataçli olan onu kendi gözlerinden (veyahutta tepeden takip eden) camera component i;  iki de biz ekledik etti üç. 

buraya kadar yaptıklarımızın malumunuz level e hiçbir etkisi yoktur :)

ancak şimdi C++ ile kod ekleyeceğiz ve eklediklerimizi C++ kodu kullanacak; level deki oyuncuya (player) ait olan kamerayı eklediğimiz kameralara yönlendirecek, ve hatta bunu yaparken yumuşak kamera geçişleri olacak. Bunun ne işimize yarayabileceğini daha sonra konuşuruz, devam edelim;


3- şimdi yeni bir C++ sınıfı yaratıyoruz; bu sınıfın Actor tipinde iki adet bağımsız değişkeni (üyesi) (variable) olacak, ki bu değişkenlere sonradan istediğimiz Actor ü atayabileceğiz; ve ürettiğimiz sınıf da zaten kendisine atanana bu iki Actor değişkeni üzerinde işlemler yapacak.

C++ kodunda yapılacak yapılacak işlem kameralar arası geçişler olması demiştik; C++ sınıfındaki değişkenlere de editörde az önce eklediğiniz camera Actor ler atanacak; ve sınıfımız belli zaman aralıkları ile o an sahadaki aktif oyuncunun bakışını (ViewTarget) rehin alarak bakışı iki değişkeninde belirtilmiş olan Camera Actor lere yönlendirecek;  amma konuştum...

işe koyulalım,

şimdi Actor sınıfından miras alarak yeni bir  C++ class ı yaratalım ve dökümandaki gibi adına da CameraDirector diyelim; siz isterseniz farklı isim de verebilirsiniz, hiç fark etmez; ama malumunuz yaptığınız şeyi anımsatacak bir isme sahip olmasında çok fayda var; işlevi ne ise ismi de onu çağrıştırmalı, yoksa tam tersi mi...  :)



Create Class buttonuna bastıktan sonra Visual Studio açılmış ve verdiğimiz isimde yani örneğimizde CameraDirector.h  header ve ona ait olan .cpp dosyaları oluşturulmuş olmalı;

şimdi ilk olarak header (.h) dosyasında yaratılmış olan ACameraDirector sınıf tanımı içine aşağıdaki kodu ekleyin:

CameraDirector.h
UPROPERTY(EditAnywhere)
AActor* CameraOne;

UPROPERTY(EditAnywhere)
AActor* CameraTwo;

float TimeToNextCameraChange;

burada az önce sözünü ettiğim iki tane şimdilik içleri boş olan Actor tipinde değişken tanımlaması yapıldı; editör ortamından camera actor ler atanacak olan değişkenler bunlar olacak.

En son tanımlanan float tipindeki değişken ise zamanlama döngüsünü sağlamak için kullanacağız; yani kameralar arası geçişlerin kaç saniyede bir olacağını bu değişkenin değeri belirleyecek;

burada dikkatinizi çeken ilk nokta, ilk iki değişkenin başında UPROPERTY varken en alttaki float olarak tanımlanan değişkende olmamasıdır:  neden ? 

hatırlayın UPROPERTY makrosu ile tanımladığımız değişkenlerimize Unreal Editörü için pek çok parametre verebiliyorduk; burada kullandığımız EditAnywhere parametresi ile tanımlanan değişken editör içinde artık görünür hale getirir; sadece görünmekle de kalmaz değeri editör ve Blueprint ortamından da değiştirilebilir olur; böylece bu değişkenlerin içinde set edilen değerler oyunu çalıştırdığımızda ya da level i tekrar yüklediğimizde resetlenmez. Örneğimizde ilk iki değişkenin değerini C++ ortamından değil editör ortamından atayacağız; editörden onlara o an canımızın istediği kameraları vereceğiz; şimdilik bilmiyoruz (aslında biliyoruz ya neyse..)  o yüzden başlarına makro geldi; ancak float tipindeki değişkeninin değerinin editörden değiştirilmesini ve hatta görünmesini istemiyoruz; onu sadece  C++ ortamında belirleyeceğiz ve öyle kalacak; o yüzden başında makro yok.

Bu tür durumları ve hangisinde makro olacağını siz kendiniz belirleyin; önceki yazılarımızda da dediğimiz gibi editör sanatçısı yani level designer in bilmesinin gerekmediği core değişkenleri elbette gizleyin, ama ona vermek istediğiniz dinamizim ve özgürlükler ve en önemlisi de parametrik esnek yapılar için makrolar ile hizmetlerine sunun.

Evet header dosyasında üç değişken tanımladıktan sonra şimdi .cpp dosyasına geçelim;

CameraDirector.cpp içinde, devam eden satırı dosyanın en üstüne ekletin,

#include "Kismet/GameplayStatics.h"

şimdi bu header dosysı nedir ? ne işe yarar ?

içini açıp incelediğimde tamamı static tipinde değişken ve fonksyonlardan oluştuğunu gördüğüm bu
GameplayStatics header dosyası bize bazı yararlı genel amaçlı fonksyonlar sağlıyor. Static olmaları da zaten bu yüzden. Yani belli bir veri tipi ya da özellikle belli bir kavrama odaklanmadan çoklu kodlamalar ile yapacağımız bir çok işlemi sadeleştirip faydalı fonksyonlar mahiyetinde burada biriktirmişler. Buradaki fonksyonlar da aslında yine Engine in diğer nesnelerini kullanıyor; yani bunları kullanmak zorunda değiliz, ama bunları kullanarak bazı işleri daha kolay yapabileceğimizi düşünüyorum;

bu fonksyonlardan biri de bu makalede ihtiyaç duyacağımız: UGameplayStatics::GetPlayerController  dir; adından da anlaşılacağı üzere indisi verilen oyuncunun PlayerController nesnesini döndürür.

şimdi ilk olarak  ACameraDirector::Tick  içine aşağıdaki kodu ekleyin:

ACameraDirector::Tick:
const float TimeBetweenCameraChanges = 2.0f;
const float SmoothBlendTime = 0.75f;
TimeToNextCameraChange -= DeltaTime;

if (TimeToNextCameraChange <= 0.0f)
{
    TimeToNextCameraChange += TimeBetweenCameraChanges;

    // Find the actor that handles control for the local player.
    APlayerController* OurPlayerController = UGameplayStatics::GetPlayerController(this, 0);
    if (OurPlayerController)
    {
        if ((OurPlayerController->GetViewTarget() != CameraOne) && (CameraOne != nullptr))
        {
            // Cut instantly to camera one.
            OurPlayerController->SetViewTarget(CameraOne);
        }
        else if ((OurPlayerController->GetViewTarget() != CameraTwo) && (CameraTwo != nullptr))
        {
            // Blend smoothly to camera two.
            OurPlayerController->SetViewTargetWithBlend(CameraTwo, SmoothBlendTime);
        }
    }
}


şimdi yukarıda ne oldu ne bitti kısaca konuşalım,

kamera belli aralıklarla değişecek demiştik; işin içinde zamanlama olduğu için de buradaki işlemler Tick fonksyonunun içinde olması gerek; Tick, hatırlayın evrensel ortamda geçen zamanı DeltaTime değişkeni ile float cinsinden parametre alıyordu; bu zamanı total süreye ekleyerek geçen zamanı ölçebiliyorduk; ilk kodlarda biz iki saniye boyunca biz azalma yaptık, şayet sıfırlanmış ise kamera değişimini tetikleyecek olan if bloğunun içine soktuk; ve tabiki if bloğuna her girdiğinde iki saniyeyi ölçen zaman sayacını yine resetledik.

if bloğunun içindeki kod ise default player ların bakışında (view) iki kamera arasında her üç saniye geçiş yapmayı sağlayacak demiştik; bunun için önce actif player in player controller i bir nesne olarak elde edildi, sonra sınıfın içindeki iki adet actor değişkeninden (kamera 1 ve 2) önce birine ve sonrada ötekine sıra ile aktarıldı, Tick fonksyonu sürekli çağrıldığı için bu bir döngü şeklinde devam etmesi sağlanmış oldu.

kodumuz şu an derlenmeye hazır, dolayısı ile ister Visual Studio içinden derleyin istyer Unreal Editor içinden compile buttonuna basın.

kodun tam hali bu şekilde olmalı:

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

#pragma once

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

UCLASS()
class HOWTO_AUTOCAMERA_API ACameraDirector : public AActor
{
    GENERATED_BODY()

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

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

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

    UPROPERTY(EditAnywhere)
    AActor* CameraOne;

    UPROPERTY(EditAnywhere)
    AActor* CameraTwo;

    float TimeToNextCameraChange;
};

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

#include "HowTo_AutoCamera.h"
#include "CameraDirector.h"
#include "Kismet/GameplayStatics.h"

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

}

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

}

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

    const float TimeBetweenCameraChanges = 2.0f;
    const float SmoothBlendTime = 0.75f;
    TimeToNextCameraChange -= DeltaTime;
    if (TimeToNextCameraChange <= 0.0f)
    {
        TimeToNextCameraChange += TimeBetweenCameraChanges;

        //Find the actor that handles control for the local player.
        APlayerController* OurPlayerController = UGameplayStatics::GetPlayerController(this, 0);
        if (OurPlayerController)
        {
            if ((OurPlayerController->GetViewTarget() != CameraOne) && (CameraOne != nullptr))
            {
                //Cut instantly to camera one.
                OurPlayerController->SetViewTarget(CameraOne);
            }
            else if ((OurPlayerController->GetViewTarget() != CameraTwo) && (CameraTwo != nullptr))
            {
                //Blend smoothly to camera two.
                OurPlayerController->SetViewTargetWithBlend(CameraTwo, SmoothBlendTime);
            }
        }
    }
}


derlediğimizde programlamış olduğumuz Actor,  CameraDirector ismi ile artık Content Browser in için de görünüyor olmalı. Elbette orada görünüp kullanılmadığı sürece bir işe yaramayacaktır :)

e hadi kullanalım,


4- Content Browser içindeki  CameraDirector ü level editör içine sürükleyip bir kopyasını yaratalım.



şimdi artık actor ün CameraOne ve CameraTwo değişkenlerine (ki onlar da actor tipinde idi) değerler atayabiliriz; bunun için ilk olarak World Outliner penceresinde bizim CameraDirector ü bulalım ve onu Details Panel içinde editleyelim:



içinde None yazan dropdown lara tıklayarak değişkenlerimzie Cube ve daha önce yarattığımız CameraActor atayalım.





şimdi play buttonuna basarsak kamera hareketlerini görebileceğiz:



gördüğünüz gibi kamera sadece hareket emiyor aynı zamanda yumuşak geçişler de yapıyor.


şimdi insanın aklına iki soru geliyor,

1.Soru: ilk actor değişkene küp aktardığımız halde, yani bir küp, Allah ın küpü, bir kamera olmadığı halde nasıl oldu da işe yaradı ??

bu sorunun yanıtı aslında daha önceki bir yazımızda mevcut. "Componentler" isimli yazımızda kamera component leri kısmında demişiz ki :
Aslında CameraActor dediğimiz şey CameraComponent e bir de SceneComponent ilave edilip genişletilerek daha gelişmiş camera özellikleri taşıyan ve de sahneye konumlandırılabilir bir Actor yaratılmasından başka birşey değildir. Yani CameraComponent, CameraActor ün içinde zaten bulunduğu gibi, başka herhangi bir Actor ün içinde de olabilir, ve ona (view) bakış özelliğini verebilir.

CameraComponent, ViewTarget i bir CameraActor ise, veya CameraComponent içeren başka bir Actor ün bFindCameraComponentWhenViewTarget özelliğini true olarak set edilmiş ise kamera özellikleri (properties) hakkında bilgiler sağlar.

yani ViewTarget olarak atadığınız şey bir kamera Actor OLMASA (!) bile içinde bir Camera Component aranıyor, ve bulunduğunda da ViewTarget olarak onun View i kullanılıyor.


2.Soru: yarattığımız sanıfın değişkenleri Actor tipinde olduğu halde nasıl oluyor da onlara Camera Actor veya Küpteki gibi bir Static Mesh komponent aktarabildik ? Programatik olarak hata vermesi gerekmez miydi ?

elbette gerekirdi; azıcık programlama bilgisi olan bile elma tipinde bir değişkene armut tipinin aktarılamayacağını bilir; ancak daha ilk konuşmalarımızda ne demiştik ?

Actor sınıfı Unreal Engine deki çoğu sınıfın temel (Base) sınıfı demiştik, çoğu sınıf ondan türetildi demiştik,




dolayısı ile buradaki durum Object Oriented Programingdeki "Poly Morphism" yani çok biçimlilik,, yanii,, bir sınıfın farklı şekillerde davranabilmesi,, yani kılıtım durumunda base sınıftan bir değişkene alt sınıfın değişkenlerin aktarılabilmesi durumudur. Bu işin detayına burada girmek istemiyorum. Ayrı bir makale konusu, sadece nedenini teorik olarak bilirsek bu bi çok konuda önümüzü açacak olduğundan yazmak istedim.  Hemen yukarıdaki resme de bakacak olursanız Camera Component in Actor den türetildiğini kod ortamında görürsünüz.


Son olarak,

normalde çoklu kamera kullanacak isek kameraları böyle tek tek yaratıp tek tek el yordamı ile atamak da çok pratik bir çözüm değil aslında; bunun yanına programatik anlamda daha şık bir çözüm üretebilirdik, mesela, kameraların içine biz dizi (Array) olarak depolandığı bir tekil değişken ile kod çok daha okunaklı ve de verimli hale gelebilirdi. Örneği basit tutmak ve biraz da dökümantasyona bağlı kalmak için bu şekilde yaptık.

içimden güçlü bir ses yine yazıyı sonlandırmam gerektiğini söylüyor :)

şimdi buraya kadar olanları bir sindirelim,

yine konuşacağız  ;)

Hiç yorum yok: