업적 처리를위한 유연한 프레임 워크를 설정하려면 어떻게해야합니까?


54

특히 "킬 x 적 "과 같은 단순한 통계 중심의 성과를 넘어서서 처리 할 수있을 정도로 유연한 성과 시스템을 구현하는 가장 좋은 방법은 무엇입니까?

나는 통계 기반 시스템보다 더 강력한 것을 찾고 있으며 "모두를 조건으로 하드 코딩"하는 것보다 더 체계적이고 유지 관리 가능한 것을 찾고 있습니다. 통계 기반 시스템에서 불가능하거나 다루기 힘든 일부 예 : "딸기 후 수박 자르기", "무적 상태에서 파이프 내려 가기"등

답변:


39

나는 일종의 강력한 해결책이 객체 지향 방식으로가는 것이라고 생각합니다.

지원하고자하는 업적에 따라 게임의 현재 상태 및 / 또는 게임 오브젝트 (플레이어 등)가 만든 액션 / 이벤트 기록을 쿼리 할 수있는 방법이 필요합니다.

다음과 같은 기본 Achievement 클래스가 있다고 가정 해 봅시다.

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 간단하고 우아한 솔루션. 축하합니다
Diego Palomar

+1 이벤트 방출 / 수집 시스템이이 문제를 처리하는 좋은 방법이라고 생각합니다.
ashes999

14

간단히 말해 특정 조건이 충족되면 업적이 잠금 해제됩니다. 따라서 원하는 조건을 확인하기 위해 if 문을 생성 할 수 있어야합니다.

예를 들어, 레벨이 완료되었거나 보스가 패배했다는 것을 알고 싶다면 이러한 이벤트가 발생할 때 부울 플래그를 true로 설정해야합니다.

그때:

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

원하는 조건에 맞게 필요에 따라 복잡하거나 단순하게 만들 수 있습니다.

Xbox 360 성과에 대한 일부 정보는 여기에서 확인할 수 있습니다 .


2
+1 훌륭한 기사, 본질적으로 내가 제안 할 내용. 모달이 업적 자체에서 텍스트를 얻도록 만들지 만 무언가를 변경하려는 경우 텍스트 사냥을 피하기 위해.
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를 직접 호출하려면 해당 "메시지"각각을 별도의 기능으로 만드십시오. 당신이 메시지를 사용하려고하는 경우, 다른 클래스뿐만 아니라 메시지를 사용할 수 있도록 (필자는 굼바 그가 살해 된 것 알고에 관심이있을 것이라고 확신한다) 메시지 매니저를 확인하고 해당 커플 링을 제거하기 위해 AchievementManager모든에 클래스 (이것은 OP가 처음에 피하는 방법을 묻는 것입니다). 그리고 문자열 리터럴이 아닌 메시지에 열거 형 또는 별도의 클래스를 사용하십시오. 문자열 리터럴을 사용하여 상태를 전달 하는 것은 항상 나쁜 생각입니다.
BlueRaja-대니 Pflughoeft

4

내가 사용한 마지막 디자인은 사용자 당 영구 카운터 집합을 기반으로 한 다음 특정 값을 치는 특정 카운터에서 성과 키를 얻었습니다. 대부분은 카운터가 0 또는 1에 불과하고 (> = 1에서 발생하는 업적) 단일 달성 / 카운터 쌍이지만 "킬링 된 X 친구들"또는 "발견 된 X 상자"에도 사용할 수 있습니다. 또한 성과가없는 것에 대한 카운터를 설정할 수 있으며 향후 사용을 위해 계속 추적됩니다.


3

마지막 게임에서 업적을 구현했을 때 모든 통계를 기반으로했습니다. 통계가 특정 값에 도달하면 성과가 잠금 해제됩니다. Modern Warfare 2 : 게임이 수많은 통계를 추적합니다! SCAR-H로 몇 번이나 촬영 했습니까? 경량 특전을 사용하는 동안 몇 마일을 질주 했습니까?

구현에서 통계 엔진을 만든 다음 게임 플레이 전체의 업적 상태를 확인하기 위해 매우 간단한 쿼리를 실행하는 업적 관리자를 만들었습니다.

내 구현은 매우 단순하지만 작업이 완료됩니다. 나는 그것에 대해 쓰고 여기에서 내 질문을 공유했습니다 .


2

이벤트 미적분을 사용하십시오 . 그런 다음 전제 조건이 충족 된 후 적용되는 전제 조건 및 조치를 작성하십시오.

  • 전제 조건 : 당신은 적 1000 명을 죽이고 다리가 2 개 있습니다
  • 액션 : 롤리팝, 슈퍼 듀퍼 샷건 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 방법입니다.
공산주의 오리
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.