20 Şubat 2012 Pazartesi

Pygame (1)

Bir önceki derste de söylediğim gibi bu derste görsel bir şeyler yapmaya başlayacağız. Bunun için pygame kütüphanesini kullanacağız.

Nedir bu Pygame?
Pygame bir grafik kütüphanesidir. Sakın bunu grafik motoru ile karıştırmayın, aynı şey değil. Pygame de Python gibi ücretsiz, açık kaynak kodlu bir yazılımdır. Temelde yaptığı şey kare, çizgi, yuvarlak gibi basit şekilleri çizmek. Sakın bunu azımsamayın! Pek çoğunuzun ismini sık sık duyduğu Metin 2 adlı mmorpg'nin grafikleri pygame ile yapıldı. Gerçi pygame ile üç boyutlu bir oyun yapmak  biraz abartıya kaçıyor ama siz de hemen hemen her türlü iki boyutlu oyunu rahatlıkla yapabilirsiniz. Daha fazla örnek isterseniz www.pygame.org sitesini inceleyebilirsiniz.

Kurulum
Sitede downloads'a tıklarsanız bir çok pygame sürümü olduğunu göreceksiniz. Siz Python versiyonunuza uygun olanını indirin. Yalnız dikkat edin, 64 bit pygame sürümleri sadece Python 2.7 için mevcut. Eğer bilgisayarınızda Python 3.xx 64 bit kurulu ise onu kaldırıp 32 bit yüklemeniz gerekecek. Doğru sürümü indirdikten sonra kurulum sırasında Python'un kurulu olduğu dosyayı göstermeniz lazım. Genelde otomatik olarak tespit ediliyor ama, onun için çok fazla uğraşmanıza gerek yok.

Her şeyi kurduysanız IDLE'ı açın ve import pygame yazıp enter'a basın. Eğer Python herhangi bir şey yazmazsa her şeyi doğru yapmışsınızdır. Yok eğer bir hata verirse demek ki bir yerde yanlışlık var.

İlk Adımlar
Buraya kadar geldiyseniz başlamaya hazırsınızdır. O halde hemen 800x600 bir ekran yapalım:

import pygame
pygame.init()
pencere = pygame.display.set_mode((800, 600))
pygame.display.update()

