UnityでSteam実績をリセットして何度でもテストする: ClearAchievementとResetAllStatsで開発用リセットU
第1回では、UnityからSteam実績を1個解除するところまでを作りました。
ただ、Steam実績は一度解除すると、そのSteamアカウントに「解除済み」として記録され続けます。テスト中に「もう一度解除前の状態に戻して、解除手順をやり直したい」というときに困ります。
そこで第2回では、開発中だけ使う「Steam実績のリセット機能」をUnityで作ります。第1回で解除した ACH_CLEAR を未解除に戻したり、すべての実績とStatsをまとめてリセットしたりして、何度でもテストできる状態にします。
第3回で扱う累計実績(Stats)の細かい話や、第4回で扱うManager設計はまだ出てきません。リセット用APIを使えるようになり、開発中のテスト効率を上げることが、第2回のゴールです。
この記事で作るもの
この記事では、次の2種類のリセット処理を作ります。
| 種類 | 使うSteamworks.NET API | 用途 |
|---|---|---|
| 個別リセット | SteamUserStats.ClearAchievement |
ACH_CLEAR のような特定の実績だけ未解除に戻す |
| 全リセット | SteamUserStats.ResetAllStats |
すべての実績とStats累計をまとめて消す |
どちらも、最後に SteamUserStats.StoreStats() を呼んでSteam側に保存するところまでが1セットです。
第1回で作った SteamAchievement.Unlock と組み合わせて、「解除 → リセット → もう一度解除」の流れをUnityエディター上で繰り返せるようにします。
また、リセット系のコードは本番ビルドに入れたくない処理なので、開発中だけ動くようにする工夫も合わせて書きます。
なぜリセット機能が必要なのか
第1回の最後で ACH_CLEAR を1回解除しました。
このあと「もう一度未解除の状態から解除して、ログがちゃんと出るか確認したい」と思っても、Steamアカウント側にはすでに「解除済み」が残っています。Unityでもう一度 SteamAchievement.Unlock("ACH_CLEAR") を呼んでも、Steamのポップアップ通知は出ません。
これは、Steamが「すでに解除されている実績をもう一度解除した」と判断するためです。コードのログは出ますが、Steamクライアント側の通知や、解除時の演出を確認するのが難しくなります。
開発中はこのテストを何度も繰り返したいので、解除済みを未解除に戻す手段が欲しくなります。
Steamworks.NETには、解除状態だけを未解除に戻す ClearAchievement(string) と、Statsの累計値ごとまとめて消す ResetAllStats(bool) が用意されています。これらを使って、テスト用のリセットUIを作ります。
なお、本番ビルドに入ったままだと、プレイヤーが誤って自分の実績を全部消してしまうことになります。リセット機能は、Unityエディター内でだけ動くようにします。本番ビルドから除外する書き方は、この記事の後半で説明します。
リセットに使う2種類のAPI
リセットには、目的に応じて2つのAPIを使い分けます。
SteamUserStats.ClearAchievement(string achievementId)
指定したAPI Nameの実績だけを未解除に戻します。
SteamUserStats.ClearAchievement("ACH_CLEAR");
SteamUserStats.StoreStats();
Statsの累計値には触りません。ACH_CLEAR の解除フローだけ確認し直したいときに使います。
SteamUserStats.ResetAllStats(bool achievementsToo)
すべてのStats累計値を初期値に戻します。
引数 achievementsToo を true にすると、Stats累計だけでなく、すべての実績の解除状態もまとめて未解除に戻します。
SteamUserStats.ResetAllStats(true);
SteamUserStats.StoreStats();
第3回で扱う「敵を100体倒す」のような累計実績まで全部やり直したいときに使います。挙動が強力なので、確認用と割り切って使います。
どちらも StoreStats() が必要
ClearAchievement も ResetAllStats も、それだけではSteamサーバ側に反映されません。リセット処理を呼んだあとに必ず SteamUserStats.StoreStats() を呼んで保存します。
第1回で「SetAchievement のあとに StoreStats を呼ぶ」と書いたのと同じ考え方です。
リセット用スクリプトを作る
第1回と同じ Assets/Scripts/Steam フォルダにリセット用のスクリプトを作ります。
-
Assets/Scripts/Steamフォルダを右クリックする -
作成 > Scripting > Empty C# Scriptを選ぶ - ファイル名を
SteamAchievementResetterにする
作成した SteamAchievementResetter.cs を開き、次のコードに置き換えます。
using UnityEngine;
using Steamworks;
public static class SteamAchievementResetter
{
public static void ClearOne(string achievementId)
{
if (!SteamManager.Initialized)
{
Debug.LogWarning("Steamを使う準備ができていないため、実績をリセットできません");
return;
}
bool clearOk = SteamUserStats.ClearAchievement(achievementId);
if (!clearOk)
{
Debug.LogWarning($"Steam実績をリセットできませんでした: {achievementId}");
return;
}
bool storeOk = SteamUserStats.StoreStats();
if (!storeOk)
{
Debug.LogWarning($"Steam実績の保存に失敗しました: {achievementId}");
return;
}
Debug.Log($"Steam実績をリセットしました: {achievementId}");
}
public static void ResetAll(bool includeAchievements)
{
if (!SteamManager.Initialized)
{
Debug.LogWarning("Steamを使う準備ができていないため、リセットできません");
return;
}
bool resetOk = SteamUserStats.ResetAllStats(includeAchievements);
if (!resetOk)
{
Debug.LogWarning($"Steam StatsとAchievementのリセットに失敗しました (achievements={includeAchievements})");
return;
}
bool storeOk = SteamUserStats.StoreStats();
if (!storeOk)
{
Debug.LogWarning($"Steam StatsとAchievementの保存に失敗しました (achievements={includeAchievements})");
return;
}
Debug.Log($"Steam StatsとAchievementをリセットしました (achievements={includeAchievements})");
}
}
このコードでやっていることは、第1回の SteamAchievement.Unlock とほぼ同じ流れです。
-
SteamManager.InitializedでSteamを使える状態か確認する - リセット用のAPI(
ClearAchievementかResetAllStats)を呼ぶ -
StoreStats()でSteam側に保存する
リセット系APIの戻り値も bool なので、第1回の解除コードと同じ形で警告を出すようにしてあります。
コードを保存したらUnityエディターに戻り、読み込みが終わるまで待ちます。コンソール ウィンドウに赤いエラーが出ていないことを確認してください。
Unity UIのボタンからリセットを試す
第1回と同じように、Unity UIのボタンから呼び出すための小さなC#スクリプトを作ります。
Assets/Scripts/Steam フォルダを右クリックして 作成 > Scripting > Empty C# Script を選び、ファイル名を SteamAchievementResetTestButton にします。
中身を次のコードに置き換えます。
using UnityEngine;
public class SteamAchievementResetTestButton : MonoBehaviour
{
[ContextMenu("Clear ACH_CLEAR")]
public void ClearClearAchievement()
{
SteamAchievementResetter.ClearOne("ACH_CLEAR");
}
[ContextMenu("Reset All Stats and Achievements")]
public void ResetAllStatsAndAchievements()
{
SteamAchievementResetter.ResetAll(true);
}
}
[ContextMenu("...")] を付けておくと、後半で説明する「Inspectorのメニューから呼ぶ」方法でも使えます。今はそのまま付けたまま進めて構いません。
保存してUnityエディターに戻り、コンソール に赤いエラーがないことを確認します。
リセット用のゲームオブジェクトを作る
-
ヒエラルキーウィンドウを右クリックする -
空のオブジェクトを作成を選ぶ - ゲームオブジェクト名を
SteamAchievementResetTesterにする -
インスペクターのコンポーネントを追加を押す -
SteamAchievementResetTestButtonを検索して追加する
Clear ACH_CLEAR ボタンを作る
個別リセット用のボタンを作ります。
-
ヒエラルキーウィンドウを右クリックする -
UI > ボタン - TextMeshProを選ぶ - 作成されたボタンを選択する
- 子オブジェクトのテキストを
Clear ACH_CLEARに変更する
続いて、ボタンの クリック時() に呼び出す処理を設定します。
-
ヒエラルキーでボタンを選択する -
インスペクターのButtonコンポーネントを探す -
クリック時()の+ボタンを押す -
SteamAchievementResetTesterゲームオブジェクトを空欄にドラッグする - 関数選択ドロップダウンから
SteamAchievementResetTestButton > ClearClearAchievement()を選ぶ
Reset All ボタンを作る
全リセット用のボタンも同じ流れで作ります。
-
ヒエラルキーウィンドウを右クリックする -
UI > ボタン - TextMeshProを選ぶ - 子オブジェクトのテキストを
Reset Allに変更する -
インスペクターのButtonのクリック時()の+ボタンを押す -
SteamAchievementResetTesterゲームオブジェクトを空欄にドラッグする - 関数選択ドロップダウンから
SteamAchievementResetTestButton > ResetAllStatsAndAchievements()を選ぶ - シーンを保存する
これで、再生中に Clear ACH_CLEAR ボタンを押せば個別リセット、Reset All ボタンを押せば全リセットが呼ばれます。
第1回のテストボタンと組み合わせて確認する
リセットUIだけでは動作確認ができないので、第1回の Unlock ACH_CLEAR ボタンと組み合わせて確認します。
第1回のボタンを残したまま、同じシーンに第2回のボタンを並べると、「解除 → リセット → 再解除」の流れを1つの画面で確認できます。
確認手順は次の通りです。
- Steamクライアントを起動してログインしておく
- Unityエディター上部の再生ボタンを押す
- 第1回で作った
Unlock ACH_CLEARボタンを押す -
コンソールウィンドウにSteam実績を解除しました: ACH_CLEARが出ることを確認する - 続けて
Clear ACH_CLEARボタンを押す -
コンソールウィンドウにSteam実績をリセットしました: ACH_CLEARが出ることを確認する - もう一度
Unlock ACH_CLEARボタンを押す - もう一度
Steam実績を解除しました: ACH_CLEARが出ることを確認する
ここまで確認できれば、開発中に「解除 → リセット → 再解除」を何度でも繰り返せます。
Reset All ボタンも同じように、解除済みの状態で押してから Unlock ACH_CLEAR をもう一度押すと再解除のフローが回ります。
Reset All は第3回で扱うStats累計値もまとめて消すため、第3回以降で累計実績を組み込んだあとに使うと、累計値が0に戻ります。第2回時点では、まだStatsを使っていないので、Reset All を押すと実績の解除状態だけが消えます。
ContextMenuからInspectorで呼ぶ
ボタンを置かずに、Inspectorから直接リセットを呼ぶ方法も使えるようにしておきます。
第1回で書いたボタンスクリプトには [ContextMenu("...")] を付けました。これを付けたメソッドは、Inspectorのコンポーネント右上のメニューから呼べるようになります。
UIボタンを毎回シーンに置きたくない、開発中だけ素早く呼びたい、というときに便利です。
手順は次の通りです。
- Unityエディター上部の再生ボタンを押す
-
ヒエラルキーでSteamAchievementResetTesterを選択する -
インスペクターのSteamAchievementResetTestButtonコンポーネントの右上にあるメニューアイコンを押す - メニューの中から
Clear ACH_CLEARを選ぶ -
コンソールウィンドウにSteam実績をリセットしました: ACH_CLEARが出ることを確認する
[ContextMenu("...")] で設定した文字がそのままメニュー名になるので、Reset All Stats and Achievements も同じ手順で呼べます。
ContextMenuは、Play Mode中だけでなくPlay Modeに入っていない状態でも表示されます。ただし、Steamと通信できるのはPlay Mode中に SteamManager.Initialized が true になっているときだけなので、リセットを実際に動かしたい場合は再生してから呼びます。
本番ビルドに含めないための工夫
ここまでで作ったリセット機能は、開発中だけ使うものです。
このまま本番ビルドに残してしまうと、プレイヤーが何らかの方法でボタンを押せたときに、自分の実績やStatsを全部消してしまうおそれがあります。本番には絶対に含めない作りにします。
第2回では、第1回で作った SteamTestScene にだけリセット用ボタンを置いている状態です。Build Settings のシーン一覧から SteamTestScene を外して本番ビルドを作れば、リセットボタン自体が本番に入りません。
確認手順は次の通りです。
- Unityエディター上部の
ファイル > ビルド設定を開く -
Scenes In Buildの一覧にSteamTestSceneが入っていないことを確認する - 入っている場合はチェックを外す、または一覧から削除する
- 本番ビルドに含めたいシーンだけがチェックされている状態にする
- その状態でビルドする
これだけで、リセット機能は本番ビルドに入りません。プレイヤーがリセットボタンを押せる経路自体が存在しないので、安全です。
リセット用スクリプト本体は Assets 内に残っていますが、参照するシーンがビルドに含まれていなければ、実行時に呼び出されることもありません。
リセット結果の確認とよくある落とし穴
リセットを呼んだあと、確認するときに困りやすいポイントがいくつかあります。
Steamクライアントの解除通知は出ないことが多い
リセット直後にもう一度実績を解除しても、Steamクライアントの右下に出る解除通知は表示されないことがあります。
Steamは「短時間に解除と未解除を行き来した実績」については、通知を出さないことがあります。コードが正しく動いているかは、コンソール ウィンドウのログとSteam側の実績一覧で判断してください。
Steam側の実績一覧の更新に少し時間がかかる
Steamクライアントの実績一覧の表示は、リアルタイムではないことがあります。
StoreStats() を呼んだ直後にSteamの実績一覧を見ても、まだ「解除済み」のままに見えることがあります。Steamクライアントを開き直したり、少し時間をおいてから見ると反映されています。
ResetAllStats(true) はStatsもすべて消す
ResetAllStats(true) を呼ぶと、すべての実績の解除状態だけでなく、第3回で扱うStats累計値もまとめて消えます。
第2回時点ではまだStatsを使っていないので影響はありませんが、第3回以降で「敵を100体倒す」のような累計実績を組み込んだあとに Reset All ボタンを押すと、その累計も0に戻ります。
意図して全リセットしたいときだけ使う、と覚えておいてください。
同じSteamアカウントで他のテスト端末も使っている場合
同じSteamアカウントで複数の端末・複数のプロジェクトから実績をテストしている場合、リセットも全端末に共有されます。
Steamのアカウントに紐付いた状態は1つしかないので、自分のSteamアカウントで他にも検証している人がいる場合は、リセットのタイミングに気をつけます。
よくある失敗
リセットしたつもりが反映されていない
ClearAchievement や ResetAllStats を呼んだあとに StoreStats() を呼んでいないと、Steam側へ反映されません。
第1回の SetAchievement と同じで、リセット系APIも最後の StoreStats() までで1セットです。
Steamを使う準備ができていない と出る
Steamクライアントが起動していないか、SteamManager.Initialized が false のままです。
第1回で作った SteamInitCheck で Steam接続OK が出る状態を先に確認してください。
Steam実績をリセットできませんでした: ACH_CLEAR と出る
実績のAPI Nameが一致していない可能性があります。
第1回で確認した通り、Steamworksパートナーサイト側の API Name とコード側の文字列を完全一致させます。大文字小文字も含めて一致している必要があります。
AppID 480を使っていると、ACH_CLEAR のような自作API Nameは存在しないため、リセットもうまく動きません。本番AppIDに切り替えて、実績を登録して公開した状態で確認します。
本番ビルドにリセットボタンが残っている
SteamTestScene を Build Settings から外し忘れていると、本番ビルドにもリセットボタンが入ります。
ファイル > ビルド設定 を開いて、Scenes In Build の一覧に SteamTestScene が入っていないことを確認してください。
Inspector右上のメニューに Clear ACH_CLEAR が出ない
[ContextMenu("Clear ACH_CLEAR")] の付け忘れか、コンパイルエラーで反映されていない可能性があります。
スクリプトを保存してUnityエディターに戻り、コンソール に赤いエラーがないことを確認してから、もう一度Inspectorのメニューを開きます。
Reset All を押したら他の実績まで消えた
ResetAllStats(true) は、その名の通りすべての実績とStats累計を消します。
ACH_CLEAR だけ未解除に戻したい場合は、ClearAchievement のほうを使います。
第2回では扱わないこと
この記事では、リセット用APIを呼べるようになり、開発中の「解除 → リセット → 再解除」のフローを回せる状態を作りました。
次のような話は第3回以降で扱います。
- 累計実績(Stats)そのものの読み書き
- Statsの初期値や
Increment Onlyの扱い - 複数の実績をまとめて管理するManager設計
-
UserStatsReceived_tなどのCallbackで完了タイミングを正確に待つ作り -
StoreStats()の呼びすぎを防ぐ、まとめて保存する作り
第2回時点では、「解除も、リセットも、StoreStats() まで含めた1セットを毎回呼ぶ」というシンプルな考え方で十分です。
次回予告
ここまでで、「ボタンを押したら解除する」「ボタンを押したら未解除に戻す」という、瞬間的に状態が変わるタイプの実績を扱ってきました。
実際のゲームでは、「敵を累計100体倒したら解除」「星アイテムを累計1000個集めたら解除」のように、複数プレイにまたがって累計値で解除する実績もよく使われます。
これらの累計実績は、第1回・第2回で使ってきた SetAchievement ではなく、Steam Statsという仕組みを使います。
次回は、SteamworksパートナーサイトでStatsを作り、Unityコードから SetStat / GetStat で累計値を読み書きして、Steam側で条件を満たしたときに自動で実績を解除するところまでを作ります。
合わせて、第2回で作ったリセットUIを使うと、Statsの累計値も Reset All で戻せることが分かります。第2回のリセット機能は、第3回以降のテストでもそのまま使えます。
Discussion