ゆべねこの足跡

主にVR、Unity、Arduinoについて書いていきます。

IFTTTのWebhooksをトリガーとしてArduinoから実行する

ArduinoEthernet Shieldを買ってみたのでIFTTTとの連携をしてみようと思いました。最初と最後でハマったので、それについても書いていきます。

目次

使用したもの

全て秋月で購入できるものです。また、Ethernet Shieldを使うにはLANケーブルも必要です。

Maker Channelの代わりにWebhooksを使う

最初のハマりポイント。どこの情報を見てもMaker Channelを使ってやっているんですよね。しかしいくらIFTTTの利用できるサービスで検索してもヒットしない...なんでや...

いろいろ調べてみたら、どうやらMaker Channelの代わりにWebhooksを使うということが判明しました。Webhooksの使い方についてはこちらを見ると分かりやすいかと思います。

回路を作る

今回はタクトスイッチを押したらWebhooksにイベント情報を送って、スマホに通知を送るという流れにします。回路はよくあるタクトスイッチを使った回路です。

f:id:yubeshineko:20180910195928j:plain

アプレットを作る

thisのところをWebhooksにします。イベントの名前は今回はButtonPlessedとでもしておきましょう。thatはNotificationsにします。また、このときにWebhooksのシークレットキーを確認しておきます。確認の仕方はこちらを参考にするとよいです。

コードを書く

Webhooksはウェブリクエストを送ってやるとイベントを発火させられるようです。ここではこちらのサイトのコードを参考にしてタクトスイッチが押されたらGETメソッドを使ってウェブリクエストを行うという処理を書いていきます。MACアドレスEthernet Shieldを買ったときにシールドの裏面についているシールに書かれています。また、コンパイルする際はEthernet2ライブラリのインストールをしてからにしてください。

ちなみに、ArduinoIDEのバージョンは1.8.5です。

#include <SPI.h>
#include <Ethernet2.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 1, 177);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 0, 0);

EthernetClient client;
char server[] = "maker.ifttt.com";

// IFTTT strings
char eventName[] = "ButtonPlessed"; // イベント名
char secretKey[] = "****************"; // シークレットキー
char s[128];//リクエストメッセージ保持用配列

const int buttonPin = 2;
int lastButtonState = LOW;

void setup() {
  Serial.begin(9600);
  while (!Serial)
  {
  }

  Serial.println("Trying to get an IP address using DHCP");
  if (Ethernet.begin(mac) == 0)
  {
    Serial.println("Failed to configure Ethernet using DHCP");

    Ethernet.begin(mac, ip, gateway, subnet);
  }
  Serial.print("My IP address: ");    Serial.println(Ethernet.localIP());
  Serial.println ();

  pinMode(buttonPin, INPUT);
}
 
 
void loop()
{
  int buttonState = digitalRead(buttonPin);
 
  if (lastButtonState == LOW && buttonState == HIGH)
  {
    Serial.println("ButtonPressed!");
  
    if ( client.connect(server, 80) )
    {
      Serial.println("connected");
      // Make a HTTP GET request
      sprintf(s, "GET http://maker.ifttt.com/trigger/%s/with/key/%s HTTP/1.1", eventName, secretKey);
      client.println(s);
      client.print("Host: ");
      client.println(server);
      client.println("Connection: close");
      client.println();
    
      Serial.println("Sent message");
      delay( 1000 );
    }
    else
    {
      Serial.println("Error: Could not make a TCP connection");
    }
  }
  lastButtonState = buttonState;
 
  while (client.available())
  {
    char c = client.read();
    Serial.print(c);
  }
  if (!client.connected())
  {
    client.stop();
  }
}

コードをコンパイルしてボードに書き込み、LANケーブルを繋げば準備は完了です。タクトスイッチを押すとスマホに通知が飛ぶようになるはずです。

飛ぶはずです...

あれ...飛ばない...?

謎のエラー

第二のハマりポイント。私の場合ですが、何度やっても通知が来ませんでした。でもシリアルモニタを確認すると

Congratulations! You've fired the ButtonPlessed eventButtonPressed!

とか書いてあるからうまくいってるはずなんだけどなぁ...

