Documentação do Steamworks
Passo a passo: Conquistas
Resumo
Use achievements to reward players for hitting certain milestones or for interacting with your game in particular ways.
Nível de integração
10 minutos e menos de 10 linhas de código. Requer integração com o SDK do Steamworks.

Introdução

Achievements can be used as a way to encourage and reward player interactions and milestones within your game. Elas costumam ser usadas para contar a quantidade de vítimas, quilômetros percorridos, tesouros abertos ou outras ações comuns no jogo, mas também servem para ajudar os jogadores a descobrir formas diferentes de jogar o jogo. When unlocked, these achievements will pop up in the corner of the players' window and will be marked within an achievement page for that player.

Visão geral técnica

The following is a quick step by step guide to integrating very basic Steam Achievements into your application in under 10 minutes and under 10 lines of code integrated into your code base. The Steamworks SDK comes with a great example application called Spacewar that shows off the full breadth of Steam features and should be your first stop to see all Steam features in action. This tutorial distills the information found in Spacewar and the Stats and Achievements API down to just the necessary information required for Steam Stats to keep things as straightforward as possible. Please note that there is a considerable amount of overlap between stats and achievements, as such if you are integrating both be aware that a lot of calls can be consolidated.

1º passo — Definição das conquistas do jogo

Conquistas são separadas por aplicativo e configuradas na página de configuração de conquistas no backend de administração de aplicativo do Steamworks. A seguir, está a lista de conquistas do aplicativo de exemplo do Steamworks, denominado Spacewar:
spacewar_achievements.png

2º passo — Encapsulamento do funcionamento de conquistas

O código a seguir independe do jogo e pode ser adicionado ao seu jogo da forma que achar melhor. A classe é completamente funcional, mas pode ser estendida para atender a necessidades adicionais. O código foi extraído dos arquivos de exemplo do Spacewar StatsAndAchievements.cpp/h. Strings literais e comentários foram traduzidos para uma melhor compreensão.

Arquivo de cabeçalho

Primeiro definimos uma estrutura para armazenar os dados de conquista recebidos do Steam e um atalho para criar objetos do tipo. Esses dados são mapeados diretamente aos existentes na página de configuração de conquistas.
#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; };

