Bagaimana saya bisa membuat kerangka kerja yang fleksibel untuk menangani pencapaian?

54

Secara khusus, apa cara terbaik untuk menerapkan sistem pencapaian yang cukup fleksibel untuk menangani melampaui pencapaian sederhana yang digerakkan oleh statistik seperti "bunuh x musuh".

Saya mencari sesuatu yang lebih kuat daripada sistem berbasis statistik dan sesuatu yang lebih terorganisir dan dapat dikelola daripada "hardcode semuanya sebagai syarat." Beberapa contoh yang tidak mungkin atau berat dalam sistem berbasis statistik: "Potong semangka setelah stroberi", "Turun pipa sambil tak terkalahkan", dll.

lti
sumber

Jawaban:

39

Saya pikir semacam solusi yang kuat akan pergi dengan cara berorientasi objek.

Bergantung pada pencapaian seperti apa yang ingin Anda dukung, Anda perlu cara untuk menanyakan keadaan saat ini dari permainan Anda dan / atau sejarah tindakan / peristiwa yang dibuat oleh objek permainan (seperti pemain).

Katakanlah Anda memiliki kelas Prestasi dasar seperti:

class AbstractAchievement
{
    GameState& gameState;
    virtual bool IsEarned() = 0;
    virtual string GetName() = 0;
};

AbstractAchievementmemegang referensi ke kondisi permainan. Ini digunakan untuk menanyakan hal-hal yang sedang terjadi.

Kemudian Anda membuat implementasi konkret. Mari kita gunakan contoh Anda:

class MasterSlicerAchievement : public AbstractAchievement
{
    string GetName() { return "Master Slicer"; }
    bool IsEarned()
    {
        Action lastAction = gameState.GetPlayerActionHistory().GetAction(0);
        Action previousAction = gameState.GetPlayerActionHistory().GetAction(1);
        if (lastAction.GetType() == ActionType::Slice &&
            previousAction.GetType() == ActionType::Slice &&
            lastAction.GetObjectType() == ObjectType::Watermelon &&
            previousAction.GetObjectType() == ObjectType::Strawberry)
            return true;
        return false;
    }
};
class InvinciblePipeRiderAchievement : public AbstractAchievement
{
    string GetName() { return "Invincible Pipe Rider"; }
    bool IsEarned()
    {
        if (gameState.GetLocationType(gameState.GetPlayerPosition()) == LocationType::OVER_PIPE &&
            gameState.GetPlayerState() == EntityState::INVINCIBLE)
            return true;
        return false;
    }
};

Maka terserah Anda untuk memutuskan kapan harus memeriksa menggunakan IsEarned()metode. Anda dapat memeriksa di setiap pembaruan game.

Cara yang lebih efisien adalah, misalnya, memiliki semacam manajer acara. Dan kemudian mendaftarkan peristiwa (seperti PlayerHasSlicedSomethingEventatau PlayerGotInvicibleEventatau hanya PlayerStateChanged) ke metode yang akan mengambil pencapaian dalam parameter. Contoh:

class Game
{
    void Initialize()
    {
        eventManager.RegisterAchievementCheckByActionType(ActionType::Slice, masterSlicerAchievement);
        // Each time an action of type Slice happens,
        // the CheckAchievement() method is invoked with masterSlicerAchievement as parameter.
        eventManager.RegisterAchievementCheckByPlayerState(EntityState::INVINCIBLE, invinciblePiperAchievement);
        // Each time the player gets the INVINCIBLE state,
        // the CheckAchievement() method is invoked with invinciblePipeRiderAchievement as parameter.
    }
    void CheckAchievement(const AbstractAchievement& achievement)
    {
        if (!HasAchievement(player, achievement) && achievement.IsEarned())
        {
            AddAchievement(player, achievement);
        }
    }
};
Splo
sumber
13
style nitpick: if(...) return true; else return false;sama denganreturn (...)
BlueRaja - Danny Pflughoeft
2
Itu adalah templat yang sangat bagus untuk menerapkan sistem pencapaian. Selain itu, masih menunjukkan gagasan bahwa Anda harus memiliki cara untuk melacak status permainan. Saya menemukan itu mungkin ide yang paling rumit.
Bryan Harrington
@Spio Anda adalah pria ahli ...! : D Solusi sederhana dan elegan. Selamat.
Diego Palomar
+1 Saya pikir sistem memancarkan / mengumpulkan acara adalah cara yang bagus untuk menangani masalah ini.
ashes999
14

