20190323のC#に関する記事は11件です。

【Unity(C#)】シンプルな入れ替えパズルの入れ替えロジック

入れ替えパズルとは

こんな感じのやつです。
15-puzzle.png
wiki

物理的に移動可能なマス目が一つだけ用意されていて、ブロックを移動させることで
イラストを完成させたり、数字を並べ替えたり、道をつないだりするゲームです。

unity1week

文字通り一週間でMade With Unityなゲームを完成させようというデスマーチです。
愉快な企画です。

今回、私も初参加しました。
毎回テーマが設定されて、そのテーマに沿った(こじつけでも可)ゲームを作成します。
今回のテーマは「つながる」でした。

入れ替えパズルの上をUnityちゃんに歩いてもらってゴールを目指すというものを作りました。

【IQ150】

入れ替えロジック

完成当初の入れ替えロジックは複雑に考えすぎてとんでもない遠回りな実装方法を取り入れてました。
今回作ったゲームもぐちゃぐちゃな実装のまま放置しているのでプレイする度に何かしらバグります。

もっとシンプルにできないかと思って考えてみました。

最終的に思いついた(というか上司からヒントもらった)のは
透明なブロックを動かして衝突した色付きブロックと入れ替えるという方法です。
MoveTransparent.png
上のとてもわかり易い図の通りです。

透明なブロックの移動範囲(今回は移動できる回数にしました)を3×3で定めて、
そのエリア内を移動させます。

以下、コードです。

    int x_MoveCount = 1;
    int z_MoveCount = 1;
    Vector3 thisObjPosition;
    Vector3 saveThisObjPosition;

    void Update()
    {

        if (Input.anyKey == false)
        {
            return;
        }

        thisObjPosition = this.gameObject.transform.position;

        if (Input.GetKeyDown(KeyCode.LeftArrow) && x_MoveCount > -1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.x -= 1;
            this.gameObject.transform.position = thisObjPosition;
            x_MoveCount -= 1;
        }

        if (Input.GetKeyDown(KeyCode.RightArrow) && x_MoveCount < 1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.x += 1;
            this.gameObject.transform.position = thisObjPosition;
            x_MoveCount += 1;
        }

        if (Input.GetKeyDown(KeyCode.UpArrow) && z_MoveCount < 1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.z += 1;
            this.gameObject.transform.position = thisObjPosition;
            z_MoveCount += 1;
        }

        if (Input.GetKeyDown(KeyCode.DownArrow) && z_MoveCount > -1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.z -= 1;
            this.gameObject.transform.position = thisObjPosition;
            z_MoveCount -= 1;
        }
    }

    void OnTriggerStay(Collider other)
    {
        //衝突してほしいゲームオブジェクトでなければ抜ける
        if (other.gameObject.tag != "Area")
        {
            return;
        }
        other.transform.position = saveThisObjPosition;
    }

透明なブロックの移動前の位置を保存

左の矢印キーを押した場合を例に見ていきます。

 if (Input.GetKeyDown(KeyCode.LeftArrow) && x_MoveCount > -1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.x -= 1;
            this.gameObject.transform.position = thisObjPosition;
            x_MoveCount -= 1;
        }

saveThisObjPosition = this.gameObject.transform.position;
で移動前の位置を保存してからポジションを変更しています。

 void OnTriggerEnter(Collider other)
    {
        //衝突してほしいゲームオブジェクトでなければ抜ける
        if (other.gameObject.tag != "Area")
        {
            return;
        }
        other.transform.position = saveThisObjPosition;
    }

衝突したら、その移動前のポジションを
衝突したゲームオブジェクトに代入しています。
other.transform.position = saveThisObjPosition;

GifCapture-201903232133398707.gif

できました。
ただこれだと同時入力(同一フレーム内で2ボタンの入力を受け取る)が可能?なのか
めちゃくちゃ早く動かすとバグります。
どうやらUnityにはキーボード入力の受付を一時的に止めるみたいな機能はないっぽいです。
なのでInputManagerみたいなのを自分で作って同時入力できないようにする必要があります。

もう少し調べて見つかったらまた記事まとめます。
追記


 if (Input.anyKey == false)
 {
    isCheckInput = false;
    return;
 }

 if (isCheckInput == false)
 {
    if (Input.GetKeyDown(KeyCode.RightArrow))
    {     
    isCheckInput = true;
    }
 }

こんな感じでいけました!
教えてくださった方ありがとうございます!

同時押し禁止についてはこちら

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

C#でHTTPSサーバ(Ver. HttpListener)

目次

  1. 目的
  2. System.Net.HttpListener とは
  3. アプリのコード
  4. 動作環境の設定手順
    1. HTTP.sys にサーバURLを登録
    2. ファイアーウォールに HTTP.sys を許可
    3. HTTP.sys にサーバ証明書とURLの関連を登録
  5. 動作確認

目的

  • C#でHTTPSサーバアプリを作成し、動作環境の設定を行う。
  • 利用するライブラリは System.Net.HttpListener とする。
  • System.Net.HttpListener を利用するため、コードよりはむしろ、動作環境の設定方法を残しておきたい。

System.Net.HttpListener とは

概要(MS-DOC)によると、HTTP.sys というアプリ(カーネルモードドライバ)を利用し、HTTPパケットの送受信を行うクラス。
HTTP.sys がSSL通信、HTTP接続の管理、HTTPヘッダの解析を行ってくれる。
API(MS-DOC)より、SSL証明書の追加には netsh コマンドを用いる必要がある。
WebSocket に関しては、Windows 8 以降では対応している。

アプリのコード

Program.cs
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Text;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var server = new SampleServer();
            server.Start(
                "http://+:8080/sample/",
                "https://+:44300/sample/");
            char ch;
            while ((ch = Console.ReadKey().KeyChar) != 'q')
                ;
            server.Stop();
        }
    }

    class SampleServer
    {
        HttpListener listener;

        public void Start(params string[] prefixes)
        {
            listener = new HttpListener();
            foreach (var prefix in prefixes)
                listener.Prefixes.Add(prefix);
            listener.Start();
            listener.BeginGetContext(OnRequested, null);
        }

        void OnRequested(IAsyncResult ar)
        {
            if (!listener.IsListening)
                return;

            HttpListenerContext context = listener.EndGetContext(ar);
            listener.BeginGetContext(OnRequested, listener);

            try
            {
                if (ProcessGetRequest(context))
                    return;
                if (ProcessPostRequest(context))
                    return;
                if (ProcessWebSocketRequest(context))
                    return;
            }
            catch (Exception e)
            {
                ReturnInternalError(context.Response, e);
            }
        }

        static bool CanAccept(HttpMethod expected, string requested)
        {
            return string.Equals(expected.Method, requested, StringComparison.CurrentCultureIgnoreCase);
        }

        static bool ProcessGetRequest(HttpListenerContext context)
        {
            var request = context.Request;
            var response = context.Response;
            if (!CanAccept(HttpMethod.Get, request.HttpMethod) || request.IsWebSocketRequest)
                return false;

            response.StatusCode = (int)HttpStatusCode.OK;
            using (var writer = new StreamWriter(response.OutputStream, Encoding.UTF8))
                writer.WriteLine($"you have sent headers:\n{request.Headers}");
            response.Close();
            return true;
        }

        static bool ProcessPostRequest(HttpListenerContext context)
        {
            var request = context.Request;
            var response = context.Response;
            if (!CanAccept(HttpMethod.Post, request.HttpMethod))
                return false;

            response.StatusCode = (int)HttpStatusCode.OK;
            using (var writer = new StreamWriter(response.OutputStream, Encoding.UTF8))
                writer.WriteLine($"you have sent headers:\n{request.Headers}");
            response.Close();
            return true;
        }

        static bool ProcessWebSocketRequest(HttpListenerContext context)
        {
            if (!context.Request.IsWebSocketRequest)
                return false;

            WebSocket webSocket = context.AcceptWebSocketAsync(null).Result.WebSocket;
            ProcessReceivedMessage(webSocket, message =>
            {
                webSocket.SendAsync(
                    Encoding.UTF8.GetBytes($"you have sent message:\n{message}"),
                    WebSocketMessageType.Text,
                    true,
                    CancellationToken.None);
            });

            return true;
        }

        static async void ProcessReceivedMessage(WebSocket webSocket, Action<string> onMessage)
        {
            var buffer = new ArraySegment<byte>(new byte[1024]);
            while (webSocket.State == WebSocketState.Open)
            {
                WebSocketReceiveResult receiveResult = await webSocket.ReceiveAsync(
                    buffer,
                    CancellationToken.None);
                if (receiveResult.MessageType == WebSocketMessageType.Text)
                {
                    var message = Encoding.UTF8.GetString(
                        buffer
                            .Slice(0, receiveResult.Count)
                            .ToArray());
                    onMessage.Invoke(message);
                }
            }
        }

        static void ReturnInternalError(HttpListenerResponse response, Exception cause)
        {
            Console.Error.WriteLine(cause);
            response.StatusCode = (int)HttpStatusCode.InternalServerError;
            response.ContentType = "text/plain";
            try
            {
                using (var writer = new StreamWriter(response.OutputStream, Encoding.UTF8))
                    writer.Write(cause.ToString());
                response.Close();
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e);
                response.Abort();
            }
        }

        public void Stop()
        {
            listener.Stop();
            listener.Close();
        }
    }
}

