無駄ロボット研究所

「Minecraft」の建築をRPAで自動化する ~劇的! マイクラ自動建築ロボの巻<その2>

設計図通りにブロックを置くワークフローを作る

『無駄ロボット研究所』ではRPA(Robotic Process Automation)ツール「UiPath Studio」を使って役に立たない、無駄なロボットを作っていきます。業務を効率化するという本来RPAで実現すべき目的とは真逆のロボットたちをお楽しみください。

 「UiPath Studio」を使って「Minecraft」の建築を自動化するロボット“マイクラ自動建築ロボ”を作っています。前回はマイクラと「Code Connection for Minecraft」を連携させ、「Excel」で建物の設計図を作成しました。今回はいよいよマイクラ自動建築ロボットを作っていくことにしましょう。

 使うアクティビティはシンプルなものばかりですが、繰り返し処理が多く、ワークフローも長くなるのが難点です。面倒なら、サンプルを使って、サクッと動かしてしまうことをおすすめしますが、ExcelやJSON、REST APIと、UiPathでは定番的なアクティビティを駆使するので、使い方を確認しておきましょう。

【マイクラ自動建築ロボが夢のTNTハウスを建築!】
「UiPath Studio」を使って「Minecraft」の建築をRPAで自動化してみた - 窓の杜

残念なお知らせ

 完成してから、話が違うと言われるのも困るので、最初にお断りしておきます。

 前回のビデオをご覧になった場合は、お気づきの方もいらっしゃるかもしれませんが、今回のロボットは「ブロックの方向」がすべて一定です。このため、一部のドアが間口に対して縦方向に付けられてしまうことなどがあります。

 なので、理想の家にしたい場合は、あとから若干の手直しが必要です。

 もちろん、爆破してしまうのであれば、どうでもいいことですが……。

全体像の把握

 さて、細かいことは気にせず、ロボットを作っていきましょう。

 まずは、ワークフローの全体像を把握しておきましょう。少し長くなりますが、今回のワークフローは、以下のようになります。

ワークフロー前半
ワークフロー後半

 大きく分けると、2つに分割できます。前半の初期化部は、ブロックの定義ファイルを読み込んだり、現在位置を割り出す処理です。一方、後半(Excelアプリケーションスコープでdesign.xlsを読み込む処理以降)は建築パートです。設計図を読み込み、その内容に従って、ブロックを繰り返し配置していく処理になります。

 一見、複雑そうに見えますが、やっていることは、単純な繰り返し作業なので、処理自体はシンプルです。

前半パートを作る

 それでは、前半パートから作っていきましょう。

[Excelアプリケーションスコープ]

 まずは、ブロックの定義ファイルを読み込みます。前回紹介したように、設計図では数字でブロックを表現していますが、実際にブロックを配置するときに使う「setblock」という命令では、「stonebrick」などのように名前でブロックを指定します。

 この対応表を、このアクティビティで読み込みます。「"itemlist.xlsx"」のようにExcelのファイル名を指定しましょう(プロジェクトと別のフォルダーにある場合はパスを指定すること)。

「列を読み込み」

 今回、「itemlist.xlsx」から読み込む必要があるのは、B列のブロック名の部分だけです。このため、[列を読み込み]アクティビティを使って、データを読み込みます。

 シート名(ここでは"item")と列のブロック名の最初のセル(ここでは"B2")を指定しておきましょう。

 その後、プロパティパネルの「結果」で[Ctrl]+[K]キーを押して「blockList」変数を作成し、ここに読み込んだブロック名を配列として格納します。なお、プロパティパネルで変数を作成すると、最適な型に自動的に設定されるので便利です(IEnumerable )。

 「{acacia_fence_gate,air,bed,lit_redstone_lamp……}」のような感じです。配列は0から始まるので、Excel表の番号とblockList内の番地が同じになるわけです。