ネットで調べてみるとこちらの記事を発見。記事によると、「Webhooks」のページから[Settings]をクリックし、飛んだ先のページの[Edit connection]をクリックしてやると改善するらしいです。この通りにやってみて再トライしてみると通知が飛ぶようになりました。

まとめ

  • Maker Channelの代わりにWebhooksを使う
  • ウェブリクエストを送ってやるとWebhooksのイベントが発火させられる
  • Webhooksがうまく動作しないときはアプレットの再接続をしてみる

これでローカルなトリガーによりネットワーク上でアクションを起こせるようになりました。次はネットワーク上のトリガーによってローカルでアクションを起こせるようにしたいですね。

NavMeshAgentを使って逃げるAIを作った話

どうも、ゆべしネコです。そういえば最近Unityいじれてないなとふと思ったので、リハビリがてら簡単なゲームを作ってみようと思いましてここ最近はUnityをいじって遊んでました。今回の目標はUniRxを使った鬼ごっこゲームを作ることです。...と言っても、ほとんどの時間を逃げるAIの作成に割かれてしまった訳ですが...笑

結果、こんなんできました。


Unity NavMeshAgentで逃げるAI

逃げる仕組み

逃げるAIって言ってもどうやって逃げるようにしたらいいのか悩みますよね。最初は逃げる方向に適当に次の目的地を設定して、そこが移動可能かどうか判定してもし移動不可ならもう一度実行する...みたいなAIがいいかと思ったのですが、なんか良さげな実装が思いつかなかったので、もっと単純化することにしました。

逃げる位置となるポジションを最初から置いておく

シーンが始まると同時にゲームオブジェクトが生成されます。

MovePointGenerete.cs

using UnityEngine;

public class MovePointGenerete : MonoBehaviour {

    [SerializeField]
    private int _genereteNum = 30;

    void Start ()
    {
        var parent = new GameObject ("MovePoints");
        for (int i = 1; i <= _genereteNum; i++)
        {
            var nextMovePoint = new GameObject();
            nextMovePoint.name = ("MovePoint(" + i + ")");
            nextMovePoint.tag = "MovePoint";
            nextMovePoint.layer = 9;
            nextMovePoint.AddComponent<SphereCollider>();
            nextMovePoint.AddComponent<NextMovePosition>();
            var rb = nextMovePoint.AddComponent<Rigidbody>();
            rb.useGravity = false;
            rb.constraints = RigidbodyConstraints.FreezePosition;
            nextMovePoint.transform.parent = parent.transform;
            nextMovePoint.transform.position = new Vector3 (Random.Range(-50, 50), 0, Random.Range(-50, 50));
        }
    }
}

ポイントはレイヤーとコライダー、Rigidbodyコンポーネントの追加ですね。この時にPhysicsウィンドウを開いてレイヤーでの衝突設定を行い、プレイヤー、逃げる敵がこのオブジェクトに触れられないようにします。この流れでこのあとどうするかわかった方もおられるのではないでしょうか。

プレイヤーに近づかれたら周辺の逃げるポジションを探して逃げる

プレイヤーに近づかれたら周辺のゲームオブジェクトを探してきます。

Enemy.cs一部抜粋