動作環境の設定手順

1.HTTP.sys にサーバURLを登録
サーバアプリを管理者権限で実行するなら必要ないが、一般ユーザで実行する場合はあらかじめHTTP.sysが監視するURLを登録しておく必要がある。
コマンドプロンプトから netsh コマンドを実行することになるが、コマンドプロンプトの起動は管理者として行うこと。
登録するURLは HttpListener.Prefixes.Add() に渡した値でよい。

cmd
> netsh http add urlacl url=http://+:8080/sample/ user=Everyone
> netsh http add urlacl url=https://+:44300/sample/ user=Everyone

2.ファイアーウォールに HTTP.sys を許可
コントロールパネルの「Windows ファイアーウォール」>「詳細設定」>「受信の規則」に「新しい規則」を追加する。
新しい規則は system プログラムを許可するような規則とする。

3.HTTP.sys にサーバ証明書とURLの関連を登録
管理者として起動したコマンドプロンプトから netsh コマンドを実行する。
サーバ証明書はあらかじめ登録しておく。pfx ファイルをエクスプローラから実行すればよい。その際、「保存場所」は「ローカルコンピュータ」にしておくこと。
その後、サーバ証明書の拇印を確認するため、mmc を起動し、スナップインに証明書を追加し、「証明書(ローカルコンピュータ)」>「個人」>「証明書」に表示される証明書を選択し、拇印を確認する。
URLは ワイルドカードが使えないので、具体的に指定する。
GUID も必要になるが、なんでもよい。

cmd
> netsh http add sslcert ipport=localhost:44300 certhash=<サーバ証明書の拇印> appid={00112233-4455-6677-8899-AABBCCDDEEFF}
> netsh http add sslcert ipport=127.0.0.1:44300 certhash=<サーバ証明書の拇印> appid={00112233-4455-6677-8899-AABBCCDDEEFF}

動作確認

  • Get の確認方法
    ブラウザで https://127.0.0.1:44300 にアクセスし、応答を確認する

  • WebSocket の確認方法
    ブラウザで https://www.websocket.org/echo.html にアクセスし、「Location」に 「wss://127.0.0.1:44300」を入力後、「Connect」を押下し、「Log」を確認する

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

メソッドチェーン・LINQ・Ix再考

概要

メソッドチェーン・LINQ・Ixの機能を見直しながら機能拡張に関して考えてみたいと思います。理論上可能なだけなものも混ざっていますので、あくまでも単純な読み物として読んでいただけると助かります。

一応ここに公開されているコードのライセンスはCC0としておきます。
CC0
また、これは、いつでもどこでもLINQ(Ix)したい!!!の改訂という扱いで書きます。

メソッドチェーン・LINQ・Ixの良い点・悪い点

上記サイトでも書いたことですが、改めて変数を用いることと比較して掻い摘んで書くと、以下のようになります。

(一時)変数を用いれば、

  • 柔軟に処理を記述できる
  • 多用すると複雑になる
  • 変数名に込めた役割以上の負担を背負わせることもある

メソッドチェーン・LINQ・Ixを用いれば、

  • 正常に動けばその後の機能分割が楽
  • 利用目的を明確化させやすい
  • 長々とした書き方になることもある
  • エラーの発見が難しくなる
  • ネストに弱い(引数がデリゲートの場合等)

という違いがあると思っています。
この点を踏まえた上で、機能拡張を考えてみましょう。

チェーンするだけのメソッド

メソッドの種類として少なくとも4種類に分けるとすると、

  • 任意の型の変数を引数にとり、任意の型で値を返すメソッド
  • 引数を取らないが、任意の型で値を返すメソッド
  • 任意の型の変数を引数に取るが、値を返さないメソッド
  • 引数も返り値も無いメソッド

だと思っています。
引数がいくつあるかは考慮せず、引数と返り値の型がどのようになっているかだけで分けました。
以上4つに関して、最大限に抽象化された、"メソッドをチェーンするだけの"メソッドを考えることにします。nullは考慮しないこととします。

まずは上から2つのメソッド、返り値があるメソッドに関して考えてみます。
上から順にそれぞれのメソッドは、Func<T, TR>、Func<TR>に代入可能です。それを踏まえると……

public static partial class Chain {
    public static TR Do<T, TR>(this T value, Func<T, TR> func) => func(value);
    public static TR Do<T, TR>(this T value, Func<TR> func) => func();
}

というメソッド(名前はもちろん適当)にすると、メソッドチェーンができます。
this T valueという引数宣言のおかげで、メソッドに返り値が存在すればチェーン可能になります。
では、戻り値のないメソッド、それぞれAction<T>とActionになりますが……、
チェーンにするためには返り値用のインスタンスを作成してそれを返すようにする他ないです。

public struct Null { }
public static partial class Chain {
    public static Null Do<T>(this T value, Action<T> action) {
        action(value);
        return new Null();
    }
    public static Null Do<T>(this T value, Action action) {
        action();
        return new Null();
    }
}

これで何の実装もないNull型のインスタンスが返り、無事にチェーンができるようになりました。

