ฉันจะตั้งกรอบงานที่ยืดหยุ่นเพื่อจัดการกับความสำเร็จได้อย่างไร


54

โดยเฉพาะอย่างยิ่งวิธีที่ดีที่สุดในการใช้ระบบความสำเร็จนั้นมีความยืดหยุ่นเพียงพอที่จะจัดการกับความสำเร็จทางสถิติอย่างง่ายๆเช่น "ฆ่าxศัตรู"

ฉันกำลังมองหาบางสิ่งที่แข็งแกร่งกว่าระบบที่อิงกับสถิติและสิ่งที่จัดระเบียบและบำรุงรักษาได้ดีกว่า "hardcode ทั้งหมดเป็นเงื่อนไข" ตัวอย่างบางอย่างที่เป็นไปไม่ได้หรือไม่แน่นอนในระบบที่ใช้สถิติ: "ฝานแตงโมหลังจากสตรอเบอร์รี่", "ลงไปในท่อในขณะที่อยู่ยงคงกระพัน" ฯลฯ

คำตอบ:


39

ฉันคิดว่าวิธีการแก้ปัญหาที่มีประสิทธิภาพน่าจะเป็นวิธีที่มุ่งเน้นวัตถุ

คุณต้องการวิธีสอบถามสถานะปัจจุบันของเกมและ / หรือประวัติของการกระทำ / เหตุการณ์ที่วัตถุเกม (เช่นผู้เล่น) ทำขึ้นอยู่กับประเภทของความสำเร็จที่คุณต้องการสนับสนุน

สมมติว่าคุณมีระดับความสำเร็จพื้นฐานเช่น:

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

AbstractAchievementถือการอ้างอิงถึงสถานะของเกม มันใช้เพื่อค้นหาสิ่งที่เกิดขึ้น

จากนั้นคุณทำการใช้งานที่เป็นรูปธรรม ลองใช้ตัวอย่างของคุณ:

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

จากนั้นขึ้นอยู่กับคุณในการตัดสินใจว่าจะตรวจสอบIsEarned()วิธีการใช้งานเมื่อใด คุณสามารถตรวจสอบการอัพเดทแต่ละเกมได้

ตัวอย่างเช่นวิธีที่มีประสิทธิภาพมากขึ้นคือการมีตัวจัดการเหตุการณ์บางประเภท และการลงทะเบียนเหตุการณ์ (เช่นPlayerHasSlicedSomethingEventหรือPlayerGotInvicibleEventหรือเพียงแค่PlayerStateChanged) เพื่อวิธีการที่จะนำความสำเร็จในพารามิเตอร์ ตัวอย่าง:

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
สไตล์ nitpick: if(...) return true; else return false;เหมือนกับreturn (...)
BlueRaja - Danny Pflughoeft

2
นั่นเป็นเทมเพลตที่ดีมากสำหรับการนำระบบความสำเร็จไปใช้ ยิ่งไปกว่านั้นมันยังแสดงให้เห็นถึงความคิดที่ว่าคุณต้องมีวิธีในการติดตามสถานะเกม ฉันพบว่าอาจเป็นความคิดที่ซับซ้อนที่สุด
Bryan Harrington

@Spio คุณเป็นคนหลัก ... ! : D ทางออกที่ง่ายและสง่างาม Congrat
Diego Palomar

+1 ฉันคิดว่าระบบปล่อย / รวบรวมเหตุการณ์เป็นวิธีที่ยอดเยี่ยมในการจัดการปัญหานี้
ashes999

14

ในระยะสั้นความสำเร็จจะถูกปลดล็อคเมื่อตรงตามเงื่อนไขที่กำหนด ดังนั้นคุณต้องสามารถผลิตถ้างบตรวจสอบเงื่อนไขที่คุณต้องการ

ตัวอย่างเช่นหากคุณต้องการทราบว่าระดับเสร็จสมบูรณ์หรือเจ้านายพ่ายแพ้คุณจะต้องมีการทำเครื่องหมายบูลีนเป็นจริงเมื่อเหตุการณ์เหล่านี้เกิดขึ้น

