Documentação do Steamworks
Passo a passo: Proezas
Resumo
Use proezas para recompensar os jogadores por alcançarem certas metas ou por interagirem com o seu jogo de formas específicas.
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

Proezas podem ser usadas como uma forma de encorajar e recompensar interações e metas de jogadores no seu jogo. Muitas vezes, são usadas para marcar o número de vítimas, distância percorrida, tesouros abertos e outras ações comuns no jogo. Além disso, podem ser usadas para ajudar os jogadores a descobrir formas diferentes de jogar o jogo. Quando uma proeza é alcançada, uma notificação irá surgir no canto da janela do jogador e a proeza ficará registada na página de proezas do jogador.

Vista geral técnica

O presente artigo é um guia passo a passo rápido para integrar proezas básicas do Steam na sua aplicação em menos de 10 minutos e com menos de 10 linhas de código integradas na sua base de código. O SDK do Steamworks vem com uma boa aplicação de exemplo, intitulada Spacewar, que demonstra toda a gama de funcionalidades do Steam e que deve ser o seu ponto de referência para ver todas as funcionalidades do Steam em ação. Este tutorial tenta ser o mais direto possível ao resumir as informações encontradas no Spacewar e na API de estatísticas e proezas a apenas o necessário para estatísticas do Steam. Tenha em atenção que estatísticas e proezas têm muito em comum. Por isso, se estiver a integrar ambas, esteja ciente que muitas das chamadas poderão ser consolidadas.

Passo 1 – Definição das proezas do jogo

As proezas são específicas para cada aplicação e configuradas na página de configuração de proezas, situada na página de administração da aplicação no Steamworks. Segue-se abaixo uma lista de proezas do Spacewar, a aplicação de exemplo do Steamworks:

spacewar_achievement_examplescreenshot.jpg

Passo 2 – Encapsulamento do funcionamento das proezas

O seguinte código não depende de nenhum jogo e pode ser adicionado ao seu jogo como quiser. A classe é completamente funcional, mas pode ser expandida facilmente para atender a quaisquer outras necessidades. Todo este código foi retirado diretamente dos ficheiros de exemplo do Spacewar StatsAndAchievements.cpp/h.

Ficheiro de cabeçalho

Primeiro, definimos uma estrutura para armazenar os dados das proezas recebidos do Steam e um macro para criar objetos desse tipo. Estes dados são mapeados diretamente para os existentes na página de configuração de proezas.
#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; };