Singkatnya, pencapaian tidak terkunci ketika kondisi tertentu terpenuhi. Jadi, Anda harus bisa menghasilkan pernyataan jika memeriksa kondisi yang Anda inginkan.

Misalnya, jika Anda ingin tahu bahwa level telah selesai atau bos dikalahkan, Anda harus membuat bendera boolean menjadi kenyataan ketika peristiwa ini terjadi.

Kemudian:

if(GlobalFlags.MasterBossDefeated == true && AchievementClass.MasterBossDefeatedAchievement == false)
{
    AchievementClass.MasterBossDefeatedAchievement = true;
    showModalPopUp("You defeated the Master Boss!  30 gamerscore");
}

Anda dapat menjadikan ini serumit atau sesederhana yang diperlukan untuk mencocokkan kondisi yang Anda inginkan.

Beberapa info tentang pencapaian Xbox 360 dapat ditemukan di sini .

Bryan Denny
sumber
2
+1 Artikel bagus, dan intinya apa yang akan saya sarankan. Meskipun saya akan membuat Modal mendapatkan teksnya dari pencapaian itu sendiri ... hanya untuk menghindari berburu teks jika Anda ingin mengubah sesuatu.
Jesse Dorsey
@Noctrine - jangan lupa kode apa pun yang diposting di sini harus diperlakukan sebagai kode semu - sering kali perlu menggunakan kode yang disederhanakan untuk menyampaikan maksudnya.
ChrisF
1
Tautan pencapaian Xbox 360 sudah mati.
Hangy
8

Bagaimana jika Anda setiap tindakan pemain mengambil pesan ke pesan AchievementManager? Kemudian manajer dapat memeriksa secara internal apakah kondisi tertentu telah dipenuhi. Objek pertama memposting pesan:

AchievementManager::PostMessage("Jump", "162");

AchievementManager::PostMessage("Slice", "Strawberry");
AchievementManager::PostMessage("Slice", "Watermelon");

AchievementManager::PostMessage("Kill", "Goomba");

Dan kemudian AchievementManagercek jika perlu melakukan apa pun:

if (!strcmp(m_Message.name, "Slice") && !strcmp(m_LastMessage.name, "Slice"))
{
    if (!strcmp(m_Message.value, "Watermelon") && !strcmp(m_LastMessage.value, "Strawberry"))
    {
        // achievement unlocked!
    }
}

Anda mungkin ingin melakukan ini dengan enumerasi alih-alih string. ;)

knight666
sumber
Ini sejalan dengan apa yang saya pikirkan, tetapi masih bergantung pada banyak hal yang dikodekan dalam satu fungsi. Di samping pemeliharaan, setidaknya setiap prestasi harus diperiksa untuk kelayakannya setiap kali melalui fungsi.
lti
2
Melakukannya? Anda dapat menempatkan persyaratan dalam skrip eksternal, selama Anda memiliki beberapa cara untuk melacak apa yang terjadi dalam permainan.
knight666
6
-1 ini berbau desain yang buruk. Jika Anda akan memanggil Manajer Achivement secara langsung, cukup buat masing-masing "pesan" itu fungsi yang terpisah. Jika Anda akan menggunakan pesan, buat pengelola pesan sehingga kelas lain juga dapat menggunakan pesan tersebut (saya yakin Goomba akan tertarik mengetahui bahwa ia telah terbunuh), dan untuk menghapus sambungan itu ke AchievementManagerdalam setiap kelas (yang mana OP bertanya bagaimana untuk menghindari di tempat pertama). Dan menggunakan enum atau kelas terpisah untuk pesan Anda, bukan string-literal - menggunakan string literal untuk menyampaikan keadaan selalu merupakan ide yang buruk.
BlueRaja - Danny Pflughoeft
4