Bunları yazarsanız karşınıza siyah bir pencere çıkacak. Burada şunu yaptık: önce pygame modülünü çağırdık, sonra pygame.init() fonksiyonu ile her şeyi varsayılan değerlere ayarladık (bu fonksiyonun tam olarak ne yaptığını bilmiyorum ama mutlaka yazılması lazım). Sonra penceremizi tanımladık. Onu da önce pygame modülünden display adlı bir modülün set_mode() diye bir fonksiyonunu çağırarak. Adından da anlaşıldığı gibi bu fonksiyon ekran ayarını yapıyor. Dikkat ettiyseniz orada çift parantez kullandım. Sebebi basit, set_mode() fonksiyonu birden çok değer alıyor (çözünürlük, derinlik, pencere kenarlarının olup olmaması gibi şeyler, ayrıntılı bilgi için buraya bakabilirsiniz), geriye kalanlar yazılmadığı zaman pygame onları varsayılan olarak ayarlıyor. Eğer başka özellikleri ayarlamak isteseydik pygame.display.set_mode((800, 600), pygame.FULLSCREEN) gibi bir şeyler yazacaktık. Son olarak da update() fonksiyonu var. Aslında update() fonksiyonu argüman olarak kare ya da kareler listesi alır ve her çağrıldığında sadece o kareleri yeniler (örnek: pygame.display.update(kare1, kare2, kare3), ama eğer herhangi bir argüman vermezsek sahnedeki bütün objeleri yeniler. Bu aşamada herhangi bir argüman vermemize gerek yok.

Bu programı çalıştırdığınızda bir sorun fark etmiş olabilirsiniz, sanki çıkan pencere donup da hata veriyormuş gibi. Aslında ortada bir hata yok, sadece pygame'e ekranı çizdikten sonra ne yapması gerektiğini söylemedik, o kadar. En sona pygame.quit() yazarsanız sorun kalmayacaktır. Pencere bir anda çıkıp kapanacak. Bunu engellemek için şimdilik daha önce yaptığımız gibi input() fonksiyonunu kullanabilirsiniz.

Şimdi penceremize biraz renk ekleyelim. Herhangi bir yüzeyi, renk ile doldurmak için yüzey_adı.fill((renk_kodu)) fonksiyonunu kullanacağız. Hemen bir örnek yapalım:


import pygame
pygame.init()
pencere = pygame.display.set_mode((800, 600))
pencere.fill((255, 255, 255))
pygame.display.update()
pygame.quit()

Gördüğünüz gibi penceremizin arka planı artık beyaz. Renk kodu olarak yazdığımız sayılar size bir şey ifade etmiyorsa hemen açıklayayım. RGB kısaltmasını hepiniz duymuşsunuzdur, Red Green Blue'dan gelir. İşte yazdığımız ilk sayı kırmızıya, ikinci sayı yeşile, üçüncüsü de maviye denk gelir. 0 - 255 arasında istediğiniz bir sayı yazabilirsiniz, sayı büyüdükçe renk miktarı da artar.

Şimdi de ekrana bir kare çizdirelim.

import pygame
pygame.init()
pencere = pygame.display.set_mode((800, 600))
kare = pygame.Rect((50, 50), (100, 100))
pencere.fill((255, 255, 255))
pygame.draw.rect(pencere, (0, 255, 0), kare)
pygame.display.update()
pygame.quit()

Önce pygame.Rect((sol_üst_köşenin_koodrinatları), (karenin_boyutları)) fonksiyonu ile bir kare tanımladık. Burada dikkat etmeniz gereken şey koordinat sisteminin sol üst köşeden başlaması. Yani (0, 0) noktası sol üst köşe, y koordinatı aşağıya gittikçe artar, x koordinatı ise sola gittikçe artar. pencere.fill()'dan sonra da pygame.draw.rect(yüzey, (karenin_rengi), karenin_kendisi) fonksiyonu ile de kareyi pencere adlı yüzeye çizdirdik. pygame.draw.rect()'in pencere.fill()'dan sonra olması öncemli, yoksa ekrana önce kare çizilirdi, sonra da bütün ekran beyaza boyanırdı ve biz kareyi görmezdik.

Şimdi gelelim asıl önemli konuya, kareyi nasıl hareket ettireceğiz? Bunun için bir döngü yaratmalıyız ki python kareyi sadece bir kere çizdirip programı sonlandırmasın. Demek ki bir adet while döngüsüne ihtiyacımız var.

import pygame, time
pygame.init()
pencere = pygame.display.set_mode((800, 600))
kare = pygame.Rect((50, 50), (100, 100))
devam = 1
while devam == 1:
    pencere.fill((255, 255, 255))
    kare.right += 1
    pygame.draw.rect(pencere, (0, 255, 0), kare)
    pygame.display.update()
    time.sleep(0.025)

Kareyi hareket ettirmek için karenin sağ kenarının koordinatını her döngüde bir piksel arttırdım. Bunu gördüğünüz gibi kare.right ile yaptım. Aynı şekilde kare.left'i de kullanabilirdim. Ya da aşağı - yukarı hareket ettirmek için kare.top ya da kare.bottom'ı da kullanabilirdim. Fark ettiyseniz bir de time modülünü import edip time.sleep() fonksiyonunu kullandım. Bunu yapmamdaki amaç her döngü sonunda bilgisayarın 0.025 saniye duraklaması. Yoksa bilgisayar hem programı çalıştırabildiği kadar hızlı çalıştırır ve muhtemelen biz kareyi görene kadar pencere dışına çıkar hem de her bilgisayarda program işlemciye bağlı olarak farklı hızlarda çalışır. Yetmiyormuş gibi işlemci de gücünün 100%'ünü sadece bunun için harcar.

Kare hareket ediyor, güzel ama şimdi de yeni bir sorun daha çıktı ortaya: pencere düzgün kapanmıyor. Hemen çaresine bakalım...

import pygame, time, sys
pygame.init()
pencere = pygame.display.set_mode((800, 600))
kare = pygame.Rect((50, 50), (100, 100))
devam = 1
while devam == 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    pencere.fill((255, 255, 255))
    kare.right += 5
    pygame.draw.rect(pencere, (0, 255, 0), kare)
    pygame.display.update()
    time.sleep(0.025)

Önce sys diye bir modül import ettim, döngüye girdikten sonra da
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
diye bir şey ekledim. Artık çarpıya bastığınızda pencere sorunsuz bir şekilde kapanacaktır. IDLE şöyle bir şey diyebilir:
Traceback (most recent call last):
  File "C:/Users/GameOver/Desktop/pencere.py", line 15, in <module>
    sys.exit()
SystemExit
aldırmayın, hata falan yok. Şimdilik de bu yazdıklarımı boş verin, ileride ne olduğunu zaten anlayacaksınız.

Biraz beklerseniz karenin ekranın dışına çıktığını fark edeceksiniz. Bunu hemen önleyelim. Kare ekranın sınırına geldiğinde öteki tarafa gitsin.


import pygame, time, sys
pygame.init()
pencere = pygame.display.set_mode((800, 600))
kare = pygame.Rect((50, 50), (100, 100))
devam = 1
sola_git = True
while devam == 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    pencere.fill((255, 255, 255))
    if kare.right >= 800:
        sola_git = True
    if kare.left <= 0:
        sola_git = False
    if sola_git:
        kare.left-= 5
    else:
        kare.right += 5
    pygame.draw.rect(pencere, (0, 255, 0), kare)
    pygame.display.update()
    time.sleep(0.025)

Şimdi sola_git diye bir değişken tanımladım ve değerini True yaptım. while döngüsünün içine ise birkaç şart ekledim.

if kare.right >= 800:
    sola_git = True

Bunnu anlamı eğer karenin sağ tarafı ekranın genişliğini (yani x = 800'ü geçerse) geçerse sola_git değişkenini True yap.


if kare.left <= 0:
    sola_git = False

Bunun anlamı da eğer karenin solu ekranın soluna gelirse (yani x = 0'ı geçerse) sola_git değişkeni False. Bundan sonraki if ve elif şartları da sola_git değişkeninin True ya da False olmasına göre karenin x koordinatını 5 arttırır ya da 5 azaltır.

Gelelim bu dersin asıl önemli konusuna; kontroller. Hemen kodu yazayım, sonra gerekli açıklamaları yaparım.


import pygame, time, sys
pygame.init()
pencere = pygame.display.set_mode((800, 600))
kare = pygame.Rect((50, 50), (100, 100))
devam = 1
while devam == 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                kare.left -= 1
            if event.key == pygame.K_RIGHT:
                kare.right += 1
            if event.key == pygame.K_UP:
                kare.top -= 1
            if event.key == pygame.K_DOWN:
                kare.bottom += 1
             
    pencere.fill((255, 255, 255))
    pygame.draw.rect(pencere, (0, 255, 0), kare)
    pygame.display.update()
    time.sleep(0.025)

Burada az önceki koda yazdığım sola_git değişkenini ve ona bağlı olan her şeyi sildim, şimdilik ona ihtiyacımız yok. Onun yerine while döngüsünün başındaki for döngüsüne ekleme yaptım. Gördüğünüz gibi çıkış fonksiyonundan hemen sonra if event.type == pygame.KEYDOWN diye bir şey ekledim. Bu "eğer klavyeden bir tuş basılırsa" anlamına gelir. Daha sonra da if event.key == pygame.K_LEFT yazdım, bunun anlamı da "eğer basılan tuş sol olursa"dır. Aynı şeyi sağ, yukarı ve aşağı için yaptım. Klavyedeki bütün tuşlardan bu şekilde girdi alabilirsiniz. Bütün tuşların karşılıklarını buradan bulabilirsiniz.

Tabi yazdığımız bu oyunda karemiz çok yavaş hareket ediyor ve her hareketi için tuşu bırakıp tekrar basmamız gerekiyor. Basılı tutunca hareket etmesini sağlamanın birkaç yolu var. En basiti while döngüsünden önce pygame.key.set_repeat(zaman_aralığı, basılma_sayısı) satırını eklemek. Zaman aralığı dediğim mili saniye  cinsinden geçecek zaman, basılma_sayısı ise o zaman içinde tuşun kaç kere basıldığıdır. Böyle anlatınca anlaşılması biraz zor oluyor ama o satırı kodunuza ekleyip sayılarla biraz oynarsanız hemen anlarsınız. Kısaca karenin basar basmaz durmandan harekete başlaması için pygame.key.set_repeat(1, 1) gibi bir şey yazmanız yeterli olacaktır.

Son olarak da çok önemli bir konu olan çarpışmaya değinmek istiyorum. Başlangıç aşamasında sadece kareleri kullanacağımız için karelerin çarpışmasını anlatacağım sizlere. Pygame'in bunun için çok güzel bir fonksiyonu var: kare1.colliderect(kare2) . Eğer kare1 kare2'ye çarpıyorsa bu fonksiyon True değerini verecektir. Yok eğer çarpmıyorsa False değerini verir. Hemen bir örnek yapalım.

import pygame, time, sys
pygame.init()
pencere = pygame.display.set_mode((800, 600))
kare = pygame.Rect((50, 50), (100, 100))
kare2 = pygame.Rect((300, 50), (100, 100))
devam = 1
pygame.key.set_repeat(1, 1)
while devam == 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                kare.left -= 1
            if event.key == pygame.K_RIGHT:
                kare.right += 1
            if event.key == pygame.K_UP:
                kare.top -= 1
            if event.key == pygame.K_DOWN:
                kare.bottom += 1

    if kare.colliderect(kare2):
        if kare.right < kare2.left + 2 :
            kare.right = kare2.left
        elif kare.left > kare2.right - 2:
            kare.left = kare2.right
        if kare.top > kare2.bottom - 2:
            kare.top = kare2.bottom
        elif kare.bottom < kare2.top + 2:
            kare.bottom = kare2.top
    pencere.fill((255, 255, 255))
    pygame.draw.rect(pencere, (0, 255, 0), kare)
    pygame.draw.rect(pencere, (255, 0, 0), kare2)
    pygame.display.update()
    time.sleep(0.025)

Bu örnekte sadece if kare.colliderect(kare2) şartından sonrasını ekledim. Sanırım çok fazla açıklamaya gerek yok. Eğer kafanız karışırsa bir kağıda orijini sol üst köşe olan bir koordinat sistemi çizin ve yazdığım şartları hayal edin.

Bu ders için bu kadarı yeter sanırım. Sonraki derste basit bir uçak oyunu yapacağız, daha da sonra çeşitli grafikler, resimler, sesler ekleyeceğiz. Kendinize çok iyi bakın.


18 Şubat 2012 Cumartesi

Python'a devam (4)

Herkese merhaba! Biraz uzun bir aradan sonra yeni bir ders yazmaya karar verdim! Hepinizin de gördüğü gibi sonunda çalışan bir oyun yazabildim. Tabi bu oyunu yazarken bir çok şey öğrendim, gaza geldim ve sizlere anlatmaya karar verdim.

Her şeyden önce koyun sayma uygulaması gibi bir şey yapmayacaksanız dokumentasyon şart! Yani tam olarak oyununuzun içeriği ne olacak, nasıl çalışacak, nerede nasıl bir fonksiyon kullanacaksınız gibi aklınıza gelen her şeyi bir kağıda yazacaksınız (Word belgesine de yazabilirsiniz tabi :P). Bunu yapmazsanız eninde sonunda her şey birbirine girecektir. Stairway to Earth'ü bile bu durumdayken geliştirmek çok zor, çünkü kod çok karıştı ve yazarken ileride şöyle bir şey eklemek istersem nasıl eklerim diye düşünmediğimden herhangi bir şeyi eklemek için çok uğraşmak gerekiyor.

İkinci öğrendiğim şey ise kullandığınız dil nesne tabanlı programlamaya (object oriented programming) müsaitse (Python gibi) bundan mutlaka faydalanın! Python bu amaç için class (sınıf) diye bir yapı kullanıyor. İşte bu derste de class'lardan bahsedeceğim.

Önce bir class yaratalım:

class oyuncu():
    tanım = "Bu bir oyuncu"
    ek_bigi = "Oyuncu bir oyunu oynayan kişidir."

Burada yaptığımız şey oyuncu adında bir sınıf yaratmak oldu. Daha önce öğrendiğimiz def'ten çok farklı değil. Şimdi bu sınıfa ait bir obje yaratalım:

oyuncu1 = oyuncu()

Şimdi oyuncu1'i çağırdığımız zaman Python bize <__main__.oyuncu object at 0x02B31650> gibi bir çıktı verecek. Tabi bu aşamada biz bunun ne anlama geldiğini ve bununla ne yapabileceğimizi bilmiyoruz. Boşverin, zaten ben de henüz bilmiyorum, bize asıl lazım olacak şey kendi yazdıklarımız. Her birisine ulaşmak için şöyle bir yol izlemelisiniz: obje_adı.ulaşmak_istediğiniz değer. Yani oyuncu1.tanım yazarsanız "Bu bir oyuncu" çıktısını alırsınız. Aynı şekilde ek_bilgi'ye de ulaşabilirsiniz. Tabi class'ı sadece bir oyuncuyu tanımlamak için kullanmadık. O halde hemen bir oyuncu daha ekleyelim:

oyuncu2 = oyuncu()

Artık iki oyuncumuz var. Ama tahmin edebileceğiniz gibi ikisi de aynı özellikleri taşıyor. İstersek her zaman bu iki değişkeni değiştirebiliriz, mesela oyuncu1.ek_bilgi = "Büyücü" dersek oyuncu1'in ek_bilgi'si artık "Oyuncu bir oyunu oynayan kişidir." yerine "Büyücü" olacak. Peki bunun daha kolay bir yolu yok mu? Bir obje yarattığımız zaman o objenin diğerlerinden farklı olmalarını nasıl sağlarız? Cevabı çok basit:
class oyuncu():
    tanım= "Bu bir oyuncu"
    ek_bilgi = "Oyuncu bir oyunu oynayan kişidir."
    def __init__(self, oyuncu_adı):
        self.adı = oyuuncu_adı

oyuncu1 = oyuncu("Anton")
oyuncu2 = oyuncu("Damla")

Şimdi oyuncu1.adı yazdığınız zaman "Anton", oyuncu2.adı yazdığınız zaman ise "Damla" çıktısını alırsınız. Peki __init__() de neymiş? self'i neden kullandık?

__init__() aynı append gibi bir fonksiyon (aslında bunlar gibi özel fonksiyonlara metot deniyor, önceden söyleseydim daha iyi olurdu tabi). Bu metot class'ın değer alabileceğini söylüyor. İlk yazdığım self ise o değişkenin sadece bir objeye ait olduğunu anlatıyor (self zorunlu bir şey değil bu arada, onun yerine ali de yazabilirsiniz ama genelde self yazılır). tanım ve ek_bilgi'nin başında self olmadığı için bu ikisi oyuncu() sınıfını kullanarak yarattığımız her obje için ortak olacaklardır. Yani oyuncu1.tanım ve oyuncu2.tanım aynı şeyi ifade ederken (tabi sonradan değiştirmediyseniz) oyuncu1.adı ile oyuncu2.adı farklı şeyleri ifade edecek.

Az önce fark ettiğiniz gibi def'i kullandım. Bu demek oluyor ki class'ların içine istediğimiz fonksiyonları tanımlayabiliyoruz. O halde oyuncu()'ya yeni bir şeyler ekleyelim


class oyuncu():
    tanım= "Bu bir oyuncu"
    ek_bilgi = "Oyuncu bir oyunu oynayan kişidir."
    def __init__(self, oyuncu_adı, can):
        self.adı = oyuncu_adı
        self.can = can
    def hasar_aldı(hasar):
        self.can -= hasar
    def can_aldı(can_miktarı):
        self.can += can_miktarı

oyuncu1 = oyuncu("Bulrog", 100)
oyuncu2 = oyuncu("zaLim_BÜyÜcü_24", 10)

devam = 1

while devam:
    girdi = input("Kim vurdu? ")
    if girdi.lower() == "bulrog":
        oyuncu2.hasar_aldı(1)
    elif girdi.lower() == "zalim_büyücü_24":
        oyuncu1.hasar_aldı(150)
    else:
        print("Öyle bir oyuncu yok.")

    if oyuncu1.can <= 2 or oyuncu2.can <= 0:
        devam = 0

Bu şekilde iki oyuncuyu kolaylıkla yönetebiliriz. Belki sadece iki obje varsa buna gerek duymayabilirsiniz ama iki değil de on oyuncu olduğunu düşünün, o zaman hepsi için canlarını, adlarını ve diğer özelliklerini tanımlamak çok uzun sürecektir. Bunun gibi bir çok durumda class'lar hayatınızı kolaylaştıracak.

Evet, artık görsel bir şeyler yapmak için hazırsınız! Bir sonraki derste pygame'den bahsedeceğim, birkaç ders sonra da basit bir uçak ya da araba yarışı oyunu yapacağız. Şimdilik sağlıklı kalın, bu kış aylarında kendinize çok dikkat edin.