テストしてみましょう。

1.Do(x => "a").Do(() => 1).Do(x => Console.WriteLine(x)).Do(() => { });

コンパイルも無事通るため、問題ありません。

ちなみに、

public static partial class Chain {
    public static Null Do(Action action)
    {
        action();
        return new Null();
    }
    public static TR Do<TR>( Func<TR> func) => func();
}

も書いておくと、

Chain.Do(() => Console.WriteLine("hoge")).Do(() => "hoge");

のように、初めのインスタンスが無くても使えるようになります。
以上によって、任意のメソッドをチェーンさせるだけのメソッド、Do関数が完成しました。

使いみち

変数の利用をチェーンの中のみに限定させることができます。(エディタ上の変数のサジェスト管理ができる)
また、関連性はあるがチェーン不可能なメソッド同士を擬似的にチェーンさせることができます。

//普通はこのように書くところを……
var x = hogehoge();
var y = fuga(x);
var z = test(y)

//このように書いても分かりづらいから……
var z = test((fuga(hogehoge())));

//こう書いてしまおう!
//しかも以後に書くコードでxやyはサジェストされない!
var z = hogehoge().Do(x => fuga(x)).Do(y => test(y));

さらに、

Hoge x, y;
bool fuga;
//適当に処理が続き……
var z = fuga? Calculate(x) : Calculate2(y);

という書き方があって、hogeがfalseだった場合の挙動を確かめたい場合、
わざわざif・else文に書き下すことなく、

var z = fuga? Calculate(x) : Chain.Do(() => Console.WriteLine("False")).Do(() => Calculate(y));

というような文を書いて一時的なデバッグができるようになります。
デバッグが終わればマウスでドラッグして消せばいいので楽です。

ただ、正直このままだと利便性に欠けます。実用的ではありません。

Console.WriteLine("hoge");
Console.WriteLine("fuga");

という書き方すら、

Chain.Do(() => Console.WriteLine("hoge")).Do(() => Console.WriteLine("fuga"));

と書けてしまうため、

  • 本来一切処理に関連がないメソッドを繋ぎ合わせてしまう
  • もっと単純に書けるのにわざわざチェーンさせてしまう

という問題があるわけです。難読化に繋がってしまいます。
変数のサジェストを制限する代償としても大きいものです。
そこで、只の難読化ではなく、より良い機能拡張にするためにはどうすればよいかを考えます。

機能改善にむけて

一つは、メソッドの返り値に宣言されている場所のクラスを用いるという方法です。
いくつかのサイトで既に紹介されていますが、例えば……

class Test {
    int value;
    Test hoge(int value) {
        //this.valueについての処理
        return this;
    }
}

のように、return this;を書くことでチェーンさせる方法が挙げられます。
他にも……

class Test {
    int value;
    Test(int value) => this.value = value;
    Test hoge(int value) {
    //valueについての処理
      return new Test(value);
    }
}

として同じ型の別のインスタンスを返したり……

class Test {
    int value;
    Test(int value) => this.value = value;
    Test hoge() {
    //valueについての処理
      return new Test(value);
    }
}

実装を隠蔽して処理させるという方法等が挙げられます。

Do関数のように関数の中身を書く必要もなく、クラス内のものであれば簡潔にチェーンができます。
この機能をうまく生かせば、コードの可読性を上げたより良いメソッドチェーンを書くことができます。
ただ、その分欠点もあり、返り値の型が固定されてしまうため、関数の汎用性に乏しくなることがあります。

もう一つは、Doメソッドに追加して何らかの機能を盛り込むという方法が挙げられます。
このような形での機能が、LINQやIxに見られます。
Doメソッド的な書き方を基本として、引数に記述された関数の前後に何らかの処理をかけることで、複雑な処理もわかりやすく記述できるようになっています。

LINQやIxの拡張を考える

IEnumerable<T>への依存を減らす実装

このページの初めに示したWebページでは、拡張の方向性を何も示さないまま機能拡張について考察してしまったため、読者に混乱を生じさせてしまいました……。(すみませんでした……。)

その点を踏まえ、今回は拡張の方向性を定義してから考察を始めます。
LINQやIxで用いられるIEnumerableは、いわばT型の値の"集まり"ということで、「多」という一文字で表記し、T型そのものの値を「一」と表記します。
すると、LINQやIxのオペレータは、3つに分類できます。

  • 多から多への変換(Select・ForEach・Concat等)
  • 多から一への変換(Average・Sum等)
  • 一から多への変換(Repeat等)

以上のオペレータを以下の3つのうちのどれかに対応させるように拡張を行うものとします。

  • 一から一への変換
  • 一から多への変換
  • 多から一への変換

このように定義して、実用的なオペレータについて考察を行いましたが、意外に少ないことが判明しました。

個人的に有用だと考えたのは、

  • Select
  • ForEach
  • AsEnumerable
  • Where

……くらいでした()
そのうちSelect・ForEachオペレータは前述のDoメソッドと同じ機能になります。
AsEnumerableもnullを考慮しなければほぼ1文で書け、

public static IEnumerable<T> AsEnumerable<T>(this T value) { yield return value; }

となります。
ところが、Whereオペレータだけは例外的で実装がかなり難しくなります。
Whereの条件を満たさなかった場合の返り値として、何を用いるかで迷います。
初めはnullを返し、他のオペレータでnullチェックを行う実装をしました。
しかし、それに伴って発生する変更と合わせてみると、実用的ではありませんでした。
また、C#8でnullを排除するプログラミング手法が増える中で、nullを用いようとするのは良くありません。
したがって、実用的なWhereオペレータの実装は現状では無理だと結論づけておきます。

以上から、IEnumerable<T>への依存を減らす方向性でのLINQ・Ix拡張はそれほど有意義なものではないことがわかりました。

タプルをLINQ・Ixする

(int id, string name)[] datas = new (int, string)[] { (5, "hoge") /*宣言が続く*/};

という配列を宣言したとしましょう。これに対してSelectオペレータを用いるときに、

datas.Select((id, name) => /*適当な実装*/);

としたいときがありますが、これは出来ず……

datas.Select(data => /*data.idとdata.nameを使う*/);

という使い方しかできません。
オペレーターが対応していないため、新たに拡張することが必要です。
仮に、タプルの要素を分解して各オペレータに適応できたと仮定します。
タプルは無限に存在しますが、現実的な個数として5こまでと仮定しましょう。
すると、Selectでは少なくとも2~5の4つのオーバーロードが必要になります。
宣言部分だけコード化すると、

public static class IEnumerableExtension {
    public static IEnumerable<TR> Select<T1, T2, TR>(this IEnumerable<(T1, T2)> values, Func<T1, T2, TR> func)
    public static IEnumerable<TR> Select<T1, T2, T3, TR>(this IEnumerable<(T1, T2, T3)> values, Func<T1, T2, T3, TR> func)
    public static IEnumerable<TR> Select<T1, T2, T3, T4, TR>(this IEnumerable<(T1, T2, T3, T4)> values, Func<T1, T2, T3, T4, TR> func)
    public static IEnumerable<TR> Select<T1, T2, T3, T4, T5, TR>(this IEnumerable<(T1, T2, T3, T4, T5)> values, Func<T1, T2, T3, T4, T5, TR> func)
}

