Ana içeriğe geç

Solana Üzerinde Eğlenceli Oyunlar İçin Enerji Sistemi Kurma

Eğlenceli oyunlar, oyundaki eylemlerin enerji harcadığı ve zamanla yenilendiği enerji sistemlerini yaygın olarak kullanır. Bu kılavuzda, Solana üzerinde nasıl bir enerji sistemi inşa edeceğimizi anlatacağız. Eğer daha önce Solana hakkında bilginiz yoksa, bunun yerine Merhaba Dünya Örneği ile başlayın.

Enerji sistemi ve bir React istemcisi ile yeni bir oyunu kolayca kurabilirsiniz. Bunu yapmak için Create Solana Game kullanın. Sadece şu komutu çalıştırın:

npx create-solana-game oyun-adınız

create-solana-game kullanımına dair bir öğretici bulabilirsiniz. Buraya tıklayın. Ayrıca aşağıdaki kılavuzda açıklanan örneğin video yürüyüşünü de bulabilirsiniz.

Anchor programı

Başlangıç olarak, oyuncunun enerji rezervlerini zamanla kademeli olarak yenileyen bir Anchor programı oluşturmaya rehberlik edeceğiz. Enerji, oyuncunun oyun içinde çeşitli eylemleri gerçekleştirmesine olanak tanıyacak. Örneğimizde, bir oduncu ağaç kesecek ve her ağaç kesme işlemi bir odun kazandıracak ve bir enerji puanı harcayacak.

Oyuncu hesabını oluşturma

İlk olarak, oyuncunun durumunu kaydeden bir hesap oluşturması gerekecek. Ayrıca, programla oyuncunun son etkileşiminin Unix zaman damgasını last_login değerinde saklayacağız. Bu durum ile belirli bir zamanda oyuncunun ne kadar enerjisi olduğunu hesaplayabileceğiz. Ayrıca, oduncunun oyunda ne kadar odun kesebileceği için bir değerimiz var.

pub fn init_player(ctx: Context<InitPlayer>) -> Result<()> {
ctx.accounts.player.energy = MAX_ENERGY;
ctx.accounts.player.last_login = Clock::get()?.unix_timestamp;
Ok(())
}
#[derive(Accounts)]
pub struct InitPlayer <'info> {
#[account(
init,
payer = signer,
space = 1000,
seeds = [b"player".as_ref(), signer.key().as_ref()],
bump,
)]
pub player: Account<'info, PlayerData>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[account]
pub struct PlayerData {
pub name: String,
pub level: u8,
pub xp: u64,
pub wood: u64,
pub energy: u64,
pub last_login: i64
}

Ağaç kesme

Daha sonra, oyuncu chop_tree talimatını çağırdığında, oyuncunun yeterli enerjisi olup olmadığını kontrol edeceğiz ve ona bir odun ile ödüllendireceğiz (oyuncunun odun sayısını artırarak).

#[error_code]
pub enum ErrorCode {
#[msg("Yeterli enerji yok")]
NotEnoughEnergy,
}

pub fn chop_tree(mut ctx: Context<ChopTree>) -> Result<()> {
let account = &mut ctx.accounts;
update_energy(account)?;

if ctx.accounts.player.energy == 0 {
return err!(ErrorCode::NotEnoughEnergy);
}

ctx.accounts.player.wood = ctx.accounts.player.wood + 1;
ctx.accounts.player.energy = ctx.accounts.player.energy - 1;
msg!("Bir ağaç kestiniz ve 1 odun elde ettiniz. {} odununuz ve {} enerjiniz kaldı.", ctx.accounts.player.wood, ctx.accounts.player.energy);
Ok(())
}

Enerjiyi hesaplama

İlginç kısım update_energy fonksiyonunda gerçekleşiyor. Ne kadar zamanın geçtiğini kontrol ediyoruz ve oyuncunun o zamandaki enerji miktarını hesaplıyoruz. Aynı şeyi istemcide de yapacağız. Enerjiyi sürekli olarak kontrol etmek yerine tembel bir şekilde güncelliyoruz. Bu, oyun geliştirmede yaygın bir tekniktir.

const TIME_TO_REFILL_ENERGY: i64 = 60;
const MAX_ENERGY: u64 = 10;

pub fn update_energy(ctx: &mut ChopTree) -> Result<()> {
let mut time_passed: i64 = &Clock::get()?.unix_timestamp - &ctx.player.last_login;
let mut time_spent: i64 = 0;
while time_passed > TIME_TO_REFILL_ENERGY {
ctx.player.energy = ctx.player.energy + 1;
time_passed -= TIME_TO_REFILL_ENERGY;
time_spent += TIME_TO_REFILL_ENERGY;
if ctx.player.energy == MAX_ENERGY {
break;
}
}

if ctx.player.energy >= MAX_ENERGY {
ctx.player.last_login = Clock::get()?.unix_timestamp;
} else {
ctx.player.last_login += time_spent;
}

Ok(())
}

JavaScript istemcisi

Burada, create-solana-game kullanarak bir örnek bulun ve bir React istemcisi ile oluşturulmuş.

Bağlantı oluşturma

Anchor.ts dosyasında, Solana blok zincirine (bu durumda, devnet) bir bağlantı oluşturuyoruz:

