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:

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>