仮眠プログラマーのつぶやき

自分がプログラムやっていて、思いついたことをつぶやいていきます。

自作ゲームやツール、ソースなどを公開しております。
①ポンコツ自動車シュライシュラー
DOWNLOAD
②流体力学ソース付き
汚いほうDOWNLOAD
綺麗なほうDOWNLOAD
③ミニスタヲズ
DOWNLOAD
④地下鉄でGO
DOWNLOAD
⑤ババドン
DOWNLOAD
⑥圧縮拳(ツール)
DOWNLOAD
⑦複写拳
DOWNLOAD
⑧布シミュレーション
DOWNLOAD
⑨minecraft巨大電卓地形データ
DOWNLOAD
⑩フリュードランダー
デジゲー博頒布α版
DOWNLOAD
⑪パズドラルート解析GPGPU版
DOWNLOAD
⑫ゲーム「流体de月面着陸」
DOWNLOAD

【Switchモンハン自動操作】Arduino Uno R3+Win10

目的

モンハンライズが発売され1カ月近く経ちますが、今回はモンハンのあるルーチンの自動化をやろうと思います。
・オトモの交易船アイテム回収
・オトモ隠密隊アイテム回収
・溶岩洞上位の探索クエ一部採掘(ポイント稼ぎ)

dekirukoto


これらの作業を自動化することにより、ハチミツやマンドラゴラ、魚類などの素材、隠密隊で手に入るモンスター等の素材も手にはいります。
さらにオトモの経験値やカムラポイントも若干ですが増えることが期待できます。

前提知識

メモ帳

プログラミングができる必要はありませんが、メモ帳でコード内の数値をいじったりコピペなどのスキルは必要です。
メモ帳の開き方すら分からないという方は難易度的にこの記事は厳しいかもしれません・・・

コマンドプロンプト、(WSL)

コマンドプロンプト、WSLを使ったことがあるorなんとなく聞いたことがあるなら尚よいです。
コマンドプロンプトはPCからArduinoにプログラムを書き込む際に必要になります。
またArduinoにプログラムを書き込む前の段階で「make」コマンドを使う必要があり、私はWSLでやっちゃっていますが、「make」コマンドを使うだけであれば他にも方法があり各々調べてやりやすい方法をとると良いです。

Arduino

電子工作ができる必要はありませんが、Arduinoがどういう製品なのかは少し知っておく必要があります。
Arduinoには10個以上のpinがついており、これらの動作をプログラム可能。例えば「1秒後にpin10を5Vにして2秒後にpin9を0Vにする」というような動作をさせることができます。

なのでpinにLEDをつないでLチカさせたり、モーターを制御させたりして遊ぶことができます。それが普通の使い方。
457j853e56h

今回SwitchにこのArduino Unoをコントローラーとして認識させて自動操作するのですが、これはやや普通の使い方から外れるので、ちょっと面倒なことをしないといけません。

またArduinoと言っても何種類もあります。
Arduinoシリーズ15種類の違い | まとめ比較表
結論から言うと

Arduino Leonardo

が良いです。
これを使ったやり方もたくさん記事があり、Arduino Unoを使ったやり方より簡単そうです。もしまだ購入してないならArduino Leonardoをおススメします。
私は良く調べずにArduino Unoを買ってしまったので以降はUnoでのやり方を解説します。

USB type

ケーブル類も何種類か必要なので「USBって何?」という方だとちょっときついかもしれません。USB Type-Cなどメジャーな規格は知っておくと良いです。

準備

ハードウェア

Nintendo Switch本体
PC (Windows 10)
Arduino Uno R3
USBケーブル:タイプAオス - タイプBオス
USB Type-Cハブ(なければUSBケーブル:タイプCオス - タイプBオスでも良い)
4sdfb09


ソフトウェア

AutoRaid-1.0.0aw.zip
lufa-d6a7df4f78898957fa6d5b63e62015ce763560d4.zip
dfu-programmer-win-0.7.2.zip

STEP1 ソフトウェアのDL(難易度:易)

・AutoRaid-1.0.0aw

↑ここのAutoWattからDL

・lufa

↑ここからDL(CODE→Download ZIP)

・dfu-programmer-win-0.7.2

↑ここからDL

STEP2 Arduino UnoのHID化(難易度:易)

[qiita]Arduino UNOを使った、キーボード機器製作法
ここを参考にArduino UnoをHID化します。
「ATmega16U2をHIDキーボードデバイスと認識させる方法」
の1~4までをやります。
この時点でPCにつないだArduino Unoが、「不明なデバイス」から「ATmega16U2」と認識されるようになっているはずです。
devicem
つまずきポイントは特になさそうですが、Arduino UnoをPCにつなげる際、ピンをショートさせながらつなげるので手が忙しいという問題はあります。ショートさせるのはドライバーでもシャー芯でもなんでも良いです。
終わったらいったんArduino UnoはPCからはずしておきます。

STEP3 Joystick.cとhexファイルの確認(難易度:易)

先ほどの記事だと、このあとArduino Unoをキーボードに擬態させるため専用のファームウェアをダウンロードしてきてArduinoに書き込むことをしています。
今回私たちはSwitchのコントローラーに擬態させるので、それ用のファームウェア(hexファイル)を自作していく必要があります。
結構大変そうですが、頭のイカれた変人 良い人たちが道筋を用意してくれているのでそれを活用させていただきます。

まず
こちらを参考に
AutoRaid-1.0.0aw.zipとlufa-d6a7df4f78898957fa6d5b63e62015ce763560d4.zipを解凍し、
AutoRaid-1.0.0awフォルダの中のlufaフォルダ(空になっている)に先ほど解凍したlufa-d6・・の中身をうつします。
無題


