Başarıları ele almak için esnek bir çerçeve nasıl kurabilirim?


54

Özellikle, " x düşmanlarını öldür" gibi basit istatistiklere dayalı başarıların ötesine geçebilecek kadar esnek bir başarı sistemi uygulamanın en iyi yolu nedir .

İstatistik temelli bir sistemden daha sağlam bir şey ve "hepsini koşullu olarak kodlamaktan" daha düzenli ve sürdürülebilir bir şey arıyorum. İstatistik tabanlı bir sistemde imkansız veya hantal olmayan bazı örnekler: "Bir çilekten sonra bir karpuz dilimleyin", "Yenilmezken bir borudan inin", vb.

Yanıtlar:


39

Bence bir tür sağlam çözüm, nesne yönelimli yoldan gitmek olacaktır.

Ne tür bir başarıyı desteklemek istediğinize bağlı olarak, oyununuzun o andaki durumunu ve / veya oyun nesnelerinin (oyuncu gibi) yapmış olduğu eylemlerin / olayların geçmişini sorgulamak için bir yol gerekir.

Diyelim ki temel bir başarı sınıfınız var:

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

AbstractAchievementOyunun durumuna atıfta bulunur. Olanları sorgulamak için kullanılır.

Sonra somut uygulamalar yaparsınız. Örneklerinizi kullanalım:

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;
    }
};

O zaman IsEarned()yöntemi kullanarak ne zaman kontrol edeceğinize karar vermek size kalmıştır . Her oyun güncellemesini kontrol edebilirsiniz.

Örneğin daha etkin bir yol, bir tür etkinlik yöneticisine sahip olmak olacaktır. Ve sonra olayları ( PlayerHasSlicedSomethingEventveya PlayerGotInvicibleEventveya basit olarak PlayerStateChanged) parametredeki başarıya götürecek bir yönteme kaydedin. Örnek:

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);
        }
    }
};

13
stil nitpick: if(...) return true; else return false;aynıdırreturn (...)
BlueRaja - Dany Pflughoeft

2
Bu bir başarı sistemini uygulamak için çok iyi bir şablon. Dahası, oyun durumlarını izlemek için bir yolun olması gerektiği fikrini hala göstermektedir. Muhtemelen en karmaşık fikri buluyorum.
Bryan Harrington

@ Spio sen usta bir adamsın ...! : D Basit ve zarif bir çözüm. Tebrik.
Diego Palomar

+1 Bir olay yayma / toplama sistemi bu sorunu çözmenin harika bir yolu olduğunu düşünüyorum.
ashes999

14

Kısacası, belirli bir koşul gerçekleştiğinde başarıların kilidi açılır. Yani istediğiniz durumu kontrol etmek için if ifadeleri üretebilmeniz gerekir.

Örneğin, bir seviyenin tamamlandığını veya bir patronun mağlup edildiğini bilmek istiyorsanız, bu olaylar gerçekleştiğinde boolean bayrağının gerçek olmasını sağlamanız gerekir.

Sonra:

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

Bunu, istediğiniz koşula uyması için gerektiği kadar karmaşık veya basit yapabilirsiniz.

Xbox 360 başarıları hakkında bazı bilgileri burada bulabilirsiniz .


2
+1 Harika makale ve aslında önereceğim şey. Her ne kadar Modal’ın metnini başarının kendisinden elde etsem de ... sadece bir şeyi değiştirmek istersen metin avından kaçınmak için.
Jesse Dorsey

@Noctrine - burada yayınlanan herhangi bir kodu unutma, sahte kod olarak kabul edilmelidir - genellikle noktaya ulaşmak için basitleştirilmiş kod kullanmak gerekir.
ChrisF,

1
Xbox 360 başarılar bağlantısı öldü.
hanny


8

Ya oyuncunun yaptığı her hareket size bir mesaj gönderirse AchievementManager? Ardından yönetici belirli koşulların karşılanıp karşılanmadığını dahili olarak kontrol edebilir. İlk nesneler mesaj gönderir:

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

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

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

Ve sonra bir AchievementManagerşey yapması gerekip gerekmediğini kontrol eder:

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

Muhtemelen bunu yine de stringler yerine numaralandırmalarla yapmak isteyeceksiniz. ;)


