paint-brush
Karakterinize Alevler İçinde Koşmayı Öğretmekile@eugene-kleshnin
3,868 okumalar
3,868 okumalar

Karakterinize Alevler İçinde Koşmayı Öğretmek

ile Eugene Kleshnin11m2023/02/28
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

Bu makale dizisi, Flame'i (ve Flutter'ı) öğrenme ve temel bir platform oyunu geliştirme yolculuğumdur. Bunu oldukça ayrıntılı hale getirmeye çalışacağım, bu nedenle Flame'e veya genel olarak oyun geliştirmeye yeni başlayan herkes için faydalı olacaktır. İlk bölümde yeni bir Flame projesi oluşturacağız, tüm varlıkları yükleyeceğiz, oyuncu karakteri ekleyeceğiz ve ona nasıl koşacağını öğreteceğiz.

People Mentioned

Mention Thumbnail
featured image - Karakterinize Alevler İçinde Koşmayı Öğretmek
Eugene Kleshnin HackerNoon profile picture
0-item

Her zaman video oyunları yapmak istemiştim. İlk işimi almama yardımcı olan ilk Android uygulamam, Android görünümleriyle yapılan basit bir oyundu. Bundan sonra, bir oyun motoru kullanarak daha ayrıntılı bir oyun yaratmak için pek çok girişimde bulunuldu, ancak bunların hepsi zaman eksikliği veya çerçevenin karmaşıklığı nedeniyle başarısız oldu. Ancak Flutter'ı temel alan Flame motorunu ilk duyduğumda, basitliği ve platformlar arası desteği ilgimi çekti ve onunla bir oyun geliştirmeyi denemeye karar verdim.


Motoru anlamak için basit ama yine de zorlayıcı bir şeyle başlamak istedim. Bu makale dizisi, Flame'i (ve Flutter'ı) öğrenme ve temel bir platform oyunu geliştirme yolculuğumdur. Bunu oldukça ayrıntılı hale getirmeye çalışacağım, bu nedenle Flame'e veya genel olarak oyun geliştirmeye yeni başlayan herkes için faydalı olacaktır.


4 makale boyunca aşağıdakileri içeren 2 boyutlu bir yan kaydırma oyunu geliştireceğim:

  • Koşabilen ve zıplayabilen bir karakter

  • Oyuncuyu takip eden bir kamera

  • Zemin ve platformlarla birlikte kayan seviye haritası

  • Paralaks arka planı

  • Oyuncunun toplayabileceği paralar ve jeton sayısını gösteren HUD

  • Kazanma ekranı


Oyunu tamamla


İlk bölümde yeni bir Flame projesi oluşturacağız, tüm varlıkları yükleyeceğiz, oyuncu karakteri ekleyeceğiz ve ona nasıl koşacağını öğreteceğiz.


Proje kurulumu

Öncelikle yeni bir proje oluşturalım. Resmi Bare Flame oyun öğreticisi, bunu yapmak için gereken tüm adımları açıklamakta harika bir iş çıkarıyor; bu yüzden onu takip etmeniz yeterli.

Eklemeniz gereken bir şey var: pubspec.yaml dosyasını ayarlarken, kitaplık sürümlerini mevcut en son sürüme güncelleyebilir veya olduğu gibi bırakabilirsiniz; çünkü bir sürümün önündeki düzeltme işareti (^), uygulamanızın en yeni olmayan en son sürümü kullanmasını sağlayacaktır. -kırılma versiyonu. ( düzeltme işareti sözdizimi )

Tüm adımları izlediyseniz main.dart dosyanız şöyle görünmelidir:

 import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; void main() { final game = FlameGame(); runApp(GameWidget(game: game)); }

Varlıklar

Devam etmeden önce oyun için kullanılacak varlıkları hazırlamamız gerekiyor. Varlıklar; resimler, animasyonlar, sesler vb.'dir. Bu serinin amaçları doğrultusunda, yalnızca oyun geliştirmede sprite olarak da adlandırılan görselleri kullanacağız.


Bir platform seviyesi oluşturmanın en basit yolu, döşeme haritalarını ve döşeme spritelarını kullanmaktır. Bu, seviyenin temelde her hücrenin hangi nesneyi / zemini / platformu temsil ettiğini gösterdiği bir ızgara olduğu anlamına gelir. Daha sonra oyun çalışırken, her hücreden gelen bilgiler ilgili kare grafiğiyle eşleştirilir.