そしてAutoRaid-1.0.0awフォルダ内に
Joystick.c
Joystick.h
Joystick.hex
などのファイルがあることを確認します。

私も良く分かってはいないですが、ちょっと解説すると

Joystick.cにはポケモンの自動ワット稼ぎコードが記載されている。ここをいじれば任意の自動操作ができる。

Joystick.hにはキーコード(Aボタンは0x04とか)が記載されている。Joystick.cからinclude(参照)されている。これは基本いじらない。

Joystick.hexはJoystick.cをArduinoが読める形式にしたファイル。これを作りたい。

最初の時点で存在しているJoystick.hexはポケモン用のコードになっているので、このままではモンハン自動操作はできません。
なので
「Joystick.cを書き換えJoystick.hexを作ってArduinoに書き込む」ということが必要になってきます。

ここの難易度がやや高めですが、逆にここさえできてしまえばモンハンに限らず色んなソフトで自動操作を実現することができます。

今は私の作ったモンハン自動コードを載せておくのでそれをコピペするだけで一応モンハン自動化はできます。Joystick.cを以下のコードに置き換えてください。


/*
Nintendo Switch Fightstick - Proof-of-Concept

Based on the LUFA library's Low-Level Joystick Demo
    (C) Dean Camera
Based on the HORI's Pokken Tournament Pro Pad design
    (C) HORI

This project implements a modified version of HORI's Pokken Tournament Pro Pad
USB descriptors to allow for the creation of custom controllers for the
Nintendo Switch. This also works to a limited degree on the PS3.

Since System Update v3.0.0, the Nintendo Switch recognizes the Pokken
Tournament Pro Pad as a Pro Controller. Physical design limitations prevent
the Pokken Controller from functioning at the same level as the Pro
Controller. However, by default most of the descriptors are there, with the
exception of Home and Capture. Descriptor modification allows us to unlock
these buttons for our use.
*/

#include "Joystick.h"

typedef enum {
  L_UP,
  L_DOWN,
  L_LEFT,
  L_UPLEFT,     // UP + LEFT
  X,
  Y,
  A,
  B,
  L,
  R,
  ZR,
  ZL,
  PLUS,
  H_UP,
  H_DOWN,
  H_LEFT,
  H_RIGHT,
  NOTHING,
} Buttons_t;

typedef struct {
  Buttons_t button;
  uint16_t duration;
} command;


static const command stepSetupController[] = {
  // ここまでコントローラーを認識させるおまじない
  // Setup controller
  { NOTHING,   30 },
  { A,         39 },
  { B,         49 },
};

static const command stepGotoSyukaijoAndTankue[] = {
  //里内にて里内移動→「集会場」
  { NOTHING,    1 },
  { PLUS,      35 },
  { H_LEFT,    15 },
  { H_DOWN,    15 },
  { H_DOWN,    15 },
  { A,         15 },
  { H_UP,      15 },
  { A,         85 },
  //ミノトにあう
  { L_UPLEFT,  90 },
  { L_LEFT,    15 },
  { NOTHING,   10 },
  { A,         85 },
  //探索クエ選択
  { A,         20 },
  { H_UP,      15 },
  { A,         60 },
  { H_UP,      10 },
  { A,         10 },
  { A,         10 },
  { A,         73 },
  { ZR,        19 },
  { A,       1030 },//クエスト出発、ロード
};

static const command stepSubcamp[] = {
  //サブキャンプへ移動
  { PLUS,      35 },
  { H_RIGHT,   10 },
  { A,         20 },
  { A,         10 },
  { H_DOWN,    10 },
  { A,         10 },
  { A,        360 },
};

static const command stepBISUMASU[] = {
  //下に向かっって3秒
  { L_DOWN,   120 },
  { A,        110 },//ここに採取ポイント
};

static const command stepNENTANSEKI[] = {
  //左に向かっって8秒+上に3秒
  { L_LEFT,   270 },
  { L_UP,      90 },
  { A,          5 },//ここに採取ポイント
  { L_UP,       5 },
  { A,          5 },//ここに採取ポイント
  { L_UP,       5 },
  { A,          5 },//ここに採取ポイント
  { L_UP,       5 },
  { A,        100 },//ここに採取ポイント
};

static const command stepWait4min[] = {
  //適度に屈伸をして全くの停止時間を作らない
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        400 },
  { B,        600 },
};

static const command stepBackToHome[] = {
  { PLUS,      55 },
  { H_RIGHT,   55 },
  { H_DOWN,    10 },
  { H_DOWN,    10 },
  { H_DOWN,    10 },
  { H_DOWN,    10 },
  { A,         55 },
  { H_LEFT,    15 },
  { A,        700 },
  { A,        100 },//これで終了。クエスト帰還まち
  { A,        100 },
  { A,        100 },
  { A,        800 },
  //帰還終了
};

//隠密と交易船の受け取り
static const command stepOtomo[] = {
  { PLUS,      35 },
  { H_LEFT,    15 },
  { H_DOWN,    15 },
  { H_DOWN,    15 },
  { A,         15 },
  { H_DOWN,    15 },
  { H_DOWN,    15 },
  { H_DOWN,    15 },
  { A,         85 },//ルームサービスにあう
  { L_LEFT,    50 },
  { A,         55 },
  { H_DOWN,    15 },
  { A,         65 },
  { A,        125 },
  { H_UP,      25 },
  { A,         65 },
  { A,         29 },//今度は依頼
  { X,         20 },
  { A,         40 },
  { H_UP,      20 },
  { A,         15 },
  { A,         10 },
  { A,         10 },
  { A,         10 },
  { A,         10 },
  { A,         10 },
  { A,         10 },
  { A,         10 },
  { A,         10 },//決定
  { A,         30 },//はい
  { B,         20 },//もどる
  { H_DOWN,    20 },//交易船
  { A,         40 },
  { A,         20 },
  { H_UP,      20 },
  { A,         20 },
  { H_UP,      20 },
  { A,         20 },
  { B,         20 },
  { B,         20 },
  { B,         50 },
};