De seguida, definimos uma classe helper, que irá encapsular todas as chamadas à API de estatísticas do Steam assim como criar todos os callbacks do Steam.
class CSteamAchievements { private: int64 m_iAppID; // O nosso AppID atual Achievement_t *m_pAchievements; // Dados das proezas int m_iNumAchievements; // O número de proezas bool m_bInitialized; // Chamámos RequestStats e recebemos o callback? 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 ); };

Ficheiro de código

Construtor

Parâmetros – O construtor recebe um ponteiro para um array de proezas, juntamente com o tamanho do array. O formato do array será abordado no código principal do jogo, mais adiante.
Retorna – Não aplicável
O que faz – O construtor inicializa alguns membros e obtém o AppID que estamos a executar atualmente. Além disso, o construtor faz hook aos métodos de callback para gerir chamadas assíncronas realizadas ao Steam. Por último, realiza uma chamada inicial à função RequestStats() para obter as estatísticas e as proezas do utilizador 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 valor booleano que representa o resultado da chamada. Se a chamada falhou, é provável que o Steam não tenha sido inicializado. Assegure-se de que tem uma aplicação Steam aberta quando tentar realizar esta chamada e que a função SteamAPI_Init foi chamada antes disso.
O que faz – Este método basicamente encapsula uma chamada à função ISteamUserStats::RequestCurrentStats, que é uma chamada assíncrona ao Steam a pedir as estatísticas e proezas do utilizador atual. Esta chamada precisa de ser realizada antes de poder definir qualquer estatística ou proeza. A chamada inicial a este método é realizada no construtor. Pode realizar a chamada mais tarde, se quiser verificar estatísticas ou proezas atualizadas.
bool CSteamAchievements::RequestStats() { // O Steam está disponível? Se não estiver, não poderemos obter estatísticas. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // O utilizador está com sessão iniciada? Se não estiver, não poderemos obter estatísticas. if ( !SteamUser()->BLoggedOn() ) { return false; } // Pedir estatísticas do utilizador. return SteamUserStats()->RequestCurrentStats(); }

SetAchievement()

Parâmetros – Uma string que identifica a proeza que quer definir (por exemplo, "ACH_WIN_ONE_GAME")
Retorna – Um valor booleano que representa o resultado da chamada. Se a chamada falhou, isso é porque o Steam não foi inicializado ou porque ainda não processou o callback da primeira chamada à função RequestStats. Não pode definir proezas enquanto o callback não for recebido.
O que faz – Este método define uma determinada proeza como alcançada e envia os resultados para o Steam. Como é possível definir uma determinada proeza várias vezes, não precisa de se preocupar em tentar definir apenas aquelas que ainda não foram definidas. Esta é uma chamada assíncrona que irá ativar dois callbacks: OnUserStatsStored() e OnAchievementStored().
bool CSteamAchievements::SetAchievement(const char* ID) { // Recebemos um callback do Steam? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // Se não, ainda não podemos definir proezas return false; }

OnUserStatsReceived()

Parâmetros – Não aplicável
Retorna – Não retorna nada
O que faz – Este método é um callback que é realizado sempre que tentar pedir estatísticas. Estatísticas e proezas são pedidas através da função RequestStats(). O método atualiza a variável membro m_pAchievements para refletir os dados de estatísticas e proezas mais recentes do Steam.
void CSteamAchievements::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // Podemos receber callbacks de estatísticas de outros jogos, ignore-os if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString("Recebeu estatísticas e proezas do Steam\n"); m_bInitialized = true; // Carregar proezas 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()

Parâmetros – Não aplicável
Retorna – Não retorna nada
O que faz – Este método é um callback que é realizado sempre que tentar armazenar estatísticas no Steam.
void CSteamAchievements::OnUserStatsStored( UserStatsStored_t *pCallback ) { // Podemos receber callbacks de estatísticas de outros jogos, ignore-os if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Estatísticas do Steam armazenadas\n" ); } else { char buffer[128]; _snprintf( buffer, 128, "StatsStored - falha, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnAchievementStored()

Parâmetros – Não aplicável
Retorna – Não retorna nada
O que faz – Este método é um callback que é realizado sempre que proezas forem armazenadas com sucesso no Steam.
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // Podemos receber callbacks de estatísticas de outros jogos, ignore-os if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Proeza do Steam armazenada\n" ); } }

Passo 3 – Integração no jogo

Segue-se uma listagem completa de excertos de código que precisam de ser integrados nos locais apropriados do jogo.

Defines e globais

Segue-se uma lista de "includes" necessários para a criação com proezas, uma enumeração (enum) com as proezas específicas do jogo e um ponteiro global para o nosso objeto helper. Note que as proezas devem ter o mesmo nome presente na página de administração no Steamworks.
... #include "steam_api.h" // Definição das proezas enum EAchievements { ACH_WIN_ONE_GAME = 0, ACH_WIN_100_GAMES = 1, ACH_TRAVEL_FAR_ACCUM = 2, ACH_TRAVEL_FAR_SINGLE = 3, }; // Array de proezas que armazena os dados e o estado das proezas 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" ), }; // Acesso global ao objeto de proezas CSteamAchievements* g_SteamAchievements = NULL; ...

Inicialização

A chamada à função SteamAPI_Init inicializa o Steam e deve ser chamada antes de qualquer outra função. Se a chamada for bem-sucedida, criamos o objeto helper ao passar o array de proezas e o tamanho do array.
... // Inicialização do Steam bool bRet = SteamAPI_Init(); // Criação do objeto SteamAchievements se o Steam foi inicializado com sucesso if (bRet) { g_SteamAchievements = new CSteamAchievements(g_Achievements, 4); } ...

Processamento de callbacks

De forma a garantir o processamento de todos os callbacks do Steam, precisamos de verificar regularmente se há novas mensagens. Para tal, adicionamos a seguinte chamada ao ciclo do jogo.
... SteamAPI_RunCallbacks(); ...

Acionamento de proezas

Acionar uma proeza é tão simples como chamar uma função com o identificador da proeza.
... if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); ...

Encerramento

Provavelmente, já deve ter uma chamada à função SteamAPI_Shutdown no seu código. Esta função encerra o Steam e deve ser chamada antes do encerramento da sua aplicação. Por último, eliminamos o objeto helper que criámos.
... // Encerramento do Steam SteamAPI_Shutdown(); // Eliminação do objeto SteamAchievements if (g_SteamAchievements) delete g_SteamAchievements; ...

Passo 4 – Testes e resolução de problemas


Para definir ou limpar estatísticas e proezas sem adicionar código ao seu jogo, pode usar a consola da aplicação Steam. Inicie o Steam com "steam.exe -console" no atalho e escreva o seguinte na consola:
  • achievement_clear <AppID> <nome da proeza>
  • reset_all_stats <AppID>