export const connection = new Connection(
"https://api.devnet.solana.com",
"confirmed",
);

Onay parametresinin confirmed olarak ayarlandığını unutmayın. Bu, işlemlerin finalize edilmeden önce confirmed olmasını beklediğimiz anlamına gelir. Bu, işlemin geçerli olduğunu söyleyen ağın süper çoğunluğunun sonuç vermesini beklediğimiz anlamına gelir. Bu yaklaşık 400ms sürer ve iptal edilmeyen onaylı bir işlem olmamıştır. Genel olarak oyunlar için, confirmed mükemmel bir işlem taahhüt düzeyidir.

Oyuncu verilerini başlatma

İlk olarak, oyuncu hesabının program adresini bulmak için player tohum dizgesini ve oyuncunun genel anahtarını kullanacağız (PDA türetmek). Sonrasında initPlayer fonksiyonunu çağırarak hesabı oluşturacağız.

const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("player", "utf8"), publicKey.toBuffer()],
new PublicKey(LUMBERJACK_PROGRAM_ID),
);

const transaction = program.methods
.initPlayer()
.accounts({
player: pda,
signer: publicKey,
systemProgram: SystemProgram.programId,
})
.transaction();

const tx = await transaction;
const txSig = await sendTransaction(tx, connection, {
skipPreflight: true,
});

await connection.confirmTransaction(txSig, "confirmed");

Hesap güncellemelerine abone olma

Sonra, JavaScript istemcisi içinde, oyuncunun hesabındaki değişiklikleri abone olup dinlemek için websocket'leri kullanacağız. Burada websocket kullanmamızın nedeni, değişiklikleri almak için daha hızlı bir yol olmasıdır.

connection.onAccountChange, hesapta meydana gelen her değişikliği istemciye gönderen bir RPC düğümüne soket bağlantısı oluşturur. Daha sonra bu verileri TypeScript türlerine çözmek için program.coder kullanabilir ve bunları oyunda doğrudan kullanabiliriz.

useEffect(() => {
if (!publicKey) {
console.log("Genel anahtar eksik");
return;
}

const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("player", "utf8"), publicKey.toBuffer()],
new PublicKey(LUMBERJACK_PROGRAM_ID),
);

const fetchPlayerData = async () => {
try {
const data = await program.account.playerData.fetch(pda);
setGameState(data);
} catch (error) {
console.error("Oyuncu verileri alınırken hata:", error);
window.alert("Oyuncu verisi bulunamadı, lütfen başlatın!");
}
};

fetchPlayerData();

const handleAccountChange = (account: AccountInfo<Buffer>) => {
try {
const decodedData = program.coder.accounts.decode("playerData", account.data);
setGameState(decodedData);
} catch (error) {
console.error("Hesap verileri çözümleme hatası:", error);
}
};

const subscriptionId = connection.onAccountChange(pda, handleAccountChange);

return () => {
connection.removeAccountChangeListener(subscriptionId);
};
}, [publicKey]);

Enerjiyi hesaplama ve geri sayımı gösterme

JavaScript istemcisinde, programda olduğu gibi oyuncunun bu noktadaki enerji miktarını önceden hesaplayabilir ve enerji ne zaman tekrar kullanılabilir olduğunu bilmesi için ona bir geri sayım zamanlayıcı gösterebiliriz:

useEffect(() => {
const interval = setInterval(async () => {
if (gameState == null || gameState.lastLogin == undefined || gameState.energy >= 10) {return;}
const lastLoginTime = gameState.lastLogin * 1000;
let timePassed = ((Date.now() - lastLoginTime) / 1000);
while (timePassed > TIME_TO_REFILL_ENERGY && gameState.energy < MAX_ENERGY) {
gameState.energy = (parseInt(gameState.energy) + 1);
gameState.lastLogin = parseInt(gameState.lastLogin) + TIME_TO_REFILL_ENERGY;
timePassed -= TIME_TO_REFILL_ENERGY;
}
setTimePassed(timePassed);
let nextEnergyIn = Math.floor(TIME_TO_REFILL_ENERGY - timePassed);
if (nextEnergyIn < TIME_TO_REFILL_ENERGY && nextEnergyIn > 0) {
setEnergyNextIn(nextEnergyIn);
} else {
setEnergyNextIn(0);
}

}, 1000);

return () => clearInterval(interval);
}, [gameState, timePassed]);

...

{(gameState && <div className="flex flex-col items-center">
{("Odun: " + gameState.wood + " Enerji: " + gameState.energy + " Sonraki enerji: " + nextEnergyIn )}
</div>)}

Bununla birlikte, artık enerji bazlı bir oyun oluşturabilirsiniz ve ayrıca birisi oyunda bir bot oluşturursa, en fazla yapabilecekleri şey optimal olarak oynamaktır. Bu, oyununuzun mantığına bağlı olarak, normal bir şekilde oyun oynamak daha da kolay olabilir.

Not: Bu oyunu, oyuncuların oyun içindeki eylemler için bazı SPL tokenleri ile ödüllendirme özelliğini ekleyerek daha da iyi hale getirebilirsiniz. Tokenler ile oyunlarda etkileşim kısmını inceleyin.