20200805のUnityに関する記事は4件です。

UnityのC#からGitを叩く

UnityのC#からGitを叩く

UnityのC#のEditor拡張スクリプトからGitコマンドを叩く機会があったので、忘れないうちにまとめます。

サンプルコマンド

今回、例として、UnityのC#から以下のコマンドを叩いてみます。

git config core.autocrlf

改行コードを自動で変換する機能が有効になっているか確認できるコマンドです。

サンプルコード

以下がサンプルコードです。外部から GetAutocrlf を叩けばコマンドが実行されます。

using System;
using System.Diagnostics;
using System.IO;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;

public class GitCommandPractice
{
    /// <summary>
    /// Gitのautocrlfを確認する。
    /// </summary>
    public void GetAutocrlf()
    {
        // gitのパスを取得する。
        string gitPath = GetGitPath();

        // gitのコマンドを設定する。
        string gitCommand = "config core.autocrlf";

        // コマンドを実行して標準出力を取得する。
        string autocrlf = GetStandardOutputFromProcess(gitPath, gitCommand).Trim();

        Debug.Log(autocrlf);
    }

    /// <summary>
    /// Gitの実行ファイルのパスを取得する。
    /// </summary>
    /// <returns>Gitのパス</returns>
    private string GetGitPath()
    {
        // Macのとき
        if (Application.platform == RuntimePlatform.OSXEditor)
        {
            // パスの候補
            string[] exePaths =
            {
                "/usr/local/bin/git",
                "/usr/bin/git"
            };

            // 存在するパスで最初に見つかったもの
            return exePaths.FirstOrDefault(exePath => File.Exists(exePath));
        }

        // Windowsはこれだけで十分
        return "git";
    }

    /// <summary>
    /// コマンドを実行して標準出力を取得する。
    /// </summary>
    /// <param name="exePath">実行ファイルのパス</param>
    /// <param name="arguments">コマンドライン引数</param>
    /// <returns>標準出力</returns>
    private string GetStandardOutputFromProcess(string exePath, string arguments)
    {
        // プロセスの起動条件を設定する。
        ProcessStartInfo startInfo = new ProcessStartInfo()
        {
            FileName = exePath,
            Arguments = arguments,
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            RedirectStandardOutput = true,
        };

        // プロセスを起動する。
        using (Process process = Process.Start(startInfo))
        {
            // 標準出力を取得する。
            string output = process.StandardOutput.ReadToEnd();

            // プロセスが終了するかタイムアウトするまで待つ。
            process.WaitForExit(TimeoutPeriod);

            return output;
        }
    }
}

解説

C# の Prosess を使って、Gitコマンドを叩き、その標準出力を読み取るという仕組みです。

GetGitPath() では、Gitの実行ファイルのパスを取得しています。

Gitの実行ファイルのパスは、環境変数Pathに登録されていることが一般的だと思うので、単に git と記述してもほとんどの場合、問題ないと思います。

ただし、私のMac環境ではそれではうまく行かなかったので、 /usr/local/bin/gitusr/bin/git の2種類を明示的にハードコーディングし、どちらか見つかった方を実行ファイルとして利用するようにしました。

さいごに

本記事作成にあたり、以下を参考にしました。ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

uGUIにおけるポインターイベントの伝播 (Unity)

前提

環境

  • Unity 2019.4.6 (LTS)

目的

  • uGUIにおける主なポインターイベントのバブリングを研究します。

公式ドキュメント

イベントの伝播を確認する

準備

  • ▼♻Scene
    • ▼?Canvas
      • ▼?Obj (0)
        • ▼?Obj (1)
          • ?Obj (2)
    • ?EventSystem

Obj (0~2)には、イベントを受け取るImageと、受け取ったイベントとhoveredを報告するCheckPointerEventをアタッチ

CheckPointerEventの内容
CheckPointerEvent.cs
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;