Desain terakhir yang saya gunakan didasarkan pada memiliki satu set counter persisten per-pengguna dan kemudian memiliki kunci prestasi dari counter tertentu yang memukul nilai tertentu. Sebagian besar adalah pasangan pencapaian / penghitung tunggal di mana penghitung hanya akan menjadi 0 atau 1 (dan pencapaian dipicu pada> = 1), tetapi Anda dapat menggunakan ini untuk "membunuh X dudes" atau "menemukan X peti" juga. Ini juga berarti Anda dapat mengatur penghitung untuk sesuatu yang tidak memiliki pencapaian, dan itu masih akan dilacak untuk penggunaan di masa mendatang.

pembuat kode
sumber
3

Ketika saya menerapkan pencapaian dalam game terakhir saya, saya menjadikan semuanya berdasarkan statistik. Pencapaian tidak terkunci saat statistik kami mencapai nilai tertentu. Pertimbangkan Modern Warfare 2: game ini melacak banyak statistik! Berapa banyak pemotretan yang Anda ambil dengan SCAR-H? Berapa mil Anda berlari saat menggunakan mer Lightweight?

Jadi dalam implementasi saya, saya hanya menciptakan mesin statistik, kemudian menciptakan manajer prestasi yang menjalankan pertanyaan yang sangat sederhana untuk memeriksa status prestasi di seluruh gameplay.

Meskipun implementasi saya cukup sederhana, itu menyelesaikan pekerjaan. Saya menulis tentang hal itu dan membagikan pertanyaan saya di sini .

Reed Olsen
sumber
2

Gunakan Kalkulus Peristiwa . Kemudian buat beberapa prasyarat dan tindakan yang diterapkan setelah prasyarat terpenuhi:

  • prasyarat: Anda membunuh 1000 musuh, Anda memiliki 2 kaki
  • tindakan: beri aku lollypop, beri aku super-duper-shotgun-13
  • tindakan pertama kali: ucapkan "Anda sangat hebat!"

Gunakan seperti (tidak dioptimalkan untuk kecepatan!):

  • Simpan semua statistik.
  • Statistik kueri untuk prasyarat.
  • Terapkan tindakan.
  • Terapkan tindakan satu kali sekali.

Jika Anda ingin membuatnya cepat:

  • Cache apa pun yang Anda inginkan, simpan bagian di beberapa pohon, hash ...
  • Lakukan perubahan secara bertahap sehingga Anda tidak menerapkan semua tindakan sepanjang waktu, tetapi ini yang baru ...)

Catatan

Sulit untuk memberikan saran terbaik karena semua hal memiliki pro dan kontra.

  • "Apa struktur data terbaik?" menyiratkan "Operasi apa yang paling ingin Anda lakukan dengannya? Mencari, menghapus, menambahkan ..."
  • Anda pada dasarnya berpikir dalam sifat-sifat ini: kemudahan pengkodean, kecepatan, kejelasan, ukuran ...
pengguna712092
sumber
0

Apa yang salah dengan cek IF setelah acara pencapaian terjadi?

if (Cinimatics.done)
   Achievement.get(CINIMATICS_SEEN);

if (EnemiesKiled > 200)
   Achievement.get(KILLER);

if (Damage > 2000 && TimeSinceFirstDamage < 2000)
   Achievement.get(MEAT_SHIELD);

InvitationAccepted = Invite.send(BestFriend);
if (InvitationAccepted)
   Achievement.get(A_FRIEND_IN_NEED);
MrValdez
sumber
3
'Saya mencari sesuatu yang lebih terorganisir dan dapat dikelola daripada "hardcode semuanya sebagai syarat." ' Meskipun ini jelas merupakan metode KISS yang bagus untuk gim kecil.
Bebek Komunis