public void RunAway ()
    {
        currentState = EnemyState.TENSION;
        coloring.material.color = Color.red;
        escapeTime = 0;
        agent.angularSpeed = 200;

        //プレイヤーと逆方向を向く
        var diff = (transform.position - player.transform.position).normalized;
        diff.y = 0;
        transform.rotation = Quaternion.FromToRotation(diff, Vector3.up);
        agent.SetDestination(GetNextPosition());
        
        //目的地に近づいたら次の目的地を検索
        agent.ObserveEveryValueChanged(d => agent.remainingDistance)
            .Where (d => d < 2.0f)
            .Where(_ => currentState == EnemyState.TENSION)
            .Subscribe (_ =>
            {
                var nextposition = GetNextPosition ();
                agent.SetDestination(nextposition);
            }).AddTo(gameObject);
        
        //一定距離離れて一定時間経ったら状態を戻す
        this.UpdateAsObservable ()
            .TakeWhile(_ => currentState == EnemyState.TENSION)
            .Select(_ => (transform.position - player.transform.position).sqrMagnitude)
            .Where(distance => distance > targetDistance * targetDistance)
            .Subscribe(distance =>
            {
                escapeTime += Time.deltaTime;
                if (escapeTime >= 10) Usual ();
            });
    }

    public Vector3 GetNextPosition ()
    {
        if (m_foundList.Count > 0) m_foundList.Clear();
        m_foundList.AddRange(Physics.OverlapSphere(transform.position, _searchRadius, mask));
        if (m_foundList.Count == 0)
        {
            //近くになかったときは半径40mの中にあるオブジェクトを獲得し、そこからランダムに選ぶ
            m_foundList.AddRange (Physics.OverlapSphere(transform.position, 40.0f, mask));
            foreach (var obj in m_foundList) Debug.Log (obj.gameObject.name);
        }
        else
        {
            for (int i = 0; i < m_foundList.Count; i++)  
            {
                var foundData = m_foundList[i];
                if (!CheckFoundObject(foundData.gameObject))
                    m_foundList.Remove( foundData );
            }
        }

        return m_foundList.Count > 0 ?  m_foundList[Random.Range(0, m_foundList.Count-1)].transform.position : Vector3.zero;
    }

    private bool CheckFoundObject( GameObject i_target )
    {
        var myPositionXZ = Vector3.Scale( transform.position, new Vector3( 1.0f, 0.0f, 1.0f ) );
        var targetPositionXZ = Vector3.Scale( i_target.transform.position, new Vector3( 1.0f, 0.0f, 1.0f ) );
        var toTargetFlatDir = ( targetPositionXZ - myPositionXZ ).normalized;

        //同位置にいるときは範囲内にいるとみなす
        if (toTargetFlatDir.sqrMagnitude <= Mathf.Epsilon) return true;
        return (Vector3.Dot (transform.forward, toTargetFlatDir)) >= m_searchCosTheta;
    }
}

まず最初にプレイヤーと逆の方向を向かせます。その後、次のポジションを見つけるのですが、ここで先ほど作ったゲームオブジェクトが役立ちます。 Physics.OverlapSphere(transform.position, _searchRadius, mask)を使って探索範囲中にある次のポジションのコライダーの配列を取得し、リストに追加します。しかし、球の中全てからランダムに選ぶのではあまりよろしくありません。なぜなら、プレイヤーの方向にある次のポジションも選択可能になってしまうからです。そこで、得られたリストのなかのデータの選別を行います。それにはこちらの記事を参考にしました。

www.urablog.xyz

得られたコライダーのポジション情報とEnemy自身のポジション情報から角度を判定し、一定範囲内にないものをリストから除外するという流れですね。これで自身の前方の一定範囲内にある次の移動地点が入ったリストができるというわけです。最後にこれらのリストからランダムに一点を選ぶようにして次のポジションを決定します。一も見つからなかったときは操作範囲を広げて、Physics.OverlapSphereメソッドを実行し、こちらでは角度判定を行わないで最終移動地点を選択するようにしています。

このように、逃げる位置を最初から作っておき、そこから角度を判定して適切な逃げる位置を選択し続けることでプレイヤーから遠ざかるような動きをするAIを作りました。

欠点

角度を取ってくるときに壁の向こうも取ってきてしまうことですね。そのため、壁の向こう側が次の移動地点に選ばれてしまうと壁でものすごく加速します。まぁ、それはそれで面白いのですが、改善点ではありますね。

あと、次のポジションのY座標が0で固定なので、Terrainや階層構造を持つステージでは使えない点ですね。平面ステージ限定でしか使えません。

その他やったこと

初めてAudioManagerを実装してみました。以前はC#の知識やUnityの知識が足りなかったため敬遠していたのですが、そろそろやってみてもいいだろうという訳でやってみました。実装にはこちらの記事のものを利用させていただきました。

kan-kikuchi.hatenablog.com

やばい...すごく使いやすい!!

感動しました。ただ、立体音響には使えなさそうなのでVRでやるには工夫が必要ですね。

まとめ

鬼ごっこで使うには十分かな? と思えるようなAIができました。リハビリにもなったので自分としては満足です。一応GitHubにもあげておきましたので、中身の詳細が気になる方はぜひダウンロードしてみてください。

GitHub - yubesi/Tag: 鬼ごっことかで使えそうな逃げるAIをUniRxを使って作ってみました。ただし、平面だけでしか使えないです。

