Pong – Devlog 3: Oyun Döngüsünü Kurmak

JAG (Just Another Game) serisinin ilk oyunu olan “Pong Oyunu”n gelişim öyküsünü ve kodalama rehberini paylaştığım seridir. Ağır kodlama içerir önden uyarmak istiyorum!

Advertisements

Haftanın Özeti

Oyuna başladığımızdan beri 3. haftayı tamamlamış olduk. İlk hafta projenin planlamasını yaptık ve projeyi başlattık. 2. ve 3. haftada oyunun temel dinamiklerini kaba haliyle tamamladık. Şuanda elimizde;

  • Skorları takip eden, ana menuden gerekli oyun bilgilerini aktaran bir Game Manager,
  • Game Manager’dan bilgileri alarak oyuncu ve oyun alanını başlatan bir Level Manager,
  • Ses ayarlarını oyun kapansa bile tutan bir Settings Manager,
  • Topun nerede olduğunu noktası noktasına takip eden ve hiç yenilmeyen bir Yapay Zeka,
  • Fizik kurallarına göre ilerleyen; duvar, kale ve oyuncularla etkileşime giren bir top ve tüm bunları yöneten bir Ball Manager,
  • Topa dokunduğu yere göre farklı açılar veren bir Player class ve objelerimiz mevcut.

Ayrıca GUI adına ihtiyacımız olan düğme ve alanların %90’nını tamamladık. Peki bunlar ne kadar vaktimizi aldı dersen kaba rakamları aşağıda vermek istiyorum. Bu sayede senin de geliştirme hakkında bir fikrin olabilir:

HaftaDokümantasyon
(Blog, GDD vs)
ToplantıGeliştirme
(Ömer – Tolga)
Toplam
Hafta 18 sa30 dk8.5 sa
Hafta 22.5 sa1 sa2 sa – 30 dk6 sa
Hafta 33 sa2.5 sa1.5 sa – 2.5 sa9.5 sa
Toplam13.5 sa4 sa6.5 sa24 sa
Süreleri yukarıya yuvarlayarak verdim.

Aslında gördüğün üzere vaktimizin çoğu bu postları ve diğer planlama için gerekli olan belgeleri hazırlamaya gitti. Bunun dışında neredeyse geliştirme süremiz kadar toplantıya vakit ayırdık. Bunlar hakkında bir kaç şey demek istiyorum.

Dokümantasyon süresi pek çok projede kişiye angarya olarak gelebilir. Hele iki kişilik bir projede bu kadar vakit ayrılması daha da anlamsız diye düşünebilirsin. Fakat ıskaladığın şey şu olur: İyi bir iletişim, yazılı bir plan olmadan yürüyen projeler sürekli değişiklikler yaşamaya mahkum olur. Bu yüzden iptal edilen oyun projelerin sayısını tahmin bile edemiyorum. Dokümantasyon ile ilgili işin güzel kısmı şu, büyük projelerde de bu süre çok daha fazla artmayacaktır. Ama işin sonunda tüm ekibin takıldığında referans alacağı bir belgenin olmasının avantajlarını ne kadar söylesem az. Bizim projemizde yazdığım bu blog postları da bu sürenin içine giriyor. Ama zaten projenin (ilk postlarda da söylediğim gibi) asıl amacı bu: Meraklılarına tüm süreci olabildiğince açıklamak.

Toplantı süresi bütün kodlarımızı konuştuğumuz, haftalık neler yaptığımızı değerlendirdiğimiz bir alan. Ayrıca her hafta bir konuyu birlikte araştırıyoruz. O yüzden biraz da eğitim tadında geçiyor ki bu beni inanılmaz bir şekilde motive ediyor. Geçen hafta static classları ve program döngüsünde verileri nasıl tutabileceğimizi çalıştık. Bu hafta da Scriptable object ve AssetDatabase’i konuştuk. Ayrıca Unity rb’deki sorunları tartıştık. Bunun ekibe sağladığı öğrenme fırsatını çok değerli buluyorum.

Hadi biraz kodlarımızı tartışalım!

GameManager

Oyunu planlarken GameManager.cs’i ana data sağlayıcı sınıf olarak planladık: Ana Menu ekranı ile Oyun ekranını arasındaki iletişimi kuran sınıf. Mantığı çok basitti;

  • Oyuncu ana ekranda oynamak istediği oyunun özelliklerini belirleyecek,
  • Game Manager bu veriyi “Oyun Ekranı”nda Level Manager’a veriyi aktaracak,
  • Level Manager elindeki verilere göre oyunun materyallerini, oyuncuların özelliklerini ilgili objelere aktarıp oyunu başlatacak,
  • Oyun sonuçları tekrardan GameManager’da toplanacak,
  • Toplanan oyun sonucu liderboard’a aktarılıp kaydedilecek.