というような感じです。長いですね。
では、IEnumerableが2つ必要なZipオペレータや3つ必要なIfオペレータだったらどうでしょうか?
単純計算で4*4=16、4*4*4=64ということで、それぞれ16種類、64種類のオーバーロードが必要になります。
今は5個までとしていますが、おそらく実際にライブラリ化するなら100種類以上は超えるでしょう。
流石にここまで来ると手作業では無理なのでコードジェネレータで何とかするかもしれませんが、
膨大な数のオーバーロードであることには変わりありません。Intellisenceがしっかり働くかもわかりません。適切とは言えないでしょう。
そこで、別のアプローチを考えることにします。

IEnumerableを組み合わせる

Zipオペレータの機能を抽象化して、

  • 値の纏まり方を変更するだけのメソッド
  • 複数の纏まりに対して処理を行うメソッド

を考えます。
前者の具体的な例としては、

(int id, string name)[] datas = new (int, string)[] { (5, "hoge") /*宣言が続く*/};
(IEnumerable<int>, IEnumerable<string>)hoge = datas.ToDivide();

と書いたときのToDivideメソッドのイメージです。
(int, string)の纏まりが、(intの纏まりとstringの纏まり)になりました。

そして、後者の例は、

(IEnumerable<int>, IEnumerable<string>)hoge = datas.ToDivide();
hoge.Map((id, name) => /*適当な実装*/)
//MapはSelectと同等の機能を持つ

と書いたときのMapメソッドのイメージです。
idとname引数はもちろん、datasに格納されていたintとstring変数になります。
このように、役割を分割すれば、仮にタプル化できる型の数をn個、オペレータの総数をa個、1つに対するオペレータのオーバーロードの個数をb個とすると、
高々n*a*b個のオーバーロードで済むはずです。指数関数的に増えた上述のアイデアとは大きな開きがあります。

実際の実装はまだ行っていません(ライブラリ化となるともう少し真面目に考える必要があるため)が、
このような機能があれば、タプルがより便利になると思いました。

Nested LINQ・Ix

もし、IxがIEnumerable<T>のタプルを扱えるようになったなら、
N重のForEachを見やすく略記できるのでは?と思いました。
基本的な考え方は「IEnumerable<T>を組み合わせる」と同じです。
ただ、ここで一つ問題が発生することもわかりました。
ネストしたForEachの略記に必要なそもそものIEnumerable<T>のCount(配列で言うLength)はバラついている可能性があります。
Countが異なるIEnumerable<T>をタプルにして、NestedForEachするだけならいいですが、そのように実装すると、同時に、Selectしたり、Aggregateを挟めるようにがなってしまいます。型では配列の長さまではわかりません。それが問題なのです。

※ここで言うSelectやAggregateは、「IEnumerable<T>を組み合わせる」の考え方で拡張したものを指します。

おわりに

もともとは、全てに対してメソッドチェーンすれば面白いし、可読性が上がるのでは?ということをきっかけにして始まりました。
その結果、そうではないことがわかりました。
本当はその時点でも止めてよかったのですが、
メソッドチェーンの本質を捉えたいと思い、思考と実践を重ねた結果、
ここまでの長々とした記事になりました。

最後までお読みいただき、本当にありがとうございました。
この記事をきっかけとして、メソッドチェーンの面白さに気づいていただければ幸いです。

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

Grasshopper からHoudini へ

Grasshopper からHoudiniに移るにあたり、
GHと似たHoudini の機能をメモ書きしたもの。
いい方法あればご教授ください~。

随時更新中

Point系

  • Construct Point

vector pos = set(0, 0, 0);
addpoint(0,pos);

またはAddコンポーネントでベクトルを入れて追加する。

  • CenterPT

対象のポリゴンのポイントを足していって、ポイントの数で割る。

vector vec = set(0,0,0);
for(int i = 0; i < @numpt ; i++)
{
vec += point(0,"P",i);
}
vec /= @numpt;
addpoint(0,vec);

Bounding Box のセンターを拾う
vector center = getbbox_center(0);

primWarngle にして各プリミティブのcenterPt 拾える

int pt = addpoint(0, @P);
removeprim(0, @primnum, 1);

boundingbox のMax と Min の座標を足して2で割る。

vector min, max;
getbbox(min,max);
vector center = (min+max)/2;

addpoint(0,center);

Explession を使う。
$CEX $CEY $CEZ 
Transformノードのパラメーターにこれを打ち込み、ピボットにしたり、移動する距離を決めると便利。

  • Brep Closed Point 位置ベクトルから一番近いジオメトリまでの距離