blockList(変数に格納する

[HTTPリクエスト]

 現在の座標を取得するために、ダミーでブロックをひとつ設置します。ポジションを取得できるAPIがあればよかったのですが、見当たらなかったので、この方法を使うことにしました。

 setblockでブロックを配置すると、配置したブロックのポジションがJSONで戻ってきます。ここから現在の座標を取得します。

 [HTTPリクエスト]アクティビティを配置し、ウィザードで次のパラメーターを指定します。

  • エンドポイント
    http://localhost:8080/setblock
  • パラメーター
    position → ~0 ~0 ~0
    tileName → stone
ウィザードでパラメーターを設定する

 「~0 ~0 ~0」の「~」は相対座標を示す記号です。つまり、プレイヤーの現在位置にブロックを設置することになります。

 なお、パラメーターは、ウィザードから設定するときは、「~0 ~0 ~0」のように、そのまま記入しますが、プロパティパネルで設定するときは次の画面のように「"~0 ~0 ~0"」のように、値を「""」で囲む必要があるので要注意です。

「""」の有無に注意

 戻ってきた座標のデータは、プロパティパネルの「結果」で受け取ります。[Ctrl]+[K]キーを押して「retPosition」という変数を作成し、ここに格納しましょう(型はJObjectに自動設定される)。

座標を格納する

[JSONをデシリアライズ]

 座標データは、次のようなJSON形式になっていますが、そのままでは構造化されていませんので、このアクティビティを使って、JSONとして扱えるように再構造化する必要があります。

{
  "position": {
    "x": 184,
    "y": 66,
    "z": 21
  }
}

 座標が格納された変数(retPosition)を指定して読み込み、プロパティパネルの「出力」で[Ctrl]+[K]キーを押して「startPosition」変数を作成し、再構造化したデータをJSONオブジェクトとして、この変数に格納し直します。

再構造化してオブジェクトとして変数に格納する

[代入]で座標を取り出す

 JSONから座標のデータを取り出しましょう。[代入]アクティビティを配置し、次のように、左辺で[Ctrl]+[K]キーで「startX」変数を作成、右辺に「Ctype(startPosition("position")("x"),Integer)」と入力します。

startX = Ctype(startPosition("position")("x"),Integer)

 JSONオブジェクトが格納されたstartPositionから、「"posittion"」の「"X"」の値を指定し、これを「Ctype」でInteger(整数)に変換しています。これで、座標に値を足していけるようになりました。

 同様に、Y座標(startY)、Z座標(startX)も取得しておきましょう。

後半パートを作る

 続いて後半パートを作ります。ここでは、設計図を読み取り、列、行、シートごとに処理を繰り返しながら、ブロックを配置していきます。

Excelアプリケーションスコープ

 まずは、設計図を読み込みます。ここではプロジェクトと同じフォルダーに保存された「"design.xlsx"」を指定しましたが、別のファイルの場合はパスを指定してください。

 なお、今回はプロパティで[自動保存]のチェックを外します。結構、長い間、ファイルを開きっぱなしで操作をするので、その間に自動保存がかからないようにします。

自動保存をOFFにする

[ワークブックの全シートを取得]

 設計図は、シートがブロックの各層に対応していますので、シートごとに処理しなければなりません。このため、このアクティビティで、全シートの名前を取得します。

 プロパティパネルの「出力」で[Ctrl]+[K]キーを押して「sheetNames」変数を作成し、ここに格納します。

シート名を配列として格納する

[繰り返し(コレクションの各要素)]

 シートを1枚ずつ処理していきましょう。[繰り返し(コレクションの各要素)]は配列などのコレクションから、値をひとつずつ取り出して、繰り返し処理ができるアクティビティです。

 「コレクション」にシート名の配列「sheetNames」を指定し、「要素」を「snItem」に変更しておきます。

 プロパティパネルでは、要素に取り出した値を扱うときの型を指定します。「TypeArgument」を「String」にしておきましょう。

 また、「出力」の「現在のインデックス」で[Ctrl]+[K]キーを押して「sheetIdx」変数に格納します。これで、sheetIdx変数に最初のシートが「0」、次のシートが「1」というように順番に値が入ります。

 シートは、ブロックの層、つまり座標では「Y座標」を示しています。この値は、後でブロックを配置するときのY座標を計算するときに使います。

Y座標に使うためにインデックスを格納する

[範囲を読み込み]

 シートごとの処理をしていきます。まずは、現在のシートから設計図を読み込みます。[範囲を読み込み]アクティビティを使って、指定したシートの内容をデータテーブルとして格納します。

 シートに「snItem」(繰り返し処理の現在のシート)を指定し、プロパティパネルの「データテーブル」で[Ctrl]+[K]キーを押して「designTable」と変数を指定します。

 これで、設計図に記載されているブロックの番号が、縦横2次元配列のデータテーブルとして格納されます。

[繰り返し(各行)]

 先ほどの[繰り返し(コレクションの各要素)]の中に、さらに繰り返し処理を配置します。今度は、データテーブルの値を処理するので、[繰り返し(各行)]を使います。このアクティビティで、データテーブルのような2次元配列を行ごとに処理できます。「コレクション」にデータテーブルの「designTable」を指定しましょう。

 そして、プロパティパネルの「現在のインデックス」で[Ctrl]+[K]キーを押して「rowIdx」変数を作成します。この値は、現在処理している行の値が順番に入ります。

 図面上では、行は奥行き、つまりZ軸の値となりますので、ブロックを配置するポジションを計算するときに、この値を使います。

[繰り返し(コレクションの各要素)]

 3重繰り返しの最後の部分です。先ほどの[繰り返し(各行)]で、図面の行を順番に処理しますが、各行には、列ごとのデータが1次配列として格納されています。この列データを、ここで順番に処理するわけです。

 「コレクション」に「row.ItemArray」と指定します。「row」は[繰り返し(各行)]の現在の行ですから、これを「.ItemArray」で配列として指定しているわけです。

 プロパティパネルでは、「TypeArgument」で「Object」を選択します。これで、先の「.ItemArray」のようにオブジェクトとして扱えます(そういう意味では先にこちらを設定した方がわかりやすい)。

 そして、他の繰り返しと同様に、「現在のインデックス」を設定します。列なので、X座標の値として使います。[Ctrl]+[K]キーで「colIdx」変数に格納しておきましょう。

インデックスを座標計算に使う

[代入]で座標を作る

 さあ、これでブロックを配置するために必要な座標の情報が集まりました。これをCode Connectionが認識できる座標の形式に整形します。

 [代入]アクティビティの左辺で[Ctrl]+[K]キーで「setPosition」変数を作成し、右辺に「(startX+colIdx).ToString+" "+(startY+sheetIdx).ToString+" "+(startZ+rowIdx).ToString」と入力します。

setPosition = (startX+colIdx).ToString+" "+(startY+sheetIdx).ToString+" "+(startZ+rowIdx).ToString

 最終的に作りたいのは、「184 66 22」のような座標の並びです。間にスペースを挟みながら、X、Y、Z座標を並べます。

 座標は、「startX」で最初に取得したスタート地点の座標に図面上のX座標「colIdx」を足すことで求められます。これを文字やスペースを連結したいので、「.ToString」を付けて文字列に変換します。同様に、Y、Zを繋げていけば最終的な座標が完成します。

[条件分岐]

 座標ができたので、次は、その座標に置くブロックを取得します。

 ブロックは、設計図のセルに番号で記入されていました。これは、designTable変数にデータテーブルとして格納され、繰り返し処理によって、現在の行、現在の列の値が「item」に格納されています。

 ただし、ここでひとつ注意点があります。設計図には、ブロック以外の値も存在します。それは、図面の両端を示す「XX」(前回記事参照)、そしてブロックの指定がない場所(空)です。

 つまり、この2つの値だった場合は、ブロックを置く必要はないわけです。

 そこで、[条件分岐]を設置して、次のような条件を「Condition」に記載します。

item.ToString = "XX" or String.IsNullOrEmpty(item.ToString)

 つまり、itemを文字列にした値が、「XX」もしくはNullか空だったら、「Then」で何も処理をしないという分岐です。

[代入]でブロック番号をブロック名にする

 一方、ブロック番号が記載されている場合は、ブロックを配置する必要があります。それが、Else側の処理です。

 ここには、まず[代入]アクティビティを配置します。itemの値は、ブロック番号ですが、この値は文字列として格納されています。これを、数値として扱えるようにするために、一旦、Integerに変換します。

 左辺で[Ctrl]+[K]キーを押して「blockNum」変数を作成し、右辺に「CType(item,Integer)」と入力します。これで、itemの値を整数に変換できます。
blockNum = CType(item,Integer)

HTTPリクエスト

 いよいよ最後のアクティビティです。

 座標も、設置するブロック(の番号)もわかったので、APIを呼び出して、ブロックを配置します。

 新しくHTTPリクエストを配置してもかまいませんが、冒頭で使ったHTTPリクエストを再利用しましょう。「UiPath Studio」では、アクティビティをコピペで複製できるので、上でコピーして、この場所に貼り付けます。

 さて、元のHTTPリクエストは、「~0 ~0 ~0」に「stone」を設置する命令です。エンドポイントは同じですが、パラメーターが違うので、プロパティパネルでこの部分を変更します。

 「オプション」の「パラメーター」で「…」をクリックし、「パラメーターパネル」を表示後、次のようにパラメーターを書き換えます。

  • パラメーター
    position → setPosition
    tileName → blockList(blockNum).toString

 setPositionは、先の代入で生成した現在の座標です。一方、blockList(blockNum).toStringは、ブロック名を指定しています。

 冒頭でitemlist.xlsxから読み込んだブロック名の一覧は「blockList」変数に格納されています。一方、配置したいブロックは、先にblockNumとして番号を取得済みです。この番号を指定して、blockListからブロック名を取り出し、「.toString」で文字列に変換すればいいわけです。

 これで、すべてのアクティビティができました。実際に実行して試してみてください。もちろん、実行する際は、「Code Connection for Minecraft」と「Minecraft」を接続しておく必要があります。前回の記事を参考に、接続した状態でワークフローを実行しましょう。