OpenSiv3Dで作ったゲームをSteamに公開してみた。（Steamサーバー編）

今回、この記事では「OpenSiv3D」で作ったゲームでSteamサーバーを使用する方法について説明します。

Steamライブラリの使い方は[Steamworks ドキュメンテーション](https://partner.steamgames.com/doc/sdk?l=japanese)にも記載されていますが、正直わかりにくいのです。

私は「OpenSiv3D」にSteamライブラリを実装しましたが、Visual Studioで制作したものであれば、やり方にそれほどの差はないはずです。

Steamサーバーを使用するには、Visual StadioにSteamライブラリを実装する必要があります。まだ実装が出来ていない方は、[こちら](https://qiita.com/ryota11ryota11/items/b1bc7305e390998586a0)からSteamライブラリを実装してみてください。

このSteamサーバーを使用することによってSteamに投稿したゲームのセーブデータをサーバー上に保存することが出来ます。またSteamアカウントが同一のものであれば異なるPCからのログインでもセーブデータを共有することが出来ます。
セーブデータなら自前でセーブファイルを作ればいいじゃないか、と思う方もいらっしゃるかもしれませんが、もし、そのゲームのアップデートを予定している場合、セーブデータもアップデートによって上書き、もしくは削除されてしまいます。ユーザーからしたらアップデートが入るたびに今までのセーブデータが消えていたら、たまったものではありませんよね？そうならないためにゲームデータをサーバーに保存するのです。

#必須関数

Steamサーバーにデータを保存するために必要なインクルードファイルは

```
#include <steam_api.h>
#include <isteamgameserverstats.h>
```

この二つです。必ずインクルードしておいてください。

まず初めに、Steamサーバーを使うにしてもランキングを実装するにしても、SteamAPIを使う上で絶対に必要な関数を３つ紹介します。

```
SteamAPI_RestartAppIfNecessary([AppID]);

SteamAPI_Init();

SteamAPI_Shutdown();	
```
上から何の関数なのか説明していきます。

####SteamAPI_RestartAppIfNecessary([AppID])
この関数は実行可能ファイルがSteam経由で起動されたかどうかを確認し、そうでなかった場合にはSteam経由で再起動します。
この関数の引数にご自身のゲームの[AppID]を渡します。
これによりSteam側がこのゲームをSteamを経由してゲームを実行したのか確認してくれます。
この関数は戻り値がbool型で

 - trueが返ってきた場合　 Steamを経由してゲームを実行していない。
 - falseが返ってきた場合　Steamを経由してゲームを実行している。　　※falseが正常


という事になります。

なぜこの確認が必要なのかというと単純に言えば「海賊版への対策」です。
Steamからゲームを購入すると実行ファイルやそれに必要なデータファイルなどがダウンロードされますよね？
もし、そのダウンロードしたデータを全てコピーして他人へ販売、配布などをされたらゲームが全く売れなくなってしまいます。

しかし、この関数を使うことにより、実行ファイルから直接実行された場合、Steam側からゲームを実行し、関数からはtrueが返ってきます。
ゲームを購入していなくてSteamアカウントのライブラリに同一のゲームが存在しない場合、ゲームは実行されません。

なので、この関数からtrueが返ってきた場合、ゲームを強制的に終了する仕組みにしておくことをお勧めします。(if文に入れる　など)
そうすれば、海賊版からゲームを実行できなくなります。

しかし、そうなった場合「Visual Stadioから実行する場合どうするんだ？」となりますよね？
Steam経由でしか実行できないなら当然、Visual Stadioからも実行することはできません。 
「それだとゲームのデバッグができないじゃないか！！」

もちろんこれには抜け道があります。

ゲームの実行ファイルと同じ場所に[steam_appid.txt]というファイル名のテキストファイルを用意して、その中にAppIDを書いておいてください。
![AppID.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637404/eaa441c6-0f0d-6b51-31dd-cbca7462e4fd.png)
こうすることによって、Steam経由でなくてもゲームを実行することが出来ます。

注意点としてはゲームをアップロードする際、このテキストファイルを消しておいてください。
Steam経由でなくてもゲームを実行することが出来る抜け道なので、抜け道を用意したままゲームをリリースすることになります。

また、この関数は必ず下記の[SteamAPI_Init()]の前に実行しましょう。
そうしないと[SteamAPI_Init()]を失敗してしまいます。

####SteamAPI_Init()
次は、[SteamAPI_Init();]です。
この関数はその名の通りSteamAPIの初期化です。
これを行っていない場合SteamAPIを使うことはできません。
この関数は戻り値がbool型で

 - trueが返ってきた場合　 初期化成功
 - falseが返ってきた場合　初期化失敗

if文に入れて使うのがいいと思います。

もし初期化に失敗した場合、次のものを確認してみてください。

 - Steamにアクセスできていない。
   - SteamにログインすればOKです。
   - インターネットに接続できているか確認。
  

 - [SteamAPI_RestartAppIfNecessary([AppID])]をしていない。
   - 上記を確認してください。


 - AppIDの設定が完了していない。
   - SteamWorksやストア設定などを完了させましょう。

 - OSが異なる。

上記を完了しているのに失敗する場合、[Steamworks APIのデバッグ](https://partner.steamgames.com/doc/sdk/api/debugging)を確認してみてください。
(私は、上記の４つを完了していれば確実に成功していました。)

####SteamAPI_Shutdown()
この関数もその名の通りSteamAPIの終了です。
SteamAPIの使用、もしくはゲームの終了のタイミングで呼び出してください。
Steam内でアプリケーションが使用しているリソースを解放する必要があります。

--------------
この３つがSteamAPIを使う上での必須関数です。SteamAPIを使う際は必ず実装しましょう。

#Steamサーバー
長くなってしまいましたが、ここからSteamサーバーの使い方を説明していきます。

####SteamWorks側の設定
まずはSteamWorks側の設定です。
SteamWorksのテクニカルツール→「サーバー＆実績」にてデータを定義してください。
![SteamCloud.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637404/7d95cf39-ace2-8eda-6ee4-f4ce01f6baa0.png)
[New Stat]でデータの定義を増やすことが出来ます。

|名称 | 説明  |
|---|---|
|ID       | 各データ用に自動的に生成されるID（気にしなくて構いません）  |
|タイプ   |データの形式(int, float など)
|API名    |データの名前(入れるデータの役割に沿った名前がいいと思います)
|設定     |データを改変できる人を設定。(基本はクライアント)
|増分のみ?|増えるだけで減らないかどうか
|最大変化 |変化できるデータ値の限度(チーター対策)
|最小値         |このデータの最小値
|最大値         |このデータの最大値
|ウィンドウ     |すみません。わかりません。(私は使っていない)
|デフォルト値   |初期値
|集計          |すべてのプレイヤーの合計値
|ディスプレイ名 |アプリ内で表示される時のこのデータの名前

以上が一つのデータに設定する内容です。
すべて完了したら[Save]を押して[公開]しましょう。
また公開してから適用されるまで時間の差があると思うので、この後の内容は、少し時間をおいてからでもいいかもしれません。

####データをサーバーに保存する
次にゲームのデータをサーバーに保存する方法です。
必要な関数とマクロは下記の6つです。
また、下記の関数を使う前には、

 - SteamAPI_RestartAppIfNecessary([AppID]);
 - SteamAPI_Init();

この2つを使ってください。

```
SteamUserStats()->RequestCurrentStats();

SteamAPI_RunCallbacks();

STEAM_CALLBACK([クラス名], [関数名], UserStatsReceived_t);

SteamUserStats()->SetStat([API名], [保存するデータ]);

SteamUserStats()->StoreStats();

SteamUserStats()->GetStat([API名], [データを入れる変数]);
```
####SteamUserStats()->RequestCurrentStats()

この関数はサーバーにデータを要求する関数です。
ただ、この関数はデータを要求する”だけ”なので、実際にデータを手に入れることはできません。

####SteamAPI_RunCallbacks()
この関数は上の関数でデータを要求した後、結果をリスナー(プレイヤー、)に向けてディスパッチします。
わかりやすく言うと、この関数を呼び出すことでサーバーから結果が返ってきたとき、それを受け取ることが出来る関数です。

####STEAM_CALLBACK([クラス名], [関数名], UserStatsReceived_t)
こちらはマクロです。

```
class SteamTest
{
    STEAM_CALLBACK(SteamTest, OnUserStatsReceived, UserStatsReceived_t)
}

SteamTest::OnUserStatsReceived(UserStatsReceived_t UserStatsReceived_t_)
{
   //関数の内容
}
```
こんな感じで宣言します。

[関数名]に入れた関数は、ここに書いた時点で、プロトタイプ宣言されているので、関数の定義は他の場所でしましょう。

RequestCurrentStats()でデータを要求し、SteamAPI_RunCallbacks()でディスパッチ出来たら、OnUserStatsReceived()が呼び出される流れです。

イメージ的には

インターネット(RequestCurrentStats())で荷物(データ)を購入(要求)→
運送会社が購入者の住所(OnUserStatsReceived())へ、荷物(データ)を運送(SteamAPI_RunCallbacks())→
購入者(クライアント)が荷物(データ)の到着または、中身の確認(OnUserStatsReceived()の中で)

こんな感じです。

また、RequestCurrentStats()で要求した瞬間にデータが手元に来るわけではなく少しだけ待機しないといけません。上の例に沿うならば、インターネットで荷物を購入した瞬間に荷物が手元に来ないのと同じです。


[OnUserStatsReceived()]が呼ばれた時点で、データの中身が確認できます。
なので、OnUserStatsReceived()の中でデータの確認をしてください。

※関数名は[OnUserStatsReceived()]でなくてもかまいません。ただ、引数には[UserStatsReceived_t]が必要です。
※引数には[UserStatsReceived_t]というのは、RequestCurrentStats()でデータを要求した場合です。引数に必要なものはSteamライブラリに記載されています。

![SteamCALL_BACK.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637404/ba9715b7-9751-d39a-1167-810f605d43e5.png)


####SteamUserStats()->SetStat([API名], [保存するデータ])

データを更新する関数です。
[API名]のところには、「SteamWorks側の設定」で作ったデータ定義の[API名]を入れます。

####SteamUserStats()->StoreStats()

SteamUserStats()->SetStat()で更新したデータを、Steamサーバーに提出する関数です。
SteamUserStats()->SetStat()でセットした後にこの関数を呼ぶようにしてください。この関数を呼ぶことにより正常にサーバーにデータが保存されます。

####SteamUserStats()->GetStat([API名], [データを入れる変数])

データを取得する関数です。
[API名]のところには、「SteamWorks側の設定」で作ったデータ定義の[API名]を入れます。

------
```
SteamUserStats()->SetStat([API名], [保存するデータ]);

SteamUserStats()->StoreStats();

SteamUserStats()->GetStat([API名], [データを入れる変数]);
```
この三つの関数は RequestUserStats()でデータを要求し、そのコールバックを介して正常に返されている必要があります。(STEAM_CALLBACK()の[関数名]が呼ばれている必要があります)
そのため、この三つの関数はSTEAM_CALLBACK()の[関数名]が呼ばれた後に行ってください。

また、[SteamUserStats()]はポインタで用意されているため、自分の扱いやすい変数にポインタをコピーしても問題ありませんが、SteamUserStats()の扱っているクラスが純粋仮想関数を使用しているため、ポインタ以外で使用することはできません。

----
以上でSteamサーバー編を終了します。

今回、紹介したものを使うことが出来ればSteamサーバーへのデータ保存などは出来るでしょう。
(私は、今回紹介ものしか使用していません。)

ランキングや実績などを実装したい場合でも前半に紹介した関数は必要になるので覚えておくといいでしょう。

また、何か間違った点がございましたら、ご指摘よろしくお願いします。

------
Steamに[私の作ったゲーム](https://store.steampowered.com/search/?developer=Matsuta%20App.)を公開しています。
無料体験版もあるのでぜひ遊んでみてください。