NEWS / BLOG
2023.02.14
UnityとUEでSteam対応のあれこれ
#テックネタ #プログラマー

こんにちは~。プログラマーの惣一郎さんです。

最近、UnityとUnrealEngine(以下、UEと略す)の双方でSteam対応を担当したんですが、特にUEの方の対応方針が調べてもイマイチよく分からなかったので、たぶんこう対応すべきという方針をUnityとUEで比較しながら紹介したいと思います。

 

line_enogu1_red.png

 

まずSteamworksの公式サイトには商用エンジンへのサポートとして以下のように書かれています。
https://partner.steamgames.com/doc/sdk/api

エンジン ネイティブサポート 情報
Unity ×

サードパーティー:Facepunch.Steamworks
サードパーティー:Steamworks.NET

Unreal Engine 4 Online Subsystem Steam

Unityに関してはValve社は公式サポートしていませんが、2つのサードパーティー製ソリューションが紹介されています。

この2つはどちらでも問題無いかと思いますが、Steamworks.NETを使った紹介記事の方が多いように見受けられましたので情報を探し易くてお勧めです。
実際、サンプルも揃っていますし、公式SDKのほぼ全てのネイティブ関数をC#で使えるようにサポートされているようですので、UnityのC#が読み書きできれば対応作業は容易です。

続いてUEの方ですが、ネイティブサポートが○で、Online Subsystem Steamを使うべし、と読み取ったのですがこれは誤解でした。
UEのOnline Subsystemというのは、Steam、XBox Live、Facebookなど各種プラットフォームのオンラインサービス(実績獲得やマッチングなど)をラッピングして共通で使えるようにしてくれている便利なシステムです。
Online Subsystem Steamは、そのOnline SubsystemのSteam版です。
ここで注意が必要なのは、Online Subsystem Steamが全てのSteam機能をラッピングしているわけではない、という事です。
Online Subsystem Steamでラッピングしてくれている機能以外は、Steamworks SDKの関数をUE C++から直接叩く必要があります。
Steamworks SDKの関数をUE C++から直接叩くには、公式サイトにも書かれていますが、build.csに次の行の追加が必要ですのでお忘れなく。

  DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam");

Steamworks SDKの関数の利用方法については、公式の関数リファレンスと一緒に、SDK付属のサンプルから検索するのが一番良かったです。
サンプルはこちら。

  {SDK解凍場所}/sdk/steamworksexample

 

line_enogu1_red.png

 

■実装準備:Unity編
Steamworks.NETをUnityプロジェクトにインストールしましょう。
Githubからダウンロードです。
https://github.com/rlabrecque/Steamworks.NET
あわせて、サンプルプロジェクトもダウンロードしておけば、利用方法はだいたい分かります。
https://github.com/rlabrecque/Steamworks.NET-Example

Steamの使いたい機能の実装方法は、例えば「Unity Steam 実績」のようにGoogle検索してやれば分かりやすい紹介記事が出てきますので参考にしましょう。
Google検索でそれらしきものが見つからない場合は、ダウンロードしたサンプルプロジェクトから利用方法を探せばだいたいどうにかなる、というのが対応方針になっていきます。

 

line_enogu1_red.png

 

■実装準備:UE編
まずはOnline Subsystem Steamの公式サイトに沿って準備をしていくことになります。
https://docs.unrealengine.com/4.26/ja/ProgrammingAndScripting/Online/Steam/
が!
しょっぱなでSteamworks SDKをダウンロードして、UE4のエンジンのビルドが求められてビックリするほどハードルが高いです!!
しかし心配いりません。
普通に Epic Game Luncher からインストールしたUEに、少し古いバージョンかもしれませんがSteamworks SDKは入っています。
上記の公式サイトの冒頭に書かれているのは、利用するSteamworks SDKを最新にするためのエンジンビルド方法、と考えたほうが良さそうです。
Steamを提供するValve社は、Steamworks SDKについて最新版の利用を推奨していますが、必須にはなっていませんので多少古くてもSteamでのアプリ公開が可能です。

