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!