Bu düşündüğümün çizgileri boyunca, ancak yine de bir fonksiyonda kodlanmış birçok şeye dayanıyor. Sürdürülebilirlik bir yana, en azından her başarının, işlev boyunca her zaman uygunluk açısından kontrol edilmesi gerekir.
lti,

2
Yapar? Şartnameleri, oyunda olanları takip etmenin bir yolu olduğu sürece harici bir komut dosyasına koyabilirsiniz.
knight666

6
-1 Bu kötü tasarımın ağları. Doğrudan AchivementManager'ı arayacaksanız, bu "mesajların" her birini ayrı bir işlev yapın. Mesajları kullanacaksanız, diğer sınıfların da mesajları kullanabilmesi için bir mesaj yöneticisi yapın (Goomba'nın öldürüldüğünü bilmekle ilgileneceğinden eminim) ve bu kuplajı AchievementManagertüm sınıf (ilk başta OP'nin nasıl önleneceğini sorduğu şeydi). İletileriniz için dize değişmezleri değil, bir enum veya ayrı sınıfları kullanın - dize değişmezleri durumu iletmek için dize değişmezleri kullanmak her zaman kötü bir fikirdir.
BlueRaja - Danny Pflughoeft

4

Kullandığım son tasarım, bir dizi kullanıcı başına kalıcı sayaç setine ve daha sonra da belli bir değere sahip belirli bir sayacı kapatmaktan elde edilen başarılara dayanıyordu. Çoğu, sayacın sadece 0 veya 1 olacağı tek bir başarı / sayaç çifti idi (ve başarı> = 1'de tetiklendi), ancak bunu "öldürülen X dudes" veya "bulunan X sandıkları" için de kullanabilirsiniz. Ayrıca, başarıları olmayan bir şey için sayaçlar ayarlayabileceğiniz ve gelecekteki kullanım için hala izleneceği anlamına gelir.


3

Son oyunumda başarıları uyguladığımda, hepsini istatistiklere dayandırdım. İstatistiklerimiz belirli bir değere ulaştığında başarıların kilidi açılır. Modern Warfare 2'yi göz önünde bulundurun: Oyun çok sayıda istatistik izliyor! SCAR-H ile kaç çekim yaptınız? Lightweight perk kullanırken kaç mil bastırdınız?

Bu yüzden benim uygulamamda, sadece bir istatistik motoru yarattım, daha sonra oyun boyunca başarıların durumunu kontrol etmek için gerçekten basit sorgular yürüten bir başarı yöneticisi yarattım.

Uygulamam oldukça basit olsa da, işin yapılmasını sağlıyor. Bunun hakkında yazdım ve sorgularımı burada paylaştım .


2

Event Calculus kullanın . Ardından, ön koşullar yerine getirildikten sonra uygulanan bazı önkoşulları ve eylemleri yapın:

  • önkoşullar: 1000 düşman öldürdün, 2 bacağın var
  • eylemler: bana lolipop ver, süper duper-shotgun-13 ver
  • ilk aksiyonlar: "çok harikasın!" de.

Şunun gibi kullanın (hız için optimize edilmemiş!):

  • Tüm istatistikleri saklayın.
  • Önkoşullar için sorgu istatistikleri.
  • İşlemleri uygula.
  • Bir defalık eylemleri bir kez uygulayın.

Hızlı yapmak istiyorsanız:

  • İstediğiniz her şeyi önbelleğe alın, bir kısmını bazı ağaçlarda saklayın ...
  • Değişiklikleri adım adım yapın, böylece tüm eylemleri her zaman uygulamayın, bunlar yenidir ...)

Not

Her şeyin artıları ve eksileri olduğu için en iyi tavsiyede bulunmak zor .

  • "En iyi veri yapısı nedir?" "Hangi işlemleri en çok yapmak istiyorsunuz? Arama, kaldırma, ekleme ..." anlamına gelir.
  • Temel olarak bu özellikleri düşünürsünüz: kodlama kolaylığı, hız, netlik, boyut ...

0

Başarı olayı gerçekleştikten sonra IF kontrollerinde yanlış olan ne ?

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);

3
“Bunları" şartlar olarak sabitle "den daha düzenli ve bakımı yapılabilir bir şey arıyorum. '. Bu kesinlikle küçük bir oyun için iyi bir KISS yöntemi olmasına rağmen.
Komünist Ördek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.