Help us understand the problem. What is going on with this article?

RPGアツマールの共有セーブAPIを使う

概要

RPGアツマールには非同期ネットゲームを想定したAPIが存在します。
その中の共有セーブAPIを使ってみた記事です。

RPGツクールMVのアクタを共有するためにやったことをまとめます。

作ったもの

共有セーブデータの保存

まずは保存する側になります。
特定のキーでセーブデータを保存すると、他のユーザーがそのセーブデータを読み取ることができます。これによってキャラクタのステータスを共有したりリプレイデータの共有を行うことができます。

共有セーブデータの保存

// APIの関数が存在するかどうかのチェック
var storageDefined = window.RPGAtsumaru && window.RPGAtsumaru.storage;
var exStorageDefined = window.RPGAtsumaru && window.RPGAtsumaru.experimental.storage;
var setItemsDefined = storageDefined && window.RPGAtsumaru.storage.setItems;

// プラグインコマンド実行時の処理を追加
var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command) {
    _Game_Interpreter_pluginCommand.apply(this, arguments);
    // プラグインコマンドでSaveShareActorをしたときに実行
    if (command === "SaveShareActor") {
        if(setItemsDefined) {
            // shareActorに共有するActorの情報を作る(後述)
            var shareActor = internal.encodeSharedActor_($gameParty.leader())
            // APIの実行
            window.RPGAtsumaru.storage.setItems([{ key: "Atsumaru Shared", value: JSON.stringify(shareActor) }])
                .then(function() {
                    // 成功時の処理
                }, function(error){
                    // エラー時の処理
                })
        }
    }
}

プラグインコマンドで発動するように実装しました。
セーブ保存のAPIで、Keyに Atsumaru Shared を指定すると、保存された文字列を他のユーザーから読み取ることができるようになります。
https://atsumaru.github.io/api-references/storage

取得が終了するまでイベントの進行を待つには?

ネットワークを介した処理なので即座に終了するわけではなく、ネットワークの状況次第で時間がかかります。
その間はイベントの進行も止めておきたい場合、下記を追加します。
(他の公式プラグインと同じ方法です)

    var _Game_Interpreter_updateWait = Game_Interpreter.prototype.updateWait;
    Game_Interpreter.prototype.updateWait = function() {
        var result = _Game_Interpreter_updateWait.apply(this, arguments);
        return result || Boolean(this._waitForSetItemsPlugin)
    };

window.RPGAtsumaru.storage.setItems の1行前に this._waitForSetItemsPlugin=true を記述して、
成功もしくはエラーのコールバックの中でfalseに設定します。
this._waitForSetItemsPlugin がtrueの間はイベントの進行が止まります。

アクタの情報をjsonで保存する

    var internal = {}

    internal.encodeSharedActor_ = function(actor) {
        var shareActor = {}
        shareActor.id        = actor._actorId
        shareActor.classId   = actor._classId
        shareActor.exp       = actor._exp
        shareActor.paramPlus = actor._paramPlus
        shareActor.equipWeapon = actor._equips[0]._itemId
        shareActor.equipsSub = []
        for(var idx=1;idx<actor._equips.length;idx++) {
            shareActor.equipsSub.push(actor._equips[idx]._itemId)
        }
        return shareActor
    }

保存する文字列は何を記録したいかによって変わりますが、今回は引数に渡されたアクタの情報を記録します。アクタの再現に必要な最低限の情報を連想配列に詰めて返します。外側に window.RPGAtsumaru.storage.setItems に渡すときに JSON.stringify で文字列にします。

共有セーブデータの読み取り

ここまででアクタ1人分の情報を保存できるようになりました。
次は他のユーザーから保存した情報を読み取る方法です。
準備として、保存した側が「ユーザー間通信の許可」を行っている必要があります。これは公式のプラグインがあるのでそれを利用します。これによって他のユーザーから共有セーブを読み取れるようになります。

ユーザー間通信の許可

自分が保存した共有セーブを他のユーザーから読み取るために行います。

https://atsumaru.github.io/api-references/common/interplayer/
AtsumaruInterplayerEnableExperimental.js を導入して、
プラグインコマンドから EnableInterplayer を実行しておきます。