Burada bahsettiğim tüm süreçleri static bir sınıf ile yapmanın kolay ve uygun olacağına karar verdik. Önce değişkenlerimizi tanımladık:

public static class GameManager
{
    public static LevelBase level;
    public static PlayerBase leftPlayer;
    public static PlayerBase rightPlayer;
    public static ScoreBase gameScore = new ScoreBase();
    public static bool isLevelFinished = false;
}

Tüm level ile ilgili verileri “LevelBase”te; Oyuncu ile ilgili verileri “PlayerBase”de; oyun skorlarını da “ScoreBase”de tanımladık. Onları GameManager içinde ‘construct’ (inşa) ettik. Ayrıca oyun loop’unun tamamlanıp tamamlanmadığını tespit etmek için “isLevelFinished” değerini ekledik. Bu sayede tüm ana verilerimizi tek bir noktada ve ‘(…base sınıflarıyla) belirlediğimiz standartlarda’ topladık. Static olması sayesinde verilere erişimimiz de kolaylaştı.

Buradaki handikapı anlatmamız gerekiyor. Böyle bir sınıf hafızada yer tutucaktır. Sınıf içinde ne kadar çok veri saklarsa hafızayı da o kadar doldurur. Bizim verilerimiz az sayıda ve küçük olduğu için bu oyunda bir sorun teşkil etmeyecektir. Fakat daha büyük oyunlarda bu ciddi bir sorun oluşturabilir. Temkinli olmakta fayda var.

public static void ResetGameManagerData()
{
     level = null;
     leftPlayer = null;
     rightPlayer = null;
}

public static void ResetScore()
{
    gameScore = new ScoreBase();
}

Değişkenler sonrası oyun verilerine sıfırlama metodlarıya bir başlangıç yaptık. Burada oyun skorunu ayırmamızdaki en temel mantık buradaki verilerin sıfırlamasını, bilgiler liderboarda aktarılmasından sonra yapacağız.

public static void CreateGameManagerData(LevelBase levelData, PlayerBase lPlayerData, PlayerBase rPlayerData)
{
    level = levelData;
    leftPlayer = lPlayerData;
    rightPlayer = rPlayerData;
}

“CreateGameManagerData” metodu Ana Menüde oyuncuların istediği bilgileri alıp kaydetmesi için var.

public static void UpdateScore(int leftScore, int rightScore)
{
    gameScore.leftScore += leftScore;
    gameScore.rightScore += rightScore;
}

Yine top her kaleye temas ettiğinde skoru kaydetmek için “UpdateScore” metodunu ekledik.

public static bool isDataLoaded()
    {
    bool value = false;
    if (level != null && leftPlayer != null && rightPlayer != null)
        value = true;
        return value;
    }

public static PlayerBase AssignRightPlayerDemoData()
{
    PlayerBase value = new PlayerBase("Right Player", PlayerLine.right);
    return value;
}
public static PlayerBase AssignLeftPlayerDemoData()
{
   PlayerBase value = new PlayerBase(PlayerType.aiEasy, PlayerLine.left);
   return value;
}

Oyunda hala ana menunün implemantasyonunu tamamlamadığımız için demo dataları ekledik. Bu son iki metod ana oyun loopu tamamlandığında çıkarılacaktır. Bu metodlarla birlikte bizim static Game Manager sınıfımızı tamamlamış olduk. Kodun tamamını görmek istersen:

public static class GameManager
{
    public static LevelBase level;
    public static PlayerBase leftPlayer;
    public static PlayerBase rightPlayer;
    public static ScoreBase gameScore = new ScoreBase();
    public static bool isLevelFinished = false;

    public static void ResetGameManagerData()
    {
        level = null;
        leftPlayer = null;
        rightPlayer = null;
    }

    public static void ResetScore()
    {
        gameScore = new ScoreBase();
    }

    public static void CreateGameManagerData(LevelBase levelData, PlayerBase lPlayerData, PlayerBase rPlayerData)
    {
        level = levelData;
        leftPlayer = lPlayerData;
        rightPlayer = rPlayerData;
    }

    public static bool isDataLoaded()
    {
        bool value = false;
        if (level != null && leftPlayer != null && rightPlayer != null)
            value = true;

        return value;
    }