なお、利用中のUEにどのバージョンのSteamworks SDKが入っているかを簡単に確認するためには、以下のパスを確認してください。

  /(UnrealEngineインストール場所)/Engine/Binaries/ThirdParty/Steamworks/Steam[Current Version]


手元のUE4.27.2の場合はv1.51が入っていました。

blog0212_01.png

公式サイトにある「アプリケーション設定を構成する」以降のDefaultEngine.iniへの記載については必要ですので、その通りに対応してください。
なお、もしSteam以外も含めたマルチプラットフォーム開発をしているのであれば、これらの記述はDefaultEngine.iniではなく、Steam用ビルドのiniファイルにのみ記載すべきだと思います。

 

line_enogu1_red.png

 

■SDK初期化

それでは以降は、幾つかのSteam機能について対応方法を部分的に紹介します。
まずはSDKの初期化です。

○Unity
SteamManagerの関数を呼べば、自動的にSteamManagerのインスタンスを生成して即座にSteamの初期化をしてくれます。
よって、しょっぱなでこのようにすれば良いです。


  if (SteamManager.Initialized)
  {
	// SteamSDK初期化成功
  } else {
	// SteamSDK初期化失敗
	Application.Quit();		// 必要ならば初期化失敗時はアプリケーションを終了
  }

○UE
正しくDefaultEngine.iniに設定していれば、Online Subsystem Steamが起動時にSteamの初期化してくれています。
実際、Steamにログインできているのかどうか確認する方法ですが、これで識別できました。

  const FUniqueNetIdRepl& APlayerController->PlayerState->GetUniqueId();


これが、GetType() == STEAM ならばSteamを利用できており、Steamクライアントを起動していないような場合は NAME_None となりました。
一例として、SteamにログインできているならユーザーのSteamIDを返す関数はこのようになります。

  FString	USteamGameFunctionLibrary::GetUserSteamID(APlayerController* playerController)
  {
	if (playerController != nullptr)
	{
		APlayerState* playerState = playerController->PlayerState;
		const FUniqueNetIdRepl& netId = playerController->PlayerState->GetUniqueId();
		if (netId.IsValid() && netId.GetType() == FName("STAM"))
		{
			return netId->ToString();
		}
	}
	return FString("00000000000000000");
  }

 

line_enogu1_red.png

 
■実績

○Unity
Steamworks.NET のサンプルが非常に分かり易く、また流用しやすいプログラムでしたので、そのまま使うと良いでしょう。

開発作業用に、獲得済みの実績をリセットする関数も用意されています。

  SteamUserStats.ClearAchievement(achievementIdName);


○UE
Online Subsystem Steam がばっちりサポートしてくれていて、ブループリント用のノードが用意されています。
まずは、.iniファイルに実績のAPI名を全て記載する必要があります。

 Achievement_0_Id="ACH_WIN_ONE_GAME"
 Achievement_1_Id="ACH_WIN_100_GAMES"
 Achievement_2_Id="ACH_TRAVEL_FAR_ACCUM"
 Achievement_3_Id="ACH_TRAVEL_FAR_SINGLE"


続いて、実績を獲得したい場合は Cache Achievements してから Write Achievement Progress で Progress=100 にしてあげるだけです。

blog0212_02.png

UEで獲得済みの実績をリセットしたい場合は、Progress=0 にしてやる事で対応されています。(ただし、Shippingビルドでない場合のみ)

 

line_enogu1_red.png

 

■クラウドセーブ
Steamでは、同じアカウント利用であれば別の端末からのプレイであってもセーブデータを共用できる機能があります。
Steamworksからクラウドセーブ対象にするフォルダとファイル名を指定してやるだけでクラウドセーブ化できてとても便利です。
また、セーブデータの保存場所をアプリのインストール場所以外(例えば MyDocuments以下など)に指定する事も可能です。

ここでは、アプリのインストール場所以外にセーブする方法の参考情報を記載します。

○Unity
もしセーブデータにUnityのPlayerPrefsを利用しているようであれば、PlayerPrefsの保存先をSteamのクラウドセーブ対象場所に指定する事ができませんので、PlayerPrefsを使用せずにファイル出力してセーブする機構を構築する必要があります。