Bu teknik kullanılarak oluşturulan oyun grafikleri gerçekten ayrıntılı veya çok basit olabilir. Mesela Super Mario bros'ta pek çok unsurun tekrarlandığını görüyorsunuz. Bunun nedeni, oyun tablosundaki her zemin döşemesi için onu temsil eden yalnızca bir zemin görselinin bulunmasıdır. Biz de aynı yaklaşımı izleyeceğiz ve elimizdeki her statik nesne için tek bir görsel hazırlayacağız.


Seviye tekrarlanan döşemelerden yapılmıştır


Ayrıca oyuncu karakteri ve madeni paralar gibi bazı nesnelerin canlandırılmasını da istiyoruz. Animasyon genellikle her biri tek bir kareyi temsil eden bir dizi hareketsiz görüntü olarak saklanır. Animasyon oynatılırken kareler birbiri ardına hareket ederek nesnenin hareket ettiği yanılsamasını yaratır.


Şimdi en önemli soru varlıkların nereden alınacağıdır. Elbette bunları kendiniz çizebilir veya bir sanatçıya sipariş verebilirsiniz. Ayrıca oyun varlıklarını açık kaynağa katkıda bulunan birçok harika sanatçı var. GrafxKid'in Arcade Platformer Assets paketini kullanacağım.


Tipik olarak görüntü varlıkları iki biçimde gelir: hareketli grafik sayfaları ve tekli hareketli görüntüler. İlki, tüm oyun varlıklarını bir arada içeren büyük bir resimdir. Daha sonra oyun geliştiricileri gerekli hareketli grafiğin tam konumunu belirler ve oyun motoru onu sayfadan keser. Bu oyun için tek hareketli karakterler kullanacağım (animasyonlar hariç, bunları tek bir görüntü olarak tutmak daha kolaydır) çünkü hareketli grafik sayfasında sağlanan tüm varlıklara ihtiyacım yok.



Zemini temsil eden tek bir sprite


Oyuncu animasyonları için 6 sprite içeren model sayfası


Animasyonu çalıştır


İster kendiniz yaratıyor olun ister bir sanatçıdan alıyor olun, oyun motoruna daha uygun hale getirmek için onları dilimlemeniz gerekebilir. Bu amaç için özel olarak oluşturulmuş araçları ( doku paketleyici gibi) veya herhangi bir grafik düzenleyiciyi kullanabilirsiniz. Adobe Photoshop kullandım, çünkü bu hareketli grafik sayfasında hareketli görüntüler arasında eşit olmayan boşluklar var, bu da otomatik araçların görüntüleri çıkarmasını zorlaştırıyordu, bu yüzden bunu manuel olarak yapmak zorunda kaldım.


Ayrıca varlıkların boyutunu da artırmak isteyebilirsiniz, ancak bu bir vektör görüntüsü değilse ortaya çıkan hareketli grafik bulanıklaşabilir. Piksel sanatı için harika sonuç veren bulduğum bir geçici çözüm, Photoshop'ta Nearest Neighbour (hard edges) yeniden boyutlandırma yöntemini kullanmaktır (veya Gimp'te Enterpolasyon Yok olarak ayarlanmıştır). Ancak varlığınız daha ayrıntılıysa muhtemelen işe yaramayacaktır.


Açıklamalara gerek kalmadan benim hazırladığım varlıkları indirin veya kendiniz hazırlayıp projenizin assets/images klasörüne ekleyin.


Yeni varlıklar eklediğinizde, bunları pubspec.yaml dosyasına şu şekilde kaydetmeniz gerekir:

 flutter: assets: - assets/images/

Geleceğe yönelik ipucu: Halihazırda kayıtlı varlıkları güncelliyorsanız değişiklikleri görmek için oyunu yeniden başlatmanız gerekir.


Şimdi varlıkları oyuna yükleyelim. Tüm varlık adlarının tek bir yerde olmasını seviyorum; bu, küçük bir oyun için harika çalışıyor çünkü her şeyi takip etmek ve gerektiğinde değişiklik yapmak daha kolay. Öyleyse lib dizininde yeni bir dosya oluşturalım: assets.dart

 const String THE_BOY = "theboy.png"; const String GROUND = "ground.png"; const String PLATFORM = "platform.png"; const String MIST = "mist.png"; const String CLOUDS = "clouds.png"; const String HILLS = "hills.png"; const String COIN = "coin.png"; const String HUD = "hud.png"; const List<String> SPRITES = [THE_BOY, GROUND, PLATFORM, MIST, CLOUDS, HILLS, COIN, HUD];