    public static PlayerBase AssignRightPlayerDemoData()
    {
        PlayerBase value = new PlayerBase("Right Player", PlayerLine.right);
        return value;
    }
    public static PlayerBase AssignLeftPlayerDemoData()
    {
        PlayerBase value = new PlayerBase(PlayerType.aiEasy, PlayerLine.left);
        return value;
    }

    public static void UpdateScore(int leftScore, int rightScore)
    {
        gameScore.leftScore += leftScore;
        gameScore.rightScore += rightScore;
    }
}

Oyun Objelerinin Tasarımı

Oyun objelerinin tasarımı hazırladığımız Level Manager için çok önemli burada neyi nasıl ve niye yaptığımızı anlatacağım biraz. Çünkü daha sonrasında referans verdiğimizde bize seçeceğimiz yolu bu hiyerarşi belirleyecek. Burada önemli bir uyarı yapmak istiyorum: Bu hiyerarşide en ufak bir değişiklik kullanacağınız kodda hataya neden olacaktır. O yüzden bu hiyerarşiyi referans alan classı az sayıda tutun ve her değişiklikte bu classı gözden geçirin. Şimdi sırasıyla objeleri açıklayalım:

Main Camera:

Oyunun renderlanacağı kameradır. Bu oyunda he sabit bir noktada ve açıda bakan bir kamera olacağı için bizim adımıza kolay bir sınıf. Yine de post-processing effect transition koyarken buraya tekrar odaklanacağız.

Directional Light:

Kamera ile benzer bir şekilde post-processing dışında çok müdahil olmayacağımız bir alan. Burada sadece tüm gölgeleri kapatmayı düşündüğümüzü söyleyip geçiyorum.

Manager_GO (GO: Game Object)

Tüm manager classlarını eklediğim boş bir oyun objesi. Genelde tüm geliştiriciler bütün manager sınıflarını bir yerde toplamayı severler. Neden mi? Kontrol edilmesini kolaylaştırıyor.

Fieldbase_GO

Oyun alanını oluşturan bütün kale, sınır ve alt yüzey burada. Özellikle sınırlar ve kaleler için özel tagler oluştururak onlara erişimimizi kolaylaştırdım. Level material, texture ve physics materialleri burada mevcut.

Ball_GO

Topun olduğu, rigidbody, collider ve BallManager kodlarının olduğu game objesi. Oyunumuzun en kritik objesi diyebilirim. Topun hızı, çarpışmasında elde ettiği bounce gücü hep burada tanımlandı (physics material ile). Bu konuyu bir sonraki blogda tüm detaylarıyla paylaşacağız.

Player_GO

Sol ve sağ oyuncunun olduğu inputları alan ve ileride power-upları da tanımlayacağımız sınıf. Yapay zeka için ayrıca bir class üretmeyip direk olarak “Player.cs” içine dahil ettik. Burada da rigidbody, collider ve physics material var. Oyuncunun topa temas ettiği noktanın merkeze olan uzaklığına göre topa vuruş açısı değişiyor.

Canvas

GUI’mizin baştacı da burada. Hem sağda bulunan düğme hem de ortadaki skor ekranını (power-up alanları da burada) buraya ekledik. Burası için de ileride detaylı bir paylaşım yapacağız.

EventSystem

Her canvas eklendiğinde otomatik olarak gelen zımbırtı. Biz buraya hiç dokunmadık. Anlatacak da çok bir şeyimiz yok açıkçası: Unity kuralları işte.

Bu Devlogu Bitirirken

Bu hafta özellikle topun hareketine odaklandık. Çünkü tam olarak istediğimiz vuruş hissini yakalayamadık. Piyasadaki diğer kod örneklerine baktığımızda da genel olarak diğer geliştiriciler de olayı tamemen fiziğe bırakıp çok müdahale etmiyorlardı. Ama falsolu ve hızlı vuruş eklemek istediğimiz için aradığımız çözümü an itibariyle bulabilmiş değiliz. Umarım bu gelecek haftaya kadar çözüme kavuşur da okuyucu seninle paylaşabilirim. Son olarak burada özellikle level manager’ı ve Sound Manager’ı eklemek istemedim, çünkü sınıfta daha değişiklik yapmayı planlıyoruz. Umarım gelecek haftalarda bunları da paylaşabilecek noktaya geliriz.


Bir sonraki yazımıza kadar muhteşem bir hafta geçirmeni diliyorum.

Advertisements

Published by Abdullah Ömer Şeker

Chasing medicine, games and life it self, he who, thinks frequently, writes sometimes but dreams a lot. Determined to exercise one day so he can still play games when he is 75.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: