PyGame ile 2D RPG Oyunu Yapalım (2. Bölüm)
Geçen bölümde proje klasörünü düzeledik, modülleri oluşturduk ve ana oyun sınıfını hazırladık. Bu bölümde bir oyuncu oluşturup klavye ile yönlendirilmesini sağlayacağız.
Grafikler
Kaplama (texture) olmayan resimlere sprite deniyor. Birden fazla sprite'ın tek dosyada olmasına da spritesheet. Oyuncunun dört tarafa da dönmüş halinin olduğu bir spritesheet'e ihtiyacımız var. Sprite Generator adresinden kolayca karakter tasarlayabilir ya da OpenGameArt'tan beğendiğiniz bir tanesini kullanabilirsiniz.

Harici dosyaların yönetimi
Oyun içinde kullanacağımız dosyalara merkezi bir yerden ulaşmak ve performansı artırmak için bir sınıf yazacağız; ResourceManager. Bu sayede hem daha okunabilir kod olacak hem de tüm dosyalar bir kere yüklenip kullanılacağı için daha hızlı olacak.
Şimdi proje klasöründeki core / managers / resourcemanager.py içine gelip aşağıdaki sınıfı oluşturun. Şimdilik sadece bir tane dosya var. İleride lazım oldukça ekleyeceğiz.
import os.path
import pygame
class ResourceManager(object):
path = os.path.abspath(__file__ + '/../../../data')
sprites = {
'player': pygame.image.load('{}{}'.format(path, '/sprites/player.png'))
}
Önce abspath fonksiyonu yardımı ile dosya konumundan üç klasör üste çıkıp, o dizindeki data klasörünün adresini aldık. Ardından sprites sözlüğü içindeki player elemanına data / sprites içindeki player.png dosyasının (az önce indirdiğiniz spritesheet) adresini verdik. Yani proje-dizini / data / sprites / player.png adresini.
İlerleyen bölümlerde bu sınıf içinde tiles, maps gibi sözlükler de oluşturacağız. Şimdilik bu kadarı yeterli.
Varlıklar
Oyun içindeki tüm varlıklar (oyuncular, düşmanlar, binalar, bitkiler, aletler vs.) için base (taban, temel) sınıf oluşturalım. Bu sınıfın görevi şöyle olacak;
Nesnenin koordinat ve boyut bilgilerini al. Animasyon için verilen spritesheet dosyasını al. Varsa nesneye ait aksiyonları al. (örneğin hareket etme) Belirlenen hıza göre spritesheet içinden gerekli bölümü kes. Hazırlanan animasyon karesini (frame) ekrana bas.
Şimdi core / __init__.py içinde aşağıdaki sınıfı oluşturun.
import time
from pygame import Rect
class Entity(object):
def __init__(self, sheet, rect, actions=None):
self.sheet = sheet
self.rect = rect
if actions:
self.actions = actions
offset = (0, 0)
fps = 0.1
updated_at = time.time()
animating = False
def update(self):
self.animate()
def animate(self):
if time.time() - self.updated_at > self.fps and self.animating:
x, y = self.offset
if x < 3:
x += 1
else:
x = 0
self.offset = (x, y)
self.updated_at = time.time()
@property
def image(self):
x, y = self.offset
width, height = self.size
self.sheet.set_clip(
Rect(
x * width,
y * height,
width, height
)
)
return self.sheet.subsurface(self.sheet.get_clip())
@property
def size(self):
return self.rect.width, self.rect.height
@property
def location(self):
return self.rect.left, self.rect.top
@location.setter
def location(self, value):
x, y = value
self.rect.left = x
self.rect.top = y
def render(self, surface):
surface.blit(self.image, self.rect)
Sınıf içindeki offset demeti spritesheet içinden alınması gereken bölümü (satır, sütun) olarak tutuyor. Varsayılan olarak (0, 0) yani sol üstten ilk kare. Sınıfın image özelliğinde satır ve sütun numaraları genişlik ve yükseklik değerleriyle çarpılıp kesilmeye başlanması gereken pikseller bulunuyor.
Rect nesnesi içinde demet halinde, x, y, genişlik ve yükseklik bilgilerini tutuyor. Sınıfın size özelliği genişlik ve yüksekliği, location özelliği x ve y değerlerini veriyor.
Animasyon hızını ayarlamak için fps, durdurmak ya da devam ettirmek için animating, son değişiklik yapılan zamanı tutmak için updated_at kullanılıyor.
Gösterilecek bölümü (x, y) belirlemek için animate fonksiyonunda şimdinin zamanından son değişiklik zamanı çıkarılıyor. Eğer sonuç fps değerinden büyüksek ve nesne animasyon (animating) halinde ise offset değerini yatayda x += 1 artırıyor. Eğer yatay offset 3'ten büyükse (ki bu resmin dışında demektir) tekrar sıfıra eşitliyor. Bu şekilde bir animasyon döngüsü elde ediliyor. Bu üç rakamı sabit değil tabi kullanılan spritesheet'teki frame sayısına göre değişir. Şimdilik 3 işimizi görüyor, ileride üzerinde oynama yaparız.
Sınıfın image özelliğinde spritesheet içinden kesilmesi gereken bölüm offset ve size özelliklerinin çarpılmasıyla bulunduktan sonra set_clip fonksiyonu ile işaretleniyor. Ardından get_clip ile kesip alınan bölüm subsurface ile spritesheet içinden çıkartırıp return ile döndürülüyor.
Render fonksiyonu parametre olarak aldığı surface içine (Game sınıfındaki self.screen) nesne resmini (self.image) koordinat ve boyut bilgilerini (self.rect) kullanarak çiziyor.
Karakterler
Oyuncu, NPC ya da hikaye kahramanları için base (taban) sınıf oluşturalım. Bu sınıf Entity nesnesinin tüm özelliklerini miras alacak.
Şimdi core / characters / __init__.py içinde aşağıdaki sınıfı oluşturun.
from .. import Entity
class Character(Entity):
speed = 4
def __init__(self, actions, sheet, rect):
super(Character, self).__init__(
actions=actions,
sheet=sheet,
rect=rect
)
def do(self, keys):
self.animating = False
for key in self.actions.keys():
if keys.get(key, False):
params = self.actions.get(key)
params.get('func')(
params.get('args')
)
x, y = self.offset
y = params.get('offset', y)
self.offset = (x, y)
self.animating = True
Karakterin hareket hızını belirlemek için speed değişkenini kullanacağız. Yapıcı fonksiyonda (__init__) gelen parametreleri super fonksiyonu ile Entity sınıfına yollamaktan başka birşey yapmadık.
Nesneye ait aksiyonları do fonksiyonu işleyecek. Önce animasyonu durduruyor, sonra actions sözlüğündeki tüm anahtarları tek tek geziyor ve eğer keys sözlüğü içindeki değeri True ise sözlük içindeki fonksiyona, yine sözlük içindeki args parametrelerini gönderiyor.
Ardından sözlükteki offset anahtarını alıp kendi offset değerinin dikey pozisyonu (y) ile değiştiriyor. Ardından animasyona devam ettiriyor.
Bu kısmın biraz havada kaldığının farkındayım. Amacı, her karaktere birbirinden farklı özellikler verebilmek. Az sonra oyuncu sınıfını yazarken anlayacaksınızdır.
Oyuncu
Buraya kadar biraz sıkılmış olabilirsiniz. Ama bir sonuca ulaşmaya çok az kaldı. Şimdi kullanıcının yönlendireceği karakteri oluşturacağız, core / characters / player.py içinde aşağıdaki sınıfı oluşturun.
import pygame
from . import Character
from ..managers.resourcemanager import ResourceManager
class Player(Character):
speed = 4
def __init__(self):
self.rect = pygame.Rect(0, 0, 32, 48)
actions = {
'273': {'func': self.move, 'args': (0, -self.speed), 'offset': 3}, # K_UP
'275': {'func': self.move, 'args': (self.speed, 0), 'offset': 2}, # K_RIGHT
'274': {'func': self.move, 'args': (0, self.speed), 'offset': 0}, # K_DOWN
'276': {'func': self.move, 'args': (-self.speed, 0), 'offset': 1}, # K_LEFT
}
super(Player, self).__init__(
actions=actions,
sheet=ResourceManager.sprites.get('player'),
rect=self.rect
)
def move(self, direction):
x, y = self.location
horizontaly, vertically = direction
x += horizontaly
y += vertically
self.location = (x, y)
Önce self.rect ile oyuncunun konumunu (0, 0) ve boyutlarını (32 x 48) belirledik. Ardından az özce oluşturduğumuz Character sınıfının kullanması için actions sözlüğünü oluşturduk.
Sözlük içindeki 273, 275, 274, 276 anahtarları sırasıyla yukarı, sağ, aşağı ve sol ok tuşlarını ifade ediyor. Her anahtarın içinde birer tane daha sözlük bulunuyor. Bunlara iç-sözlük diyelim.
İç-sözlüğün 'func' anahtarı aksiyon için gerekli olan fonksiyonu gösteriyor, 'args' ise bu fonksiyonun parametrelerini. X ve y değerlerini speed kadar artırıp azaltarak oyuncunun hareket etmesini sağladık. Son olarak 'offset' sprite içindeki dikey konumu ifade ediyor.
Daha önce de olduğu gibi alınan parametreleri super fonksiyonuyla ebeveyn sınıfa (Character) gönderdik. Character sınıfından farklı olarak bu sefer sheet değerini parametre olarak almayıp ResourceManager içinden çağırdık.
Yani aldığımız dosyanın yolculuğu şöyle olacak; ResourceManager > Player > Character > Entity.
Son olarak actions sözlüğündeki offset verisini işlemek üzere move fonksiyonunu tanımladık. Burada basitçe oyuncuyu verilen yöne doğru hareket ettiriyoruz.
Oyuncular Sahneye
Şimdi, main.py içinde Player sınıfını import edin.
from core.characters.player import Player
Ardından Game sınıfı içinde yeni bir Player örneği oluşturun.
class Game:
def __init__(self, display, flags, depth):
pygame.init()
pygame.display.set_caption("2D RPG")
self.screen = pygame.display.set_mode(display, flags, depth)
self.running = True
clock = pygame.time.Clock()
fps = 30
player = Player()
...
Son olarak render fonksiyonu içinde oyuncunun render'ını çağırın.
...
def render(self):
self.screen.fill((255, 255, 255))
self.player.render(self.screen)
pygame.display.update()
...
İşte bu kadar! Şimdi python main.py ile oyunu başlatın ve karakteri ok tuşları ile yönlendirmeyi deneyin.

Kolay gelsin!