// Main entry point.
int main(void) {
  // We'll start by performing hardware and peripheral setup.
  SetupHardware();
  // We'll then enable global interrupts for our use.
  GlobalInterruptEnable();
  // Once that's done, we'll enter an infinite loop.
  for (;;)
  {
    // We need to run our task to process and deliver data for our IN and OUT endpoints.
    HID_Task();
    // We also need to run the main USB management task.
    USB_USBTask();
  }
}

// Configures hardware and peripherals, such as the USB peripherals.
void SetupHardware(void) {
  // We need to disable watchdog if enabled by bootloader/fuses.
  MCUSR &= ~(1 << WDRF);
  wdt_disable();

  // We need to disable clock division before initializing the USB hardware.
  clock_prescale_set(clock_div_1);
  // We can then initialize our hardware and peripherals, including the USB stack.

  #ifdef ALERT_WHEN_DONE
  // Both PORTD and PORTB will be used for the optional LED flashing and buzzer.
  #warning LED and Buzzer functionality enabled. All pins on both PORTB and \
           PORTD will toggle when printing is done.
  DDRD  = 0xFF; //Teensy uses PORTD
  PORTD =  0x0;
                  //We'll just flash all pins on both ports since the UNO R3
  DDRB  = 0xFF; //uses PORTB. Micro can use either or, but both give us 2 LEDs
  PORTB =  0x0; //The ATmega328P on the UNO will be resetting, so unplug it?
  #endif
  // The USB stack should be initialized last.
  USB_Init();
}

// Fired to indicate that the device is enumerating.
void EVENT_USB_Device_Connect(void) {
  // We can indicate that we're enumerating here (via status LEDs, sound, etc.).
}

// Fired to indicate that the device is no longer connected to a host.
void EVENT_USB_Device_Disconnect(void) {
  // We can indicate that our device is not ready (via status LEDs, sound, etc.).
}

// Fired when the host set the current configuration of the USB device after enumeration.
void EVENT_USB_Device_ConfigurationChanged(void) {
  bool ConfigSuccess = true;

  // We setup the HID report endpoints.
  ConfigSuccess &= Endpoint_ConfigureEndpoint(JOYSTICK_OUT_EPADDR, EP_TYPE_INTERRUPT, JOYSTICK_EPSIZE, 1);
  ConfigSuccess &= Endpoint_ConfigureEndpoint(JOYSTICK_IN_EPADDR, EP_TYPE_INTERRUPT, JOYSTICK_EPSIZE, 1);

  // We can read ConfigSuccess to indicate a success or failure at this point.
}

// Process control requests sent to the device from the USB host.
void EVENT_USB_Device_ControlRequest(void) {
  // We can handle two control requests: a GetReport and a SetReport.

  // Not used here, it looks like we don't receive control request from the Switch.
}

// Process and deliver data from IN and OUT endpoints.
void HID_Task(void) {
  // If the device isn't connected and properly configured, we can't do anything here.
  if (USB_DeviceState != DEVICE_STATE_Configured)
    return;

  // We'll start with the OUT endpoint.
  Endpoint_SelectEndpoint(JOYSTICK_OUT_EPADDR);
  // We'll check to see if we received something on the OUT endpoint.
  if (Endpoint_IsOUTReceived())
  {
    // If we did, and the packet has data, we'll react to it.
    if (Endpoint_IsReadWriteAllowed())
    {
      // We'll create a place to store our data received from the host.
      USB_JoystickReport_Output_t JoystickOutputData;
      // We'll then take in that data, setting it up in our storage.
      while(Endpoint_Read_Stream_LE(&JoystickOutputData, sizeof(JoystickOutputData), NULL) != ENDPOINT_RWSTREAM_NoError);
      // At this point, we can react to this data.

      // However, since we're not doing anything with this data, we abandon it.
    }
    // Regardless of whether we reacted to the data, we acknowledge an OUT packet on this endpoint.
    Endpoint_ClearOUT();
  }

  // We'll then move on to the IN endpoint.
  Endpoint_SelectEndpoint(JOYSTICK_IN_EPADDR);
  // We first check to see if the host is ready to accept data.
  if (Endpoint_IsINReady())
  {
    // We'll create an empty report.
    USB_JoystickReport_Input_t JoystickInputData;
    // We'll then populate this report with what we want to send to the host.
    GetNextReport(&JoystickInputData);
    // Once populated, we can output this data to the host. We do this by first writing the data to the control stream.
    while(Endpoint_Write_Stream_LE(&JoystickInputData, sizeof(JoystickInputData), NULL) != ENDPOINT_RWSTREAM_NoError);
    // We then send an IN packet on this endpoint.
    Endpoint_ClearIN();
  }
}


#define ECHOES 2
int echoes = 0;
USB_JoystickReport_Input_t last_report;

int report_count = 0;
int hold_LX = STICK_CENTER;
int hold_LY = STICK_CENTER;
int hold_RX = STICK_CENTER;
int hold_RY = STICK_CENTER;
int bufindex = 0;
int bufindexloop = 0;//もどった回数
int duration_count = 0;