Daha sonra gelecekte tüm oyun mantığını içerecek başka bir dosya oluşturun: game.dart

 import 'package:flame/game.dart'; import 'assets.dart' as Assets; class PlatformerGame extends FlameGame { @override Future<void> onLoad() async { await images.loadAll(Assets.SPRITES); } }


PlatformerGame , oyunumuzu temsil eden ana sınıftır ve Flame motorunda kullanılan temel oyun sınıfı olan FlameGame genişletir. Bu da Alev'in temel yapı taşı olan Component genişletir. Görüntüler, arayüz ve efektler de dahil olmak üzere oyununuzdaki her şey Bileşenlerdir. Her Component bileşen başlatıldığında çağrılan bir onLoad zaman uyumsuz yöntemi vardır. Genellikle tüm bileşen kurulum mantığı oraya gider.


Son olarak, varlık sabitlerimizin nereden geldiğini açıkça belirtmek için daha önce oluşturduğumuz ve as Assets eklediğimiz assets.dart dosyamızı içe aktardık. Ve SPRITES listesinde listelenen tüm varlıkları oyun görüntüleri önbelleğine yüklemek için images.loadAll yöntemini kullandı.


Daha sonra main.dart yeni PlatformerGame oluşturmamız gerekiyor. Dosyayı aşağıdaki gibi değiştirin:

 import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; import 'game.dart'; void main() { runApp( const GameWidget<PlatformerGame>.controlled( gameFactory: PlatformerGame.new, ), ); }

Tüm hazırlıklar tamamlanıyor ve işin eğlenceli kısmı başlıyor.


Oyuncu karakteri ekleme

Yeni bir lib/actors/ klasörü ve bunun içinde yeni bir theboy.dart dosyası oluşturun. Bu, oyuncu karakterini temsil eden bileşen olacak: The Boy.

 import '../game.dart'; import '../assets.dart' as Assets; import 'package:flame/components.dart'; class TheBoy extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { TheBoy({ required super.position, // Position on the screen }) : super( size: Vector2.all(48), // Size of the component anchor: Anchor.bottomCenter // ); @override Future<void> onLoad() async { animation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, // For now we only need idle animation, so we load only 1 frame textureSize: Vector2.all(20), // Size of a single sprite in the sprite sheet stepTime: 0.12, // Time between frames, since it's a single frame not that important ), ); } }

Sınıf, animasyonlu sprite'lar için kullanılan bir bileşen olan SpriteAnimationComponent genişletir ve daha sonra oyun önbelleğinden görüntüler yüklemek veya genel değişkenler almak için oyun nesnesine referans vermemize olanak tanıyan HasGameRef karışımına sahiptir.


onLoad yöntemimizde, assets.dart dosyasında bildirdiğimiz THE_BOY sprite sayfasından yeni bir SpriteAnimation oluşturuyoruz.


Şimdi oynatıcımızı oyuna ekleyelim! game.dart dosyasına dönün ve onLoad yönteminin altına aşağıdakileri ekleyin:

 final theBoy = TheBoy(position: Vector2(size.x / 2, size.y / 2)); add(theBoy);

Oyunu şimdi çalıştırırsanız The Boy'la tanışabiliriz!


Çocukla Tanışın

Oyuncu hareketi

Öncelikle The Boy'u klavyeden kontrol etme özelliğini eklememiz gerekiyor. game.dart dosyasına HasKeyboardHandlerComponents mix'ini ekleyelim.

 class PlatformerGame extends FlameGame with HasKeyboardHandlerComponents


Sonra theboy.dart ve KeyboardHandler karışımına dönelim:

 class TheBoy extends SpriteAnimationComponent with KeyboardHandler, HasGameRef<PlatformerGame>


Ardından TheBoy bileşenine bazı yeni sınıf değişkenleri ekleyin:

 final double _moveSpeed = 300; // Max player's move speed int _horizontalDirection = 0; // Current direction the player is facing final Vector2 _velocity = Vector2.zero(); // Current player's speed


Son olarak klavye girişlerini dinlemeye izin veren onKeyEvent yöntemini geçersiz kılalım:

 @override bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) { _horizontalDirection = 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyA) || keysPressed.contains(LogicalKeyboardKey.arrowLeft)) ? -1 : 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyD) || keysPressed.contains(LogicalKeyboardKey.arrowRight)) ? 1 : 0; return true; }