float dist = xyzdist(0,(位置ベクトル)

  • Evaluate Surface 位置ベクトルから一番近いジオメトリーのUvとプリミティブ番号と最短距離を返す

int primNum:
vector closedUv;
float dist = xyzdist(0,位置ベクトル,primNum,closedUv)

  • nearPoints
    位置ベクトルから近いPointの番号を得る
    int nearpoint = nearpoint(0,位置ベクトル)
    int nearpoints[] = nearpoints(0,@P,(探す最大距離);

  • Near Pointのuv会得
    xyzdist(1, @P, prm, uv);

  • RemoveDuplicate
    Fuseコンポーネントを使う。
    RemoveDegenerater
    Keep Unused にチェック入れておく。

  • Point on curve
    Resemple node
    Max Lenght のところに0.25っていれると4つくらいできる。ポイントができる。(lengh1のとき)
    CreateUvでPointのUv拾える。

vector multi = (p0+p1)/2; で中点を拾える(直線に対して)

Transform系

move
①ポイントの座標に任意の数を足す
@P.x += 8;
@P.y += 8;
@P.z += 8;

②Transform Compornent
$CEX, $CEY, $CEZ
$SIZEX, $SIZEY, $SIZEZ
$XMIN, $YMIN, $ZMIN, $XMAX, $YMAX, $ZMAX

のExplession と一緒に使うと便利

③Vecotr = new Vector でポイントの位置を変える

  • amplitude move
    Vector move = set(x,y,z);
    Normarize(move);
    @P += move * Value(距離)

  • Orient
    移動先のポイントを作って
    CopyStampコンポーネント。

  • Rotate
    Transform Node

//NomalVector
vector N = primuv(1, "N", prm, uv);

//Cross Dot
vector v = cross(v@N ,cross(v@D, v@N))

//Distance TwoPoints
flaot dis = distance(vec1,vec2);

List系

  • itemList point メゾットで任意のポイントからアトリビュートを拾う。

vector pos = point(0,"P",index);

(0Geometry のindex番目のPアトリビュートを読みとる)

  • Replace Items
    AとBを入れ替えるなら
    Aを指定 → 保存 → AにBを入れる →Bに保存したAを入れる

  • CullIndex(配列から消す
    removeindex(&array[], int index)
    int removevalue(&array[], value)

int removepoint(int geohandle, int point_number)

それかdelete ノード

  • List Length(アトリビュートの数)

int length = len(配列)

  • Cull Pattern
    消したいPointを任意のグループに追加またはアトリビュートで印をつけといてして、
    DeleteCompornet。

  • removeprim(0,@primnum,0)
    最後が1ならpointを消す。 0なら残す。、

  • sort
    sort Compornent がある

  • そのたC#の簡単な配列機能

int[] array = { 0, 1, 2 };
List list = new List(array);
list.Add(3);
int[] result = list.ToArray();

  • Jitter Point wangle でポイントの順をランダムに並び替える 乱数を作ってそれをインデックスにして入れ替えていく。
for(int i = 0; i < @numpt; i++)
{
    @index[i] = i;
}


for(int i = 0; i < @numpt; i++){
    int j = int(rand(i*`chs("../Controlloer/seed")`)*@numpt);
    int stor = @index[i]; 
    @index[i] = @index[j];
    @index[j] = stor;
}

Group系

グループへの追加
int setpointgroup(int geohandle, string name, int point_num, int value, string mode="set")

int setprimgroup(int geohandle, string name, int prim_num, int value, string mode="set")`

それかCreate Group ノードを使う。

Surface系

  • explode Surface
    FuseNode を使う。Uniqueにチェック入れるとプリミティブ同士が切れる。

  • Brep WireFrame
    ConvertLine Nodeをつなぐ

  • CullDuplicateEdgths
    DivideノードのRemoveSharedEdgesにチェック入れる。

  • 重なっているエッジを取り出すなら
    primitive ノードのFace/FullからCloseUをUnrollにする。

  • BoundingBox

Curve系

  • line from Two point int line = addprim(0,"polyline"); addvertex(0,line); addvertex(1,line);

addvertex(追加するポイント、ターゲットのPrim);

math系

  • Radom
    float x = floor(rand(seed)*4)+1;
    で 1-4の乱数作れる

  • 小数点以下切り捨て
    floor(float)
    のかたち
    範囲を超えたら切り捨て
    clamp(targetValue,min,max);

  • Remap
    fit(FitさせるNumber,SorceMin,SourceMax,TargetMin,TargetMax);
    max< min とすれば -x〜0 にもRemap 可能。

//((float)@ptnum)/(@numpt-1)
で、ポイントに0〜1の値をアトリビュートできる
ただ、ptnum をintからFloatにしないと割れないので、先頭の(float)をに注意。

  • graphMapper float dist = chramp("../CONTROLLER/height_ramp",dist); distを0-1にしておけば、Rampの値になおせる。

float remapNum = (float)@ptnum/(float)(npoints(0));
@P.y = chramp('myRamp',remapNum);
の形で各ポイントをランプに対応させる。

  • π
    $PI

  • 絶対値をとる
    abs(数値)

Visual系

  • Noise PointnのZ座標に掛け合わせた場合
    @P.y = noise(ch("scale")*@P.z);

  • VectorDisplay
    @N = Vector (可視化したいベクター)
    で、ノーマルを表示する。

コンソール

void printf(string format, ...)

  • パネル 

printf("%f\n",変数名);

Attribiute

GH にはないけどよく使う。

  • Point にアトリビュートに値を入れる。
    int pt = setpointattribute(0,"アトリビュート名",int index,入れる値,"set");

  • Detailのアトリビュートを参照する。
    type detail(0,"アトリビュート名",0)

  • Derailのアトリビュートに値を入れる。
    setdetailattribute(0,"アトリビュート名",値,"set")

  • Primにアトリビュートに値を入れる。
    setprimattrib(int geohandle, string name, int prim_num, value[], string mode="set")

  • Primのアトリビュートを参照する。
    prim(geometry, string attribute_name, int primnumber)

- アトリビュートの平均とか最大値を出す
AttributePromoteNode を使う
対象のアトリビュート クラス 移した後のクラス どういう操作をしてから移すか を決める。

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

ASCII、Unicode、システムのANSIエンコーディングの概要を表示

概要

ASCIIエンコーディング、Unicodeエンコーディング、言語設定により指定されたシステムのANSIエンコーディングの概要を表示するサンプルコードです。表示例は下記の通りです。

ASCIIエンコーディング
CodePage: 20127
EncodingName: US-ASCII
WebName: us-ascii
HeaderName: us-ascii
BodyName: us-ascii

Unicodeエンコーディング
CodePage: 1200
EncodingName: Unicode
WebName: utf-16
HeaderName: utf-16
BodyName: utf-16

言語設定に依存するANSIエンコーディング
CodePage: 932
EncodingName: 日本語 (シフト JIS)
WebName: shift_jis
HeaderName: iso-2022-jp
BodyName: iso-2022-jp

サンプルコード

using System;
using System.Globalization;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            // ASCIIエンコーディングの概要
            ConsoleWriteCodePageSummary("ASCIIエンコーディング", Encoding.ASCII);
            // Unicodeエンコーディングの概要
            ConsoleWriteCodePageSummary("Unicodeエンコーディング", Encoding.Unicode);
            // 言語設定に依存するANSIエンコーディングの概要
            var ansiCodePage = CultureInfo.CurrentCulture.TextInfo.ANSICodePage;
            ConsoleWriteCodePageSummary("言語設定に依存するANSIエンコーディング",
                Encoding.GetEncoding(ansiCodePage));
        }

        private static void ConsoleWriteCodePageSummary(string caption, Encoding encoding)
        {
            Console.WriteLine(caption);
            Console.WriteLine($"CodePage: {encoding.CodePage}");
            Console.WriteLine($"EncodingName: {encoding.EncodingName}");
            Console.WriteLine($"WebName: {encoding.WebName}");
            Console.WriteLine($"HeaderName: {encoding.HeaderName}");
            Console.WriteLine($"BodyName: {encoding.BodyName}");
            Console.WriteLine();
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アプリのスクリーンショットをpngファイルにする

概要

アプリのスクリーンショットをpngファイルにするマクロ。
事前にC#をビルドする必要あり。

screenshot_to_png.gif

  • 画像ではマクロをショートカットに登録しています。

背景

アプリのスクリーンショット画像をセルに書いてあるファイル名で保存したい。
「スクリーンショットを取ってExcelに貼り付ける」記事は調べると出てくる。

が、ちょっと違う。コレジャナイ。

嵌ったポイント1 - Excelへの貼り付け用の例しか見つからない

Excelだけでクリップボードからpngファイル化する例はほとんど見つけられませんでした。
近いのは・・・ペイント(mspaint.exe), ADODB, SendKeysを駆使した

です。

C#でクリップボード→pngファイルを組んでしまったほうが安定するかな・・・と考えて, C#でコンソールぽく実装します(作り方はフォームアプリ)。

嵌ったポイント2 - アプリが最小化されているとうまく取得できない

最小化されている場合, AppActivateだけではアプリ画面が最前面化されないようです。

そこで, Word Taskオブジェクトを使って, ウィンドウの状態を標準に戻してからアクティブにしています。

' ターゲットアプリのタイトルバー文字列
Const APP_TITLE = "ターゲット"
・・・
    '****
    ' 1. ターゲットアプリをアクティブにする
    '****

    ' 最小化されている場合, AppActivateしても画面が出てこない.
    ' WordのTaskオブジェクトでwindowStateを操作して, 標準の状態に戻したあと,
    ' アクティブにする

    If objWord.Tasks(APP_TITLE).WindowState = 2 Then
        objWord.Tasks(APP_TITLE).WindowState = 0
    End If

    Dim AppFullName
    AppFullName = objWord.Tasks(APP_TITLE).Name

    AppActivate AppFullName, False

嵌ったポイント3 - Windowsのアニメーション効果が有効だとタイミングが合わない

Windows10のアニメーション効果が有効だと, AppActivate後に少しWaitを入れないとダメなようです。
「アニメーション後」を取る方法がなさそうだったのでSleepでアクティブ待ちをしています。

    '****
    ' 2. ターゲットアプリのアクティブ待ち
    '****
    ' Windows10のアニメーション効果が有効な場合, AppActivateのあと少しウェイトを入れる必要がある
    Call Sleep(300) 

嵌ったポイント4 - AppActivateは完全なタイトルバーの文字列でないといけない

objWord.Tasks(APP_TITLE)は部分一致でいける一方でAppActivateは完全なタイトルバーの文字列(プロセスのフレンドリ名?)でないとダメなようです。

objWord.Tasks(APP_TITLE).Nameで取得したものAppActivateに渡します。

    Dim AppFullName
    AppFullName = objWord.Tasks(APP_TITLE).Name

    AppActivate AppFullName, False

コード

VBA - 主体となる部分

Option Explicit

' win32api
Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

'仮想キーコード
Const VK_SNAPSHOT = &H2C ' Printscreen
Const VK_MENU = &H12 ' Alt

' ターゲットアプリのタイトルバー文字列
Const APP_TITLE = "ターゲット"


' ターゲットアプリのスクリーンショット画像(.png)を取得する.
' 出力ファイル名は選択したセルの文字列を使う
Public Sub GetScreenshotPng()

    Dim objWsh
    Set objWsh = CreateObject("WScript.Shell")

    Dim objFso
    Set objFso = CreateObject("Scripting.FileSystemObject")

    Dim objWord
    Set objWord = CreateObject("Word.Application")


    '****
    ' 0. エラーチェック
    '****
    ' ターゲットアプリの存在確認
    If Not objWord.Tasks.Exists(APP_TITLE) Then
        MsgBox APP_TITLE & "が見つかりません"
        GoTo ReleaseObjects
    End If

    ' ファイル名の確認
    Dim outFileName
    outFileName = Selection.Value
    If outFileName = "" Then
        MsgBox "出力ファイル名が空白です"
        GoTo ReleaseObjects
    End If


    '****
    ' 1. ターゲットアプリをアクティブにする
    '****

    ' 最小化されている場合, AppActivateしても画面が出てこない.
    ' WordのTaskオブジェクトでwindowStateを操作して, 標準の状態に戻したあと,
    ' アクティブにする

    If objWord.Tasks(APP_TITLE).WindowState = 2 Then
        objWord.Tasks(APP_TITLE).WindowState = 0
    End If

    Dim AppFullName
    AppFullName = objWord.Tasks(APP_TITLE).Name

    AppActivate AppFullName, False


    '****
    ' 2. ターゲットアプリのアクティブ待ち
    '****
    ' Windows10のアニメーション効果が有効な場合, AppActivateのあと少しウェイトを入れる必要がある
    Call Sleep(300)


    '****
    ' 3. Alt + PrintScreenを押下する
    '****
    'Alt + PrintScreen
    Call keybd_event(VK_MENU, 0, 1, 0) 'Alt押下
    Call keybd_event(VK_SNAPSHOT, 0, 1, 0) 'PrintScreen押下
    Call keybd_event(VK_SNAPSHOT, 0, 3, 0) 'PrintScreen離し
    Call keybd_event(VK_MENU, 0, 3, 0) 'Alt離し


    '****
    ' 4. クリップボードの画像をpngファイル化する
    '****

    ' get_clipboard_image.exeのパスを作成(.xlsmと同じフォルダにあることを期待)
    Dim cmd
    cmd = """" & objFso.BuildPath(ThisWorkbook.Path, "get_clipboard_image.exe") & """"

    ' get_clipboard_image.exeの引数(出力ファイル名)を作成(.xlsmと同じフォルダに出力することを期待)
    Dim arg
    arg = """" & objFso.BuildPath(ThisWorkbook.Path, Selection.Value) & """"

    ' 「get_clipboard_image.exe 出力ファイル名」を実行
    Call objWsh.Run(cmd & " " & arg, 0, True)


ReleaseObjects:
    objWord.Quit
    Set objWord = Nothing
    Set objFso = Nothing
    Set objWsh = Nothing
End Sub

注1

xlsmと同じフォルダに「get_clipboard_image.exe」が置かれることを決めうちした実装になっています。

png出力先も「xlsmと同じフォルダ」の決めうちです。ファイル名だけ選択したセルからとってきています。

注2

' ターゲットアプリのタイトルバー文字列
Const APP_TITLE = "ターゲット"

を変更することでスクリーンショットを取るアプリを変更できます。

この記事ではHTAで作成したダミーアプリをターゲットにしています。

注3

簡単化のため, マクロ呼び出しごとに各種オブジェクトを生成+破棄してますが、 実際使うときはWorkBookを開いたときに生成してそれを使いまわす形がいいです。
※ 概要の動画gifのように特に最初のスクリーンショットが重くなる。

注4

objWord.Tasks(APP_TITLE)は部分一致で判定しているので, 似ているタイトルのアプリがいるとうまく動作しない可能性があります。objWord.Tasks(APP_TITLE)をFor Eachでまわして, フルネームを判定に使えばもう少しマシになると思います。

フルネーム
    Dim AppFullName
    AppFullName = objWord.Tasks(APP_TITLE).Name

C# - クリップボードの画像をpngファイル化するアプリ

コンソール画面が出ると気になるので「何もフォームを作らないフォームアプリ」を作成します。
プロジェクト作成時は「Windowsフォームアプリケーション」を指定し,

image.png

プロジェクト名を「get_clipboard_image」とします。
image.png

ソリューションエクスプローラでProgram.csを開きます。
image.png

これを以下のコードで置き換えます。

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;//追加
using System.Drawing.Imaging;//追加

namespace get_clipboard_image
{
    static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static int Main(string[] args)
        {
            //Application.EnableVisualStyles();
            //Application.SetCompatibleTextRenderingDefault(false);
            //Application.Run(new Form1());

            if(args.Length == 1)
            {
                var fname = args[0];

                var clip = Clipboard.GetDataObject();
                // 画像ファイルのみ取り込み
                var bmp = clip.GetData(typeof(Bitmap)) as Bitmap;
                if (bmp != null)
                {
                    bmp.Save(fname, ImageFormat.Png);
                    Clipboard.Clear();
                    return 0;
                }
                else
                {
                    MessageBox.Show("クリップボードに画像が見つかりませんでした。");
                    return 1;
                }
            }
            else
            {
                MessageBox.Show("使い方:get_clipboard_image.exe [ファイル名]");
                return 2;
            }
        }
    }
}

「Release」「Any CPU」でビルドすればget_clipboard_image.exeができます。
xlsmと同じフォルダにおいてください。

image.png

html - ターゲットアプリ

ダミーのターゲットアプリ。
なんでもよかったので古の技術HTA
テキストエディタに貼り付けて「target.hta」で保存すれば終わりです。

ダミーのターゲットアプリは、「<html><head><title>ターゲット</title></head></html>」の一行だけでもいいですし, 「ターゲット.txt」をメモ帳で開いても代替できます。

target.hta
<html>
<head>
<title>ターゲット</title>
</head>
<script type="text/javascript">
window.onload = function myCheck(){
    window.resizeTo(400,120);
    start=new Date();
    setInterval("DispTime()",1);
}

function DispTime(){    
    now = new Date();
    document.getElementById("display").innerText = now.getTime() - start.getTime()
}   
</script>
<body>
<font size=7><div id="display"></div></font>
</body>
</html>

参考

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

C# 文字列のANSI文字列→Unicode文字列変換時の注意

概要

ANSI文字列(ここでは純粋なASCIIエンコーディングの文字列ではなくシステム上でANSI版として扱われる文字列。日本語ではShift-JISエンコーディングの文字列)を格納したバイト配列byte[]をstring型へ変換する場合等、ANSI文字列を扱う場合はエンコーディング時のコードページに注意が必要となります。具体的にはANSI文字列をC#のstring型(Unicode)に変換する場合は次の処理が必要となります。

using System.Text;

var buffer = ...
var ansiCodePage = CultureInfo.CurrentCulture.TextInfo.ANSICodePage;
var s = Encoding.GetEncoding(ansiCodePage).GetString(buffer);

なお、P/Invokeではマーシャリングにより自動変換されるので気にする必要はありません。

詳細

ANSIコードページはシステムの言語設定により異なり、Windowsの日本語環境では基本的にShift-JISです。この設定はSystem.Text.CultureInfo.CurrentCulture.TextInfo.ANSICodePageにより取得することができます。

System.Text.EncodingにはASCIIEncodingが存在します。これはANSIエンコーディングの元となる純粋なASCIIエンコーディングであり、先頭の文字はAですがシステム上のANSIエンコーディングとは異なります。ひらがなや漢字等はACSIIエンコーディングに含まれないため、意図的にANSIエンコーディングを指定しないと文字化けが生じます。

また、日本語環境を決め打ちすればGetEncoding("Shift-JIS")やGetEncoding("SJIS")等を使用することもできますが、System.Text.CultureInfo.CurrentCulture.TextInfo.ANSICodePageを使用すれば各言語環境に対応することができます。

サンプルコード

次のサンプルコードはASCIIエンコーディングとShift-JISエンコーディングによる日本語の扱いの違いを示します。

using System;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        private static class NativeMethods
        {
            [DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
            public static extern void lstrcpynA(
                byte[] lpString1,
                string lpString2,
                int iMaxLength);
        }

        static void Main(string[] args)
        {
            var buffer = new byte[256];
            NativeMethods.lstrcpynA(buffer, "自分用", buffer.Length);

            // ASCIIエンコーディングとしてUnicodeへ変換
            Console.WriteLine(Encoding.ASCII.GetString(buffer)); // "?????p"

            // Shift-JISエンコーディングと決め打ちしてとしてUnicodeへ変換
            Console.WriteLine(Encoding.GetEncoding("SJIS").GetString(buffer)); // "自分用"

            // 現在の言語設定のANSIコードページを利用してUnicodeへ変換
            var ansiCodePage = CultureInfo.CurrentCulture.TextInfo.ANSICodePage;
            Console.WriteLine(Encoding.GetEncoding(ansiCodePage).GetString(buffer)); // "自分用"
        }
    }
}

注意する場合の例

  • PEファイルからCHAR型(C/C++)を読み込む場合
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# P/Invoke時の文字列型マーシャリングの考察

概要

文字列型の引数を伴うプラットフォーム呼び出し(P/Invoke)を実行するとき、引数の型によりマーシャリング動作が変わります。具体的にはC#側がstring/StringBuilder/byte[]型、プラットフォーム側(C/C++側)がLPWSTR/LPSTR(UnmanagedType.LPWStr/LPStr)の場合について次表の動作が得られます。LPTSTR(UnmanagedType.LPTStr)の場合はDllImport属性のCharSetで指定された値によりLPWSTRまたはLPSTRが選択されます。

この動作により、文字列型を渡すだけの場合はstring型、呼び出し側で処理された結果が必要な場合はstring型(LPWSTR変換限定)、StringBuilder型が適することが分かります。処理された結果が必要な場合はbyte[]型も使用できますが、自分でエンコーディングする必要があります。なお、各動作はIn、Out属性の追加により呼び出し前後の動作を無効化することができます。また、string型はBSTR変換(UnmanagedType.BStr)でもLPWSTR変換と同様に動作します。

C#側の型 プラットフォーム側の型 呼び出し時動作 C#側の変数内容
string LPWSTR 文字列部分のポインタを渡す。 一部書き換わる。
string LPSTR 新しいANSI文字列を作成して渡す。 書き換わらない。
StringBuilder LPWSTR 文字列部分のポインタを渡す。 一部書き換わる。
StringBuilder LPSTR 新しいANSI文字列を作成して渡し、呼び出し後のANSI文字列を元の文字列に上書きする。 全部書き換わる。
byte[] LPWSTR そのまま渡す。 一部書き換わる
byte[] LPSTR そのまま渡す。 一部書き換わる

実行結果からの考察なので、仕様の誤解などがあればご指摘いただけると嬉しいです。

サンプルコード

上記の動作は次のサンプルコードで確認することができます。なお、システムディレクトリのパス長として適当な数値256を使用しているため、パスがこれ以上の場合は正常なパスが取得されません。

using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        private static class NativeMethods
        {
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern uint GetSystemDirectoryW(
                string lpBuffer, uint uSize);
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
            public static extern uint GetSystemDirectoryA(
                [MarshalAs(UnmanagedType.LPWStr)]string lpBuffer, uint uSize);
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
            public static extern uint GetSystemDirectoryA(
                StringBuilder lpBuffer, uint uSize);
            // CharSetは無意味
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern uint GetSystemDirectoryA(
                byte[] lpBuffer, uint uSize);
        }

        static void Main(string[] args)
        {
            var buffer1 = new string('0', 256);
            var ret1 = NativeMethods.GetSystemDirectoryW(buffer1, (uint)buffer1.Length);
            // buffer1は「システムディレクトリのパス+'\0'+(0の繰り返し)」。

            var buffer2 = new string('0', 256);
            var ret2 = NativeMethods.GetSystemDirectoryA(buffer2, (uint)buffer2.Length);
            // buffer2は「('0'の繰り返し)」。

            var buffer3 = new StringBuilder(new string('0', 256));
            var ret3 = NativeMethods.GetSystemDirectoryA(buffer3, (uint)buffer3.Capacity);
            // buffer3は「システムディレクトリのパス+'\0'+(0の繰り返し)」。

            var buffer4 = new byte[256];
            var ret4 = NativeMethods.GetSystemDirectoryA(buffer4, (uint)buffer4.Length);
            var ansiCodePage = CultureInfo.CurrentCulture.TextInfo.ANSICodePage;
            var s4 = Encoding.GetEncoding(ansiCodePage).GetString(buffer4);
            // buffer4は「システムディレクトリのパス+'\0'+(0の繰り返し)」(ANSI文字列)
            // s4は「システムディレクトリのパス」
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UI.Button をスクリプトで複製した時に、onClick イベントも複製される時・されない時

スクリプトで複製したボタン
このボタンのコールバックを削除したい。リセットしたい。全消ししたい。
ときに、イベントも複製されるんだっけ...されないんだっけ...と思って。

  • 環境 2018.3.8f1

1. スクリプトで、AddListener() した場合。(非永続的)

onClick イベントは複製されない。引き継がれない

2. インスペクタ上から登録した場合。(永続的)

onClick イベントも複製される。引き継がれる


削除する。(削除はできないから呼ばれないようにする)

int persistentEventCount = btn.onClick.GetPersistentEventCount();
for (int i = 0; i < persistentEventCount; i++)
    btn.onClick.SetPersistentListenerState(i, UnityEventCallState.Off);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UI.Image にペイントする。線を手書きする。

描けた。
スクリーンショット

参考
Find x,y cordinates of click on UI.Image new gui system - Unity Answers
Unityでお絵描きしてみる
背景が透明なTexture2Dを作る
Enumerable.Repeat(TResult, Int32) Method (System.Linq) | Microsoft Docs
Sprite.Create - Unity スクリプトリファレンス


なんかきっとどこかで必要になったら使おう。

環境

  • バージョン 2018.3.8f1

Canvasは、 Screen Space – Overlay
Imageのピボットは、(0.5、0.5)
ImageのGameObjectにアタッチする。

using UnityEngine;
using System;
using System.Linq;
using UnityEngine.UI;
using UnityEngine.EventSystems;

/// <summary>
/// お絵描き
/// </summary>
public class Painter : MonoBehaviour
{
    Texture2D texture;
    Vector3 beforeMousePos;

    Color bgColor = Color.white;
    Color lineColor = Color.black;

    void Start()
    {
        var img = GetComponent<Image>();
        var rt = GetComponent<RectTransform>();
        var width = (int)rt.rect.width;
        var height = (int)rt.rect.height;
        texture = new Texture2D(width, height, TextureFormat.ARGB32, false);
        img.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);

        //背景が透明なTexture2Dを作る
        //http://d.hatena.ne.jp/shinriyo/20140520/p2
        Color32[] texColors = Enumerable.Repeat<Color32>(bgColor, width * height).ToArray();
        texture.SetPixels32(texColors);
        texture.Apply();
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            beforeMousePos = GetPosition();
        }
        else if (Input.GetMouseButton(0))
        {
            Vector3 v = GetPosition();
            LineTo(beforeMousePos, v, lineColor);
            beforeMousePos = v;
            texture.Apply();
        }
    }

    /// <summary>
    /// UIのクリック座標のx、y座標を求める - 新しいguiシステムの画像 - Unity Answers
    /// https://answers.unity.com/questions/892333/find-xy-cordinates-of-click-on-uiimage-new-gui-sys.html
    /// </summary>
    public Vector3 GetPosition()
    {
        var dat = new PointerEventData(EventSystem.current);
        dat.position = Input.mousePosition;

        var rect1 = GetComponent<RectTransform>();
        var pos1 = dat.position;
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rect1, pos1,
            null, out Vector2 localCursor))
            return localCursor;

        int xpos = (int)(localCursor.x);
        int ypos = (int)(localCursor.y);

        if (xpos < 0) xpos = xpos + (int)rect1.rect.width / 2;
        else xpos += (int)rect1.rect.width / 2;

        if (ypos > 0) ypos = ypos + (int)rect1.rect.height / 2;
        else ypos += (int)rect1.rect.height / 2;

        Debug.Log("Correct Cursor Pos: " + xpos + " " + ypos);
        return new Vector3(xpos, ypos, 0);
    }

    /// <summary>
    /// Unityでお絵描きしてみる
    /// http://tech.gmo-media.jp/post/56101930112/draw-a-picture-with-unity
    /// </summary>
    public void LineTo(Vector3 start, Vector3 end, Color color)
    {
        float x = start.x, y = start.y;
        // color of pixels
        Color[] wcolor = { color };

        if (Mathf.Abs(start.x - end.x) > Mathf.Abs(start.y - end.y))
        {
            float dy = Math.Abs(end.x - start.x) < float.Epsilon ? 0 : (end.y - start.y) / (end.x - start.x);
            float dx = start.x < end.x ? 1 : -1;
            //draw line loop
            while (x > 0 && x < texture.width && y > 0 && y < texture.height)
            {
                try
                {
                    texture.SetPixels((int)x, (int)y, 1, 1, wcolor);
                    x += dx;
                    y += dx * dy;
                    if (start.x < end.x && x > end.x ||
                        start.x > end.x && x < end.x)
                    {
                        break;
                    }
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                    break;
                }
            }
        }
        else if (Mathf.Abs(start.x - end.x) < Mathf.Abs(start.y - end.y))
        {
            float dx = Math.Abs(start.y - end.y) < float.Epsilon ? 0 : (end.x - start.x) / (end.y - start.y);
            float dy = start.y < end.y ? 1 : -1;
            while (x > 0 && x < texture.width && y > 0 && y < texture.height)
            {
                try
                {
                    texture.SetPixels((int)x, (int)y, 1, 1, wcolor);
                    x += dx * dy;
                    y += dy;
                    if (start.y < end.y && y > end.y ||
                        start.y > end.y && y < end.y)
                    {
                        break;
                    }
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                    break;
                }
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分がコントリビュートしているリポジトリで、参照しているライブラリが古い場合の対処

C# で貢献しているプロジェクトの新しいバージョンを引っ張ってくると、どうやらバージョンが古い。Issue とか上げてみたんだけど、キャッシュをクリアしてと言われた。

あぁ、、、またやってしまった。こんなもんに時間をかけてアホやなと思うので、記録しておく。余裕で数時間ふっとんだ。

https://github.com/Azure/azure-functions-durable-extension/issues/667

nuget のクリアの方法

さて、nuget のキャッシュのクリアだが、次のリファレンスがある。

global-packages

%userprofile%\.nuget\packages

おなじみの場所で、全てのNugetのパッケージがダウンロードされる場所。ディレクトリができているので消せばよい。今回はこれでもダメだった。

http-cache

%localappdata%\NuGet\v3-cache
NUGET_HTTP_CACHE_PATH 環境変数が設定されている場合はそちらを使う。

Visual Studio Package Manager と、dotnet ツールが使う予定のところ。ここのキャッシュは30分らしいが今回はここもglobal-packages に加えてこちらもクリアしないと有効にならなかった。

temp

%temp%\NugetScratch

NuGet がオペレーションの一時ファイルを保存するところ

plugins-cache

%localappdata%\NuGet\plugins-cache
NUGET_PLUGINS_CACHE_PATH 環境変数が設定されている場合はそちら

NuGetが、オペレーション要求の結果を保存する場所

確認のプロセス

Method Not Found など、古いバージョンに起因する問題が起きたら、キャッシュを疑ったほうがよさそう。自分はいつもドタバタしてて、ひとよりアホみたいに時間使うことがおおいんだけど、自分にメソッドを適用したらましになるかな?

  • VS のクリアと再ビルド
  • 上記の nuget のキャッシュのクリア
  • テストの実施

これを最低限やってみるようにしよう。

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