Google play game services の Saved game を Unity から使う。

f:id:eiki_okuma:20180929105308p:plain

資料が少なすぎる。

ので書きます。

マトモな解説資料ここくらい?↓
https://answers.unity.com/questions/894995/how-to-saveload-with-google-play-services.html

 

Saved games って何?

 Android のグーグルプレイには AppStore における Game Center のように Google play game services (GPGs) というものがあり、そのうち一つの機能に Saved game というモノがある。いわゆるクラウドセーブで、どの端末でも同じ所から始められるようにする、というコンセプトらしい。

 アイテム課金を扱うならクラウドセーブは必須だし、あるなら利用しましょうってことで使ってみることにした……が、資料(特に日本語記事)が少ない! ので今回の実装をまとめておく。

 

準備

 準備の各項目はググれば出てくるので詳細説明なし。

  1. GPGs を Unity に組み込む。
  2. GooglePlayConsole からゲームサービスを選び、アプリを紐付け。保存済みゲームを ONにする。
  3. Android でアプリを起動し、GPGs が正常に読み込まれていることを確認する。(内部テストなどで Google Play から直接ダウンロードしないと認証されないっぽい)

実装

初期化

#if UNITY_ANDROID
    PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder().EnableSavedGames().Build();
    PlayGamesPlatform.InitializeInstance( config );
    PlayGamesPlatform.Activate();
#endif
    Social.localUser.Authenticate( ( bool s ) => { if ( s ) { DebugEngine.REPORT( "[Social] Authenticate succeed."); } else { DebugEngine.REPORT( "[Social] Authenticate failed..." ); } } );

クラウドのセーブデータを呼び出す

const string cCloudSaveName = "Gamename_savedata";
int mCloudSaveResult = -1;
byte[] mSaveData;
public void BackupDataToCloud()
{
	mCloudSaveResult = 0;
	((PlayGamesPlatform)Social.Active).SavedGame.OpenWithAutomaticConflictResolution(
		cCloudSaveName,
		DataSource.ReadCacheOrNetwork,
		ConflictResolutionStrategy.UseOriginal,
		OnSavedGameOpenedAsSave
	);
}

 cCloudSaveNameの名前は自由に変えるべし。
 mSaveData にはあらかじめセーブデータを入れておく。
 SavedGame が Open になるとコールバックに設定した OnSavedGameOpenedAsSave が自動で呼び出される。

 ConflictResolutionStrategy.UseOriginal はセーブデータ衝突時の解決方法で、UseLongestPlaytime を用いて最も長いセーブデータを優先する方式をとっても良い。(その場合は後述の SavedGameMetadataUpdate Builder()WithUpdatedPlayedTime() を噛ませる)

クラウドのセーブデータに書き出し

void OnSavedGameOpenedAsSave( SavedGameRequestStatus status, ISavedGameMetadata game )
{
	if ( status != SavedGameRequestStatus.Success ) { mCloudSaveResult = -1; return; }
	var updatedMetadata = new SavedGameMetadataUpdate.Builder().Build();
	// saving to cloud
	((PlayGamesPlatform)Social.Active).SavedGame.CommitUpdate( game, updatedMetadata, mSaveData, OnSavedGameWritten );
}

// 完了
void OnSavedGameWritten( SavedGameRequestStatus status, ISavedGameMetadata gameMetadata) 
{
	mCloudSaveResult = status == SavedGameRequestStatus.Success ? 1 : -1;
}

特筆すべき点はなし。成功失敗関わらず、OnSavedGameWritten が呼び出されて完了。

クラウドのセーブデータを読み出す

public void RestoreDataFromCloud()
{
	mCloudSaveResult = 0;
	((PlayGamesPlatform)Social.Active).SavedGame.OpenWithAutomaticConflictResolution(
		cCloudSaveName,
		DataSource.ReadCacheOrNetwork,
		ConflictResolutionStrategy.UseOriginal,
		OnSavedGameOpenedAsLoad
	);
}

//----------------------------------------------------------------------------
void OnSavedGameOpenedAsLoad( SavedGameRequestStatus status, ISavedGameMetadata game )
{
	((PlayGamesPlatform)Social.Active).SavedGame.ReadBinaryData( game, OnSavedGameLoaded );
}

// 完了
void OnSavedGameLoaded(SavedGameRequestStatus status, byte[] data)
{
	mSaveData = data;
	mCloudSaveResult = status == SavedGameRequestStatus.Success ? 1 : -1;
// セーブデータをゲーム内に読み込む処理など諸々をここに。 }

こちらはもっと単純。同様にクラウドセーブを呼び出し、Binary にロード。
おわり。

ゲーム内からセーブデータ管理 UI を呼び出す

public void ShowSelectSavedGameUI()
{
	((PlayGamesPlatform)Social.Active).SavedGame.ShowSelectSavedGameUI( Social.localUser.userName, 3, false, true,
		(SelectUIStatus status, ISavedGameMetadata saveGame) => { ; } );
}

これはオプションで。ShowSelectSaveGameUI を呼ぶだけでクラウドセーブの追加・削除が行える UI を呼び出せる。楽。

 

セーブデータをバイナリデータにする凡例

どうやって byte[] を用意するかという話だが、自分のゲームの実装だとこんな感じ。

string cSaveDataName = "SaveData.dat"
byte[] SaveData()
{
	byte[] bytes = null;
	MemoryStream stream = new MemoryStream();
	using( BinaryWriter w = new BinaryWriter( stream ) )
	{
		w.write( いろいろ );
		w.write( いろいろ );
		w.write( いろいろ );

		using ( var file = File.OpenWrite( cSaveDataName ) )
		{
			// bytes に書き込む
			bytes = new byte[stream.Length];
			System.Array.Copy( stream.GetBuffer(), bytes, stream.Length );
			
			// file に書き込む
			file.Write( stream.GetBuffer(), 0, (int)stream.Length );
			
			// stream を閉じる
			stream.Close();
		}
	}
	return bytes;
}

Load の場合も同様で、通常は

new BinaryReader( File.OpenRead( cSaveDataName ) ) 

とするところを、クラウドからロードする時は

new BinaryReader( new MemoryStream( bytes ) )

としてあげればOK。*1

実運用どうする?

 毎回クラウドからセーブ+クラウドからロードしても良いのだが、セーブはともかくロードが読み込み待ちになるので、あくまでバックアップとして使いたい場合はちょっとまどろっこしい。

 というわけで、実運用的には

  1. セーブは毎回クラウドに、任意のタイミングで復旧可能
  2. クラウドに保存・復旧はゲーム内メニューからのみ行う

 というのが良さそう。はむころりんはガチャのリセマラが出来てしまうので前者。

 

 何か質問などあればお気軽に。

 

 

*1:本当は MemoryStream は別途 close する必要があるのでちゃんと実装するなら変数に取っておくべし