Steamworks 문서
단계별 지침: 도전 과제

소개

애플리케이션에 아주 기본적인 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_api.dll을 찾을 수 없어 애플리케이션을 시작할 수 없습니다. 애플리케이션을 재설치하면 문제가 해결될 수 있습니다.
Steam_api.dll이 실행 파일과 같은 디렉터리에 있는지 확인해 주세요.

[S_API FAIL] SteamAPI_Init() 호출에 실패했습니다. 실행 중인 Steam 또는 로컬 steamclient.dll을 찾을 수 없습니다.
Steam 클라이언트가 실행 중이 아닐 가능성이 높습니다. Steam을 시작하고 로그인해 주세요.

[S_API FAIL] SteamAPI_Init() 호출에 실패했습니다. appID를 찾을 수 없습니다.
Steam_appid.txt 파일이 제자리에 없을 가능성이 높습니다. Steam_appid.txt 파일을 소스 폴더에 넣고 파일에 appID 번호가 있는지 확인해 주세요.