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


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.