public class CheckPointerEvent : MonoBehaviour
    , IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler  // ドラッグ
    , IPointerEnterHandler, IPointerExitHandler // 領域出入
    , IPointerUpHandler, IPointerDownHandler, IPointerClickHandler // クリック
{

    public void OnBeginDrag (PointerEventData eventData) {
        Debug.Log ($"{name}.OnBeginDrag [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
    }

    public void OnEndDrag (PointerEventData eventData) {
        Debug.Log ($"{name}.OnEndDrag [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
    }

    public void OnDrop (PointerEventData eventData) {
        Debug.Log ($"{name}.OnDrop [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
    }

    private List<GameObject> lastDragHoverd; // 最後のドラッグ・ログ
    public void OnDrag (PointerEventData eventData) {
        if (lastDragHoverd == null || !lastDragHoverd.SequenceEqual (eventData.hovered)) { // 異なるときだけ
            Debug.Log ($"{name}.OnDrag [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
            lastDragHoverd = new List<GameObject> (eventData.hovered);
        }
    }

    public void OnPointerEnter (PointerEventData eventData) {
        Debug.Log ($"{name}.OnPointerEnter [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
    }

    public void OnPointerExit (PointerEventData eventData) {
        Debug.Log ($"{name}.OnPointerExit [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
    }

    public void OnPointerUp (PointerEventData eventData) {
        Debug.Log ($"{name}.OnPointerUp [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
    }

    public void OnPointerDown (PointerEventData eventData) {
        Debug.Log ($"{name}.OnPointerDown [{string.Join(", ", eventData.hovered.ConvertAll (o => o.name))}]");
    }

    public void OnPointerClick (PointerEventData eventData) {
        Debug.Log ($"{name}.OnPointerClick [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
    }

}

試行と結果

親「エンター、ダウン、ドラッグ、アップ、イグジット」
Obj (0).OnPointerEnter []
Obj (0).OnPointerDown [Obj (0), Canvas]
Obj (0).OnBeginDrag [Obj (0), Canvas]
Obj (0).OnPointerUp [Obj (0), Canvas]
Obj (0).OnPointerClick [Obj (0), Canvas]
Obj (0).OnEndDrag [Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (0), Canvas]
親「エンター、ダウン、ドラッグ、イグジット、ドラッグ、アップ」
Obj (0).OnPointerEnter []
Obj (0).OnPointerDown [Obj (0), Canvas]
Obj (0).OnBeginDrag [Obj (0), Canvas]
Obj (0).OnDrag [Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (0), Canvas]
Obj (0).OnDrag []
Obj (0).OnPointerUp []
子「エンター、ダウン、ドラッグ、アップ、イグジット」
Obj (1).OnPointerEnter []
Obj (0).OnPointerEnter [Obj (1)]
Obj (1).OnPointerDown [Obj (1), Obj (0), Canvas]
Obj (1).OnBeginDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnPointerUp [Obj (1), Obj (0), Canvas]
Obj (1).OnPointerClick [Obj (1), Obj (0), Canvas]
Obj (1).OnEndDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnPointerExit [Obj (1), Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (1), Obj (0), Canvas]
子「エンター、ダウン、ドラッグ、イグジット、ドラッグ、アップ」
Obj (1).OnPointerEnter []
Obj (0).OnPointerEnter [Obj (1)]
Obj (1).OnPointerDown [Obj (1), Obj (0), Canvas]
Obj (1).OnBeginDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnPointerExit [Obj (1), Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (1), Obj (0), Canvas]
Obj (1).OnDrag []
Obj (1).OnPointerUp []
Obj (1).OnEndDrag []
孫「エンター、ダウン、ドラッグ、アップ、イグジット」
Obj (2).OnPointerEnter []
Obj (1).OnPointerEnter [Obj (2)]
Obj (0).OnPointerEnter [Obj (2), Obj (1)]
Obj (2).OnPointerDown [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnBeginDrag [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnDrag [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnPointerUp [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnPointerClick [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnEndDrag [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (1).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
孫「エンター、ダウン、ドラッグ、イグジット、ドラッグ、アップ」
Obj (2).OnPointerEnter []
Obj (1).OnPointerEnter [Obj (2)]
Obj (0).OnPointerEnter [Obj (2), Obj (1)]
Obj (2).OnPointerDown [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnBeginDrag [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (1).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnDrag []
Obj (2).OnPointerUp []
Obj (2).OnEndDrag []
  • 他に、ドラッグして他のオブジェクトにドロップすると、落とされたオブジェクトにのみOnDropが届く様子を確認できました。

考察

結果の解釈

  • OnPointerEnterOnPointerExitはターゲット以降もバブルアップ(親へ伝播)されます。
    • その他(クリック、ドラッグ)はターゲットで留められてバブルアップされません。
    • このイベントによる違いは、ボタンを親子に配置することでも確認できます。
      • 子ボタンをポインターで指すと、親ボタンもHighlighted状態になりますが、クリックしても親ボタンには伝わりません。
  • hoveredは、「マウスが上にのっているオブジェクトのスタックリスト」と説明されていますが、イベント発生直前の親を辿るようです。
    • 特に、OnPointerEnterでは侵入直前の状態を示すため、ポインターが孫に侵入した場合、孫では侵入前なので何もなし、バブルアップされた子では孫に侵入済み、さらにバブルアップされた親では孫と子に侵入済みとなるようです。
    • 他のイベントの多くは、直前が既にオブジェクトに乗った状態なので、総親のCanvasまで辿られています。
    • 対して、オブジェクトから外れてからドラッグを終えると、何にも乗っていないことになります。

疑問

  • イベントによって、ターゲットがイベントを受け取った後のバブルアップの有無が異なるようですが、どのようなルールなのでしょうか?
    • 固定的なものなのか、設定あるいは制御可能なものなのでしょうか?
  • いろいろ調べて見ましたが、関係するような記述は発見できませんでした。
    • 制御できても良さそうなものですが、「そういうもの」として設計されているということでしょうか。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Distance Joint 2Dの解説

今回はphysics2Dの中にあるDistance Joint 2Dについて解説していこうと思います。

Distance Joint 2Dとは

Distance Joint 2Dは、2つのオブジェクト間の距離を保たせるための機能です。
近づきもせず、離れもせず。そんな感じの機能です。
jointというのは「関節」や「付け根」などの意味を持っているそうです。

Distance Joint 2Dの各パラメータ

distanceJoint2d_param.png
所々パラメータの名前が見切れていてすみません。
パラメータが多いですが頑張って1つずつ見ていきましょう。

・Enable Collision

このパラメータは、アタッチしたオブジェクトと当たり判定をするかどうかというのを決める為のものです。
これのチェックボックスにチェックをすると衝突するようになります。
チェックを外すと、衝突判定をせずにアタッチしたオブジェクトをすり抜けられるようになります。

・Connected Rigid Body

ここにオブジェクトをアタッチすると、そのオブジェクトとつながった状態になります。
ここにオブジェクトをアタッチしない場合、ワールド空間とつながった状態になります。

・Auto Configure Connected Anchor

2つ下の「Connected Anchor」を自動的に決めるためのものです。
あまりこれを使うのはオススメしません。これを使うとオブジェクトが変な挙動をする可能性があるからです。

・Anchor

このオブジェクト側の基準点を決めるところです。
ワールド座標ではなく、このオブジェクトのローカル座標になっているので注意してください。
私はX、Yともに0にすることをオススメします。

・Connected Anchor

アタッチしたオブジェクト側の基準点を決めるところです。
ここで使われている座標はアタッチしたオブジェクトのローカル座標です。
こちらもX、Yともに0にすることをオススメします。

・Auto Configure Distance

ここのチェックボックスにチェックを入れると二つのアンカーの距離が下のDistanceに入力されます。
注意点として、このチェックボックスにチェックを入れたままプロジェクトを実行して、どちらかのオブジェクトの座標を直接いじると、Distanceの値が変わってしまいます。もしかしたら他にもDistanceの値が変わってしまうやり方があるかもしれないので、チェックは1回付けたら外しておきましょう

・Distance

ここでは、2つのアンカーの距離を決めます。
2つのオブジェクトの距離がここで決められた距離よりも離れていた場合、両方のオブジェクトが決められた距離まで近づいていきます。

・Max Distance Only

ここのチェックボックスにチェックを入れると、2つのオブジェクトはDistanceで決められた距離よりも近づくことができるようになります。
ロープで繋がっているようなイメージです。

・Break Force

二つのオブジェクトのつながりを断ち切り、Distance Joint 2Dを削除するために必要な力を決めます。
infinityだと絶対に切れることはありません。
また、infinityに戻したいときは「inf」と入力してください。

スクリプトの一例

private void OnCollisionEnter2D(Collision2D collision)
{
this.GetComponent().enableCollision = true;
this.GetComponent().connectedBody = collision.rigidbody;
this.GetComponent().anchor = new Vector2(0, 0);
this.GetComponent().connectedAnchor = new Vector2(0, 0);
this.GetComponent().distance = 2;
this.GetComponent().maxDistanceOnly = true;
this.GetComponent().breakForce = Mathf.Infinity;
}

これはぶつかってきたオブジェクトを捕まえる的なプログラムです。
あらかじめこのオブジェクトのBody Type(Rigid Body2dのパラメータ)をkinematicにして床とかに触れないようにするか、if文を使って捕まえられるオブジェクトを限定する必要があります。

おわりに

今回は前回よりもパラメータが多いうえにややこしいのも結構あって大変でした。
Joint系は恐らく全部こんな感じだと思いますが、あと8個あるのでそれは全部記事にしていこうと思います。
私もまだまだ理解できていないところが多いので、記事も書きながら勉強していく所存です。なので、この記事を読んでくださった皆様のなかで間違いなどを見つけたら、ご指摘いただけるとこちらとしても嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】UnityWebRequestで画像ファイルを送信してPHPで受け取る最小実装

UnityWebRequestで画像ファイルを送信しPHPで受け取る最小の実装を備忘録として残します。

Unity側

前提条件として画像ファイルはバイト配列として取得している状態を作ります。

以前執筆したコチラの記事でTextureをPNGにエンコードしたバイト配列作成までを紹介しているので、参考になるかもしれません。
【Unity】RawImageのTextureをPNGにエンコードする方法

IEnumerator Send(byte[] bytes)
{
    var form = new WWWForm();
    // "file"はPHP側で$_FILESから取得するキー ex)$_FILES["file"]
    form.AddBinaryData("file", bytes, "image.png", "image/png");
    var req = UnityWebRequest.Post("http://localhost:8080/api.php", form);
    yield return req.SendWebRequest();
}

PHP側

api.php
$fileName = $_FILES["file"]["name"];
// api.phpと同じディレクトリに保存されます
move_uploaded_file($_FILES["file"]["tmp_name"], $fileName);

最後に

エラーハンドリングを考慮するとUnityもPHPも記述することはまだまだありますが、本記事では肝となる部分の実装のみを紹介しました。

環境

  • Unity2019.4.4f1
  • PHP 7.2.16
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む