(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

(:3[kanのメモ帳]



Steamのランキング(リーダーボード)をUnityで実装する【Unity】【Steam】


このエントリーをはてなブックマークに追加


この記事でのバージョン
Unity 2018.4.8f1


はじめに

今回はUnityで作ったゲームに、Steamのランキングを実装する方法の解説です!

f:id:kan_kikuchi:20200206045054j:plain


ちなみにVRゲームでも全く同じ方法で実装可能です。

なお、Steamworksへの登録が済んでいる前提ですので、あしからず。


また、実績とは違い実装が結構面倒なので、



アセットを使ってみても良いかもしれません。(こちらは未確認)

Easy Steam Leaderboards - Asset Store



Steam(ブラウザ)上での設定

まずはSteam(ブラウザ)上での設定について解説していきます。


ランキングは「テクニカルツール」「Steamworks 設定を編集」にある

f:id:kan_kikuchi:20191013161607j:plain


「データ&実績」「ランキング」から設定する事が出来ます。

f:id:kan_kikuchi:20200130044335j:plain


諸々を入力して、作成を押すとランキングが追加出来る感じです。

f:id:kan_kikuchi:20200130050348j:plain


なお、各項目の意味は以下の通り。

  • 名前-ここには、内部開発用に意味のある名前を設定します。
  • コミュニティ名-ランキングをコミュニティハブに表示する場合は、ここに一般公開用の名前を設定します。 名前が入力されていない場合は、ランキングは表示されません。
  • 並べ替え方法-ランキングの並べ替え順序を設定します。 順位が基準のランキングでは昇順を使用し、 ハイスコアが基準の場合は、降順を使用してください。
  • 表示タイプ-ランキングで表示するデータのタイプを決定します。 数値、秒またはミリ秒から選択します。
  • 書き込み-これが「信頼済み」に設定されている場合、クライアントによってランキングスコアを設定することはできず、SetLeaderboardScore WebAPIによってのみ設定できます。 デフォルトでは設定されていません。
  • 読み取り-これが「フレンド」に設定されている場合、ゲームはユーザーのフレンドのランキングスコアのみを読み取ることができます。すべてのスコアはWebAPIによって常に読み取り可能です。 デフォルトでは設定されていません。


作成すると、ランキング一覧に表示され、登録済みのスコアを確認する事も出来ます。

f:id:kan_kikuchi:20200130050312j:plain



Steamworks.NETの導入

次はプロジェクトにSteamworks.NETを導入するのですが、これをそのまま入れるより



Steamworks.NET-Exampleというサンプル付きのものを導入した方が色々便利です。



上記のGitHubからプロジェクトを取得したら、Assets以下を自分のプロジェクトへコピーします。

f:id:kan_kikuchi:20191012075058j:plain
f:id:kan_kikuchi:20191012075612j:plain


この時、Steamworks.NET-Exampleなどのディレクトリにまとめて入れると後々分かりやすいです。

f:id:kan_kikuchi:20191012075618j:plain


デフォルトでサンプルのゲームが設定されているので、

この状態でMainSceneを実行し、ボタンを押すと、

実績が解除され、お馴染みの実績解除演出(?)が右上に表示されます。

(VRではVR上にポップアップが表示される)

f:id:kan_kikuchi:20191012090956j:plain
f:id:kan_kikuchi:20191012091400g:plain


なお、Steamを起動してないとエラーが出ます。

[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.

f:id:kan_kikuchi:20191012085325j:plain


自分のゲームを設定する時は

Assetsと同じ階層(Unityからは見えない所)にあるsteam_appid.txtにAppIDを設定し、

Unityを再起動します。(再起動しないと変更が反映されない)

f:id:kan_kikuchi:20191015112047j:plain


ここでいうAppIDとはSteamのゲームごとに設定されているIDの事です。

f:id:kan_kikuchi:20191013055301j:plain



実装

最後にランキングの取得やスコアの送信をプログラムで実装する方法をざっくりと解説していきます。

なお、実装にはusing Steamworks;が必要です。


初期化

まずはSteamManager.Initializedで初期化を行う必要があります。

なお、返り値が初期化が成功したかのbool値となっています。

//初期化
if (SteamManager.Initialized){
  //初期化が成功したらAppIDを表示(480はサンプルのID)
  Debug.Log($"Steamの初期化成功, AppID : {SteamUtils.GetAppID()}");
}
else {
  Debug.Log("Steamの初期化失敗");
}
f:id:kan_kikuchi:20191013055437j:plain


ちなみに、ユーザ名はSteamFriends.GetPersonaNameで取得可能です。


ランキングの取得

ランキングの取得はログイン後、CallResultのCreateで作成し、

そこにFindLeaderboardで取得したSteamAPICall_tをSetで設定する感じです。

具体的には以下のような感じ。

using System;
using UnityEngine;
using Steamworks;

/// <summary>
/// ランキングのサンプル
/// </summary>
public class RankingSample : MonoBehaviour {
  
  //=================================================================================
  //初期化
  //=================================================================================
  
  private void Start(){
    if (SteamManager.Initialized){
      Debug.Log($"Steamの初期化成功, AppID : {SteamUtils.GetAppID()}");
      //初期化が成功したらリーダーボードを探す
      FindLeaderboard();
    }
    else {
      Debug.LogWarning("Steamの初期化失敗");
    }
  }
  
  //=================================================================================
  //リーダーボードの取得
  //=================================================================================

  //リーダーボードの取得
  private void FindLeaderboard() {
    Debug.Log("リーダーボードの検索開始");
    
    //リーダーボード(RankingSample)を探す
    CallResult<LeaderboardFindResult_t>.Create().Set(SteamUserStats.FindLeaderboard("RankingSample"), OnFindLeaderboard);
  }
  
  //リーダーボードの取得完了
  private void OnFindLeaderboard(LeaderboardFindResult_t result, bool failure) {
    //リーダーボードが見つかったか判定
    if (failure || result.m_bLeaderboardFound == 0) {
      Debug.LogWarning("リーダーボードが見つかりませんでした");
      return;
    }
    //リーダーボードの情報表示
    var leaderboard = result.m_hSteamLeaderboard;
    Debug.Log($"リーダーボードの名前 : {SteamUserStats.GetLeaderboardName(leaderboard)}");
    Debug.Log($"リーダーボードに登録されている数 : {SteamUserStats.GetLeaderboardEntryCount(leaderboard)}");
    Debug.Log($"リーダーボードの並べ替え方法 : {SteamUserStats.GetLeaderboardSortMethod(leaderboard)}");
    Debug.Log($"リーダーボードの表示タイプ : {SteamUserStats.GetLeaderboardDisplayType(leaderboard)}");
  }

}
f:id:kan_kikuchi:20200131101502j:plain


なお、ブラウザ上でランキングの設定を変更したりすると、

リーダーボードが見つからない事がありますが、そういう場合はUnityを再起動すると直ります。


スコアの送信

スコアの送信はログイン&リーダーボードの取得後、

リーダーボードの取得と同じ要領で行います。具体的には以下のような感じ。

using System;
using UnityEngine;
using Steamworks;

/// <summary>
/// ランキングのサンプル
/// </summary>
public class RankingSample : MonoBehaviour {
  
  //=================================================================================
  //初期化
  //=================================================================================
  
  private void Start(){
    if (SteamManager.Initialized){
      Debug.Log($"Steamの初期化成功, AppID : {SteamUtils.GetAppID()}");
      //初期化が成功したらリーダーボードを探す
      FindLeaderboard();
    }
    else {
      Debug.LogWarning("Steamの初期化失敗");
    }
  }
  
  //=================================================================================
  //リーダーボードの取得
  //=================================================================================

  //リーダーボードの取得
  private void FindLeaderboard() {
    Debug.Log("リーダーボードの検索開始");
    
    //リーダーボード(RankingSample)を探した後、スコア送信のメソッド(UploadScore)を実行。
    CallResult<LeaderboardFindResult_t>.Create().Set(SteamUserStats.FindLeaderboard("RankingSample"), UploadScore);
  }
  
  //=================================================================================
  //スコアの送信
  //=================================================================================

  //スコア送信
  private void UploadScore(LeaderboardFindResult_t result, bool failure) {
    //リーダーボードが見つかったか判定
    if (failure || result.m_bLeaderboardFound == 0) {
      Debug.LogWarning("リーダーボードが見つかりませんでした");
      return;
    }
      
    //送信する情報
    var call = SteamUserStats.UploadLeaderboardScore(
      result.m_hSteamLeaderboard, //送信するリーダーボード 
      ELeaderboardUploadScoreMethod.k_ELeaderboardUploadScoreMethodKeepBest,//送信するスコアがベストを超えていれば更新する
      58,              //送信するスコア
      new int[0], //ユーザがスコアを獲得した方法に関する追加情報?
      0       //ユーザがスコアを獲得した方法に関する追加情報?
    );
    
    //スコア送信
    Debug.Log($"スコア送信開始");
    CallResult<LeaderboardScoreUploaded_t>.Create().Set(call, OnUploadScore);
  }
  
  //スコア送信完了
  private void OnUploadScore(LeaderboardScoreUploaded_t result, bool failure) {
    //スコア送信が上手くいったか判定
    if (failure || result.m_bSuccess != 1) {
      Debug.LogWarning("スコア送信が失敗しました");
      return;
    }
    Debug.Log($"スコア送信完了");
      
    //更新結果を確認
    Debug.Log($"現在のスコア : {result.m_nScore}");
    Debug.Log($"スコアが更新されたか : {result.m_bScoreChanged}");
    Debug.Log($"スコア送信前の順位 : {result.m_nGlobalRankPrevious}");
    Debug.Log($"スコア送信後の順位 : {result.m_nGlobalRankNew}");
  }
  
}
f:id:kan_kikuchi:20200131102958j:plain


なお、送信したスコアはブラウザ上でも確認可能です。

f:id:kan_kikuchi:20200131095439j:plain



順位の取得

順位の取得もログイン&リーダーボードの取得後、

リーダーボードの取得と同じ要領で行います。具体的には以下のような感じ。

using System;
using UnityEngine;
using Steamworks;

/// <summary>
/// ランキングのサンプル
/// </summary>
public class RankingSample : MonoBehaviour {
  
  //=================================================================================
  //初期化
  //=================================================================================
  
  private void Start(){
    if (SteamManager.Initialized){
      Debug.Log($"Steamの初期化成功, AppID : {SteamUtils.GetAppID()}");
      //初期化が成功したらリーダーボードを探す
      FindLeaderboard();
    }
    else {
      Debug.LogWarning("Steamの初期化失敗");
    }
  }
  
  //=================================================================================
  //リーダーボードの取得
  //=================================================================================

  //リーダーボードの取得
  private void FindLeaderboard() {
    Debug.Log("リーダーボードの検索開始");
    
    //リーダーボード(RankingSample)を探した後、順位の取得のメソッド(DownloadEntries)を実行。
    CallResult<LeaderboardFindResult_t>.Create().Set(SteamUserStats.FindLeaderboard("RankingSample"), DownloadEntries);
  }

  //=================================================================================
  //順位の取得
  //=================================================================================
  
  //順位の取得
  private void DownloadEntries(LeaderboardFindResult_t result, bool failure) {
    //リーダーボードが見つかったか判定
    if (failure || result.m_bLeaderboardFound == 0) {
      Debug.LogWarning("リーダーボードが見つかりませんでした");
      return;
    }
      
    //取得する情報
    var call = SteamUserStats.DownloadLeaderboardEntries(
      result.m_hSteamLeaderboard, //送信するリーダーボード 
      ELeaderboardDataRequest.k_ELeaderboardDataRequestGlobal, //順位を取得する範囲
      0, //取得する順位の一番上
      100 //取得する順位の一番下
    );
    
    //順位の取得送信
    Debug.Log("順位の取得開始");
    CallResult<LeaderboardScoresDownloaded_t>.Create().Set(call, OnDownloadEntriese);
  }
  
  //順位の取得完了
  private void OnDownloadEntriese(LeaderboardScoresDownloaded_t result, bool failure) {
    //順位の取得が上手くいったか判定
    if (failure) {
      Debug.LogWarning("順位の取得が失敗しました");
      return;
    }
    Debug.Log($"順位の取得完了");
      
    
    //登録されている順位の個数を確認
    Debug.Log($"取得した順位の個数 : {result.m_cEntryCount}");

    //各順位の情報を確認
    for (int i = 0; i < result.m_cEntryCount; i++) {
      //各順位の情報を取得
      LeaderboardEntry_t leaderboardEntry;
      SteamUserStats.GetDownloadedLeaderboardEntry(result.m_hSteamLeaderboardEntries, i, out leaderboardEntry, new int[0], 0);
      
      //情報を表示
      Debug.Log($"順位 : {leaderboardEntry.m_nGlobalRank}");
      Debug.Log($"ID : {leaderboardEntry.m_steamIDUser}");
      Debug.Log($"スコア : {leaderboardEntry.m_nScore}");
      Debug.Log($"ユーザ名 : {SteamFriends.GetFriendPersonaName(leaderboardEntry.m_steamIDUser)}");
    }
    
  }
  
}
f:id:kan_kikuchi:20200201183607j:plain


なお、特定のユーザ(自分とか)の順位を取得したい場合

callの部分を以下のように書き換えます。

//取得する情報
CSteamID[] targetIDs = { SteamUser.GetSteamID() };//取得したい情報のIDに、自分のIDを設定
var call = SteamUserStats.DownloadLeaderboardEntriesForUsers(
  result.m_hSteamLeaderboard, //送信するリーダーボード 
  targetIDs,       //取得したいUserのID
  targetIDs.Length //取得したい情報の個数
);



おわりに

スコアを送信するにしても、順位を取得するにしても、リーダーボードを検索しなくてはならないため、

最初に書いた通り、ランキング周りは結構実装が面倒でした。


もしかしたらそのうち、実績の実装と併せて

Steam用の便利クラスみたいなのを作るかもしれません。


参考