共有セーブデータの読み取り

    // APIの関数が存在するかどうかのチェック
    var exStorageDefined = window.RPGAtsumaru && window.RPGAtsumaru.experimental.storage;
    var getSharedItemsDefined = exStorageDefined && window.RPGAtsumaru.experimental.storage.getSharedItems

    var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function(command) {
        _Game_Interpreter_pluginCommand.apply(this, arguments);
        // (さっきの続き)
        // プラグインコマンドでGetShareActorRecentUserをしたときに実行
        else if( command === "GetShareActorRecentUser" )
        {
            if(getSharedItemsDefined)
            {
                // 取得したセーブデータ(Json)を$gameTempで保持する
                $gameTemp._sharedActors = {}
                // APIの実行
                window.RPGAtsumaru.experimental.storage.getSharedItems(/*取得するUserIdの配列*/)
                    .then(function(result) {
                        // Keyの配列を取得する
                        var keys = Object.keys(result)
                        for(var idx=0; idx < keys.length;idx++)
                        {
                            var key = keys[idx]
                            if( Number(key) != /*自分のUserId*/) // 自身のUserIdは含めない
                            {
                                // JSON.parseで連想配列に直して保持する
                                var parsed = JSON.parse(result[key])
                                // 例) {12353:{id:15,classId:1542,...},52431:{id:12,classId:124,...}}
                                $gameTemp._sharedActors[key] = parsed
                            }
                        }
                    }, function(error){
                        // エラー時の処理
                    })
            }
        }

これで引数に渡したUserIdのユーザーの共有セーブデータを $gameTemp._sharedActors で保持します。
例えばフレンドのリストを記録しておいてそのUserIdで配列を作れば、フレンドのセーブデータの取得ができます。
後述する「最近プレイしたユーザー」で取得すると、自分のUserIdが含まれるので除外しておく必要があります。

取得が完了するまでイベントを止めたい場合は、
「取得が終了するまでイベントの進行を待つには?」に記載したような待ちの処理を同様に入れてください。

最近プレイしたユーザーの取得

https://atsumaru.github.io/api-references/user
AtsumaruGetUserInformationExperimental.js を導入して
プラグインコマンドから GetUserInformation を実行しておきます。

一部改造してあります。
元の挙動だと変数( $gameVariables )に保存するようになっていますが $gameTemp に配列で保持します。
配列にしておくと後で window.RPGAtsumaru.experimental.storage.getSharedItems に渡しやすくなります。

window.RPGAtsumaru.experimental.user.getRecentUsers()
   .then(function(recentUsers) {
      for (var i = 0; i < 100; i++) {
         var user = recentUsers[i];
         // 変数でなくgameTempに配列を保持、自身のIDは除く
         if(user && user.id != 0 && Number(user.id) != /*自分のUserId*/)
         {
            $gameTemp._recentUserIds.push(user.id)
            $gameTemp._recentUserNames.push(user.name)
         }
         //     $gameVariables.setValue(offset + i, user ? user.id : 0);
         //     $gameVariables.setValue(offset + i + 100, user ? user.name : 0);
      }
   }, function(error) {
      // エラー時の処理
   }

自分の情報の取得

https://atsumaru.github.io/api-references/user
AtsumaruGetSelfInformationExperimental.js を導入して
プラグインコマンドから GetSelfInformation を実行しておきます。

ここまでの記述で /*自分のUserId*/ と書いてある部分がありますが、
これはこのプラグインで取得して $gameVariables の中で保持しています。

$gameActorsにアクタの情報を構築

    // $gameTemp._sharedActorsからgameActorへのコピー
    internal.dstSharedActorId = 3
    internal.setupSharedActor = function(userId)
    { 
        if(!$gameTemp._sharedActors) return false

        var sharedActor = $gameTemp._sharedActors[userId]
        if(!sharedActor) return false
        var dstActor = $gameActors.actor(RQ03x.dstSharedActorId)
        var srcActor = $gameActors.actor(sharedActor.id)

        // sharedActorから持ってくるもの
        dstActor._classId   = sharedActor.classId
        dstActor._exp       = Object.assign({},sharedActor.exp)
        dstActor.changeExp(dstActor.currentExp(),false)
        dstActor._paramPlus =  sharedActor.paramPlus.concat()
        // equipsのデコード
        dstActor._equips = [];
        for (var i = 0; i < dstActor.equipSlots().length; i++) 
        {
            dstActor._equips[i] = new Game_Item()
        }
        dstActor._equips[0].setObject($dataWeapons[sharedActor.equipWeapon])
        for(var idx=0; idx<sharedActor.equipsSub.length;idx++)
        {
            if(sharedActor.equipsSub[idx]>0)
             {
                dstActor._equips[idx+1].setObject($dataArmors[sharedActor.equipsSub[idx]])
            }
        }

        // IDをもとにコピーするもの
        dstActor.setName( srcActor.name() + "(助っ人)" )
        dstActor.setNickname( srcActor.nickname() ) 
        dstActor._faceName = srcActor._faceName
        dstActor._faceIndex = srcActor._faceIndex
        dstActor._characterName = srcActor._characterName
        dstActor._characterIndex = srcActor._characterIndex
        dstActor._battlerName = srcActor._battlerName
        dstActor.setProfile(srcActor._profile)

        dstActor.setTp(0)
        dstActor.recoverAll()
        dstActor.refresh()

        return true
    }

「アクタの情報をjsonで保存する」で作ったjsonから$gameActorsにコピーする部分です。
仲間に入れるUserIdを引数にこれを実行します。
internal.setupSharedActor(12345) を実行すると、UserId=12345のユーザーのアクタが、ID=3のアクタ(アクタのデータベースで3番目)の位置にコピーされます。名前は末尾に「(助っ人)」という文字が付きます。
共有セーブから持ってきたデータを保持しておく用のアクタ(今回はID=3)を決めておいてその場所にコピーしています。

まとめ

共有セーブデータを使って $gameActors の情報を共有する方法をまとめました。

  • 共有セーブデータの保存
    • アクタの情報を保存する
  • 共有セーブデータを読み取る
    • ユーザー間通信の許可
    • 共有セーブデータの読み出し
      • 最近プレイしたユーザーの取得
      • 自分の情報の取得
    • UserIdを引数に$gameActorsにアクタの情報を構築する
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away