//サブキャンプ→ビスマス→サブキャンプ→燃炭石のルーチン
void SBSNRutin(int *starti,int *endi,command *cm_tmp){
	//サブキャンプへ移動
	*starti=*endi;
	*endi+=sizeof(stepSubcamp) / sizeof(stepSubcamp[0]);
	if ((bufindex>=*starti)&(bufindex<*endi)){
		*cm_tmp=stepSubcamp[bufindex-*starti];
		return;
	}
	//ビスマス鉱石採取
	*starti=*endi;
	*endi+=sizeof(stepBISUMASU) / sizeof(stepBISUMASU[0]);
	if ((bufindex>=*starti)&(bufindex<*endi)){
		*cm_tmp=stepBISUMASU[bufindex-*starti];
		return;
	}
	//サブキャンプへ移動
	*starti=*endi;
	*endi+=sizeof(stepSubcamp) / sizeof(stepSubcamp[0]);
	if ((bufindex>=*starti)&(bufindex<*endi)){
		*cm_tmp=stepSubcamp[bufindex-*starti];
		return;
	}
	//燃炭石
	*starti=*endi;
	*endi+=sizeof(stepNENTANSEKI) / sizeof(stepNENTANSEKI[0]);
	if ((bufindex>=*starti)&(bufindex<*endi)){
		*cm_tmp=stepNENTANSEKI[bufindex-*starti];
		return;
	}
	
	return;
}

//現在のbufindexからコマンドを返す
command MainRutin()
{
	int starti=0;
	int endi=0;
	command cm_tmp;
	cm_tmp.duration=0;//0はnullという意味。
	
	//コントローラー認識ステップ
	starti=endi;
	endi+=4;
	if (bufindex<4){
		return stepSetupController[bufindex];
	}
	
	//集会場クエ受注出発
	starti=endi;
	endi+=sizeof(stepGotoSyukaijoAndTankue) / sizeof(stepGotoSyukaijoAndTankue[0]);
	if ((bufindex>=starti)&(bufindex<endi)){
		return stepGotoSyukaijoAndTankue[bufindex-starti];
	}
	
	//クエ中、炭鉱
	SBSNRutin(&starti,&endi,&cm_tmp);
	if (cm_tmp.duration!=0)return cm_tmp;
	
	//4分まつ
	starti=endi;
	endi+=sizeof(stepWait4min) / sizeof(stepWait4min[0]);
	if ((bufindex>=starti)&(bufindex<endi)){
		return stepWait4min[bufindex-starti];
	}
	
	//クエ中、炭鉱
	SBSNRutin(&starti,&endi,&cm_tmp);
	if (cm_tmp.duration!=0)return cm_tmp;
	
	//帰還
	starti=endi;
	endi+=sizeof(stepBackToHome) / sizeof(stepBackToHome[0]);
	if ((bufindex>=starti)&(bufindex<endi)){
		return stepBackToHome[bufindex-starti];
	}
	
	//帰還したら次のループへ
	if (bufindex>=endi){
		bufindex=4;
		bufindexloop++;
	}
	return stepGotoSyukaijoAndTankue[0];
}


//おとも
command OtomoRutin(){
	if (bufindex<41 +4){
		return stepOtomo[bufindex-4];
	}else{
		bufindex=4;
		bufindexloop++;
	}
	return stepGotoSyukaijoAndTankue[0];
}


void GetNextReport(USB_JoystickReport_Input_t* const ReportData) {
  // Prepare an empty report
  memset(ReportData, 0, sizeof(USB_JoystickReport_Input_t));
  ReportData->LX = STICK_CENTER;
  ReportData->LY = STICK_CENTER;
  ReportData->RX = STICK_CENTER;
  ReportData->RY = STICK_CENTER;
  ReportData->HAT = HAT_CENTER;

  //Hold stick
  ReportData->LX = hold_LX;
  ReportData->LY = hold_LY;
  ReportData->RX = hold_RX;
  ReportData->RY = hold_RY;

  // Repeat ECHOES times the last report
  if (echoes > 0)
  {
    memcpy(ReportData, &last_report, sizeof(USB_JoystickReport_Input_t));
    echoes--;
    return;
  }
  //次のコマンドをgetしてくる
  command cmd;
  
  if (bufindexloop%6==5){
	  cmd=OtomoRutin();
  }else{
	  cmd=MainRutin();
  }
  
  Buttons_t bt;//step[bufindex].button
  bt=cmd.button;
  
  switch (bt)
  {
	case L_UP:
	  ReportData->LY = STICK_MIN;
	  break;

	case L_LEFT:
	  ReportData->LX = STICK_MIN;
	  break;

	case L_DOWN:
	  ReportData->LY = STICK_MAX;
	  break;
	  
	case L_UPLEFT:
	  ReportData->LY = STICK_MIN;
	  ReportData->LX = STICK_MIN;
	  break;

	default:
	  break;
  }
	  
  if (duration_count>5)bt=NOTHING;//ボタン系は125ms以上おさないで残りは待ち
  
  switch (bt)
  {
	case A:
	  ReportData->Button |= SWITCH_A;
	  break;

	case B:
	  ReportData->Button |= SWITCH_B;
	  break;

	case X:
	  ReportData->Button |= SWITCH_X;
	  break;

	case Y:
	  ReportData->Button |= SWITCH_Y;
	  break;

	case R:
	  ReportData->Button |= SWITCH_R;
	  break;

	case ZR:
	  ReportData->Button |= SWITCH_ZR;
	  break;

	case ZL:
	  ReportData->Button |= SWITCH_ZL;
	  break;

	case PLUS:
	  ReportData->Button |= SWITCH_PLUS;
	  break;

	case H_UP:
	  ReportData->HAT = HAT_TOP;
	  break;

	case H_DOWN:
	  ReportData->HAT = HAT_BOTTOM;
	  break;
	  
	case H_LEFT:
	  ReportData->HAT = HAT_LEFT;
	  break;
	  
	case H_RIGHT:
	  ReportData->HAT = HAT_RIGHT;
	  break;
	default:
	  break;
  }

  duration_count++;
  if (duration_count > cmd.duration)
  {
    bufindex++;
    duration_count = 0;
  }

  // Prepare to echo this report
  memcpy(&last_report, ReportData, sizeof(USB_JoystickReport_Input_t));
  echoes = ECHOES;
}