แล้ว:

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

คุณสามารถทำให้มันซับซ้อนหรือเรียบง่ายได้ตามต้องการเพื่อให้ตรงกับสภาพที่คุณต้องการ

ข้อมูลบางอย่างเกี่ยวกับความสำเร็จของ Xbox 360 สามารถพบได้ที่นี่


2
+1 บทความที่ยอดเยี่ยมและเป็นสิ่งที่ฉันอยากจะแนะนำ แม้ว่าฉันจะทำให้ Modal ได้รับข้อความจากความสำเร็จของตัวเอง ... เพียงเพื่อหลีกเลี่ยงการล่าสัตว์ข้อความหากคุณต้องการเปลี่ยนบางสิ่งบางอย่าง
Jesse Dorsey

@Noctrine - อย่าลืมรหัสใด ๆ ที่โพสต์ที่นี่ควรถือว่าเป็นรหัสหลอก - บ่อยครั้งที่จำเป็นต้องใช้รหัสที่ง่ายเพื่อให้ได้คะแนน
ChrisF

1
ลิงก์ความสำเร็จของ Xbox 360 นั้นตายแล้ว
Hangy


8

ถ้าคุณทุกการกระทำผู้เล่นจะโพสต์ข้อความไปที่AchievementManager? จากนั้นผู้จัดการสามารถตรวจสอบภายในว่าตรงตามเงื่อนไขที่กำหนดหรือไม่ วัตถุแรกโพสต์ข้อความ:

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

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

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

จากนั้นAchievementManagerตรวจสอบว่าจำเป็นต้องทำอะไรหรือไม่:

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

คุณอาจต้องการทำสิ่งนี้ด้วยการแจกแจงแทนการใช้สตริง ;)


นี่คือสิ่งที่ฉันคิด แต่มันก็ยังขึ้นอยู่กับหลาย ๆ สิ่งที่ถูกฮาร์ดโค้ดในฟังก์ชั่นเดียว การบำรุงรักษากันอย่างน้อยที่สุดทุกความสำเร็จจะต้องมีการตรวจสอบสำหรับการมีสิทธิ์ทุกครั้งที่ผ่านฟังก์ชั่น
lti

2
ทำมัน? คุณสามารถใส่เงื่อนไขไว้ในสคริปต์ภายนอกตราบใดที่คุณมีวิธีการติดตามสิ่งที่เกิดขึ้นในเกม
knight666

6
-1 การออกแบบที่แย่นี้ หากคุณจะโทรหา AchivementManager โดยตรงเพียงแค่ทำให้ "ข้อความ" แต่ละรายการเป็นฟังก์ชันแยกต่างหาก หากคุณกำลังจะใช้ข้อความสร้าง message-manager เพื่อให้คลาสอื่น ๆ สามารถใช้ข้อความได้เช่นกัน (ฉันแน่ใจว่า Goomba จะสนใจในการรู้ว่าเขาถูกฆ่าตาย) และเพื่อลบการเชื่อมต่อนั้นAchievementManagerในทุก ๆ ชั้นเรียน (ซึ่งเป็นสิ่งที่ OP ถามถึงวิธีหลีกเลี่ยงตั้งแต่แรก) และใช้ enum หรือชั้นเรียนที่แยกต่างหากสำหรับข้อความของคุณไม่สตริงตัวอักษร - ใช้ตัวอักษรของสตริงที่จะผ่านทั่วรัฐเป็นเสมอความคิดที่ดี
BlueRaja - Danny Pflughoeft

4

