#はじめに
GooglePlayには無料で利用できるSaved Gamesという名前のクラウドセーブ機能が準備されています。容量の上限は3MBです。(2018.4.16現在)
日本語情報が少なく、Unityでの実装に苦労したので共有したいと思い記事を書きました。
developers.google.comのSaved Gamesのページ
#Unityアプリ側
##動作
- バックアップボタンを押すだけの操作
- =>セーブデータがなければバックアップ
- =>ローカルのプレイよりプレイ時間の短いデータしかクラウドにないならクラウドにセーブ
- =>ローカルのプレイよりプレイ時間の長いデータがクラウドにあるならクラウドからロード
##実装コード
- GooglePlayServicesの導入に関してはここでは触れません
- このスクリプトはゲーム開始時に存在し破壊されないオブジェクトのコンポーネントであることを前提にしています。
- stringをクラウドとやりとりするコードを以下に記します。セーブデータを1つのstringにする処理はここでは触れません(自分はJsonUtility等でやっています)
using UnityEngine;
using System;
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using GooglePlayGames.BasicApi.SavedGame;
using System.Text;
using UnityEngine.SocialPlatforms;
public class CloudManager : MonoBehaviour {
public static CloudManager Instance;
public DateTime openedSavedGame_commitDate;
bool isSaving;
static string saveFileName = "SaveFileName_AsYouLike";
static string saveString = "";
bool hasInternetConnection { get { return Application.internetReachability != NetworkReachability.NotReachable; } }
bool isAuthed { get { return Social.Active.localUser.authenticated; } }
#region lifeCycle
void Awake() {
Instance = this;
}
void Start(){
//ログイン処理例
PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
.EnableSavedGames()
.Build();
PlayGamesPlatform.InitializeInstance(config);
PlayGamesPlatform.Activate();
}
#endregion
#region input
//バックアップボタンから呼ぶメソッド
public void PressCloudSyncButton() {
OpenSavedGame();
}
#endregion
//まずクラウド上のファイルをOPEN
public void OpenSavedGame() {
if (!hasInternetConnection) {
Debug.Log("No Internet Connection");
return;
}
if (!isAuthed) {
Debug.Log("Not Authed");
return;
}
isSaving = false;
Debug.Log("Opening savedGame in the cloud");
((PlayGamesPlatform)Social.Active).SavedGame.OpenWithAutomaticConflictResolution(
saveFileName,
DataSource.ReadCacheOrNetwork,
ConflictResolutionStrategy.UseLongestPlaytime,
OnSavedGameOpened
);
}
public void SaveToCloud() {
if (!hasInternetConnection) {
Debug.Log("No Internet Connection");
return;
}
if (isAuthed) {
isSaving = true;
//first, open the file
((PlayGamesPlatform)Social.Active).SavedGame.OpenWithAutomaticConflictResolution(
saveFileName,
DataSource.ReadCacheOrNetwork,
ConflictResolutionStrategy.UseLongestPlaytime,
OnSavedGameOpened
);
} else {
Debug.Log("Not Authed");
return;
}
}
#region savedGames Callbacks
ISavedGameMetadata currentOpenedSavedGameMetadata;
// OPENできたらwrite/readする
void OnSavedGameOpened(SavedGameRequestStatus status, ISavedGameMetadata gameMetadata) {
if (status == SavedGameRequestStatus.Success) {
openedSavedGame_commitDate = gameMetadata.LastModifiedTimestamp;
currentOpenedSavedGameMetadata = gameMetadata;
if (isSaving) {
BuildBackupDate();
byte[] data = ToBytes(saveString);
var builder = new SavedGameMetadataUpdate.Builder();
var updateMetadata = builder
.WithUpdatedPlayedTime(GameController.statTotalBattleTime)
.WithUpdatedDescription("SAVED AT:" + DateTime.UtcNow.ToLocalTime().ToShortDateString())
.Build();
((PlayGamesPlatform)Social.Active).SavedGame.CommitUpdate(gameMetadata, updateMetadata, data, OnSavedGameWritten);
} else {
SAVEDGAME_STATUS stat = SAVEDGAME_STATUS.Default;
if (gameMetadata.Description == null || gameMetadata.Description == "") {
stat = SAVEDGAME_STATUS.NoData;
}
if (gameMetadata.TotalTimePlayed > GameController.statTotalBattleTime) {
stat = SAVEDGAME_STATUS.LongerPlaytime;
} else {
if (gameMetadata.TotalTimePlayed == GameController.statTotalBattleTime) {
stat = SAVEDGAME_STATUS.Default;
} else {
stat = SAVEDGAME_STATUS.ShorterPlaytime;
}
}
switch (stat) {
case SAVEDGAME_STATUS.Default:
Debug.Log("バックアップデータの更新は不要です");
break;
case SAVEDGAME_STATUS.NoData:
case SAVEDGAME_STATUS.LongerPlaytime:
case SAVEDGAME_STATUS.ShorterPlaytime:
OpenDialog(stat);
break;
}
}
}
}
public void LoadSavedGameData() {
((PlayGamesPlatform)Social.Active).SavedGame.ReadBinaryData(currentOpenedSavedGameMetadata, OnSavedGameDataRead);
}
void OnSavedGameDataRead(SavedGameRequestStatus status, byte[]data) {
if (status == SavedGameRequestStatus.Success) {
ProcessCloudData(data);
} else {
Debug.LogWarning("Error: Reading game:" + status);
}
}
void OnSavedGameWritten(SavedGameRequestStatus status, ISavedGameMetadata gameMetadata) {
if (status == SavedGameRequestStatus.Success) {
GameController.Instance.gameProgress.cloudSaveNum++;
GameController.Instance.SaveGameProgress();
Debug.Log("Game " + gameMetadata.Description + " written");
} else {
Debug.LogWarning("Error: saving on the cloud " + status);
}
}
#endregion
void ProcessCloudData(byte[]cloudData) {
DistributeLoadedData(cloudData);
}
string FromBytes(byte[] bytes) {
return Encoding.UTF8.GetString(bytes);
}
byte[] ToBytes(string data) {
return Encoding.UTF8.GetBytes(data);
}
enum SAVEDGAME_STATUS {
Default,
NoData,
ShorterPlaytime,
LongerPlaytime
}
//ダイアログ表示,YES NOを選択すると対応するメソッドを呼ぶ。具体的な内容にはこの記事では触れない
void OpenDialog(SAVEDGAME_STATUS stts) {
Debug.Log("OPEN DIALOG:" + stts);
switch (stts) {
default:
break;
case SAVEDGAME_STATUS.NoData:
Debug.Log("バックアップデータがありません、バックアップしますか?");
break;
case SAVEDGAME_STATUS.ShorterPlaytime:
Debug.Log("バックアップしますか?");
break;
case SAVEDGAME_STATUS.LongerPlaytime:
Debug.Log("バックアップしたデータをロードしますか?");
break;
}
}
void QuitApp() {
Debug.Log("QuitApp");
Application.Quit();
}
void BuildBackupData() {
//バックアップしたいデータをここで代入する
saveString = "(バックアップしたいデータ)";
}
//ロードしたデータをゲームに反映する処理
void DistributeLoadedData(byte[] dataFromCloud) {
if (dataFromCloud == null) {
Debug.Log("No data saved on the cloud yet");
return;
}
Debug.Log("Decoding cloud data from bytes.");
string stringFromCloud = FromBytes(dataFromCloud);
if (stringFromCloud == "") {
Debug.Log("No data saved on the cloud yet");
return;
}
//データを反映する
HogeHoge(SaveString);
//念の為アプリを落とす
QuitApp();
}
}
-プレイ時間とクラウドにセーブ成功した回数を記録しておくクラスの例
public class GameController : MonoBehaviour {
public static GameController Instance;
public static double statTotalBattleTime;
public class GameProgress{
public int cloudSaveNum;
}
public GameProgress gameProgress;
}
- 参照したサイト
[GPG Tutorial – Saved Games (Code)]http://resocoder.com/2017/03/10/gpg-tutorial-saved-games-code/
#FireBase側設定
- FireBaseの導入に関してはここでは触れません
##1. GooglePlayConsole=>ゲームサービス=>(アプリ名)=>ゲームの詳細⇒保存済みゲーム(オンを選択)
##2. APIコンソール プロジェクトでGooglePlayGameServicesとDriveAPIにチェックマークがあることを確認(なければリンクから設定)
##3. ゲームサービス=>(アプリ名)=>公開=>ゲームに対する変更の公開=>反映を待つ(24時間程度?)
-
自分はここで落とし穴に落ちました。この項目の存在に気づかず、公開しなくてもベータテスターはSavedGamesの機能が利用できるため、うまく出来ていると思いこんでそのままアプリをリリースしてしまいました…。状況を認識するのに1週間以上かかりました。
-
公開前状態ではリリースのAPKからこんなLOGがLOGCATに出てきます
Exception in com/google/android/gms/games/snapshot/Snapshots.load: java.lang.IllegalStateException: Cannot use snapshots without enabling the 'Saved Game' feature in the Play console.
- NullReferenceなんですよねこの後。口に酸っぱいもの上がってきますよねこんなの。
- 公開から一晩寝かせておくと、上記Errorは綺麗に消えていました。
##「公開」が必須手順なら未公開の変更があるって警告表示出してほしい
##このUIは罠すぎるので腹立ち紛れに記事を投稿することにしました