Làm cách nào tôi có thể thiết lập một khung linh hoạt để xử lý thành tích?


54

Cụ thể, cách tốt nhất để thực hiện một hệ thống thành tích đủ linh hoạt để xử lý vượt xa các thành tích dựa trên thống kê đơn giản như "tiêu diệt x kẻ thù".

Tôi đang tìm kiếm thứ gì đó mạnh mẽ hơn hệ thống dựa trên thống kê và thứ gì đó có tổ chức và có thể bảo trì hơn là "mã hóa tất cả chúng dưới dạng điều kiện". Một số ví dụ không thể hoặc khó sử dụng trong một hệ thống dựa trên thống kê: "Cắt một quả dưa hấu sau quả dâu tây", "Đi xuống một đường ống trong khi bất khả chiến bại", v.v.

Câu trả lời:


39

Tôi nghĩ rằng một loại giải pháp mạnh mẽ sẽ là đi theo hướng đối tượng.

Tùy thuộc vào loại thành tích bạn muốn hỗ trợ, bạn cần một cách để truy vấn trạng thái hiện tại của trò chơi của bạn và / hoặc lịch sử hành động / sự kiện mà các đối tượng trò chơi (như người chơi) đã thực hiện.

Giả sử bạn có một lớp Thành tích cơ bản như:

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

AbstractAchievementgiữ một tham chiếu đến trạng thái của trò chơi. Nó được sử dụng để truy vấn những điều đang xảy ra.

Sau đó, bạn thực hiện cụ thể. Hãy sử dụng các ví dụ của bạn:

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

Sau đó, tùy bạn quyết định khi nào nên kiểm tra bằng IsEarned()phương pháp. Bạn có thể kiểm tra tại mỗi bản cập nhật trò chơi.

Ví dụ, một cách hiệu quả hơn sẽ có một loại quản lý sự kiện. Và sau đó đăng ký các sự kiện (chẳng hạn như PlayerHasSlicedSomethingEventhoặc PlayerGotInvicibleEventđơn giản PlayerStateChanged) vào một phương thức sẽ lấy thành tích trong tham số. Thí dụ:

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
phong cách nitpick: if(...) return true; else return false;giống nhưreturn (...)
BlueRaja - Danny Pflughoeft

2
Đó là một khuôn mẫu rất tốt để thực hiện một hệ thống thành tích. Hơn nữa, nó vẫn thể hiện ý tưởng rằng bạn phải có cách theo dõi trạng thái trò chơi. Tôi thấy đó có lẽ là ý tưởng phức tạp nhất.
Bryan Harrington

@Spio bạn là một bậc thầy ...! : D Giải pháp đơn giản và thanh lịch. Chúc mừng.
Diego Palomar

+1 Tôi nghĩ rằng một hệ thống phát / thu thập sự kiện là một cách tuyệt vời để xử lý vấn đề này.
tro999

14

Chà, tóm lại, thành tích được mở khóa khi một điều kiện nhất định được đáp ứng. Vì vậy, bạn cần có khả năng tạo ra các câu lệnh if để kiểm tra điều kiện bạn muốn.

Ví dụ: nếu bạn muốn biết rằng một cấp đã hoàn thành hoặc một ông chủ bị đánh bại, bạn cần phải có cờ boolean biến thành sự thật khi những sự kiện này xảy ra.

Sau đó:

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

Bạn có thể làm điều này phức tạp hoặc đơn giản khi cần để phù hợp với điều kiện mà bạn muốn.

Một số thông tin về thành tích Xbox 360 có thể được tìm thấy ở đây .


2
+1 Bài viết tuyệt vời, và về cơ bản là những gì tôi sẽ đề xuất. Mặc dù tôi sẽ làm cho Modal lấy văn bản của nó từ chính thành tích đó ... chỉ để tránh việc săn văn bản nếu bạn muốn thay đổi điều gì đó.
Jesse Dorsey

@Noctrine - đừng quên bất kỳ mã nào được đăng ở đây nên được coi là mã giả - thường cần sử dụng mã đơn giản hóa để có được điểm.
ChrisF

1
Liên kết thành tích Xbox 360 đã chết.
nôn nao


8

Điều gì sẽ xảy ra nếu bạn thực hiện mọi hành động mà người chơi sẽ gửi một thông điệp tới AchievementManager? Sau đó, người quản lý có thể kiểm tra nội bộ xem một số điều kiện đã được đáp ứng. Đối tượng đầu tiên gửi tin nhắn:

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

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

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

Và sau đó AchievementManagerkiểm tra nếu nó cần phải làm bất cứ điều gì:

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

Có lẽ bạn sẽ muốn làm điều này với bảng liệt kê thay vì chuỗi. ;)


Đây là những gì tôi nghĩ, nhưng nó vẫn dựa vào một loạt các công cụ được mã hóa cứng trong một chức năng. Bảo trì sang một bên, ít nhất mọi thành tích phải được kiểm tra đủ điều kiện mỗi lần thông qua chức năng.
lti

