Steamworks 문서
단계별 지침: 도전 과제
요약
도전 과제를 사용해, 특정 목표를 달성했거나 다양한 방법으로 게임에서 상호작용한 플레이어들에게 보상을 제공할 수 있습니다.
통합 레벨
10줄 미만의 코드를 10분 이내에 통합할 수 있습니다. Steamworks SDK를 통합해야 합니다.

소개

도전 과제는 게임에서 특정 목표를 달성했거나 플레이어 상호작용에 대해 보상하고 이를 권장하는 데 사용됩니다. 주로 게임에서 적을 처치한 수, 이동 거리, 상자를 열거나 이와 비슷한 동작을 표시할 때 사용됩니다. 또한 플레이어가 좀 더 다양한 게임 플레이 방법을 찾는 데 도움을 줄 수도 있습니다. 도전 과제들을 잠금 해제하면 플레이어의 화면 구석에 알림 팝업이 표시되며 해당 플레이어의 도전 과제 페이지에도 달성 표시가 나타납니다.

기술 개요

애플리케이션에 아주 기본적인 Steam 도전 과제를 통합하는 과정을 단계별로 간단히 보여 드리겠습니다. 코드 베이스에 10줄 미만의 코드를 10분 이내에 통합할 수 있는 방법입니다. Steamworks SDK에는 Steam의 모든 기능을 구현해 볼 수 있는 Spacewar라는 예시용 애플리케이션이 포함되어 있어, Steam 기능을 실제로 시험해 볼 수 있는 좋은 기회를 제공합니다. 이 튜토리얼은 Spacewar와 통계 및 도전 과제 API에서 찾을 수 있는 정보 중 필요한 정보만을 간추려 Steam 통계를 최대한 간단하게 활용할 수 있도록 했습니다. 통계와 도전 과제 사이에는 겹치는 내용이 상당히 많으므로, 두 개를 모두 통합할 경우 다수의 호출을 통합해야 한다는 점에 유의해 주세요.

1단계 - 게임 도전 과제 정의하기

도전 과제는 애플리케이션에 따라 다르며, Steamworks 앱 관리자 백앤드의 도전 과제 구성 페이지에서 설정할 수 있습니다. 다음은 Steamworks 샘플 앱인 Spacewar의 도전 과제 목록입니다.
spacewar_achievements.png

2단계 - 도전 과제 캡슐화 작업

다음 코드는 게임과 별도로 존재하며 필요에 따라 게임에 추가할 수 있습니다. 클래스는 그 자체로도 작동하지만, 필요에 따라 손쉽게 확장할 수 있습니다. 모든 코드는 Spacewar 예제 파일 StatsAndAchievements.cpp/h에서 직접 가져온 것입니다.

헤더 파일

먼저 Steam에서 받는 도전 과제 데이터를 보존할 구조를 정의합니다. 그리고 해당 형식의 개체 생성을 위한 매크로를 제공합니다. 이 데이터는 도전 과제 구성 페이지의 내용을 바로 매핑합니다.
#define _ACH_ID( id, name ) { id, #id, name, "", 0, 0 } struct Achievement_t { int m_eAchievementID; const char *m_pchAchievementID; char m_rgchName[128]; char m_rgchDescription[256]; bool m_bAchieved; int m_iIconImage; };