การออกแบบล่าสุดที่ฉันใช้นั้นมีพื้นฐานมาจากการมีชุดเคาน์เตอร์แบบต่อผู้ใช้อย่างถาวรและจากนั้นให้ปิดการทำงานของตัวนับจำนวนหนึ่งเพื่อตีค่าที่แน่นอน ส่วนใหญ่เป็นคู่สำเร็จ / คู่เดียวที่เคาน์เตอร์จะเป็น 0 หรือ 1 เท่านั้น (และความสำเร็จนั้นเกิดขึ้นที่> = 1) แต่คุณสามารถใช้สิ่งนี้สำหรับ "kill X dudes" หรือ "พบหีบ X" ได้เช่นกัน นอกจากนี้ยังหมายความว่าคุณสามารถตั้งค่าตัวนับสำหรับสิ่งที่ไม่มีความสำเร็จได้และจะยังคงถูกติดตามเพื่อใช้ในอนาคต


3

เมื่อฉันนำความสำเร็จมาใช้ในเกมสุดท้ายของฉันฉันทำสถิติทั้งหมด ความสำเร็จจะถูกปลดล็อคเมื่อสถิติของเราถึงค่าที่แน่นอน พิจารณา Modern Warfare 2: เกมกำลังติดตามสถิติมากมาย! คุณถ่ายภาพด้วย SCAR-H กี่ภาพ คุณวิ่งไปได้กี่ไมล์ในขณะที่ใช้งาน Lightweight Perk

ดังนั้นในการดำเนินการของฉันฉันเพียงสร้างเครื่องมือสถิติจากนั้นสร้างผู้จัดการผลสัมฤทธิ์ทางการเรียนที่ใช้คำสั่งง่ายๆเพื่อตรวจสอบสถานะของความสำเร็จตลอดการเล่นเกม

ในขณะที่การใช้งานของฉันค่อนข้างง่าย แต่ก็ทำให้งานเสร็จ ผมเขียนเกี่ยวกับเรื่องนี้และใช้ร่วมกันคำสั่งของฉันที่นี่


2

ใช้แคลคูลัสเหตุการณ์ จากนั้นสร้างเงื่อนไขเบื้องต้นและการกระทำบางอย่างที่นำไปใช้หลังจากมีเงื่อนไขเบื้องต้นแล้ว:

  • เงื่อนไข: คุณฆ่าศัตรู 1,000 คนคุณมี 2 ขา
  • การกระทำ: ขอ lollypop ให้ฉันหน่อย -duper-shotgun-13
  • การกระทำครั้งแรก: พูดว่า "คุณยอดเยี่ยมมาก!"

ใช้เหมือน (ไม่เหมาะสำหรับความเร็ว!):

  • เก็บสถิติทั้งหมด
  • สถิติการสืบค้นสำหรับเงื่อนไขเบื้องต้น
  • ใช้การกระทำ
  • ใช้การกระทำเพียงครั้งเดียวครั้งเดียว

หากคุณต้องการทำให้รวดเร็ว:

  • แคชอะไรก็ได้ที่คุณต้องการเก็บชิ้นส่วนไว้ในต้นไม้บางต้นแฮช ...
  • ทำการเปลี่ยนแปลงที่เพิ่มขึ้นดังนั้นคุณจะไม่ใช้การกระทำทั้งหมดตลอดเวลา แต่สิ่งเหล่านี้เป็นสิ่งใหม่ ... )

บันทึก

เป็นการยากที่จะให้คำแนะนำที่ดีที่สุดเนื่องจากทุกสิ่งมีข้อดีข้อเสีย

  • "โครงสร้างข้อมูลที่ดีที่สุดคืออะไร" แปลว่า "การทำงานที่คุณต้องการทำกับมันมากที่สุดคือการค้นหาลบเพิ่ม ... "
  • โดยทั่วไปคุณคิดว่าในคุณสมบัติเหล่านี้: ความง่ายในการเขียนโค้ด, ความเร็ว, ความคมชัด, ขนาด ...

0

เกิดอะไรขึ้นกับการตรวจสอบIFหลังจากเหตุการณ์ความสำเร็จเกิดขึ้น

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
'ฉันกำลังมองหาบางสิ่งที่เป็นระเบียบและดูแลรักษาได้ดีกว่า ' แม้ว่านี่จะเป็นวิธี KISS ที่ดีสำหรับเกมเล็ก ๆ
The Duck Communist
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.