YAMADA TAISHI’s diary

ゲームについてとか私の日記とか。このブログのあらゆるコードは好きにどうぞ。利用規約があるものは記事内のGitHubのRepositoryのリンクで貼られていると思うので、そちらを参照ください。

【備忘録】ゲーム制作者向け(Pythonで)RPAツールを作ってみる

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) / Twitter )です。
私はゲーム業界にいるわけですが、面倒くさい作業は結構な割合で企画職がやってくれます。
私が前にシステムエンジニアをしていた時は結構な割合で自動化ツールが豊富にありました。
しかし、現在はソコまで自動化ツールは見当たりません。企画職である方々には何が自動化出来るのか判別出来ないのと、
プログラマーがソコまで気が回っていないからだと思いました。
なので、そういったツールを民主化させる第一歩としてRPAツールを作る方法を共有できれば良いなと思い今回作ってみることにしました。

目次


既存RPAもどきツールについて


ゲーム業界にも自動化ツールはありますが、ほとんどがレガシーな技術、エクセルVBAがせいぜいです。
今回はあえて処理速度の観点からゲームプログラマーが絶対触らないであろうPythonを触ってツールを作っていこうと思います。

環境


Windows10
Python3.9
PyCharm2021.3系(VSCodeとかでも良いと思います)

利用プラグイン
PyAutoGui
Pillow
opencv-python

参考記事


qiita.com

qiita.com

qiita.com

learnedmark.com

qiita.com

今回自動化する流れ


音声テキストを生成するツールVoicePeakというのがあるが、csv,tsv,改行区切りなどのインプット形式でインプット可能だが、
どのキャラでインポートするかまでは指定できない。

一括で初回ぐらいはやってしまいたいが、テキスト形式では無理がある。
なので、そこの指定を自動化出来るツールを作ろうと思い今回対応。

大まかとなる流れはこうだ。

Spreadsheetでセリフを管理

GASによりJson形式でセリフを吐き出し

Pythonを使いVoicepeakに取り込み

Spreadsheetでセリフを管理


Spreadsheetでは以下のような構成にしました。

GASによりJson形式でセリフを吐き出し


GASは以下のような内容にして、
ボタンを押したらJson形式でファイルがダウンロードされるようにしました。

GAScript

function getJsonData(sheetName) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  
  var maxRow = sheet.getLastRow();
  var maxColumn = sheet.getLastColumn();
  
  var keys = [];
  var data = [];

  for (var x = 1; x <= maxColumn; x++) {
    keys.push(sheet.getRange(1, x).getValue());
  }

  //実際のデータが2行目からなので【y = 2】から開始
  for (var y = 2; y <= maxRow; y++) {
    var json = {};
    if(sheet.getRange(y, 1).getValue() === false){
      continue;
    }
    for (var x = 2; x <= maxColumn; x++) {
      if(x === 2){
        const sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("シート2");
        json[keys[x-1]] = vlookup(sheet.getRange(y, x).getValue(),sheet2,2);
      }else{
        json[keys[x-1]] = sheet.getRange(y, x).getValue();
      }
    }
    //データ格納
    data.push(json);
  }
  Logger.log(data);
  return JSON.stringify(data, null, '\t');
}

function getJson(){
  return getJsonData('シート1');
}

function main() {
  
  var dl_html = HtmlService.createTemplateFromFile("dl_dialog").evaluate();
  SpreadsheetApp.getUi().showModalDialog(dl_html, "JSONファイルをダウンロード");
}

function vlookup(value,sheet,column) {
  let returnValue = "1";
  for (var i = 2; i <= sheet.getLastRow(); i++) {
    if(value == sheet.getRange(i,1).getValue()){
      returnValue = sheet.getRange(i,column).getValue();
      break;
    }
  }    
  return returnValue;
}

HTML

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script type='text/javascript'>
      function downloadJson(elm) {
        var content = <?= getJson(); ?>;
        var blob = new Blob([content], { "type": "application/json" });
        document.getElementById("download").href = window.URL.createObjectURL(blob);
      }
  </script>
  </head>
  <body>
    <!-- JSONダウンロードボタン:json名は適宜設定してください-->
    <a id="download" href="#" download="VoicePeak.json" onclick="downloadJson()">ダウンロード</a>
  </body>
</html>

Pythonを使いVoicepeakに取り込み


自動化の仕組み


今回はPyAutoGuiを利用しますので、要素の検査とかは必要ありません。
要素の検査をして色々するツールの方が動作の確実性が高いですが、実装の敷居が若干上がます。
後、単純に私がPython使いたい。

