Solana Oyun Geliştirme için Merhaba Dünya
Bu geliştirme kılavuzunda, Solana blok zincirini kullanarak basit bir zincir üstü oyunu geçeceğiz. Bu oyuna Küçük Macera adı verilmektedir ve Anchor framework'ü kullanılarak oluşturulmuş başlangıç dostu bir Solana programıdır. Bu programın amacı, oyuncuların konumlarını takip etmelerini ve sola ya da sağa hareket etmelerini sağlayan basit bir oyun oluşturmayı göstermektir.
Tam kaynak kodunu, tarayıcınızdan dağıtılabilir halde bulabilirsiniz. Bu Solana Playground örneği.
Anchor framework'ü ile tanışmak isterseniz, Solana Kursu içinde yer alan Anchor modülüne göz atabilirsiniz.
Video Geçişi
Başlarken
Başlangıç Solana geliştirmemizi hızlandırmak için, zincir üstü programımızı kodlamak, oluşturmak ve dağıtmak amacıyla Solana Playground (web tabanlı IDE) kullanacağız. Bu, Solana geliştirmeye başlamak için yerel olarak hiçbir şey kurmamızı gerektirmeyecektir.
Solana Playground
Solana Playground adresine gidin ve yeni bir Anchor projesi oluşturun. Eğer Solana Playground'a yeniyseniz, ayrıca bir Playground Cüzdanı oluşturmanız gerekecek. İşte Solana Playground'u kullanmanın bir örneği:
Başlangıç Program Kodu
Yeni bir Playground projesi oluşturduktan sonra, lib.rs
dosyasındaki varsayılan başlangıç
kodunu aşağıdaki kod ile değiştirin:
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod tiny_adventure {
use super::*;
// talimat işleyicileri buraya eklenecek
}
// yapılar buraya eklenecek
fn print_player(player_position: u8) {
if player_position == 0 {
msg!("Bir Yolculuk Başlıyor!");
msg!("o.......");
} else if player_position == 1 {
msg!("..o.....");
} else if player_position == 2 {
msg!("....o...");
} else if player_position == 3 {
msg!("........\\o/");
msg!("Sonuna ulaştınız! Harika!");
}
}
Bu oyunda, oyuncu 0 konumundan başlayacak ve sola veya sağa hareket edebilecektir. Oyuncunun oyun boyunca ilerlemesini göstermek için, yolculuklarını görüntülemek üzere mesaj kayıtlarını kullanacağız.
Oyun Verisi Hesaplarını Tanımlama
Oyunun inşasının ilk adımı, oyuncunun konumunu depolayacak zincir üstü hesap için bir yapı tanımlamaktır.
GameDataAccount
yapısı, oyuncunun mevcut konumunu işarete eden player_position
adlı
tek bir alana sahiptir.
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod tiny_adventure {
use super::*;
...
}
// Oyun Veri Hesabı yapısını tanımlayın
#[account]
pub struct GameDataAccount {
player_position: u8,
}
...
Program Talimatları
Küçük Macera programımız yalnızca 3
talimat işleyicisinden
oluşmaktadır:
initialize
- oyuncunun konumunu depolamak için bir zincir üstü hesap kurarmove_left
- oyuncunun konumunu sola hareket ettirmesine olanak tanırmove_right
- oyuncunun konumunu sağa hareket ettirmesine olanak tanır
Başlatma Talimatı
initialize
talimatımız, mevcut değilse GameDataAccount
ı başlatır,
player_position
değerini 0'a ayarlar ve bazı mesaj kayıtları yazdırır.
initialize
talimatı 3 hesaba ihtiyaç duyar:
new_game_data_account
- başlattığımızGameDataAccount
signer
-GameDataAccount
ın başlatılması için ödeme yapan oyuncusystem_program
- yeni bir hesap oluştururken gerekli olan bir hesap
#[program]
pub mod tiny_adventure {
use super::*;
// GameDataAccount'ı başlatan ve konumu 0'a ayarlayan talimat
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.new_game_data_account.player_position = 0;
msg!("Bir Yolculuk Başlıyor!");
msg!("o.......");
Ok(())
}
}
// Başlatma talimatı için gereken hesapları belirtin
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init_if_needed,
seeds = [b"level1"],
bump,
payer = signer,
space = 8 + 1
)]
pub new_game_data_account: Account<'info, GameDataAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
...
Bu örnekte, GameDataAccount
adresi için bir Program Türetilmiş Adres (PDA)
kullanılır.
Bu, adresi daha sonra belirlemek için deterministik olarak bulmamızı sağlar. Bu örnekte, PDA'nın
tek bir sabit değer (level1
) ile tohumlanarak oluşturulduğu ve programımızı yalnızca bir
GameDataAccount
oluşturmaya sınırladığı önemlidir. init_if_needed
kısıtlaması,
GameDataAccount
ın yalnızca mevcut değilse başlatılmasını sağlar.
Mevcut uygulamanın, GameDataAccount
üzerinde kimlerin değişiklik yapabileceğine dair
hiçbir kısıtlama olmadığını belirtmekte fayda var. Bu, oyunu herkesin oyuncunun hareketini
kontrol edebildiği çok oyunculu bir deneyime dönüştürmektedir.
Alternatif olarak, göndericinin adresini initialize
talimatında ek bir tohum olarak
kullanabilirsiniz; bu, her oyuncunun kendi GameDataAccount
ını oluşturmasına olanak tanır.
Sola Hareket Talimatı
Artık bir GameDataAccount
hesabını başlatabildiğimize göre, oyuncunun player_position
değerini güncellemesine olanak tanıyan move_left
talimatını uygulayalım.
Bu örnekte, sola hareket etmek sadece player_position
değerini 1 azaltmak anlamına
gelmektedir. Ayrıca en alt konumu 0 olarak ayarlayacağız. Bu talimat için gereken tek
hesap GameDataAccount
olacaktır.
#[program]
pub mod tiny_adventure {
use super::*;
...
// Sola hareket talimatı
pub fn move_left(ctx: Context<MoveLeft>) -> Result<()> {
let game_data_account = &mut ctx.accounts.game_data_account;
if game_data_account.player_position == 0 {
msg!("Başlangıçta geri döndünüz.");
} else {
game_data_account.player_position -= 1;
print_player(game_data_account.player_position);
}
Ok(())
}
}
// move_left talimatı için gereken hesabı belirtin
#[derive(Accounts)]
pub struct MoveLeft<'info> {
#[account(mut)]
pub game_data_account: Account<'info, GameDataAccount>,
}
...
Sağa Hareket Talimatı
Son olarak, move_right
talimatını uygulayalım. Benzer şekilde, sağa hareket
etmek player_position
değerini 1 artırmak anlamına gelecektir. Ayrıca, maksimum
konumu 3 ile sınırlayacağız.
Bu talimat için gerekli olan tek hesap GameDataAccount
olacaktır.
#[program]
pub mod tiny_adventure {
use super::*;
...
// Sağa hareket talimatı
pub fn move_right(ctx: Context<MoveRight>) -> Result<()> {
let game_data_account = &mut ctx.accounts.game_data_account;
if game_data_account.player_position == 3 {
msg!("Sonuna ulaştınız! Harika!");
} else {
game_data_account.player_position = game_data_account.player_position + 1;
print_player(game_data_account.player_position);
}
Ok(())
}
}
// move_right talimatı için gereken hesabı belirtin
#[derive(Accounts)]
pub struct MoveRight<'info> {
#[account(mut)]
pub game_data_account: Account<'info, GameDataAccount>,
}
...
Oluşturma ve Dağıtma
Artık Küçük Macera programımızı tamamladık! Nihai programınız aşağıdaki gibi olmalıdır:
use anchor_lang::prelude::*;
// Bu programın kamu anahtarıdır ve proje oluşturduğunuzda
// otomatik olarak güncellenecektir.
declare_id!("BouPBVWkdVHbxsdzqeMwkjqd5X67RX5nwMEwxn8MDpor");
#[program]
mod tiny_adventure {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.new_game_data_account.player_position = 0;
msg!("Bir Yolculuk Başlıyor!");
msg!("o.......");
Ok(())
}
pub fn move_left(ctx: Context<MoveLeft>) -> Result<()> {
let game_data_account = &mut ctx.accounts.game_data_account;
if game_data_account.player_position == 0 {
msg!("Başlangıçta geri döndünüz.");
} else {
game_data_account.player_position -= 1;
print_player(game_data_account.player_position);
}
Ok(())
}
pub fn move_right(ctx: Context<MoveRight>) -> Result<()> {
let game_data_account = &mut ctx.accounts.game_data_account;
if game_data_account.player_position == 3 {
msg!("Sonuna ulaştınız! Harika!");
} else {
game_data_account.player_position = game_data_account.player_position + 1;
print_player(game_data_account.player_position);
}
Ok(())
}
}
fn print_player(player_position: u8) {
if player_position == 0 {
msg!("Bir Yolculuk Başlıyor!");
msg!("o.......");
} else if player_position == 1 {
msg!("..o.....");
} else if player_position == 2 {
msg!("....o...");
} else if player_position == 3 {
msg!("........\\o/");
msg!("Sonuna ulaştınız! Harika!");
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init_if_needed,
seeds = [b"level1"],
bump,
payer = signer,
space = 8 + 1
)]
pub new_game_data_account: Account<'info, GameDataAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct MoveLeft<'info> {
#[account(mut)]
pub game_data_account: Account<'info, GameDataAccount>,
}
#[derive(Accounts)]
pub struct MoveRight<'info> {
#[account(mut)]
pub game_data_account: Account<'info, GameDataAccount>,
}
#[account]
pub struct GameDataAccount {
player_position: u8,
}
Program tamamlandığına göre, artık Solana Playground üzerinde derleyip dağıtma zamanıdır!
Eğer bu ilk kez Solana Playground kullanıyorsanız, önce bir Playground Cüzdanı oluşturun
ve bir Devnet uç noktasına bağlı olduğunuzdan emin olun. Ardından solana airdrop 5
komutunu
çalıştırın. Yeterli SOL'unuz olduğunda, programı oluşturup dağıtın. Eğer komut başarısız olursa,
devnet SOL alma yollarını
kontrol edebilirsiniz.
İstemci ile Başlayın
Bir sonraki bölüm, oyununuzla etkileşimde bulunmak için basit bir istemci tarafı uygulaması
oluşturmanıza rehberlik edecektir. Kodları ayrıntılı açıklamalarla birlikte inceleyeceğiz.
Solana Playground'ta client.ts
dosyasına gidin ve sonraki bölümlerden kod parçalarını ekleyin.
GameDataAccount Hesap Adresini Türetilmesi
Öncelikle, findProgramAddress
fonksiyonunu kullanarak GameDataAccount
için PDA'yı
türetilmeye çalışalım.
Bir
Program Türetilmiş Adres (PDA)
, programın ID'si ve ek tohumlar kullanılarak türetilen tekil bir adresdir.
// İşlediğiniz programla etkileşimde bulunurlarsa herkesin karakteri kontrol edebileceği PDA adresi
const [globalLevel1GameDataAccount, bump] =
await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from("level1", "utf8")],
pg.program.programId,
);
Oyun Durumunu Başlatma
Sonraki adımda, önceki adımdaki PDA'yı kullanarak oyun veri hesabını elde etmeye çalışalım.
Eğer hesap mevcut değilse, programımızdan initialize
talimatını çağırarak oluşturacağız.
let txHash;
let gameDateAccount;
try {
gameDateAccount = await pg.program.account.gameDataAccount.fetch(
globalLevel1GameDataAccount,
);
} catch {
// Hesabın zaten başlatılıp başlatılmadığını kontrol edin, aksi takdirde başlatın
txHash = await pg.program.methods
.initialize()
.accounts({
newGameDataAccount: globalLevel1GameDataAccount,
signer: pg.wallet.publicKey,
systemProgram: web3.SystemProgram.programId,
})
.signers([pg.wallet.keypair])
.rpc();
console.log(`Günlükleri görmek için 'solana confirm -v ${txHash}' komutunu kullanın`);
await pg.connection.confirmTransaction(txHash);
console.log("Bir yolculuk başlıyor...");
console.log("o........");
}
Sola ve Sağa Hareket
Artık oyunun keyfini çıkarmak için sola veya sağa hareket ediyoruz. Bu, işlemi
Solana ağına göndererek yapılır; programdan moveLeft
veya moveRight
talimatını
çağırarak gerçekleşecektir.
Bu adımı istediğiniz kadar tekrar edebilir, her biri zincir üzerinde hareket mantığını uygulayarak oyuncunun durumunu güncelleyebilir.
// Artık buraya oynayabilir, sola ve sağa hareket edebilirsiniz
txHash = await pg.program.methods
//.moveLeft()
.moveRight()
.accounts({
gameDataAccount: globalLevel1GameDataAccount,
})
.signers([pg.wallet.keypair])
.rpc();
console.log(`Günlükleri görmek için 'solana confirm -v ${txHash}' komutunu kullanın`);
await pg.connection.confirmTransaction(txHash);
gameDateAccount = await pg.program.account.gameDataAccount.fetch(
globalLevel1GameDataAccount,
);
console.log("Oyuncu pozisyonu:", gameDateAccount.playerPosition.toString());
Oyuncunun Pozisyonunu Kaydetme
Son olarak, gameDateAccount
içinde depolanan playerPosition
değerine
dayalı olarak karakterin pozisyonunu kaydetmek için bir switch
ifadesi kullanalım.
Bu, oyundaki karakterin hareketinin görsel temsilini sağlayacaktır.
switch (gameDateAccount.playerPosition) {
case 0:
console.log("Bir yolculuk başlıyor...");
console.log("o........");
break;
case 1:
console.log("....o....");
break;
case 2:
console.log("......o..");
break;
case 3:
console.log(".........\\o/");
break;
}
İstemci Programını Çalıştırma
Son olarak, Solana Playground'da "Çalıştır" butonuna tıklayarak istemciyi çalıştırın. Çıktınız aşağıdaki gibi olmalıdır:
İstemci çalışıyor...
client.ts:
Benim adresim: 8ujtDmwpkQ4Bp4GU4zUWmzf65sc21utdcxFAELESca22
Benim bakiyem: 4.649749614 SOL
Günlükleri görmek için 'solana confirm -v 4MRXEWfGqvmro1KsKb94Zz8qTZsPa9x99oMFbLBz2WicLnr8vdYYsQwT5u3pK5Vt1i9BDrVH5qqTXwtif6sCRJCy' komutunu kullanın
Oyuncu pozisyonu: 1
....o....
Tebrikler! Küçük Macera oyununu istemciden başarıyla oluşturdunuz, dağıttınız ve çağırdınız.
Olanakları daha da göstermek için, Next.js frontend'i üzerinden Küçük Macera programıyla etkileşim kurmanın nasıl olduğunu gösteren bir demo inceleyebilirsiniz. Ayrıca bu Next.js projesinin kaynak koduna burada göz atabilirsiniz.
Sırada Ne Var?
Temel oyun tamamlandıktan sonra, yaratıcılığınızı serbest bırakın ve kendi fikirlerinizi uygulamak için bağımsız olarak deneyimlerinizi zenginleştirin. İşte birkaç öneri:
- Oyun içi metinleri değiştirin ve ilginç bir hikaye oluşturun. Bir arkadaşınızı özel anlatımınızdan geçirip zincir üstü işlemleri nasıl ortaya çıktığını gözlemleyin!
- Oyuncuları
SOL Ödülleri
ile ödüllendiren bir sandık ekleyin veya oyuncunun ilerledikçe madeni paralar toplamasına vetokenlarla etkileşime girmesine
olanak tanıyın. - Oyuncunun yukarı, aşağı, sola ve sağa hareket etmesine olanak tanıyan bir ızgara oluşturun ve daha dinamik bir deneyim için birden fazla oyuncu tanıtın.
İkinci Bölüm
Küçük Macera oyununu rehberli geliştirmeye Küçük Macera - İkinci Bölüm
ile devam edebilirsiniz,
burada SOL'u programda nasıl depolayacağımızı ve ödül olarak oyunculara nasıl dağıtacağımızı göstereceğiz.
Daha Fazla Kaynak
Ayrıca burada daha fazla Solana oyun geliştirme kaynaklarını keşfedebilirsiniz:
Başlamak için kılavuz
Solana Oyun SDK'ları
Örneklerle öğrenin
Enerji Sistemi
Oyunlardaki NFT'ler
Oyunlarda Token