○UE
もしセーブデータにSaveGameオブジェクトを使用しているならば、ファイルの保存先を変更するにはエンジン改造が必要です。
ただし、エンジン改造したくない場合に SaveGameToSlot (または AsyncSaveGameToSlot)の保存先を変更するアイデアもあり、紹介されているサイトがあったのですが、今見るとリンク先に繋がりませんでした。残念。
実装のアイデアとしては、FGenericSaveGameSystem を継承したクラスを作成し、

 virtual FString GetSaveGamePath(const TCHAR* Name);


このバーチャル関数の中身を自分が保存したいパスに変更してやるという事と、UGameplayStatics::SaveGameToSlot している部分をこれまたエンジンのソースコードをコピペした関数を自作して、中で

 ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem();


している部分を先に自作したFGenericSaveGameSystem継承クラスのインスタンスに置き換える、というものでした。

 

line_enogu1_red.png

 

■テキスト入力
話題のSteam Deckで動作確認済みのバッチを取得するためには幾つかのハードルがありますが、テキスト入力はコントローラだけを使用して入力できる実装が必要です。
そのために、Steamwork SDKが用意してくれているオンスクリーンキーボードが ShowGamepadTextInput です。
動作時には、このようなオンスクリーンキーボードが表示されます。
blog0212_03.png

○Unity
Steamworks.NETの SteamUtils.ShowGamepadTextInput 関数を使用します。

	bool isShowTextInput = SteamUtils.ShowGamepadTextInput(EGamepadTextInputMode.k_EGamepadTextInputModeNormal,
		EGamepadTextInputLineMode.k_EGamepadTextInputLineModeSingleLine,
		"description",
		10,
		"原文");

○UE
Online Subsystemではサポートされていません。
C++から直接、SDK関数を叩く必要があります。

	ISteamUtils* steamUtilsPtr = SteamUtils();
	if (steamUtilsPtr != nullptr)
	{
		bool isShowTextInput steamUtilsPtr->ShowGamepadTextInput(
		    EGamepadTextInputMode::k_EGamepadTextInputModeNormal,
		    EGamepadTextInputLineMode::k_EGamepadTextInputLineModeSingleLine,
		    TCHAR_TO_ANSI(*description),
		    (uint32)charMax,
		    TCHAR_TO_ANSI(*existingText));
	}

○確認方法
ShowGamepadTextInput は返り値として、オンスクリーンキーボードを表示できたかどうかが bool で得られます。
そしてこの機能は、SteamのBigPictureモードでのみ表示できます。
通常、アプリ開発時にはビルドしたとしてもBigPictureモードで動作させることができませんので、つまりこの ShowGamepadTextInput の動作を確認する事ができません。
これはツライ!
ですが、疑似的にBigPictureモードで動作させて、ShowGamepadTextInputからの結果を得る実験だけなら可能です。

手順1
 Steamアプリを起動して、左下の「ゲームを追加」→「非Steamゲームを追加」で自分で作成したビルドを指定します。

blog0213_04.png

手順2
 SteamアプリをBigPictureモードにして起動します。

手順3
 ライブラリを探して、手順1で登録したアプリを起動します。

これで、ShowGamepadTextInputの返り値は true になります。
ただし、実際のゲーム画面の上にオンスクリーンキーボードは表示されませんでしたが、Steamアプリの方を見てみるとそちらにテキスト入力画面が表示され、実際に入力したテキストを送信するとアプリ側で受け取る事が出来ていました。

Steamworksにアップロードして、Steamアプリからインストール&実行できる環境の場合はゲーム画面の上に表示されます。

 

※なお、ちょっと前まで画面全体を覆うこのスクリーンショットのようなオンスクリーンキーボードだったのですが、原稿執筆時に確認のために起動してみたら見た目が全然変わっていてびっくりしました。

blog0213_05.png

 

line_enogu1_red.png

 

今回の記事は以上です。

UnityまたはUEでSteam対応する際に少しでも参考になれば幸いです。