もしPyAutoGui以外を使ってWindowsで要素の精査を行うならコチラ↓の利用を推奨。
https://accessibilityinsights.io/

仕組みとしては簡単で、画像認識や座標指定などでクリック位置を特定してPython経由でマウスやキーボードを操作してあげる感じです。
そのためクラウド上の挙動は出来ません。

PyAutoGuiインストール


pipコマンドを打ってインストールします。

大分前のことで忘れたけど↓あたり?

pip install pyautogui
pip install opencv-python
pip install Pillow

pythonのコードを書く


main.py

import json
import os
import tkinter.filedialog
import tkinter.messagebox
import pyautogui
import time
import pygetwindow
import keyboard
import pyperclip


# Jsonを読み込むところ
def open_json(json_path):
    json_file = open(json_path, 'r', encoding="utf-8")
    json_dict = json.load(json_file)  # 辞書型変数にデータをつっこむ
    json_file.close()
    print('json_dict:{}'.format(type(json_dict)))
    return json_dict


# 指定の画像が表示されるまで待つ
def wait_picture(f, time_out):
    print(f)
    ret = None
    while ret is None:
        ret = pyautogui.locateOnScreen(f, grayscale=True, confidence=.7)
        print(ret)
        if ret is not None:
            return ret
        time.sleep(0.1)
        time_out -= 1
        print(time_out)
        if time_out < 0:
            return None


# プロジェクトを作り直す
def new_project():
    while True:
        time_zero_item = wait_picture("image/0.png", 1)
        if time_zero_item is not None:
            x, y = pyautogui.center(time_zero_item)
            pyautogui.click(x, y - 15)  # 一旦アイテムの上にカーソル持っていく
            time.sleep(0.05)
            keyboard.press_and_release('ctrl+n')
            item = wait_picture("image/No.png", 1)
            pyautogui.click(item)
            time.sleep(0.05)
        break  # なければ終わる


def window_resize():
    voicepeak.maximize()  # 最大化して
    time.sleep(0.1)
    voicepeak.restore()  # もとに戻す
    time.sleep(0.1)


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    exe_name = 'VoicePeak Json Importer'
    root = tkinter.Tk()
    root.withdraw()
    file_type = [("", "*.json")]
    directory = os.path.abspath(os.path.dirname(__file__))
    tkinter.messagebox.showinfo(exe_name, '処理ファイルを選択してください')
    file_path_str = tkinter.filedialog.askopenfilename(filetypes=file_type, initialdir=directory)
    print('読み込みファイル:', file_path_str)

    if file_path_str == "":
        exit()

    title = 'VOICEPEAK'
    voicepeak = [g for g in pygetwindow.getWindowsWithTitle(title) if g.title == title][0]
    voicepeak.activate()
    time.sleep(1)
    window_resize()
    new_project()
    window_resize()
    voicepeak.activate()

    mainItem = wait_picture("image/addword.png", 10)
    pyautogui.click(mainItem)  # 「クリックしてセリフを入力してください」をクリック

    json_data = open_json(file_path_str)
    for row in json_data:
        print('row:{}'.format(row))
        select_voice = row['VoiceData']
        if bool(select_voice != "1"):
            pulldown_item = wait_picture("image/PulldownItem.png", 1)
            pyautogui.click(pulldown_item)
            x, y = pyautogui.center(pulldown_item)
            time.sleep(0.1)
            y = (25 * (select_voice - 1)) + y + 30
            pyautogui.moveTo(x-10, y)   # 何故か一度動かしてからじゃないとクリック反応しない……
            pyautogui.click(x-10, y)

        word = wait_picture("image/addword.png", 10)
        pyautogui.click(word)

        voice_text = row['VoiceText']
        pyperclip.copy(voice_text)
        keyboard.press_and_release('ctrl+v')
        time.sleep(0.05)
        keyboard.press('enter')
        pyautogui.scroll(-500)

使った画像たち




(超久しぶりにPython触ったけど、命名規則特殊だね)

後はビルドしてexeファイルを実行できるようにする。

これで実行できるようになった

実際の実行の様子


まとめ


かなり前に書いていたのですが、Scriptなど公開してなかったので公開してみました。
アップデートがあってVoicepeakは設定周り保存できるようになったりした気がしますが、同じようにGUIツールの自動化ツールは作れるので是非試してみてください。
皆様の助けになれば幸いです。