2
Phải không? Bạn có thể đặt các điều kiện trong một kịch bản bên ngoài, miễn là bạn có một số cách theo dõi những gì đã xảy ra trong trò chơi.
hiệp sĩ666

6
-1 cái này của thiết kế xấu. Nếu bạn định gọi trực tiếp AchivementManager, chỉ cần biến mỗi "tin nhắn" đó thành một chức năng riêng. Nếu bạn định sử dụng tin nhắn, hãy tạo trình quản lý tin nhắn để các lớp khác cũng có thể sử dụng tin nhắn (Tôi chắc chắn Goomba sẽ quan tâm đến việc biết mình đã bị giết) và xóa mọi liên kết đó với AchievementManagermọi người lớp (đó là những gì OP đã hỏi làm thế nào để tránh ở nơi đầu tiên). Và sử dụng một enum hoặc các lớp riêng biệt cho các tin nhắn của bạn, không phải là chuỗi ký tự - sử dụng chuỗi ký tự chuỗi để chuyển qua trạng thái luôn là một ý tưởng tồi.
BlueRaja - Daniel Pflughoeft

4

Thiết kế cuối cùng tôi sử dụng dựa trên việc có một bộ đếm liên tục cho mỗi người dùng và sau đó có khóa thành tích từ một bộ đếm nhất định đạt một giá trị nhất định. Hầu hết là một cặp thành tích / bộ đếm duy nhất trong đó bộ đếm sẽ chỉ là 0 hoặc 1 (và thành tích được kích hoạt vào> = 1), nhưng bạn cũng có thể sử dụng số này cho "các nhân vật X bị giết" hoặc "tìm thấy các rương X". Điều đó cũng có nghĩa là bạn có thể thiết lập bộ đếm cho thứ gì đó không có thành tích và nó vẫn sẽ được theo dõi để sử dụng trong tương lai.


3

Khi tôi thực hiện thành tích trong trò chơi cuối cùng của mình, tôi đã làm cho tất cả dựa trên thống kê. Thành tích được mở khóa khi số liệu thống kê của chúng tôi đạt đến một giá trị nhất định. Hãy xem xét Modern Warfare 2: trò chơi đang theo dõi hàng tấn chỉ số! Bạn đã chụp được bao nhiêu bức ảnh với SCAR-H? Có bao nhiêu dặm đã bạn chạy nước rút trong khi sử dụng perk Lightweight?

Vì vậy, trong quá trình thực hiện, tôi chỉ cần tạo một công cụ thống kê, sau đó tạo một trình quản lý thành tích chạy các truy vấn thực sự đơn giản để kiểm tra trạng thái thành tích trong suốt quá trình chơi trò chơi.

Mặc dù việc thực hiện của tôi khá đơn giản, nhưng nó hoàn thành công việc. Tôi đã viết về nó và chia sẻ các truy vấn của tôi ở đây .


2

Sử dụng tính toán sự kiện . Sau đó thực hiện một số điều kiện tiên quyết và hành động được áp dụng sau khi điều kiện tiên quyết được đáp ứng:

  • Điều kiện tiên quyết: bạn đã giết 1000 kẻ thù, bạn có 2 chân
  • hành động: cho tôi lollypop, cho tôi siêu duper-shotgun-13
  • lần đầu tiên hành động: nói "bạn thật tuyệt vời!"

Sử dụng nó như thế nào (không được tối ưu hóa cho tốc độ!):

  • Lưu trữ tất cả các số liệu thống kê.
  • Thống kê truy vấn cho điều kiện tiên quyết.
  • Áp dụng các hành động.
  • Áp dụng hành động một lần một lần.

Nếu bạn muốn làm cho nó nhanh:

  • Lưu trữ bất cứ thứ gì bạn muốn, lưu trữ các phần của nó trong một số cây, băm ...
  • Thay đổi gia tăng để bạn không áp dụng tất cả các hành động mọi lúc nhưng đây là những hành động mới ...)

Ghi chú

Thật khó để đưa ra lời khuyên tốt nhất vì tất cả mọi thứ đều có ưu và nhược điểm.

  • "Cơ sở hạ tầng tốt nhất là gì?" ngụ ý "Hoạt động nào bạn muốn thực hiện với nó nhất? Tìm kiếm, xóa, thêm ..."
  • Về cơ bản, bạn nghĩ về các tính chất này: dễ mã hóa, tốc độ, rõ ràng, kích thước ...

0

Có gì sai với kiểm tra IF sau khi sự kiện thành tích xảy ra?

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
'Tôi đang tìm kiếm thứ gì đó có tổ chức và có thể bảo trì hơn là "mã hóa tất cả chúng dưới dạng điều kiện." '. Mặc dù đây chắc chắn là một phương pháp KISS tốt cho một trò chơi nhỏ.
Vịt Cộng sản
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.