UWSC × Nox でゲームマクロを作成する。(入門～adb、あいまい画像検索対応まで)

ソーシャルゲーム等をandroidエミュレータなどを使ってPC上でプレイすることも増えてきました。
NOX等のandroidエミュレータを使用してプレイする場合、UWSC等の自動化ソフトを使用することである程度の操作を自動化することができます。
今回はNOXのゲームマクロの作り方について既存のサイトからの情報をまとめつつ、ゲームマクロ独自の部分について解説したいと思います。

#ゲームマクロはどんな処理をしている？ 
- 画像認識でゲーム内の画像の有無と位置を確認。
- 取得した画像の座標を元にクリック

基本的にはこの2つに対して条件などを組み合わせて処理を行います。

#基本① 画像認識とクリック
UWSCでのゲームマクロは上の2つの処理が基本ですが、これに関しては他のサイトでわかりやすく解説されていますのでそちらのサイトを紹介します。

[【UWSC】画像認識　超入門編 UWSC　面倒な作業を自動化！](https://matome.naver.jp/odai/2142741595874523301)
UWSCでの画像認識の入門に必要な知識が一通り紹介されています。
画像認識のための画像の作り方は私もこちらの方法を使用しています。


[条件分岐するif(ifb)文の使い方とサンプルコード](http://kobuji.me/samplecode/if-ifb/)
条件で分岐するIFB-ENDIFの構文については以下に詳しく紹介されています。

ただしUWSCデフォルトのCHKIMGを使ったこのやり方の場合、ドット単位で完全一致しないと認識してくれないため、微妙に半透明だったり表示される場所が違うだけで認識できず処理が止まってしまいます。
この問題に対する対策は後で紹介します。

現時点でのコードはこのようになります。

```
ifb chkimg("image1.bmp")             //" "内に指定した画像を認識したら処理開始
    btn(left,click,g_img_x,g_img_y)  //画像の座標をマウスの左ボタンでクリック

elseif chkimg("image2.bmp")          //1つ目の画像が見つからなかったら別の画像を検索
    btn(left,click,g_img_x,g_img_y)  //画像の座標をマウスの左ボタンでクリック

else                                 //上記の条件全てに当てはまらなかった場合
    print "画像が見つかりません"    　  //メッセージを出す

endif
```

#基本② ループ処理
上記のスクリプトではifb-endif間を一回だけ処理が行われ、画像が認識されなければ1回で処理が終わってしまいます。
1秒おきにずっと画像認識を回し続けたい場合ループ処理を行います。
ループ処理を行う場合は以下のような文になります。

```
While True
//この中に実行したい処理を書く。
Wend
``` 
While 条件とすると、条件を満たすまでWendまでの間をループします。
また、While Trueとすると無限にループさせるため、今回はこれを使用しています。
実行開始時に1回だけ実行する処理はWhile Trueの上に記述しておきましょう。


処理を一定時間止めて待機させる場合は以下のようになります。

```
sleep(3.5) //()内の秒数待機
``` 

ループ処理と待機処理を追加して1秒ごとに画像認識を行うスクリプトは以下のようになります。

```
While True                           //Wendまでの処理を繰り返す。
ifb chkimg("image1.bmp")             //" "内に指定した画像を認識したら処理開始
    btn(left,click,g_img_x,g_img_y)  //画像の座標をマウスの左ボタンでクリック

elseif chkimg("image2.bmp")          //1つ目の画像が見つからなかったら別の画像を検索
    btn(left,click,g_img_x,g_img_y)  //画像の座標をマウスの左ボタンでクリック

else                                 //上記の条件全てに当てはまらなかった場合
    print "画像が見つかりません"    　  //メッセージを出す。

endif

    sleep(3.5)                       //()内の秒数待機

Wend
```

#発展① あいまい画像検索
UWSCデフォルトのCHKIMG関数を使用すると、ドット単位で完全一致する画像しか検索できず、同じ画像でも表示される場所が違うだけで認識できなくなってしまいます。
ゲームにおいては認識させたい画像がマップ上で別の場所に有ったりすることも多いため、あいまい画像検索ができるCHKIMGX関数を使うほうがオススメです。

CHKIMGXの配布と解説を行っている記事がありますので紹介します。
[UWSCであいまい画像検出する方法と、入手方法など](https://qiita.com/hirohiro77/items/2969db0494d1a2056e87)

基本的に通常のCHKIMG関数を置き換える形で使えますが通常のCHKIMGと違う点があるので注意する必要があります。

###使用したいuwsファイルと同じ階層にCHKIMGX用のファイルを入れておく必要がある。

```
 NekoAddon.dll
 ChkImgX.dll
 ChkImgX.uws
```

(ファイルはリンク先のChkImgX-ver1.2.1.zipに同梱されています。)

###開始時にセットアップ用の処理を書いておく必要がある。

```
CALL ChkImgX.uws　//CHKIMGX関数を別ファイルから呼び出しておく。
startup_chkimgx() //CHKIMGX関数開始
```

###画像認識時に必要な引数が色々違う。
全てを記述すると長くなってしまいますので先ほどと同じCHKIMGXの解説を参考にして下さい。
[UWSCであいまい画像検出する方法と、入手方法など](https://qiita.com/hirohiro77/items/2969db0494d1a2056e87)

###画像認識した際の座標が保存される変数が異なる。

```
G_IMGX_X //CHKIMGX関数で認識した画像のX座標
G_IMGX_Y //CHKIMGX関数で認識した画像のY座標
```
IMGの後にXが付きます。CHKIMGから置き換える際に替え忘れないようにしましょう。

###裏で動作させる場合ウィンドウIDが必要。
chkimgxの場合、ウィンドウIDを取得することで裏で画像検索をさせることができ、他のウィンドウで隠れていても画像を認識することができます。
ウィンドウIDの取得はgetidでウィンドウの名前を検索することで取得でき、NoxであればNoxPlayerとなります。
マルチインスタンス等で端末名を変更している場合はNoxウィンドウの上部に「NOXロゴ ウィンドウ名 ヴァージョン」という並びで表示されています。

```
winid = getid("NoxPlayer")  　　　//ゲームウィンドウを名前で検索して取得
print "ウィンドウIDを取得しました。"　　//以下2行は確認用なので特に必要なし。
print "ID.NOは"　+ winid
```


###サンプルコード
```
winid = getid("NoxPlayer")  //ゲームウィンドウを名前で検索して取得
print "ウィンドウIDを取得しました。"
print "ID.NOは"　+ winid

//chkimgx関数用のuwsをインクルード
CALL ChkImgX.uws

//CHKIMGXに関する設定項目 ゲームに応じて一括で変更するため最初にまとめて設定
x1  = 0               // 検出範囲の左上X座標
y1  = 0               // 検出範囲の左上Y座標
x2  = 540             // 検出範囲の右下X座標 G_SCREEN_Wは特殊関数で画面の横幅
y2  = 980             // 検出範囲の右下Y座標 G_SCREEN_Hは特殊関数で画面の縦幅
Detect_num = 1        // 何個目に検出した場所を対象にするか
Threshold_delta  = 5  // 発見する画像と誤差 n% の指定
Target_color = -1     // 色相を指定して検出できるが、フルカラー判定なら-1を指定

startup_chkimgx() //CHKIMGX関数開始

While True                       //Wendまでの処理を繰り返す。

//" "内に指定した画像をCHKIMGXで認識したら処理開始
ifb CHKIMGX("image1.bmp", winid, x1, y1, x2, y2, Detect_num, Threshold_delta, Target_color) 

    btn(left,click,g_imgx_x,g_imgx_y)  //画像の座標をマウスの左ボタンでクリック

//1つ目の画像が見つからなかったら別の画像を検索
elseif CHKIMGX("image2.bmp", winid, x1, y1, x2, y2, Detect_num, Threshold_delta, Target_color)
    btn(left,click,g_imgx_x,g_imgx_y)  //画像の座標をマウスの左ボタンでクリック

else                                   //上記の条件全てに当てはまらなかった場合
    print "画像が見つかりません"    　    //メッセージを出す。

endif
    sleep(3.5)                         //()内の秒数待機

Wend
```


#発展② adbを使った裏マクロ(マウスを取られないマクロ)化
btn関数を使用したクリック方法では、クリックが必要になる度にマウスが取られてしまい、PC作業の妨げになります。その場合、adbを使いAndroidエミュレーターに直接画面のタップ情報を送る方法がおすすめです。

##adbとは？
元はデバッグ等を目的に作られたもので、adb経由で各種コマンドを送ることでadbに接続された端末上で様々な動作を実行できるツールです。

##adbの導入
NOXで使用する場合には、NOXと一緒に既にnox_adbという専用のadbがインストールされています。なので、通常のadbを使う際に必要なplatform-toolsやandroid studioなどのインストールは必要ありません。

また、デバイスの認識もNoxの起動中であれば自動でnox_adbに接続してくれるようになっています。マルチインスタンスで複数端末を同時に立ち上げている場合は指定が必要です。

基本的にはadbのコマンドはnox_adbでも同じように使えるので、リファレンスが見つからずnox_adbのコマンドがわからない！という人はadbコマンドを紹介しているサイトを参考にしてみてください。
[よく使うadbのコマンド](https://qiita.com/t2low/items/cb37cec5f864c4748e14)

##adbでNOXを制御
adbでタップ情報を送るスクリプトは以下のようになります。

```
DOSCMD("cd C:\Program Files (x86)\Nox\bin & nox_adb shell input touchscreen tap 450 450")
```
これをuwsに記述し実行すればエミュレータ画面上(450,450)の位置がタップされるはずです。
内訳は以下のようになっています。

```
DOSCMD("送りたいコマンド")  //""の中のコマンドをコマンドプロンプトから実行する。
cd 設定したいパス　        //cd以降のパスにコマンドプロンプトのカレントディレクトリを変更する。
&　　　　　　　　　　　　　//２つのコマンドを連続して送ることができる。

C:\Program Files (x86)\Nox\bin　//nox_adbが配置されているパス。

nox_adb shell input touchscreen tap 450 450
//nox_adbにタップ情報を送る。
//tap の後の数値が座標。 tap X座標 Y座標 の順番
```


※NOXをデフォルトの場所ではなく別の場所に保存している場合はcd以下にnox_adbが保存されているパスを指定してください。

コマンドプロンプトのカレントディレクトリをnox_adbのあるパスに変更
nox_adbにタップ情報を送信

この２つのコマンドを組み合わせて実行しています。
環境変数にnox_adbのパスを追加する方法もありますが、windows10ではOSの保護機能でブロックされるため毎回cdでカレントディレクトリを変更する方法を取っています。
正常にタップ操作が通っているか確認するために、テストする際はタップエフェクトの出るゲームがオススメです。

##adbに送るコマンドに変数を使う
上の方法では、タップする座標が固定になってしまいます。
なので、DOSCMDで送るコマンドに変数を入れる方法を解説します。サンプルコードは以下。

```
CMD = "cd C:\Program Files (x86)\Nox\bin & nox_adb shell input touchscreen tap " + (G_IMGX_X) + " " + (G_IMGX_Y)
print CMD //CMDを確認する場合は使用する。
DOSCMD(CMD)
```

DOSCMDでは単独の変数か" "で囲った文字列を送ることができます。
なので、先にCMDという変数に送りたい文字列を格納してしまってから、DOSCMDでCMDの中身をコマンドプロンプトに送って実行します。
変数で送る場合は""で囲う必要はありません。
また、空白が消えていたりしないか気を付けて下さい。座標の間には空白が必要です。
変数と変数の間の空白は「+ " " +」で実現できます。

#サンプルコード(完成形)

```
winid = getid("NoxPlayer")  //ゲームウィンドウを名前で検索して取得
    print "ウィンドウIDを取得しました。"
    print "ID.NOは"　+ winid
    winPosX = STATUS(winid,ST_X)
    winPosY = STATUS(winid,ST_Y)
    winsizeX = STATUS(winid,ST_WIDTH)
    winsizeY = STATUS(winid,ST_HEIGHT)
    print "位置X 位置Y 幅 高さ"
    print winPosX + "   " + winPosY + "   " + winsizeX + "   " + winsizeY


//取得した座標の確認
print "位置X 位置Y 幅 高さ"
print winPosX + "   " + winPosY + "   " + winsizeX + "   " + winsizeY

//androidの画面のサイズを設定。(個人の端末による)
adbsizeX = 540
adbsizeY = 960

//ウィンドウサイズとandroid画面サイズの差からNOXのフレームサイズを割り出す。
windowflamesize = winsizeY - adbsizeY
print "ウィンドウサイズは" + " " + windowflamesize

MOUSEORG(winid,MORG_DIRECT,MORG_FORE)       //MOUSEORGの設定

//chkimgx関数用のuwsをインクルード
CALL ChkImgX.uws

//CHKIMGXに関する設定項目 ゲームに応じて一括で変更するため最初にまとめて設定
x1  = 0               // 検出範囲の左上X座標
y1  = 0               // 検出範囲の左上Y座標
x2  = 540             // 検出範囲の右下X座標 G_SCREEN_Wは特殊関数で画面の横幅
y2  = 980             // 検出範囲の右下Y座標 G_SCREEN_Hは特殊関数で画面の縦幅
Detect_num = 1        // 何個目に検出した場所を対象にするか
Threshold_delta  = 5  // 発見する画像と誤差 n% の指定
Target_color = -1     // 色相を指定して検出できるが、フルカラー判定なら-1を指定

startup_chkimgx() //CHKIMGX関数開始

While True                       //Wendまでの処理を繰り返す

//" "内に指定した画像を認識したら処理開始(曖昧認識)
ifb CHKIMGX("image1.bmp", winid, x1, y1, x2, y2, Detect_num, Threshold_delta, Target_color)         
    print "image1を発見しました。"       //画像を発見したらログに表示


    //検索した座標を別の変数に代入しておく
    attack_x=g_imgx_x
    attack_y=g_imgx_y-windowflamesize //ウィンドウの枠の分座標がズレるので補正

    //画像認識で取得した値を代入してDOSCMDで送る内容を作成
    CMD = "cd C:\Program Files (x86)\Nox\bin & nox_adb shell input touchscreen tap " + (attack_x) + " " + (attack_y)
    print CMD                        //CMDの内容を確認
    DOSCMD(CMD)                      //Noxに操作を送信
    sleep(1)                         //操作送信後1秒待機

//1つ目の画像が見つからなかったら別の画像を検索
elseif CHKIMGX("image2.bmp", winid, x1, y1, x2, y2, Detect_num, Threshold_delta, Target_color) 
    print "image2を発見しました。"       //画像を発見したらログに表示

    //検索した座標を別の変数に代入しておく
    deffence_x=g_imgx_x
    deffence_y=g_imgx_y-windowflamesize //ウィンドウの枠の分座標がズレるので補正

    //画像認識で取得した値を代入してDOSCMDで送る内容を作成
    CMD = "cd C:\Program Files (x86)\Nox\bin & nox_adb shell input touchscreen tap " + (deffence_x) + " " + (deffence_y)
    print CMD                        //CMDの内容を確認
    DOSCMD(CMD)                      //Noxに操作を送信
    sleep(1)                         //操作送信後1秒待機

else                                 //上記の条件全てに当てはまらなかった場合
    print "画像が見つかりません"        //メッセージを出す。

endif                                //ifbの処理の終了地点
    sleep(3.5)                       //()内の秒数待機

Wend
```

##画像認識の座標とadbのタップ座標のズレと対策
adbでタップ情報を送信する場合、タップされる場所はandroidエミュレーターの画面のサイズを基準とした座標となります。
ただし、CHKIMGで返される座標はスクリーン全体を基準とした座標になるので、タップを行う場合は補正してあげる必要があります。

###MOUSEORGによる補正
手打ちしてもいいのですが、MOUSEORGを使用すればCHKIMGでウィンドウ基準での座標を取得できます。

```
MOUSEORG(ウィンドウのID, [基準指定] , [画面取得指定])

基準指定
 0             ： ウィンドウ位置
MORG_CLIENT (1)： クライアント領域の位置を基準にする
MORG_DIRECT (2)： 指定ウィンドウ(オブジェクト)へマウス、キー情報を直接送る

画面取得指定
0           ： 指定アプリがフォアグランドかバックグランドかにより検索画面の取得方法を変更
MORG_FORE   ： フォアグランドとして可視画面(デスクトップ)を検索画面とする
MORG_BACK   ： バックグランドとしてアプリより画面を取得して検索画面とする（他の画面がオーバーラップしていてもOK）
```

ウィンドウ基準でCHKIMGの座標を取得するには、基準指定の引数に「MORG_DIRECT」と記述しましょう。
画面取得指定は、ウィンドウが前面に表示されている場合のみ取得するか、他のウィンドウに隠れている場合も取得するかを選ぶことができます。
ウィンドウのちらつきが気になる場合はMOUSEORGでMORG_FOREを指定することで前面に見えている部分しか取得できない代わりにかなりちらつきを抑えることができます。

###MORG_BACKについて
 - MORG_BACKを指定すれば画像認識が裏で働きます。
 - ただし、MORG_BACKの場合は画像認識時にウィンドウがちらつくという欠点があります。
 - また、win10ではMORG_BACKは動作しません。(win8.1以前なら可能)
 - この問題についてはまだ解決されていません。(UWSC自体更新停止されているため自作するしかない？)
--なので、ここではMORG_FOREをお勧めします。

MOUSEORGは、記述された後全てのCHKIMGに効果を及ぼすため、使用する場合はループ前の最初の部分に記述しましょう。

MOUSEORGについてのリファレンスはこちら
http://canal22.org/kansu/mouseorg/
http://www.tokyotools-programming.com/uwsc/mouseorg/

##ウィンドウ枠によるズレとその対策
下記の画像のように、ウィンドウ基準で座標を取得しても、androidの画面範囲とウィンドウサイズにはウィンドウの枠の分の差が出てしまいます。
![windowflamesample.png](https://qiita-image-store.s3.amazonaws.com/0/296712/d8578f09-a6a5-c8b1-b5c5-3df97b3c2c4e.png)

また、画面サイズやOSによって枠の大きさは変化しますが、「ウィンドウサイズ-androidの画面サイズ」で求めることができます。

```
winsizeX = STATUS(winid,ST_WIDTH)     //取得したウィンドウの幅を取得
winsizeY = STATUS(winid,ST_HEIGHT)    //取得したウィンドウの高さ

//取得した座標の確認
print "幅 高さ"
print winsizeX + "   " + winsizeY

//androidの画面のサイズを設定。(個人の端末による)
adbsizeX = 540
adbsizeY = 960

//ウィンドウサイズとandroid画面サイズの差からNOXのフレームサイズを割り出す。
windowflamesize = winsizeY - adbsizeY
print "ウィンドウサイズは" + " " + windowflamesize


ifb CHKIMGX("image1.bmp", winid, x1, y1, x2, y2, Detect_num, Threshold_delta, Target_color)
    attack_x=g_imgx_x
    attack_y=g_imgx_y - windowflamesize //ウィンドウの枠の分座標がズレるので補正
```

###追記 DOSCMDでコマンドを送る場合のドライブについて
```
CMD = "cd C:\Program Files (x86)\Nox\bin & nox_adb shell input touchscreen tap " + (G_IMGX_X) + " " + (G_IMGX_Y)
print CMD //CMDを確認する場合は使用する。
DOSCMD(CMD)
```
カレントディレクトリの変更+nox_adbへのタップ情報の送信を行っているこの部分ですが、nox_adbがDドライブにあって「cd D:\Program Files (x86)\～」のような形になる場合はコマンドが通らないことがあります。
その際はドライブを跨いだカレントディレクトリの変更をする必要があるので、そのコマンドを追記して上げてください。

※現在多忙+Dドライブにnoxを再インストールして検証する時間が無いので参考サイトを貼っておきます。一番確実なのはC:にnoxをインストールする方法です。

参考URLは以下
http://nemoplus.hateblo.jp/entry/20090130/1233321806
https://jibunlife.hateblo.jp/entry/cmd-cd-drive