Artık _horizontalDirection oyuncu sağa hareket ederse 1'e, oyuncu sola hareket ederse -1'e ve oyuncu hareket etmezse 0'a eşittir. Ancak oyuncunun pozisyonu henüz değişmediği için bunu henüz ekranda göremiyoruz. update yöntemini ekleyerek bunu düzeltelim.


Şimdi oyun döngüsünün ne olduğunu açıklamam gerekiyor. Temel olarak bu, oyunun sonsuz bir döngüde çalıştırıldığı anlamına gelir. Her yinelemede, mevcut durum Component's yöntem render işlenir ve ardından yöntem update yeni bir durum hesaplanır. Yöntemin imzasındaki dt parametresi, son durum güncellemesinden bu yana geçen milisaniye cinsinden süredir. Bunu aklınızda tutarak theboy.dart aşağıdakileri ekleyin:

 @override void update(double dt) { super.update(dt); _velocity.x = _horizontalDirection * _moveSpeed; position += _velocity * dt; }

Her oyun döngüsü döngüsü için, mevcut yönü ve maksimum hızı kullanarak yatay hızı güncelleriz. Daha sonra hareketli grafiğin konumunu güncellenmiş değerin dt ile çarpılmasıyla değiştiririz.


Neden son kısma ihtiyacımız var? Eğer konumu sadece hızla güncellerseniz, o zaman sprite uzaya doğru uçup gidecektir. Ama daha küçük olan hız değerini kullanabilir miyiz diye sorabilirsiniz. Yapabiliriz, ancak oyuncunun hareket etme şekli farklı saniye başına kare (FPS) oranları nedeniyle farklı olacaktır. Saniyedeki kare (veya oyun döngüleri) sayısı, oyun performansına ve çalıştırıldığı donanıma bağlıdır. Cihaz performansı ne kadar iyi olursa, FPS o kadar yüksek olur ve oynatıcı o kadar hızlı hareket eder. Bunu önlemek için hızı son kareden geçen süreye bağlı hale getiriyoruz. Bu şekilde sprite herhangi bir FPS'de benzer şekilde hareket edecektir.


Tamam, eğer oyunu şimdi çalıştırırsak şunu görmeliyiz:


Çocuk X ekseni boyunca konumunu değiştiriyor


Harika, şimdi çocuğun sola gittiğinde dönmesini sağlayalım. Bunu update yönteminin altına ekleyin:

 if ((_horizontalDirection < 0 && scale.x > 0) || (_horizontalDirection > 0 && scale.x < 0)) { flipHorizontally(); }


Oldukça kolay mantık: Mevcut yönün (kullanıcının bastığı ok) hareketli grafiğin yönünden farklı olup olmadığını kontrol ederiz, ardından hareketli grafiği yatay eksen boyunca çeviririz.


Şimdi koşu animasyonunu da ekleyelim. İlk önce iki yeni sınıf değişkenini tanımlayın:

 late final SpriteAnimation _runAnimation; late final SpriteAnimation _idleAnimation;


Daha sonra onLoad şu şekilde güncelleyin:

 @override Future<void> onLoad() async { _idleAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, textureSize: Vector2.all(20), stepTime: 0.12, ), ); _runAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(20), stepTime: 0.12, ), ); animation = _idleAnimation; }

Burada daha önce eklediğimiz boşta kalma animasyonunu sınıf değişkenine çıkardık ve yeni bir çalıştırma animasyonu değişkeni tanımladık.


Sonra yeni bir updateAnimation yöntemi ekleyelim:

 void updateAnimation() { if (_horizontalDirection == 0) { animation = _idleAnimation; } else { animation = _runAnimation; } }


Ve son olarak update yönteminin altındaki bu yöntemi çağırın ve oyunu çalıştırın.

Animasyonu çalıştırma ve hareketli grafiği çevirme


Çözüm

İlk bölüm için bu kadar. Flame oyununun nasıl kurulacağını, varlıkların nerede bulunacağını, bunları oyununuza nasıl yükleyeceğinizi ve harika bir animasyonlu karakterin nasıl oluşturulacağını ve klavye girişlerine göre nasıl hareket ettirileceğini öğrendik. Bu bölümün kodunu github'ımda bulabilirsiniz.


Bir sonraki makalede, Tiled kullanarak nasıl oyun seviyesi oluşturulacağını, Alev kamerasını nasıl kontrol edeceğinizi ve paralaks arka planının nasıl ekleneceğini ele alacağım. Bizi izlemeye devam edin!

Kaynaklar

Her bölümün sonuna harika yaratıcıların ve öğrendiğim kaynakların bir listesini ekleyeceğim.