Steamworks APIを使って実績を解除してみました。
Steamworks APIを導入
https://tsukinowa.hatenablog.jp/entry/2018/08/07/172931Steamworks設定を編集
Steamworks APIを導入したら、まずSteamworks設定で新しいデータと実績を追加します。
まず整数型の統計を一つ作りました。API名の文字列をスクリプトで指定します。
2つの実績を追加しました。片方は「進行状況」で上のデータを指定して、最小値を0、最大値を20にしました。
統計と実績を追加したら公開します。公開するとストアページの「解除できる Steam 実績」にこれらの実績が表示されました。
Steamworks APIの初期化
Stats APIとAchievements APIを使用するには、SteamAPI_InitメソッドでSteamworks APIを初期化します。
SteamManagerでは、SteamManager.Initializedをはじめに取得しようとしたときに、SteamManagerクラスのインスタンス化とSteamworks APIの初期化が行われているようです。
RequestCurrentStats
統計の値を変更したり実績を解除するには、先にSteamUserStats.RequestCurrentStatsメソッドを呼んで、ユーザーの統計データと実績を取得します。
if (SteamManager.Initialized)
{
m_bRequestedStats = SteamUserStats.RequestCurrentStats();
}
実績の解除
実績を解除するには、SteamUserStats.SetAchievementメソッドの引数に実績のAPI名を渡します。実績を解除したらSteamUserStats.StoreStatsメソッドで変更をアップロードします。
SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_6_1");
SteamUserStats.StoreStats();
また、Steamworks設定で進行状況に設定したデータがアンロック値に達すると自動的に解除されます。統計データの取得と設定には、SteamUserStats.GetStat/SetStatメソッドを使います。
SteamUserStats.GetStat("stat_1", out int stat_1);
stat_1++;
SteamUserStats.SetStat("stat_1", stat_1);
SteamUserStats.StoreStats();
スクリプト
今回は、ゲームを始めると実績の情報を画面に表示して、ボタンでデータのインクリメントや実績の解除、実績やデータのリセットをしました。
まず、実績を表示するためのパネルのプレハブを作りました。UIパネルの子に名前や説明のためのテキストと、アイコンの表示するImageオブジェクトがあります。
パネルにはスクリプトが付いています。
using UnityEngine;
using UnityEngine.UI;
public class TestAchievementPanel : MonoBehaviour
{
[SerializeField] Text nameText;
[SerializeField] Image iconImage;
[SerializeField] Text descText;
public void SetUp(string name, Sprite icon, string desc)
{
nameText.text = name;
iconImage.sprite = icon;
descText.text = desc;
}
}
ヒエラルキーウィンドウに空のゲームオブジェクトと、実績の情報やボタン、stat_1の値を表示するテキストのためのCanvasを作りました。
空のゲームオブジェクトにメインのスクリプトを付けます。スクリプトには、stat_1の値を表示するテキストオブジェクトと、実績を表示するパネルのプレハブ、そのプレハブの親になるパネルオブジェクトをアタッチしています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Steamworks;
using UnityEngine.UI;
public class SteamworksTest : MonoBehaviour
{
// stat_1を表示するテキスト
[SerializeField] Text stat1Text;
// 実績パネルのプレハブ
[SerializeField] TestAchievementPanel achievementPanelPrefab;
// 実績パネルの親パネル
[SerializeField] Image achievementPanelBase;
// RequestCurrentStatsでデータが取得できたかどうか
bool m_bRequestedStats;
// Start is called before the first frame update
void Start()
{
RequestStats();
// データの取得ができたら
if(m_bRequestedStats)
{
// UIを更新
UpdateAchievementsDisplay();
}
}
// 初期化とデータの取得
void RequestStats()
{
if (SteamManager.Initialized)
{
m_bRequestedStats = SteamUserStats.RequestCurrentStats();
}
}
// UIを更新
void UpdateAchievementsDisplay()
{
// 表示されている実績のパネルを削除
for(int i = 0; i < achievementPanelBase.transform.childCount; i++)
{
Destroy(achievementPanelBase.transform.GetChild(i).gameObject);
}
// 定義された実績を一つずつ見ていく
for (int i = 0; i < SteamUserStats.GetNumAchievements(); i++)
{
// パネルをインスタンス化
var panel = Instantiate(achievementPanelPrefab, achievementPanelBase.transform);
// API名を取得
var apiName = SteamUserStats.GetAchievementName((uint)i);
// 実績の名前を取得
var name = SteamUserStats.GetAchievementDisplayAttribute(apiName, "name");
// 実績の説明を取得
var desc = SteamUserStats.GetAchievementDisplayAttribute(apiName, "desc");
// 実績のアイコンのハンドルを取得
var iconHandle = SteamUserStats.GetAchievementIcon(apiName);
// アイコンのサイズを取得
uint imgWidth, imgHeight;
bool success = SteamUtils.GetImageSize(iconHandle, out imgWidth, out imgHeight);
// サイズを取得できたら
if (success && imgWidth > 0 && imgHeight > 0)
{
// RGBAデータを取得
byte[] imageRGBA = new byte[imgWidth * imgHeight * 4];
success = SteamUtils.GetImageRGBA(iconHandle, imageRGBA, imageRGBA.Length);
if (success)
{
// テクスチャに変換
Texture2D texture = new Texture2D((int)imgWidth, (int)imgHeight, TextureFormat.RGBA32, false, true);
texture.LoadRawTextureData(imageRGBA);
texture.Apply();
// テクスチャを垂直に反転
var pixels = texture.GetPixels32();
System.Array.Reverse(pixels);
texture.SetPixels32(pixels);
texture.Apply();
// Spriteを作成
var icon = Sprite.Create(texture, new Rect(0, 0, imgWidth, imgHeight), new Vector2(1f, 0.5f));
// 実績のパネルに表示
panel.SetUp(name, icon, desc);
}
}
}
// stat_1の値をテキストに表示
SteamUserStats.GetStat("stat_1", out int stat_1);
stat1Text.text = stat_1 + "";
}
// stat_1をインクリメント
public void IncrementStat_1()
{
if (!m_bRequestedStats) return;
// stat_1の値を取得
SteamUserStats.GetStat("stat_1", out int stat_1);
// インクリメント
stat_1++;
// stat_1の値を設定
SteamUserStats.SetStat("stat_1", stat_1);
// 変更をアップロード
SteamUserStats.StoreStats();
// テキストに表示
stat1Text.text = stat_1 + "";
// 値が10の時
if (stat_1 == 10)
{
// プログレスバーを表示
SteamUserStats.IndicateAchievementProgress("NEW_ACHIEVEMENT_6_0", 10, 20);
}
// 20の時
if (stat_1 >= 20)
{
// UIを更新
UpdateAchievementsDisplay();
}
}
// 2つ目の実績をアンロック
public void UnlockAchievement1()
{
if (!m_bRequestedStats) return;
// 実績をアンロック
SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_6_1");
// 変更をアップロード
SteamUserStats.StoreStats();
// UIを更新
UpdateAchievementsDisplay();
}
// データをリセット
public void ResetAllStats()
{
if (!m_bRequestedStats) return;
// 実績も含めてデータをリセット
SteamUserStats.ResetAllStats(true);
// UIを更新
UpdateAchievementsDisplay();
}
}
まずusingディレクティブで、Steamworks名前空間の型をインポートします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Steamworks;
using UnityEngine.UI;
public class SteamworksTest : MonoBehaviour
{
アタッチするUIやプレハブ、データが取得できたかどうかのbool変数を定義します。
// stat_1を表示するテキスト
[SerializeField] Text stat1Text;
// 実績パネルのプレハブ
[SerializeField] TestAchievementPanel achievementPanelPrefab;
// 実績パネルの親パネル
[SerializeField] Image achievementPanelBase;
// RequestCurrentStatsでデータが取得できたかどうか
bool m_bRequestedStats;
Startメソッドでは、Steamworks APIの初期化とデータ取得した後、UIを更新します。
void Start()
{
RequestStats();
// データの取得ができたら
if(m_bRequestedStats)
{
// UIを更新
UpdateAchievementsDisplay();
}
}
SteamManager.Initializedは、SteamManagerのインスタンスのm_bInitialized変数の値が返りますが、インスタンスがない場合はインスタンス化されます。そのときにAPIが初期化されます。
void RequestStats()
{
if (SteamManager.Initialized)
{
m_bRequestedStats = SteamUserStats.RequestCurrentStats();
}
}
初期化に成功したら、ユーザーの現在のデータと実績を要求します。
UIを更新するメソッドでは、まず実績のパネルをすべて削除します。
void UpdateAchievementsDisplay()
{
// 表示されている実績のパネルを削除
for(int i = 0; i < achievementPanelBase.transform.childCount; i++)
{
Destroy(achievementPanelBase.transform.GetChild(i).gameObject);
}
Steamworks設定で定義された実績を一つずつ見ていって、実績の情報を表示するパネルをインスタンス化します。
for (int i = 0; i < SteamUserStats.GetNumAchievements(); i++)
{
// パネルをインスタンス化
var panel = Instantiate(achievementPanelPrefab, achievementPanelBase.transform);
SteamUserStats.GetNumAchievementsメソッドで定義された実績の数が返ります。
その後、実績の名前やアイコン、説明などの情報を取得してUIに表示します。そのためにはインデックスから実績のAPI名を取得します。SteamUserStats.GetAchievementNameメソッドの引数に実績のインデックスを渡しています。
var apiName = SteamUserStats.GetAchievementName((uint)i);
SteamUserStats.GetAchievementDisplayAttributeメソッドで実績の名前と説明を取得できます。第一引数のAPI名、第二引数にキーを渡します。名前の場合のキーは「name」、説明は「desc」です。実績が非公開化どうかのbool値は「hidden」を指定します。
// 実績の名前を取得
var name = SteamUserStats.GetAchievementDisplayAttribute(apiName, "name");
// 実績の説明を取得
var desc = SteamUserStats.GetAchievementDisplayAttribute(apiName, "desc");
実績のアイコンでは、まずハンドルを取得して、ハンドルからRGBAデータを取得し、それをテクスチャに変換してからSpriteを作成しています。
ハンドルの取得には、SteamUserStats.GetAchievementIconメソッドの引数の実績のAPI名を渡します。
var iconHandle = SteamUserStats.GetAchievementIcon(apiName);
RGBAデータやテクスチャにサイズの指定が必要なので、SteamUtils.GetImageSizeの引数にハンドルを渡してアイコンのサイズを取得しています。
uint imgWidth, imgHeight;
bool success = SteamUtils.GetImageSize(iconHandle, out imgWidth, out imgHeight);
成功したらサイズからバッファを作って、SteamUtils.GetImageRGBAメソッドにアイコンのハンドルとバッファを渡します。
if (success && imgWidth > 0 && imgHeight > 0)
{
// RGBAデータを取得
byte[] imageRGBA = new byte[imgWidth * imgHeight * 4];
success = SteamUtils.GetImageRGBA(iconHandle, imageRGBA, imageRGBA.Length);
Texture2D.LoadRawTextureDataメソッドを使って、それをテクスチャに変換します。
if (success)
{
// テクスチャに変換
Texture2D texture = new Texture2D((int)imgWidth, (int)imgHeight, TextureFormat.RGBA32, false, true);
texture.LoadRawTextureData(imageRGBA);
texture.Apply();
ImageConversion.LoadImageメソッドを使うと画像のサイズが 8*8 になってうまくいきませんでした。
また、このままではテクスチャが逆さまに表示されたので、垂直に反転しています。
var pixels = texture.GetPixels32();
System.Array.Reverse(pixels);
texture.SetPixels32(pixels);
texture.Apply();
テクスチャからスプライトを作成して実績のパネルに表示します。
// Spriteを作成
var icon = Sprite.Create(texture, new Rect(0, 0, imgWidth, imgHeight), new Vector2(1f, 0.5f));
// 実績のパネルに表示
panel.SetUp(name, icon, desc);
}
}
}
最後に、stat_1の値を取得してテキストに表示しています。
// stat_1の値をテキストに表示
SteamUserStats.GetStat("stat_1", out int stat_1);
stat1Text.text = stat_1 + "";
}
stat_1の値を1増加させるメソッドでは、値を取得後に1足してからセットして、変更をアップロードしテキストに表示します。
public void IncrementStat_1()
{
if (!m_bRequestedStats) return;
// stat_1の値を取得
SteamUserStats.GetStat("stat_1", out int stat_1);
// インクリメント
stat_1++;
// stat_1の値を設定
SteamUserStats.SetStat("stat_1", stat_1);
// 変更をアップロード
SteamUserStats.StoreStats();
// テキストに表示
stat1Text.text = stat_1 + "";
実績を定義するときの、進行状況の最大値を20にしているので、この値が20になると実績「NEW_ACHIEVEMENT_6_0」が自動的にアンロックされます。今回はSteamUserStats.IndicateAchievementProgressメソッドを使って、値が半分の10に達するとプログレスバーを表示しています。
if (stat_1 == 10)
{
// プログレスバーを表示
SteamUserStats.IndicateAchievementProgress("NEW_ACHIEVEMENT_6_0", 10, 20);
}
値が20になると自動で実績が解除されるので表示を更新しています。
if (stat_1 >= 20)
{
// UIを更新
UpdateAchievementsDisplay();
}
}
実績「NEW_ACHIEVEMENT_6_1」をアンロックするだけのメソッドも作りました。解除してすぐに変更をアップロードしてUIを更新しています。
public void UnlockAchievement1()
{
if (!m_bRequestedStats) return;
// 実績をアンロック
SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_6_1");
// 変更をアップロード
SteamUserStats.StoreStats();
// UIを更新
UpdateAchievementsDisplay();
}
また、データをリセットするメソッドも作りました。SteamUserStats.ResetAllStatsメソッドの引数にtrueを渡すと実績もリセットされます。
public void ResetAllStats()
{
if (!m_bRequestedStats) return;
// 実績も含めてデータをリセット
SteamUserStats.ResetAllStats(true);
// UIを更新
UpdateAchievementsDisplay();
}
}
Canvasに3つのボタンを作り、stat_1の増加、実績のアンロックそしてデータのリセットメソッドをそれぞれ割当てています。
スクリプトを実行
プレイボタンを押すと、実績名とアイコン、定義されたすべての実績の説明が表示されます。
未達成の彩度の無いアイコンが表示されています。
実績のアンロックのボタンをクリックすると、右側の実績が達成アイコンに変わって、実績アンロック通知ダイアログも表示されます。
ボタンでstat_1の値を増加できます。
値が10になると進行状況のポップアップが表示されます。
20になると左の実績も解除されます。
リセットのボタンを押すと、実績が未達成になって値も0にリセットされます。
Steamworks設定の実績の進行状況の最大値を21にして公開してみます。
すると、値が20ではなく21のときにアンロックされました。