私の書いたJoystick.cのコードに関してはApache License 2.0とします。(改変、再公開などご自由にお使い下さい)

このコードでは基本的に以下の行動を繰り返します。
・①カムラの里で溶岩洞(上位)の探索クエを受注、出発
・②溶岩洞でサブキャンプ1に移動、周辺のビスマス光石や燃石炭を採取
・③5分経過したら帰還
・①~③を5回施行後、自宅のルームサービスから隠密隊のアイテム回収、隠密隊出発、交易船のアイテム回収

STEP4 makeコマンドを使えるように設定する(難易度:高)

私のやり方(WSLを使ったやり方)を書いておきます。
WSLは入っているものとします。(まだならhttps://qiita.com/matarillo/items/61a9ead4bfe2868a0b86 こういうのを参考に)

WSLを起動
ubu

すると黒い画面がでます。
ここにはlinuxコマンドを打って操作します。

まず(やらなくても良いですがパッケージ更新したければ)
sudo apt update
sudo apt upgrade
でパッケージを更新

次にgcc makeコマンドを使えるよう
sudo apt install build-essential

essensialコマンド2
としてインストール

次に
cd /mnt/z/AutoRaid-1.0.0aw

これで先ほど解凍してできたJoystick.cのある階層に現在地を移動させます。
「Joystick.cのある階層」を一字一句間違いなく指定しないとエラーがでて先に進めません。
ここがつまづきポイントになりそうですが、
例えば私なら最初にJoystick.cのあるところを開いておき、
cd説明

矢印の部分をクリック

cd説明2

こうなるのでこれをコピーすれば
Z:\AutoRaid-1.0.0aw
という文字が得られます。

Zを小文字のzにして:を消して、\(円マーク)を/(バックスラッシュ)にして
cd /mnt/z/AutoRaid-1.0.0aw
先のこのコマンドがでできたというわけですね・・・

普通Cドライブ上で作業することが多いので皆さんのほうではZはCになっているかと思います。(私はramdisk上でやってるのでZになってます)

ちゃんと階層が移動できたか確認するために

ls

と打ってみましょう。

ls
このように
Joystick.cを含んだファイルの一覧が表示されればokです。

そこまでできたら

make
とコマンドを打ちます。
すると恐らく


「致命的エラー: avr/io.h: そのようなファイルやディレクトリはありません」
「make: avr-gcc: コマンドが見つかりませんでした」
「make: *** [lufa/LUFA/Build/DMBS/DMBS/gcc.mk:167: build_begin] エラー 127」

といったようなエラーがでるので

sudo apt-get install avr-libc gcc-avr

で足りないパッケージをインストールして
再度makeを実行

すると
make後

こんなふうに大量のメッセージが表示されますが、赤い文字でerrorとか書いてなければ大丈夫!

今の操作でhexファイルが生成されたはずです。
windowsのエクスプローラーにもどり
makehex確認

こんな感じに更新日時が現在の時刻になっているはずです。
もしなっていなければJoystick.hexが生成されなかったということなので、どこかがまずかったということです。頑張ってコマンド見直すなりググって解決して下さい・・・。

ちなみに黒い画面で一回打ったコマンドは、↑↓キーを押すことで履歴をだせるので再度同じのを打たなくても済みます。

STEP5 Arduinoに書き込み(難易度:中)

これを参考にArduinoをDFUモードでPCに接続します。
このとき必ずDFUモードでつながないといけないので注意してください。

以下のサイトも参考にしつつ

まずはcmd(コマンドプロンプト)を起動します。
次にdfu-programmer.exeがある階層にcdで移動します。

cd /d Z:/dfu-programmer-win-0.7.2

例によってここも正しい階層を一字一句間違いなく指定しないとエラーで先に進めません。
cddfuprog
私の場合はZ:/dfu-programmer-win-0.7.2のフォルダの中にdfu-programmer.exeがあるのでこのようなコマンドになっています。

次に先ほど作成した
Joystick.hex
をdfu-programmer.exeと同じフォルダ内にコピーします。
hex移動

次にコマンドプロンプトで

dfu-programmer.exe atmega16u2 erase

と打って実行します。
エラーがでていたらArduinoがDFUモードでつながってないということなのでさしなおしてください。

次に
dfu-programmer.exe atmega16u2 flash Joystick.hex

でhexファイルの情報をArduinoに書き込みます。
arwrtn

こんな感じの出力になるはず。
これにて完了。ArduinoをPCから外します。

STEP6 Arduino UnoをSwitchにつなげる(難易度:易)

これは完成形を見たほうがはやいでしょう。
bg354556he564


事前準備(モンハン側)

つないだ瞬間から自動操作が始まります。
なので開始するタイミングが地味に重要で、NPCと会話中につなぐとプラスボタンを押したのにメニューが開けず、結果想定しない動作が行われることになります。画面キャプチャ&フェーズ認識をしてるわけではないので、想定しない画面が出ても順番通りにボタンが入力され続けるので注意です。

他にも自動操作前にしておく必要のある注意点があるのですが全部あげておくと

・自動操作開始のタイミングは、カムラの里内でメニュー(プラスボタン)が開ける状態であること

5

・溶岩洞のサブキャンプを解放させておくこと

サブキャンプ1だけ解放してれば多分バグりませんが検証してないです。
no title

・交易船で3枠とも依頼しておくこと

1枠でも多分バグりませんが検証してないです。
3

・隠密隊は依頼した状態にしておくこと

依頼してない状態だとアイテム回収の時点でバグります。
2

・隠密隊用のオトモアイルーを4匹以上準備しておき、オーダー画面で一番上に隠密隊用のアイルーが選べるようにしておくこと

ここは地味にAボタンが10連続で押されるところです
1


完成!


実演動画(2番目のリンク)↓


なお自動操作を終了する場合はArduinoのケーブルを引っこ抜くしかありません。
大前提ですが、自動操作は自己責任で行って下さい。

リミテーション

今回の手法にはいろいろ制限があります。
原因ははっきりわかりませんが、あまり大量のメモリを使うプログラムだと、コンパイルがうまく行ったとしてもArduinoが予期しない動作を起こす場合がありました。
例えば
  { PLUS,      55 },
  { H_RIGHT,   55 },
  { H_DOWN,    10 },
  { H_DOWN,    10 },
  { H_DOWN,    10 },
  { H_DOWN,    10 },
  { A,         55 },
  { H_LEFT,    15 },
・・・・と、このコマンド数がだいたい200を超えてくるとまともに動かないという事象が起こりました。
Arduinoの問題なのかは不明ですが、どちらにせよこのまま動作を継ぎ足ししてはすぐ限界がきてしまい、あまり複雑な行動を取らせることは無理なようです。
またキャプチャーボードで現在の画面の状況をリアルタイムで監視しているわけではないので、ラグなどの原因でボタン入力のタイミングがずれるとその後が悲惨なことになります。

さらに実際の動作確認デバッグのとき、Joystick.cのプログラムを書き換えてからArduinoに書き込みSwitchに接続、という繰り返しをするも少々だるいです。

https://www.youtube.com/watch?v=hDI7YCTNfiA
こちらを見るにキャプチャーボードでPCに画面取得→PCで操作コマンド指示出し→BluetoothでSwitchへ
という形式がより発展性がありそうと感じました。
そのうちチャレンジしてみたいと思います。

いかがでしたか?参考文献

パディングなしでバンクコンフリクトを解消する方法 CUDA 行列転置

シェアードメモリとバンク

GPUを使った行列転置でほぼ頻出話題のバンクコンフリクト(Bank Conflict)とパディング(Padding)。前提知識としてCUDA等のGPUプログラミングでシェアードメモリ(共有メモリ)を明示的に扱う際に、Bankを意識しないと遅くなっちゃいますよという話があります。
bank

シェアードメモリは4byte刻みで32個のBankに分かれています。
例えばthread 0がSMEM[0]、thread 1がSMEM[32]にアクセスするようなプログラムだと(SMEMは4byte型とします)、どちらも同じBank0にアクセスすることになるのでリプレイ(replay)が発生して遅くなります。

行列転置バンクコンフリクトありver

つまり普通にシェアードメモリを使って行列転置をしようとすると
jurai

こんな感じにShared Memoryから値を読み込む際にバンクコンフリクトが起こってしまっています。
ちなみにここではわかりやすくBankは0~3までとしています。

行列転置パディングありver

そこでパディングをいれることで参照Bankがずれるのでこれを解消できます。そこそこ有名なテクニックです。
Kepler GPUアーキテクチャとプログラム最適化
プログラムを高速化する話Ⅱ 〜GPGPU編〜

sample0

概念図
juraip

これでShared Memoryに書き込むときも読み込むときも、Bank競合が起きてないことが分かります。
でも貴重なShared Memoryに未使用領域があるのはちょっと気持ち悪くないですか。

行列転置パディングなしver

上の概念図がすでに大きなヒントでした。
sinki
これでも読み書きともバンクコンフリクトは起こってないですね。
メモリアクセスの仕方としては
code0

こんな感じにx方向のズレ幅をyごとにずらしてやればいいです。

一応コードの全貌を載せておきます。

#include<stdio.h>
#include<cuda.h>
#include<cuda_runtime.h>

// --matrix size --
const int nimax=8192,njmax=8192;
// -- the number of threads per block --
const int BS=32;
__device__ float idata[nimax][njmax],odata[nimax][njmax];

__global__ void transposeNaive(){
  int ni,nj;
  ni=blockIdx.y*blockDim.y+threadIdx.y;
  nj=blockIdx.x*blockDim.x+threadIdx.x;
  odata[nj][ni]=idata[ni][nj];
  return;
}

__global__ void transposeShared(){
  int ni,nj;
  __shared__ float tile[BS][BS];
  ni=blockIdx.y*blockDim.y+threadIdx.y;
  nj=blockIdx.x*blockDim.x+threadIdx.x;
  tile[threadIdx.y][threadIdx.x]=idata[ni][nj];
  __syncthreads();
  ni=blockIdx.x*blockDim.x+threadIdx.y;
  nj=blockIdx.y*blockDim.y+threadIdx.x;
  odata[ni][nj]=tile[threadIdx.x][threadIdx.y];
  return;
}

__global__ void transposeSharedNobankconf(){
  int ni,nj;
  __shared__ float tile[BS][BS+1];
  ni=blockIdx.y*blockDim.y+threadIdx.y;
  nj=blockIdx.x*blockDim.x+threadIdx.x;
  tile[threadIdx.y][threadIdx.x]=idata[ni][nj];
  __syncthreads();
  ni=blockIdx.x*blockDim.x+threadIdx.y;
  nj=blockIdx.y*blockDim.y+threadIdx.x;
  odata[ni][nj]=tile[threadIdx.x][threadIdx.y];
  return;
}


__global__ void transposeSharedNobankconf_nopading(){
  int ni,nj;
  __shared__ float tile[BS][BS];
  ni=blockIdx.y*blockDim.y+threadIdx.y;
  nj=blockIdx.x*blockDim.x+threadIdx.x;
  tile[threadIdx.y][(threadIdx.x+threadIdx.y)%BS]=idata[ni][nj];
  __syncthreads();
  ni=blockIdx.x*blockDim.x+threadIdx.y;
  nj=blockIdx.y*blockDim.y+threadIdx.x;
  odata[ni][nj]=tile[threadIdx.x][(threadIdx.x+threadIdx.y)%BS];
  return;
}


void transpose(float hidata[][njmax],float hodata[][njmax]){
  int ni,nj;
  for(ni=0;ni<nimax;ni++){
    for(nj=0;nj<njmax;nj++){
      hodata[nj][ni]=hidata[ni][nj];
    }
  }
  return;
}

void check_mat(float C[][njmax],float CC[][njmax]){
  int ni,nj,flg;
  flg=0;
  for(ni=0;ni<nimax;ni++){
    for(nj=0;nj<njmax;nj++){
      if(fabs(C[ni][nj]-CC[ni][nj])>1.0e-8){
        flg=1;
        printf("%d %d %lf %lf\n",ni,nj,C[ni][nj],CC[ni][nj]);
      }
    }
  }
  if(flg==1){
    printf("Calculation error.\n");
  }else{
    printf("OK.\n");
  }
  return;
}




float hidata[nimax][njmax],hodata[nimax][njmax],htdata[nimax][njmax];

int main(){
  int ni,nj;
  dim3 grid,blck;
  grid.x=nimax/BS; grid.y=njmax/BS;
  blck.x=BS; blck.y=BS;
  // -- set initial value --
  srand(248309);
  
  
  for(ni=0;ni<nimax;ni++)
  {
    for(nj=0;nj<njmax;nj++)
    {
      hidata[ni][nj]=(float)rand()*1.1;
    }
  }
  
  
  cudaMemcpyToSymbol(idata,hidata,nimax*njmax*sizeof(float));
  transpose(hidata,htdata);
  // -- 1. transpose by simple kernel --
  transposeNaive<<<grid,blck>>>();
  transposeNaive<<<grid,blck>>>();
  cudaMemcpyFromSymbol(hodata,odata,nimax*njmax*sizeof(float));
  check_mat(htdata,hodata);
  
  // -- 2. transpose with shared memory --
  transposeShared<<<grid,blck>>>();
  transposeShared<<<grid,blck>>>();
  cudaMemcpyFromSymbol(hodata,odata,nimax*njmax*sizeof(float));
  check_mat(htdata,hodata);
  
  // -- 3. transpose with shared memory and without bank conflict --
  transposeSharedNobankconf<<<grid,blck>>>();
  transposeSharedNobankconf<<<grid,blck>>>();
  cudaMemcpyFromSymbol(hodata,odata,nimax*njmax*sizeof(float));
  check_mat(htdata,hodata);

  // -- 4. transpose with shared memory and without bank conflict and nopading --
  transposeSharedNobankconf_nopading<<<grid,blck>>>();
  transposeSharedNobankconf_nopading<<<grid,blck>>>();
  cudaMemcpyFromSymbol(hodata,odata,nimax*njmax*sizeof(float));
  check_mat(htdata,hodata);
  

  cudaDeviceReset();
  
  return 0;
}


8192×8192要素の行列の転置をするコードとなっています。

速度比較


normal

1.transposeNaive:シェアードメモリすら使ってない。グローバルメモリへコアレスアクセスできてないので一番遅い
2.transposeShared:シェアードメモリ内でバンクコンフリクト発生
3.transposeNobankconf:パディング入れてるやつ
4.transposeNobankconf_nopading:パディング入れてないやつ


まずグローバルメモリにコアレスアクセスできてない1番、これは論外です。次に2,3のバンクコンフリクトのあるなし、これだけで2倍違うことがわかります。
今回考案した4番は、速度的に3番に劣ってないことがわかります。

で、これは何の役に立つんですか?

さきほど書いたよう、パディング入りは無駄なメモリ領域を確保しています。なのでOccupancyが100%になる前にシェアードメモリ容量が律速になるような問題では以下のようなことが発生します。

1Blockあたり16.25KBのシェアードメモリを使うプログラムの場合
nvvp2

1Blockあたり16.0KBのシェアードメモリを使うプログラムの場合
nvvp

このようにOccupancyが上がることが期待できます。

なお、上記の行列転置のコードのカーネルはすべてOccupancy100%です。(Shared Memoryを使い切る前にOccupancy100%になる)

CUDA Unified Memoryを扱う方法 (CとPyCUDAのコード)

最小限のコードであまり落ちてなかったので自分のメモ用としても

まずはCUDA版(nvccでコンパイルするやつ)
#include <stdio.h>
__global__ void VecAdd(float* A, float* B, float* C) {
	int id = 256 * blockIdx.x + threadIdx.x;
	C[id]=A[id]+B[id];
}

int main() {
	int N = 1024;
	// Allocate 3 arrays on GPU
	float *d_A, * d_B, * d_C;
	cudaMallocManaged(&d_A, N * sizeof(float));
	cudaMallocManaged(&d_B, N * sizeof(float));
	cudaMallocManaged(&d_C, N * sizeof(float));
	// CPU init
	for(int i=0;i<N;i++){
		d_A[i]=d_B[i]=1.0*i;
	}

	//gpu kernel
	VecAdd <<<N/256, 256>>> (d_A, d_B, d_C);
	cudaDeviceSynchronize();//wait

	for(int i = 0;i<N;i++){
		printf("%f ",d_C[i]);
	}
	
	cudaFree(d_A);
	cudaFree(d_B);
	cudaFree(d_C);
	return 0;
}

つぎにPyCUDA版(2020.1)
from pycuda.autoinit import context
import pycuda.driver as cuda
from pycuda.compiler import SourceModule
import numpy as np

mod = SourceModule("""
__global__ void doublify(float *a)
{
    a[threadIdx.x] *= 2;
}
""")
doublify = mod.get_function("doublify")

a = cuda.managed_empty(shape=12, dtype=np.float32, mem_flags=cuda.mem_attach_flags.GLOBAL)
a[:] = np.linspace(0, 11, len(a)) # Fill array on host
doublify(a, grid=(1,1), block=(len(a),1,1))
context.synchronize() # Wait for kernel completion before host access
print(a)
del a #これでガベージコレクションに削除してもらうことで、GPUメモリも解放される

ほんの19行でかけた aにCPUからもGPUからもアクセスできている。

Xcode+Unity+NCMB+古すぎるPCでハマった。Cannot find protocol declaration for 'ASAuthorizationControllerDelegate'

なかなか厄介なエラーにぶち当たったのでメモ

Mac OS : 10.13.6(High Sierra)
Xcode : 10.1
Unity 2020.1.2f1
NCMB v4.2.0


Unityで自作アプリを作成して、Xcodeでビルドして自前のiPhoneでデバッグしようとしたが
Cannot find protocol declaration for 'ASAuthorizationControllerDelegate': did you mean 'UINavigationControllNCMBAppleAuth.m'

とかいうエラーが発生。
e1

e2

e3


これは16個あるうちの最初のエラーで他にもいろいろあるようだけど、何が根本的な原因か全然わからなかった。
ちなみにNCMBを使っているのでデバッグにもApple Developer登録(1万円払うやつ)が必要だと思って、それはすでに取得してある。


でこれの解決方法だが、「PCを新しくする」をして解決した。

ここで使用していたPCはMacBook Air 2011 Midとかなり古かった。調べるとこのモデルはOSがどれだけ新しくしてもMac OS  10.13.6(High Sierra)まで。
(2020/8時点で最新の)Mac OS 10.15 Catalinaじゃないと使えないXcodeバージョンというのがあり、それでBuildを試したら謎のエラーはなくなった。

開発環境は古すぎるとダメという教訓であった。

【Unity】Android BuildでAPIレベル29以降にする方法

UnityでAndroid 10(API レベル 29)以降を導入

私の環境

OS:Windows 10
Unity Ver:2019.1.3f1 , 2019.4.8f1 , 2020.1.2f1
日付:2020/8/22

UnityのAndroid Buildで指定できるのはAPI 28(Android 9)まで

Google Play ストアに新規でアプリを公開する際に、API 29以上じゃないと怒られるようになりました。
googlestore

api_google

どうも2020/8/3から強制的にそうなったようで、私がこの沼にハマったのは8/22。なんとタイミングの悪いことか・・・おかげで情報がほとんどなく数時間消費してしまいました。

というのも、よりにもよってUnityのAndroid Buildで指定できるのはAPI 28(Android 9)までで、API 29(Android 10)で強行しようとするとこんなエラーがでます。

"Unable to install additional SDK platform. Please run the SDK Manager manually to make sure you have the latest set of tools and the required platforms installed. See the Console for details."

api29

自分でSDK Manager使ってなんとかしろということです。

SDK Managerの場所をみつけてAPI 29(Android 10)以降を導入

まずUnityがインストールされているフォルダを見つけ、その中のSDK Managerの場所をみつけます。
2020年現在、UnityHubで管理している方が多いでしょう。Unity 2019.4.8f1ならば
C:\Program Files\Unity\Hub\Editor\2019.4.8f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\tools\bin
こんなフォルダが作られているはずです。

このフォルダ内にsdkmanager.batというファイルがあることを確認します。
次にコマンドプロンプトを管理者権限で起動

cd C:\Program Files\Unity\Hub\Editor\2019.4.8f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\tools\bin

でディレクトリを移動して下記コマンドを1行ずつ実行
sdkmanager "build-tools;29.0.3"
sdkmanager "build-tools;30.0.2"
sdkmanager "platforms;android-29"
sdkmanager "platforms;android-30"

これでAPI 29とAPI 30がUnity 2019.4.8f1で使えるようになりました。

api30


ちなみにsdkmanager --listで導入できるパッケージ一覧が表示されます。

cmd

ここからさっきのAPI 29と30を拾ってきてました。他のはよくわからない・・・

デバッグを無効にする必要があります

やっと上のエラーを解決してもまだまだたくさんトラブルが降ってきます。タイトルとはずれますが、直後にこのトラブルに遭遇したので一応メモ

debug


"デバッグ可能なAPKまたはAndroid App Bundleをアップロードしました。セキュリティ上の理由により、Googl Playに公開するにはデバッグを無効にする必要があります。"

これはここを見て解決
https://teratail.com/questions/245964

別のバージョンコードを使用する必要があります

ここでさらに次のトラブルが降ってきます。
versioncode_

"バージョンコード 4 のAPK または Android App Bundle がすでに存在するために、別のバージョン コードを使用する必要があります。 "

これは簡単で、UnityのBuild Settings→Player Settings→Playerで

versioncode

ここの数字を変えてBuildしなおせば良いです。


最後に


※以上の知見は2020/8/22時点での対処法です。
プロフィール

toropippi

記事検索
アクセスカウンター

    QRコード
    QRコード
    • ライブドアブログ