다음으로 Steam 통계 API 호출을 모두 래핑하고 Steam 콜백을 모두 생성할 헬퍼 클래스를 정의합니다.
class CSteamAchievements { private: int64 m_iAppID; // 현재 AppID Achievement_t *m_pAchievements; // 도전 과제 데이터 int m_iNumAchievements; // 도전 과제 수 bool m_bInitialized; // 요청 통계를 호출하고 콜백을 수신했나요? public: CSteamAchievements(Achievement_t *Achievements, int NumAchievements); ~CSteamAchievements(); bool RequestStats(); bool SetAchievement(const char* ID); STEAM_CALLBACK( CSteamAchievements, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived ); STEAM_CALLBACK( CSteamAchievements, OnUserStatsStored, UserStatsStored_t, m_CallbackUserStatsStored ); STEAM_CALLBACK( CSteamAchievements, OnAchievementStored, UserAchievementStored_t, m_CallbackAchievementStored ); };

코드 파일

생성자

매개변수 - 생성자는 도전 과제 배열과 배열 길이를 가리키는 포인터를 받습니다. 해당 배열의 서식에 대해서는 추후에 주요 게임 코드에서 다루겠습니다.
반환값 - 해당 없음
기능 - 생성자는 일련의 멤버를 초기화하고 현재 실행 중인 AppID를 가져옵니다. 또한, Steam에 대한 비동기 호출을 처리하는 콜백 메서드를 연결합니다. 마지막으로 현재 사용자의 통계와 도전 과제를 가져오기 위해 RequestStats()를 최초로 호출합니다.
CSteamAchievements::CSteamAchievements(Achievement_t *Achievements, int NumAchievements): m_iAppID( 0 ), m_bInitialized( false ), m_CallbackUserStatsReceived( this, &CSteamAchievements::OnUserStatsReceived ), m_CallbackUserStatsStored( this, &CSteamAchievements::OnUserStatsStored ), m_CallbackAchievementStored( this, &CSteamAchievements::OnAchievementStored ) { m_iAppID = SteamUtils()->GetAppID(); m_pAchievements = Achievements; m_iNumAchievements = NumAchievements; RequestStats(); }

RequestStats()

매개변수 - 없음
반환값 - 호출의 성공 여부를 나타내는 부울 값. 호출이 실패했다면 Steam이 초기화되지 않았기 때문일 가능성이 높습니다. 이 호출을 시도할 때는 Steam 클라이언트가 열려 있는지, SteamAPI_Init가 먼저 호출되었는지 반드시 확인해 주세요.
기능 - 이 메서드는 기본적으로 현재 사용자의 통계 및 도전 과제를 Steam에 요청하는 비동기 호출인 ISteamUserStats::RequestCurrentStats에 호출을 랩핑합니다. 이 호출은 통계 및 도전 과제를 하나라도 설정하기 전에 이루어져야 합니다. 이 메서드에 대한 최초 호출은 생성자에서 이루어집니다. 이후 업데이트된 통계 또는 도전 과제 상태를 확인하고자 하는 경우 언제든지 다시 호출할 수 있습니다.
bool CSteamAchievements::RequestStats() { // Steam이 실행 중인가요? 그렇지 않은 경우 통계를 얻을 수 없습니다. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // 사용자가 로그인했나요? 그렇지 않은 경우 통계를 얻을 수 없습니다. if ( !SteamUser()->BLoggedOn() ) { return false; } // 사용자 통계를 요청하세요. return SteamUserStats()->RequestCurrentStats(); }

SetAchievement()

매개변수 - 설정하고자 하는 도전 과제의 문자열 식별자 (예: ‘ACH_WIN_ONE_GAME’)
반환값 - 호출의 성공 여부를 나타내는 부울 값. 호출이 실패했다면 Steam이 초기화되지 않았거나 RequestStats에 대한 최초 호출의 콜백을 아직 처리하지 않았을 수 있습니다. 해당 콜백을 수신하기 전에는 어떤 도전 과제도 설정할 수 없습니다.
기능 - 이 메서드는 주어진 도전 과제를 달성 상태로 설정한 후 그 결과를 Steam에 전송합니다. 주어진 도전 과제는 여러 번 설정할 수 있으므로 기존에 설정되지 않은 도전 과제에 대해서만 설정을 하려고 고심할 필요가 없습니다. 이것은 비동기 호출로써 OnUserStatsStored()OnAchievementStored()의 두 가지 콜백을 호출합니다.
bool CSteamAchievements::SetAchievement(const char* ID) { // Steam으로부터 콜백을 수신했나요? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // 그렇지 않은 경우 도전 과제를 설정할 수 없습니다. return false; }

OnUserStatsReceived()

매개변수 - 해당 없음
반환값 - 없음
기능 - 이 메서드는 통계 요청을 시도할 때마다 호출되는 콜백입니다. 통계와 도전 과제는 RequestStats()를 사용하여 호출합니다. 해당 메서드는 Steam에서 반환된 최신 통계 및 도전 과제를 반영하는 m_pAchievements 멤버 변수를 업데이트합니다.
void CSteamAchievements::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // 다른 게임 통계의 콜백을 수신할 경우 무시하세요. if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString("Received stats and achievements from Steam\n"); m_bInitialized = true; // 도전 과제를 불러오세요. for ( int iAch = 0; iAch < m_iNumAchievements; ++iAch ) { Achievement_t &ach = m_pAchievements[iAch]; SteamUserStats()->GetAchievement(ach.m_pchAchievementID, &ach.m_bAchieved); _snprintf( ach.m_rgchName, sizeof(ach.m_rgchName), "%s", SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID, "name")); _snprintf( ach.m_rgchDescription, sizeof(ach.m_rgchDescription), "%s", SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID, "desc")); } } else { char buffer[128]; _snprintf( buffer, 128, "RequestStats - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnUserStatsStored()

매개변수 - 해당 없음
반환값 - 없음
기능 - 이 메서드는 Steam에 통계 저장을 시도할 때마다 호출되는 콜백입니다.
void CSteamAchievements::OnUserStatsStored( UserStatsStored_t *pCallback ) { // 다른 게임 통계의 콜백을 수신할 경우 무시하세요. if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Stored stats for Steam\n" ); } else { char buffer[128]; _snprintf( buffer, 128, "StatsStored - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnAchievementStored()

매개변수 - 해당 없음
반환값 - 없음
기능 - 이 메서드는 Steam에 도전 과제가 성공적으로 저장될 때마다 호출되는 콜백입니다.
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // 다른 게임 통계의 콜백을 수신할 경우 무시하세요. if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Stored Achievement for Steam\n" ); } }

3단계 - 게임에 통합하기

다음은 게임 안의 적절한 위치에 통합할 때 필요한 코드 조각의 전체 목록입니다.

정의 및 전역

다음은 도전 과제를 빌드하는 데 필요한 항목, 게임별 도전 과제의 열거, 도우미 개체에 대한 전역 포인터를 포함한 목록입니다. 도전 과제는 Steamworks 관리자 페이지의 도전 과제와 일치해야 한다는 점에 유의해 주세요.
... #include "steam_api.h" // 도전 과제 정의 enum EAchievements { ACH_WIN_ONE_GAME = 0, ACH_WIN_100_GAMES = 1, ACH_TRAVEL_FAR_ACCUM = 2, ACH_TRAVEL_FAR_SINGLE = 3, }; // 도전 과제와 도전 과제의 상태에 대한 데이터를 가지고 있는 도전 과제 모음 Achievement_t g_Achievements[] = { _ACH_ID( ACH_WIN_ONE_GAME, "Winner" ), _ACH_ID( ACH_WIN_100_GAMES, "Champion" ), _ACH_ID( ACH_TRAVEL_FAR_ACCUM, "Interstellar" ), _ACH_ID( ACH_TRAVEL_FAR_SINGLE, "Orbiter" ), }; // 도전 과제 개체의 전역 액세스 CSteamAchievements* g_SteamAchievements = NULL; ...

초기화

SteamAPI_Init을 호출하면 Steam 전체가 초기화되므로 가장 먼저 호출해야 합니다. 해당 호출이 성공적일 경우 도전 과제 배열과 배열의 크기를 전달해 도우미 개체를 생성합니다.
... // Steam 초기화 bool bRet = SteamAPI_Init(); // Steam이 성공적으로 초기화되었을 경우, Steam 도전 과제 개체 생성 if (bRet) { g_SteamAchievements = new CSteamAchievements(g_Achievements, 4); } ...

콜백 처리하기

모든 Steam 콜백을 처리하려면 신규 메시지를 규칙적으로 확인해야 합니다. 그렇게 하려면 게임 루프에 위 호출을 추가하면 됩니다.
... SteamAPI_RunCallbacks(); ...

도전 과제 작동시키기

도전 과제는 도전 과제 식별자와 단일 호출을 전달하는 것으로 간단하게 트리거할 수 있습니다.
... if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); ...

종료

SteamAPI_Shutdown 호출은 이미 귀하의 코드 안에 있을 것입니다. 이 호출은 Steam을 종료하는 것으로 애플리케이션에서 나가기 전에 호출해야 합니다. 마지막으로, 생성한 도우미 개체를 삭제합니다.
... // Steam 종료 SteamAPI_Shutdown(); // Steam 도전 과제 개체 삭제 if (g_SteamAchievements) delete g_SteamAchievements; ...

4단계 - 테스트 및 문제 해결


게임에 코드를 추가하지 않고 통계나 도전 과제를 설정하거나 지우려면, Steam 클라이언트 콘솔을 사용하세요. steam.exe -console 실행 후 다음을 입력하세요.
  • achievement_clear <appid> <achievement name>
  • reset_all_stats <appid>