Markdown記法の基礎

はじめまして。ゆべしネコと申します。
はてなブログでの初めてのブログ投稿というわけで今後使っていくであろうMarkdown記法の備忘録的なものを書いていきます。

目次

Markdown記法とはなんぞや

Markdown(マークダウン)は、文書を記述するための軽量マークアップ言語のひとつである。本来はプレーンテキスト形式で手軽に書いた文書からHTMLを生成するために開発されたものである。

Markdown - Wikipedia

要するに単純な記号を使って簡単にHTMLを生成できる記法ですね。それだけでなく、PDFに変換したりもできるらしいです。拡張子は .md となります。

見出し

行頭に#と半角スペースをつけてやると見出しが書けます。#の数がh1 ~ h6の数字の部分に対応します。

# h1
## h2
### h3
#### h4
##### h5
###### h6

結果

h1

h2

h3

h4

h5
h6

強調

強調したい文字を*で囲みます。HTMLで言うところのemタグです。より強い強調をするときは**で囲みます。HTMLで言うところのstrongタグですね。なお、*_で代用できます。

*クラムボン*  
_クラムボン_  
**クラムボン**  
__クラムボン__  

結果

クラムボン
クラムボン
クラブボン
クラムボン

段落、改行

一行空けて書き始めると段落分けができ、行末で半角スペースを2つ以上入れると改行ができます。

二疋ひきの蟹かにの子供らが青じろい水の底で話していました。  <!-- <-空白が2つ入力されている --> 
『クラムボンはわらったよ。』  
『クラムボンはかぷかぷわらったよ。』  
『クラムボンは跳はねてわらったよ。』 
『クラムボンはかぷかぷわらったよ。』  

<!-- 1行開ければ段落分けできる--> 

上の方や横の方は、青くくらく鋼はがねのように見えます。そのなめらかな天井てんじょうを、つぶつぶ暗い泡あわが流れて行きます。

やまなし - 青空文庫

引用とリンク

行頭に>と半角スペースを書くことで引用ができます。
また、[リンク文字列](URL)を書くことでリンクを貼ることができます。

> バーチャルリアリティのバーチャルが仮想とか虚構あるいは擬似と訳されているようであるが、これらは明らかに誤りである.

[バーチャルリアリティとは - 日本バーチャルリアリティ学会](https://vrsj.org/about/virtualreality/)

結果

バーチャルリアリティバーチャルが仮想とか虚構あるいは擬似と訳されているようであるが,これらは明らかに誤りである.

バーチャルリアリティとは - 日本バーチャルリアリティ学会

水平線

*, _, -いづれかを3つ以上入力すると水平線が書けます。

****
----
____

結果




リスト

ハイフン、プラス記号、アスタリスクいづれかと半角スペースでリストが書けます.

- りんご
- みかん

<!-- 番号付きリストも書ける -->
    1. はやぶさ
    1. やまびこ

結果

ソースコード

一行だけならバッククォートで囲みます。複数行ならば行頭に半角スペースを4つ入れます。また、シンタックスハイライトに対応している言語ならばコードを```言語名とバッククォート3つで囲むとハイライトされます。

`int foo = 0;`

    var tmp = 0;
    tmp = a;
    a = b;
    b = tmp;

```html
<h1> html </h1>
<span style="color: #0000cc"> こんにちは </span>
````

結果

int foo = 0;

var tmp = 0
tmp = a;
a = b;
b = tmp;
<h1> html </h1>
<span style="color: #0000cc"> こんにちは </span>

その他

ブログでは使わないと思いますがコメントは<!-- -->で書くことができます。ただし、前後に空行が必要です。
また、記号のエスケープをするにはバックスラッシュ\を前に挿入するとできます。

まとめ

とりあえずこれぐらいできればいいかくらいのものを紹介しました。他にも表を作ったりはてな記法を書いたりもできるらしいですが、それはまた今度ということで。皆さんもMarkdown記法を使いましょう!

参考文献

メモ書きやドキュメント作成に便利な「Markdown記法」を使ってみよう : ビジネスとIT活用に役立つ情報

はてなブログのMarkdownの書式まとめ - nasust life blog

はてなブログは何で書く? Markdownで決まりでしょ! - ニートが学ぶプログラミング