Next we define a helper class that will wrap all of the Steam Stats API calls as well as creating all of the Steam callbacks.
class CSteamAchievements { private: int64 m_iAppID; // Nosso AppID atual Achievement_t *m_pAchievements; // Dados das conquistas int m_iNumAchievements; // Quantidade de conquistas bool m_bInitialized; // Chamamos a funcao RequestStats e recebemos o retorno de chamada? 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 ); };

Arquivo de código

Construtor

Parâmetros - The constructor takes a pointer to an array of achievements along with the length of the array. A formatação do vetor será abordada no código principal do jogo, mais adiante.
Retorna — Nada
O que faz — O construtor inicializa alguns membros e recupera o AppID em execução. Além disso, ele associa os métodos de retorno de chamada para tratar chamadas assíncronas feitas ao Steam. Por último, realiza uma chamada inicial à função RequestStats() para recuperar as estatísticas e conquistas do usuário atual.
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()

Parâmetros — Nenhum
Retorna — um bool que representa o sucesso da chamada. Se a chamada falhou, a causa comum é a não inicialização do Steam. Confirme que há uma instância do cliente Steam aberta ao realizar esta chamada e que a função SteamAPI_Init foi chamada anteriormente.
O que faz — Este método basicamente encapsula uma chamada à função ISteamUserStats::RequestCurrentStats, que é uma chamada assíncrona ao Steam requisitando as estatísticas e conquistas do usuário atual. Esta chamada deve ser realizada antes de definir qualquer estatística ou conquista. A chamada inicial a este método é realizada no construtor. É possível chamá-la posteriormente, quando quiser, caso deseje verificar se há estatísticas ou conquistas atualizadas.
bool CSteamAchievements::RequestStats() { // Steam carregado? Caso contrario, nao podemos recuperar estatisticas. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // Usuario com sessao inciada? Caso contrario, nao podemos recuperar estatisticas. if ( !SteamUser()->BLoggedOn() ) { return false; } // Recuperar estatisticas do usuario. return SteamUserStats()->RequestCurrentStats(); }

SetAchievement()

Parâmetros - The string identifier of the Achievement that you want to set (ie. "CONQ_VENCER_UMA_PARTIDA")
Retorna — um bool que representa o sucesso da chamada. Se a chamada falhou, então o Steam não está inicializado ou o processamento do retorno da primeira chamada à função RequestStats ainda não foi realizado. Não é possível definir conquistas até o recebimento do retorno de chamada.
O que faz — Este método define uma dada conquista como alcançada e envia os resultados ao Steam. É possível definir uma dada conquista várias vezes, então não se preocupe em definir conquistas já alcançadas. Esta é uma chamada assíncrona que disparará dois retornos de chamada: OnUserStatsStored() e OnAchievementStored().
bool CSteamAchievements::SetAchievement(const char* ID) { // Ja recebemos o retorno de chamada do Steam? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // Caso contrario, ainda nao podemos definir conquistas return false; }

OnUserStatsReceived()

Parâmetros — Nenhum
Retorna — Nada
O que faz — Este método é um retorno de chamada que é chamado toda vez que você tentar recuperar estatísticas. Estatísticas e conquistas são recuperadas por meio da função RequestStats(). O método atualiza a variável-membro m_pAchievements para apontar aos dados de estatísticas e conquistas mais recentes do Steam.
void CSteamAchievements::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // Podemos receber retornos de chamada de estatisticas de outros jogos, ignore-os if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString("Recebeu estatisticas e conquistas do Steam\\n"); m_bInitialized = true; // Carregar conquistas 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 - falhou, %d\\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnUserStatsStored()

Parâmetros — Nenhum
Retorna — Nada
O que faz — Este método é um retorno de chamada disparado sempre que o jogo tentar armazenar estatísticas no Steam.
void CSteamAchievements::OnUserStatsStored( UserStatsStored_t *pCallback ) { // Podemos receber retornos de chamada de estatisticas de outros jogos, ignore-os if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Estatisticas do Steam armazenadas\\n" ); } else { char buffer[128]; _snprintf( buffer, 128, "StatsStored - falha, %d\\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnAchievementStored()

Parâmetros - N/A
Retorna - Nothing
O que faz — Este método é um retorno de chamada, disparado sempre que conquistas são armazenadas com sucesso no Steam.
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // Podemos receber retornos de chamada de estatisticas de outros jogos, ignore-os if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Conquista do Steam armazenada\\n" ); } }

Step 3 - Integrating into your game

A seguir, encontra-se uma listagem completa de trechos de códigos que precisam ser integrados ao jogo nos locais apropriados.

Defines e globais

A seguir, está uma lista de includes necessários para a construção com conquistas, uma enumeração com as conquistas específicas do jogo e um ponteiro global ao nosso objeto auxiliar. Observe que as conquistas devem ter o mesmo nome que o "Nome na API" na página de administração do Steamworks.
... #include "steam_api.h" // Definicao das conquistas enum EAchievements { ACH_WIN_ONE_GAME = 0, ACH_WIN_100_GAMES = 1, ACH_TRAVEL_FAR_ACCUM = 2, ACH_TRAVEL_FAR_SINGLE = 3, }; // Vetor de conquistas, para armazenar dados e o estado das conquistas Achievement_t g_Achievements[] = { _ACH_ID( ACH_WIN_ONE_GAME, "Vencedor" ), _ACH_ID( ACH_WIN_100_GAMES, "Campeao" ), _ACH_ID( ACH_TRAVEL_FAR_ACCUM, "Interestelar" ), _ACH_ID( ACH_TRAVEL_FAR_SINGLE, "Orbitador" ), }; // Acesso global ao objeto auxiliar de conquistas CSteamAchievements*g_SteamAchievements = NULL; ...

Inicialização

The call to SteamAPI_Init initializes all of Steam and must be called before anything else. If that call succeeds then we create the helper object by passing in the array of achievements along with the size of the array.
... // Inicializacao do Steam bool bRet = SteamAPI_Init(); // Criacao do objeto SteamAchievements se o Steam foi inicializado com sucesso if (bRet) { g_SteamAchievements = new CSteamAchievements(g_Achievements, 4); } ...

Processamento de retornos de chamada

To ensure that we process all Steam callbacks we need to regularly pump for new messages. This is achieved by adding this call to the game loop.
... SteamAPI_RunCallbacks(); ...

Disparo de conquistas

Triggering an achievement is as simple as a single call passing along the achievement identifier.
... if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); ...

Encerramento

The call to SteamAPI_Shutdown is probably something you already have in your code. Ela encerra o Steam e deve ser chamada antes do encerramento do aplicativo. Por último, excluímos o objeto auxiliar criado.
... // Encerramento do Steam SteamAPI_Shutdown(); // Exclusao do objeto SteamAchievements if (g_SteamAchievements) delete g_SteamAchievements; ...

4º passo — Testes e solução de problemas


Para alterar ou remover o valor de uma estatística ou conquista sem adicionar código ao jogo, use o console do cliente Steam. Inicie o cliente Steam por meio do comando "steam.exe -console" e então execute os seguintes comandos no console:
  • achievement_clear <AppID> <nome da conquista> (para definir a conquista como não alcançada)
  • reset_all_stats <AppID> (para zerar todas as estatísticas)