Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all 9366 articles
Browse latest View live

MVCの基礎の"き"

$
0
0

MVCとは?

MVC(Model View Controller モデル・ビュー・コントローラ)は、ユーザーインタフェースをもつアプリケーションソフトウェアを実装するためのデザインパターンである。
アプリケーションソフトウェアの内部データを、ユーザーが直接参照・編集する情報から分離する。そのためにアプリケーションソフトウェアを以下の3つの部分に分割する。
(wikipeiaより)

MVCそれぞれのざっくりとした役割

M:Model

■そのアプリケーションが扱う領域のデータと手続き
■SQLとのやりとりなど実際の処理
■編集マン・技術(TV業界でいうところの)

V:View

■モデルのデータを取り出してユーザが見るのに適した形で表示する。
■画面に表示される顔。HTMLやJavaScriptを記述する。
■営業or代理店(TV業界でいうところの)

C:Controller

■ユーザからの入力をモデルへのメッセージへと変換してモデルに伝える
■Viewから入力された値をModelに渡すなどViewとModelの橋渡し的な役割。
■ディレクター(TV業界でいうところの)
1920px-MVC-Process.svg.png
(画像:wikipediaより)

MVCのメリット・デメリット

メリット

機能が分割化されているため、改修・変更を加えやすい。
また、改修・変更を加えても影響範囲が広くならない、または分かりやすい。
処理の流れが煩雑化しにくい。

デメリット

流れを理解するまでに時間がかかる。


[Unity] partialを使ってコンフィグを分けてみる

$
0
0

※あくまで個人的にこう使うと便利かもレベルです。
(partialって何?ってのはここでは省きます。簡単に言えばクラスの定義を複数ファイルにまたいで行うことができるもの。)

ゲーム内のコンフィグをまとめてみる

Unityでゲームを作る上でファイルのパスやステータスなどの定数、PlayerPrefsのkeyなどの定数を定める静的クラスを作ると思います。
例えば、ファイルのパス、敵のステータス、PlayerPrefsのKeyをまとめたConfigクラスを作ったとします。

Config.cs

Config.cs
public static class Config
{
    public static class Path
    {
        public const string Prefabs = "Prefabs/";
        public const string BGM = "Audio/BGM/";
        public const string SE = "Audio/SE/";
    }

    public static class Enemy
    {
        public const int HP = 10;
        public const int Power = 5;
        public const float Speed = 2.5f;
    }

    public static class Key
    {
        public const string SaveData = "SaveData";
    }
}

現状はまだ内部に3つのクラスしかないのでまだゴチャゴチャしていないのですが、これが制作が進むにつれてプレイヤーのステータスであったり、タグの名前、ファイルの名前などのクラスをどんどん追加していくと可読性も下がりますし何よりどれがどこにあるのかが分からなくなってきます。
そんな時にpartialを使ってファイルを分けます。
例として今回は上のConfigクラスをpartialクラスにして大きく2つのファイルに分けてみます。
大まかな分けかたの基準として今回はアプリの設定(Path、Key)ゲームの設定(Enemy)として、新たにAppSettingGameSettingのファイルを作り、分けてみます。

AppSetting.cs

AppSetting.cs
public static partial class Config
{
    public static class Path
    {
        public const string Prefabs = "Prefabs/";
        public const string BGM = "Audio/BGM/";
        public const string SE = "Audio/SE/";
    }

    public static class Key
    {
        public const string SaveData = "SaveData";
    }
}

GameSetting.cs

GameSetting.cs
public static partial class Config
{
     public static class Enemy
    {
        public const int HP = 10;
        public const int Power = 5;
        public const float Speed = 2.5f;
    }
}

※ファイル名とクラス名が一致しませんがstaticかつpartialクラスであるため問題ありません。
こうすることで同じクラスに属してかつファイルを分けることができます。

[WPF/xaml] xaml+C#で当番決めのためのルーレットを作る

$
0
0

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

週一回のミーティングの司会役を決めるためにルーレットを使っているが、なんとなくxamlでもできそうな気がしたので試しに作ってみたい。

イメージはシンプルにこんな感じ。
image.png

やり方

下記のようにしてやる。
キーワードは「StoryBoard」。

  • [xaml]ルーレットの円を作成。
  • [C#]人数分の名前を、stringのListで持つ(以降、人数はListの件数で判断)
  • [C#]人数分の名前を、円の中に書く。
  • [C#]人数分の区切りの線を引く。
  • [C#]名前と区切り線を、一人分の角度*List番号分回転させて表示する。
  • [xaml]に、ルーレット(円の中身全体)を永遠に回し続けるStoryBoardを作成する。
  • [C#]ボタンをおしたら、ルーレットを回すStoryBoardをスタートする。

※一人分の角度は360/人数

コード

xaml

MainWindow.xaml
<Window x:Class="WpfApp38.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp38"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="600"
        Loaded="Window_Loaded"
        Name="Root">

    <Window.Resources>
        <!-- ルーレットのアニメーション定義 -->
        <Storyboard  x:Key="StartRoulettea">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="RouletteMain" Storyboard.TargetProperty="(Grid.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)"
                                           RepeatBehavior="Forever" >
                <LinearDoubleKeyFrame KeyTime="00:00:00.0" Value="0" />
                <LinearDoubleKeyFrame KeyTime="00:00:00.5" Value="360" />
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>

    <Grid>
        <Viewbox Margin="20">
            <Grid Name="RouletteMain" RenderTransformOrigin="0.5,0.5">
                <Grid.RenderTransform>
                    <TransformGroup>
                        <!-- 回転の角度 -->
                        <RotateTransform Angle="0"/>
                    </TransformGroup>
                </Grid.RenderTransform>

                <!-- ルーレットの外側の円 -->
                <Ellipse Name="RouletteEllipse" Stroke="Black" StrokeThickness="5" Width="500" Height="500" >
                    <Ellipse.Fill>    
                        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">    
                            <GradientStop Color="Red" Offset="0.0" />    
                            <GradientStop Color="Orange" Offset="0.2" />    
                            <GradientStop Color="Yellow" Offset="0.4" />    
                            <GradientStop Color="LimeGreen" Offset="0.6" />    
                            <GradientStop Color="Blue" Offset="0.8" />    
                            <GradientStop Color="Violet" Offset="1.0" />    
                        </LinearGradientBrush>    
                    </Ellipse.Fill>
                </Ellipse>
            </Grid>

        </Viewbox>

        <!-- 上の線 -->
        <Rectangle Stroke="Black" StrokeThickness="10" Height="50" Width="5" VerticalAlignment="Top"/>

        <!-- スタートボタン -->
        <Button Name="StartButton" Width="100" Height="50" Content="スタート" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="10" Click="Button_Click"/>
    </Grid>
</Window>

コードビハインド(cs)

MainWindow.xaml.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace WpfApp38
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// ルーレット回転中かどうか
        /// </summary>
        bool IsRounding = false;

        /// <summary>
        /// メンバー一覧
        /// ここにメンバー名をAddしたら、
        /// Window_Loaded()の中で枠を自動でつくる
        /// </summary>
        List<string> Members = new List<string>();

        /// <summary>
        /// コンストラクタ
        /// ここでメンバー登録をする
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            Members.Add("Aさん");
            Members.Add("Bさん");
            Members.Add("Cさん");
            Members.Add("Dさん");
            Members.Add("Eさん");
            Members.Add("Fさん");
            Members.Add("Gさん");
            Members.Add("Hさん");
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // 一人あたりの使用する角度を決める
            int anglePerOne = 360 / Members.Count;

            // 人数分の線を引き、名前のテキストを作成する
            for (int i = 0; i < Members.Count; i++)
            {
                ////////////////////////
                // 人数分の区切り線を引く
                ////////////////////////
                var tfgLine = new TransformGroup();
                tfgLine.Children.Add(new RotateTransform(i * anglePerOne));

                var line = new Line()
                {
                    X1 = 0,
                    Y1 = 0,
                    X2 = 0,
                    Y2 = RouletteEllipse.Width / 2,
                    StrokeThickness = 5,
                    Stroke = Brushes.Red,
                    Fill = Brushes.Transparent,
                    VerticalAlignment = VerticalAlignment.Top,
                    HorizontalAlignment = HorizontalAlignment.Center,
                    RenderTransformOrigin = new Point(0, 1.0),
                    RenderTransform = tfgLine
                };

                RouletteMain.Children.Add(line);

                ////////////////////////
                // 人数分の名前を書く
                ////////////////////////
                int textHeight = 30;
                var tfgText = new TransformGroup();
                tfgText.Children.Add(new RotateTransform(-90 + (anglePerOne / 2) + (i * anglePerOne)));

                var text = new TextBlock()
                {
                    Text = Members[i],
                    Width = RouletteEllipse.Width / 2,                  // ルーレットの円の半分
                    VerticalAlignment = VerticalAlignment.Center,
                    HorizontalAlignment = HorizontalAlignment.Right,
                    TextAlignment = TextAlignment.Center,
                    FontSize = textHeight,
                    RenderTransformOrigin = new Point(0, 0.5),
                    RenderTransform = tfgText
                };

                RouletteMain.Children.Add(text);
            }
        }

        /// <summary>
        /// ルーレットのスタート/ストップ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // ルーレットを回すためのStoryBoardを検索
            var sb = FindResource("StartRoulettea") as Storyboard;

            if (IsRounding == false)
            {
                // 回転開始(スタート)
                sb.Begin();

                StartButton.Content = "ストップ";
            }
            else
            {
                // 回転停止(ストップ)
                sb.Pause();

                StartButton.Content = "スタート";
            }

            IsRounding = !IsRounding;
        }
    }
}

できあがり

image.png

備考

背景のグラデーションあると見た目がさみしくないが、超目押し可能となるので問題。

コード

https://github.com/tera1707/WPF-/tree/master/031_XamlRoulette

参考

++C++
https://ufcpp.net/study/dotnet/wpf_xamlani.html

自分のページ
[WPF/xaml]Storyboardでアニメーションをつくる
https://qiita.com/tera1707/items/a7fcdd95fc3120ae3c8b

リソースの音声データを再生する

$
0
0

音声データを再生したいが、waveファイルやmp3ファイルを別ファイルで配布しないでリソースで同梱して扱いたい。というのを叶える方法を調べてみた。

  • 扱う音声データはwavとmp3(NAudioが扱えるものなら何でも良いはず)
  • wavはPlaySoundを使用し、mp3はNAudioを使用した

Visual Studio 2019を使っているが、昔のバージョンでもいけるはず。またusing変数宣言はC# 8.0の記法なので適宜置き換えて読んでほしい。
また、エラー処理や例外処理は端折っているので、適宜補うべし。

ソリューション構成

C#のプロジェクトを作成し、あらかじめ音声データファイルをリソースに登録しておく。
wavの場合、リソースの種別は「オーディオ」となる。
mp3の場合、リソースの種別は「ファイル」となる。
これらは勝手に設定されて、変更できなさそう。(調査不足)

ソリューション
    |- SampleProj   ※C#プロジェクト名
        |- Properties
            |- aiueo.wav ※音声データ1
            |- dindon.mp3 ※音声データ2
            |- Resources.resx
                |- Resources.Designer.cs
...

登録したリソースは、次のように自動的に実装が作られる。リソースの名前や定義に、拡張子は含まれないようだ。
これらのリソース定義を使用しても良いし、自分でResourceManagerを使って取得しても良い。リソース定義を使った場合は、wav(オーディオ)はstreamで、mp3(ファイル)はbyte[]で取得できる。
以下では、リソースをstring等で選ぶことを考えて、自分でResourceManagerを使って取得する方法で記述している。

Resources.Designer.cs
namespace SampleProj.Properties {
    // ...省略...
    internal static System.IO.UnmanagedMemoryStream aiueo {
        get {
            return ResourceManager.GetStream("aiueo", resourceCulture);
        }
    }

    internal static byte[] dindon {
        get {
            object obj = ResourceManager.GetObject("dindon", resourceCulture);
            return ((byte[])(obj));
        }
    }
}

PlaySoundによるwavの再生

winmm.dllからPlaySoundをインポートする。
引数で渡す予定のFlagも、別途enumで定義しておく。

[DllImport("winmm.dll", CharSet = CharSet.Auto)]
private static extern bool PlaySound(byte[] sound, IntPtr hMod, PlaySoundFlags flags);

[System.Flags]
public enum PlaySoundFlags : int
{
    SND_MEMORY = 0x0004,  // メモリの再生の意
}

再生はPlaySoundにbyte配列を渡すことで行える。
オーディオリソースはstreamなので、いったん別のbyte配列に読み込む。

using var target = SampleProj.Properties.Resources.ResourceManager.GetStream("aiueo");
byte[] buffer = new byte[target.Length];
target.Read(buffer, 0, buffer.Length);
target.Close();
// 再生
_ = PlaySound(buffer, IntPtr.Zero, PlaySoundFlags.SND_MEMORY);

停止も同じAPIで、第一引数をnullに、第三引数を0にする。これ以上のPlaySoundの利用方法は省略。

// 停止
_ = PlaySound(null, IntPtr.Zero, 0);

NAudioによるmp3の再生

NAudioは次で配布されているMs-PLライセンスの、.NET向け音声再生APIである。dll一つで扱える簡単さでありながら様々な音声形式に対応する。wav形式も対応しているので、PlaySoundではなくNAudioを使うこともできる。
https://github.com/naudio/NAudio

再生は、NAudioのAPIであるMp3FileReaderMemoryStreamを渡すことで行う。
再生状態はWaveOut.PlaybackStateで判別できる。これをnowaitでポーリングすると音声再生が乱れたのでwaitを入れている。多分ちゃんと調べる方法はある。(調査不足)

// WaveOutのインスタンスはクラスフィールドで持つなりする。
var player = new NAudio.Wave.WaveOut();  // 要Dispose

// リソースからデータの取得
byte[] buffer = (byte[])Properties.Resources.ResourceManager.GetObject("dindon");
using var stream = new MemoryStream(buffer)
{
    Position = 0  // 先頭位置からの再生を意味するが、streamを新規にnewするときは不要なはず
};
using WaveStream pcm = new Mp3FileReader(stream);
player.Init(pcm);
// 再生
player.Play();

// 待ち(ここ調査不足)
while (player.PlaybackState == PlaybackState.Playing)
{
    await Task.Delay(10);
}

停止はWaveOut.Stop()で行える。

// 停止
player.Stop();

まとめ

exe単体ないしはdllの同梱のみで、音声データを再生する方法を調べた。
とにかく再生できれば良かったので、再生中かどうかを考慮したり音声デバイスを選んだりなどは本記事では省略している。

  • リソースで埋め込んだwave形式とmp3形式のデータを再生する
  • PlaySoundとNAudioの2つの方法で再生する

[SerializeField]属性をプリプロセッサ(#if〜#endif)で括ったらどうなるのか

$
0
0

特に理由のない意地悪がUnity2019.2.2を襲う――!!

using UnityEngine;

public class IfDefIjiwaru : MonoBehaviour
{
#if PLATFORM_STANDALONE
    [SerializeField]
    private string Ijiwaru;
#endif
}

実験

まずは対象のプラットフォームで適当に値を入れてみます。
スクリーンショット 2019-11-01 0.16.21.png
そしておもむろに対象プラットフォーム切り替え。
スクリーンショット 2019-11-01 0.17.11.png
フィールドが消えました。
スクリーンショット 2019-11-01 0.16.48.png
元のプラットフォームに戻すと、入力したはずの値が消えています。
スクリーンショット 2019-11-01 0.21.44.png
ここで諦めてはいけません。
UnityのSceneファイルはYAMLなので、Sublime Textなどのテキストエディタで開けます。
見れば値そのものは残っている様子。
スクリーンショット 2019-11-01 0.23.00.png
ということで、シーンを一度閉じて読み込み直すとこのように値が復帰しました。
スクリーンショット 2019-11-01 0.16.21.png

まとめ

そうなんだ そういうことも あるんだね

【Unity】uGUIのドロップダウンメニューをスクリプトから作る

$
0
0

myGIf.gif

Main.cs
static Dropdown GenDropdown(RectTransform parent)
    {
        Dropdown dropdown = new GameObject("dropdown").AddComponent<Dropdown>();
        RectTransform dropdownRC = dropdown.GetComponent<RectTransform>();
        dropdownRC.SetParent(parent);
        Util.SetAnchorPointZero(dropdownRC);

        dropdown.captionText = Util.AddTextRight(dropdownRC); // 選択中のテキストが選択される     
        {
            // dropdown.targetGraphicを設定してない場合、dropdown.captionTextの領域を開けばドロップダウンメニューが開く
            dropdown.targetGraphic = dropdown.gameObject.AddComponent<Image>(); // ドロップダウンメニューを開くための画像領域
            dropdown.targetGraphic.color = Color.green;
        }        

        RectTransform template = new GameObject("template").AddComponent<RectTransform>(); // 一覧表示される際のひな型(テンプレート)
        template.gameObject.SetActive(false);
        template.SetParent(dropdownRC);
        Util.SetAnchorPointZero(template);
        Toggle tog = Util.GenToggle(template, dropdownRC.sizeDelta.x);
        RectTransform togRC = tog.GetComponent<RectTransform>();
        togRC.localPosition = new Vector2(0, -dropdownRC.sizeDelta.y);
        Text text = Util.AddTextRight(togRC); // 一覧の表示されるテキスト

        dropdown.template = template;
        dropdown.itemText = text;

        return dropdown;
    }

    void Test()
    {
        Dropdown dropdown = GenDropdown(GameObject.Find("Canvas").GetComponent<RectTransform>());
        dropdown.options.Add(new Dropdown.OptionData { text = "AAA" });
        dropdown.options.Add(new Dropdown.OptionData { text = "BBB" });
        dropdown.options.Add(new Dropdown.OptionData { text = "CCC" });
        dropdown.onValueChanged.AddListener((data) => MyOnValueChanged(data));
    }

    static public void MyOnValueChanged(int listNum)
    {
        Debug.Log(listNum); // BBBを選択した場合、1が返される。dropdown.optionsのリストのインデックスが返される。
    }

    void Start()
    {
        Test();
    }
Util.cs
static public RectTransform SetAnchorPointZero(RectTransform rect)
    {
        // アンカーポイントを左上に合わせ、ローカル座標をリセットする。
        rect.anchorMin = Vector2.up;
        rect.anchorMax = Vector2.up;
        rect.pivot = Vector2.up;
        rect.localPosition = Vector3.zero;
        rect.rotation = Quaternion.identity;
        rect.localScale = Vector3.one;
        return rect;
    }

    static public Toggle GenToggle(RectTransform parent, float checkBoxSize = 50)
    {
        Toggle tog = new GameObject("togle").AddComponent<Toggle>();
        tog.isOn = true;
        Image background = new GameObject("background").AddComponent<Image>();
        Image checkmark = new GameObject("checkmark").AddComponent<Image>();

        RectTransform togRC = tog.GetComponent<RectTransform>();
        checkmark.rectTransform.SetParent(background.rectTransform);
        background.rectTransform.SetParent(togRC);

        togRC.sizeDelta = Vector2.one * checkBoxSize;
        background.rectTransform.sizeDelta = Vector2.one * checkBoxSize;
        checkmark.rectTransform.sizeDelta = Vector2.one * checkBoxSize * 0.8f;
        checkmark.color = Color.red;

        tog.graphic = checkmark;
        tog.targetGraphic = background;

        togRC.SetParent(parent);
        SetAnchorPointZero(togRC);

        return tog;
    }

    // あるRectTransformの右側に文字を付け加える
    static public Text AddTextRight(RectTransform rc, float offsetX_scale = 1.2f)
    {        
        Text text = new GameObject("text").AddComponent<Text>();
        text.font = Resources.Load<Font>("Font/mplus-1c-black");
        if (text.font == null)
            throw new Exception("フォントが見つかりません!");        
        text.fontSize = (int)(rc.sizeDelta.y * 0.7f); // 0.7倍までなら文字が描画される。
        text.horizontalOverflow = HorizontalWrapMode.Overflow;
        text.rectTransform.sizeDelta = new Vector2(100, rc.sizeDelta.y);
        text.rectTransform.SetParent(rc);
        SetAnchorPointZero(text.rectTransform).localPosition = new Vector2(rc.sizeDelta.x * offsetX_scale, 0);
        return text;
    }

【C#】Genericの型を値型に限定したい

$
0
0

はじめに

C#のGenericは非常によく利用されます。
このGenericはwhereによって指定できる型をある程度制限できますが、こと値型のみに限定するということができません。

ここで言う値型とは

  • byte
  • sbyte
  • short
  • ushort
  • int
  • uint
  • long
  • ulong
  • float
  • double
  • decimal

を指します。

ただし、これらの型が共通で継承しているinterfaceをwhereで指定することでそこそこに限定できるようです。

確認環境

Unity 2019.2.0f1
IL2CPP
Android

実装

実装としてはこれだけです。
例はメソッドですがクラスでも一緒です。

void Hoge<T>() where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
}

これらのinterfaceを全て実装している独自のstructなどがあればGenericに指定できてしまいますが、ほぼほぼ無いと思うのでこれで大丈夫でしょう。
実際に最初に記述した値型以外の型を指定するとコンパイルエラーになります。

参考

https://stackoverflow.com/questions/805264/how-to-define-generic-type-limit-to-primitive-types

注意

全ての型のリファレンスを確認したわけではないのでもしかしたらこれらの型以外にも一致するものがあるかもしれません。

おまけ

この制約を利用して指定した型で合計と平均を集計するクラスを実装してみました。

IL2CPPでの実装

計算を行う必要がありますが、Tのままでは計算ができないため、計算時は全てdecimalに変換しています。

AggregateIL2CPP.cs
namespace MyEngine
{
    using System;

    /// <summary>
    /// データ集計管理クラス
    /// </summary>
    public class Aggregate<T> where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        #region Properties

        /// <summary> 合計値 </summary>
        public T Sum { get; private set; }

        /// <summary> 平均値 </summary>
        public T Average { get; private set; }

        /// <summary> 集計データのサンプル数 </summary>
        public int SampleNum { get; private set; }

        #endregion


        #region Constructor

        public Aggregate()
        {
            Clear();
        }

        #endregion


        #region Methods

        private T ConvertValue(object data)
        {
            return (T)Convert.ChangeType(data, typeof(T));
        }

        #endregion


        #region API

        /// <summary>
        /// 集計データを初期化する
        /// </summary>
        public void Clear()
        {
            SampleNum = 0;
            Sum = ConvertValue(0);
            Average = ConvertValue(0);
        }

        /// <summary>
        /// 集計データを追加
        /// </summary>
        /// <param name="data"></param>
        public void Add(T data)
        {
            SampleNum++;
            Sum = ConvertValue(Sum.ToDecimal(null) + data.ToDecimal(null));
            Average = ConvertValue(Sum.ToDecimal(null) / SampleNum);
        }

        #endregion
    }
}

Monoでの実装

Monoの場合はConvertをdynamicに置き換えることができます。

AggregateMono.cs
namespace MyEngine
{
    using System;

    /// <summary>
    /// データ集計管理クラス
    /// </summary>
    public class Aggregate<T> where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        #region Properties

        /// <summary> 合計値 </summary>
        public T Sum { get; private set; }

        /// <summary> 平均値 </summary>
        public T Average { get; private set; }

        /// <summary> 集計データのサンプル数 </summary>
        public int SampleNum { get; private set; }

        #endregion


        #region Constructor

        public Aggregate()
        {
            Clear();
        }

        #endregion


        #region API

        /// <summary>
        /// 集計データを初期化する
        /// </summary>
        public void Clear()
        {
            SampleNum = 0;
            Sum = (dynamic)0;
            Average = (dynamic)0;
        }

        /// <summary>
        /// 集計データを追加
        /// </summary>
        /// <param name="data"></param>
        public void Add(T data)
        {
            SampleNum++;
            Sum += (dynamic)data;
            Average = (dynamic)Sum / SampleNum;
        }

        #endregion

    }
}

使用方法

適当なオブジェクトにアタッチしたら実行できます。
Aggregateのintを他の型に変えるとその型で集計するようになります。
試しにfloatにすると少数まで集計します。

PCの場合
 左クリック : 一回のみ集計
 右クリック : 毎フレーム集計

Android実機の場合
 1本指でタッチ : 一回集計
 2本指でタッチ : 毎フレーム集計
 3本指でタッチ : 集計クリア

AggregateTest.cs
using UnityEngine;
using MyEngine;

using Random = System.Random;

public class AggregateTest : MonoBehaviour
{
    Aggregate<int> _aggregate = new Aggregate<int>();

    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        bool isUpdate = false;

#if UNITY_EDITOR
        if (Input.GetMouseButtonDown(0) || Input.GetMouseButton(1))
        {
            isUpdate = true;
        }
#else

        if (Input.touchCount == 1 && Input.touches[0].phase == TouchPhase.Began)
        {
            isUpdate = true;
        }
        else if (Input.touchCount == 2)
        {
            isUpdate = true;
        }
        else if (Input.touchCount == 3)
        {
            _aggregate.Clear();
        }
#endif

        if (isUpdate)
        {
            Random rand = new Random();
            _aggregate.Add((int)(rand.NextDouble() * 100));
        }
    }

    private void OnGUI()
    {
        GUILayout.Label($"合計={_aggregate.Sum:N}\n平均={_aggregate.Average:N}\nサンプル数={_aggregate.SampleNum:N}");
    }
}

C# よく使うusing 名前空間 / (Windows)Form テンプレ作った (Visual Studio使わない人向け)

$
0
0

自分用メモ

テキトーなサンプルコードを作るときに重宝するはず。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

class XXXX : Form
{
    XXXX()
    {
        //Text = ;
        //ClientSize = new Size(,);
        //Controls.Add(xx);
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new XXXX());
    }
}

各名前空間の主な用途

面倒くさいのでコード上にコメントで記載。
正直、用途を忘れたものがある

using System;
using System.Collections;
using System.Collections.Generic;     // ジェネリック: List<xxx>, Dictionary<xxx>, ...
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;                 // Size, Point
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;                      // File
using System.Reflection;
using System.Runtime.InteropServices; // アンマネージコード(Win32API, COMなど)との連携: Marshal
using System.Text;                    // 文字コード処理: Encoding
using System.Text.RegularExpressions; // 正規表現:  Regex, Match
using System.Threading;               // スレッド: Thread
using System.Threading.Tasks;         // タスク:  Task
using System.Windows.Forms;           // フォーム: Form, Button, TextBox, ...

usingディレクティブを使うことのメリット・デメリット

メリット
・短く書ける
 (コードが見やすい。)

デメリット
・どの名前空間のクラスを使っているのかが読み取りづらくなる。
 (意図しないクラスを使ってしまったり、他人に分かりづらいかもしれない。)
コンパイルが多少遅くなるかもしれない(未確認だが、無視できるレベルのはず)

追記:Visual Studio Code (VSCode) スニペット追加

コメント頂いたので、スニペットを作ってみました。(初作成)
usingallとタイプすると出ます(下記のprefixのところで変更できます)。スニペット便利!

スニペットの追加(自作)の仕方:
https://webbibouroku.com/Blog/Article/vscode-snippets

さらに追記:usingだけ入れたい場合に邪魔しないように、ちょっと修正しました。

csharp.json

{
    // Place your snippets for csharp here. Each snippet is defined under a snippet name and has a prefix, body and 
    // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
    // same ids are connected.
    // Example:
    "using directive": {
        "prefix": "using",
        "body": [
            "using "
        ],
        "description": "using directive"
    },
    "CSharp Template": {
        "prefix": "usingall",
        "body": [
            "using System;",
            "using System.Collections;",
            "using System.Collections.Generic;",
            "using System.ComponentModel;",
            "using System.Data;",
            "using System.Diagnostics;",
            "using System.Drawing;",
            "using System.Drawing.Drawing2D;",
            "using System.Drawing.Imaging;",
            "using System.IO;",
            "using System.Reflection;",
            "using System.Runtime.InteropServices;",
            "using System.Text;",
            "using System.Text.RegularExpressions;",
            "using System.Threading;",
            "using System.Threading.Tasks;",
            "using System.Windows.Forms;",
            "",
            "class XXXX : Form",
            "{",
            "    XXXX()",
            "    {",
            "        //Text = ;",
            "        //ClientSize = new Size(,);",
            "        //Controls.Add(xx);",
            "    }",
            "    ",
            "    [STAThread]",
            "    static void Main(string[] args)",
            "    {",
            "        Application.Run(new XXXX());",
            "    }",
            "}",
            ""
        ],
        "description": "Insert the template code"
    }
}


C#初心者の自分用メモ:最初に出てくるものの解読

$
0
0

Unity C#にでてくる最初の構文を解読しました

以下、サンプルコード。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class *** : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
    }
}

こちらのコードを部分的に解読しました。

(注意)自分用のメモのため分かりやすさは追求していないです!!

using System.Collections; / using System.Collections.Generic; / using UnityEngine; について

「System.Collections」「System.Collections.Generic」「UnityEngine」は『名前空間』という。

名前空間とはクラスを種類ごとに整理する仕組みのこと。

ざっくり言うと、いろいろな機能があるお道具箱で、「System.Collections」「System.Collections.Generic」「UnityEngine」というお道具箱を使う(using)という意味の一文。

public class *** : MonoBehaviour について

public

他のクラスからどこからでも見えるようにする」という意味の予約語。
予約語とは「その言語内ですでに役割があるからオリジナルでは定義できない」という約束があるもの。
これが無いと、Unityエディタで使えなかったり、他のクラスから中身が見えない状態になってしまう。
逆に、隠すことを明示する場合はprivateを使う。

class ***

処理するデータとそれを処理する命令部分(メソッド)をひとつにまとめ(クラス)にするもの。
classに続く***が『クラス名』となり、このクラスを呼び出すことができる。
注意点としてUnityのルールでひとつの.csファイルで、ファイル名と同じクラスをひとつだけ宣言すると定めてあるので、それを守らないとエラーになる。

: MonoBehaviour

この後にあるクラスから派生していることを示す。
つまりクラス***は次の「MonoBehabiour」と言うクラスから派生しているよ、という意味。
MonoBehaviourは、Unityで作られるゲーム中で出てくる物体などの動作のきっかけ(イベント)で動かす処理のかたまり(メソッド)等をつなぐ役割をする。
Unityで扱うオブジェクトは、このMonoBehaviourから自動的に派生するようになっている。

void Start()とVoid Update() について

void Start()

スクリプト起動直後に1度だけ実行したい処理を書く。

void Update()

アニメーションなど「動かし続けたい」ものや、「ずっと処理し続けたい」ことを書く。
ゲームはフレーム単位ごとに更新され、その更新ごとにUpdate関数内の処理を繰り返してくれる。

WindowsでNFCタグを読み取る

$
0
0

【目的】

Windows端末で社員証などのNFCタグからタグの固有ID(IDm)を取得して、アプリケーションで利用します。
先に公開した「Visual Studio 2019 によるExcelアドインの作成」「Visual Studio 2019 によるExcelアドインの作成 - VBAからアドイン内のメソッドを呼び出す」と併用することで、ExcelからAddIn経由でNFC情報を扱うことも可能になります。

【使用機器】

機器 備考
パソコン Windows 10 Pro
NFCリーダ Sony PaSoRi RC-S380/S
開発環境 VisualStudio 2019/2017

【各種ライブラリなど】

Windows環境で利用できるNFC開発環境を以下にリストアップします。
比較的容易に実装できて、無償で利用できるPSCS(またはPCSC-sharp)の利用をお勧めします。

Personal Computer/Smart Card (winscard.dll)

Windows7以降ではOSに標準で添付 (無償)
DLLImportすれば.Net開発環境(C#、VB)から利用可能 (← 以下にサンプルコードあり)

【winscard.h header - MSDN】

【Advanced Card Systems Ltd.の仕様書】

PCSC-sharp

上記のPCSC(winscard.dll)を.Net環境から使えるようにするラッパー。
ライセンス形態はこちら
PCSC.Iso7816と組み合わせて使う。

【PCSC - Nuget】
【PCSC.Iso7816 - Nuget】

【PCSC-sharp - Github】

Windows.Devices.SmartCards

UWPアプリ用の標準ライブラリ (無償)

【Windows.Devices.SmartCards Namespace - MSDN】

SDK for NFC Starter Kit

SONYが提供しているSDK。
商用利用は有償。

【SDK for NFC Starter Kit - SONY】

nfcpy

Python用のNFCライブラリ。
以前はPython2にしか対応していなかったが、最近Python3に対応したらしい。
Windowsでは別途ドライバの導入作業が必要。(要管理者権限)
ライセンス形態はEUPL1.1

【nfcpy - PyPi】
【nfcpy - Github】

【PCSCによる処理フロー】

基本的には以下の処理フローを踏襲します。

image.png

1. SCardEstablishContext

リソースマネージャに接続してハンドルを取得します。

2. SCardListReaders

PCに接続されているNFCリーダを取得します。(複数可)
取得できなかった場合は接続されるまでループするか、エラーで処理を中止します。

3. SCardConnect

接続されているNFCリーダを指定して、カード(NFCタグ)に接続します。
カードと接続できなかった場合、接続されるまでループするか、エラーで処理を中止します。
なお、NFCリーダ上に読み取れるカードがなかった場合はエラーとなるため、基本的にはループしてカードと接続できるまで待ちます。

4. SCardTransmit

接続したカードにコマンドを送信し、結果を受信します。
ここでIDmを取得したり、カードに保存されている情報を読取り/書込みをお行います。
コマンドや受信データは取得したい情報によって異なるため、該当の仕様書を確認する必要があります。

5. SCardDisconnect

カードから切断します。
これで一連の処理は完了です。

winscard.dllによるサンプル実装 (C# コンソールアプリ)

プログラム本体

Program.cs
using PCSC;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PCSC_Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            IntPtr hContext = IntPtr.Zero;

            // ##################################################
            // 1. SCardEstablishContext
            // ##################################################
            Console.WriteLine("***** 1. SCardEstablishContext *****");
            uint ret = Api.SCardEstablishContext(Constant.SCARD_SCOPE_USER, IntPtr.Zero, IntPtr.Zero, out hContext);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                string message;
                switch (ret)
                {
                    case Constant.SCARD_E_NO_SERVICE:
                        message = "サービスが起動されていません。";
                        break;
                    default:
                        message = "サービスに接続できません。code = " + ret;
                        break;
                }
                throw new ApplicationException(message);
            }

            if (hContext == IntPtr.Zero)
            {
                throw new ApplicationException("コンテキストの取得に失敗しました。");
            }
            Console.WriteLine(" サービスに接続しました。");


            // ##################################################
            // 2. SCardListReaders
            // ##################################################
            Console.WriteLine("***** 2. SCardListReaders *****");
            uint pcchReaders = 0;

            // NFCリーダの文字列バッファのサイズを取得
            ret = Api.SCardListReaders(hContext, null, null, ref pcchReaders);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                // 検出失敗
                throw new ApplicationException("NFCリーダを確認できません。");
            }

            // NFCリーダの文字列を取得
            byte[] mszReaders = new byte[pcchReaders * 2]; // 1文字2byte
            ret = Api.SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                // 検出失敗
                throw new ApplicationException("NFCリーダの取得に失敗しました。");
            }


            UnicodeEncoding unicodeEncoding = new UnicodeEncoding();
            string readerNameMultiString = unicodeEncoding.GetString(mszReaders);

            // 認識したNDCリーダの最初の1台を使用
            int nullindex = readerNameMultiString.IndexOf((char)0);
            var readerName = readerNameMultiString.Substring(0, nullindex);
            Console.WriteLine(" NFCリーダを検出しました。 " + readerName);



            // ##################################################
            // 3. SCardConnect
            // ##################################################
            Console.WriteLine("***** 3. SCardConnect *****");
            IntPtr hCard = IntPtr.Zero;
            IntPtr activeProtocol = IntPtr.Zero;
            ret = Api.SCardConnect(hContext, readerName, Constant.SCARD_SHARE_SHARED, Constant.SCARD_PROTOCOL_T1, ref hCard, ref activeProtocol);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードに接続できません。code = " + ret);
            }
            Console.WriteLine(" カードに接続しました。");



            // ##################################################
            // 4. SCardTransmit
            // ##################################################
            Console.WriteLine("***** 4. SCardTransmit *****");
            uint maxRecvDataLen = 256;
            var recvBuffer = new byte[maxRecvDataLen + 2];
            var sendBuffer = new byte[] { 0xff, 0xca, 0x00, 0x00, 0x00 };  // ← IDmを取得するコマンド

            Api.SCARD_IO_REQUEST ioRecv = new Api.SCARD_IO_REQUEST();
            ioRecv.cbPciLength = 255;

            int pcbRecvLength = recvBuffer.Length;
            int cbSendLength = sendBuffer.Length;

            IntPtr handle = Api.LoadLibrary("Winscard.dll");
            IntPtr pci = Api.GetProcAddress(handle, "g_rgSCardT1Pci");
            Api.FreeLibrary(handle);

            ret = Api.SCardTransmit(hCard, pci, sendBuffer, cbSendLength, ioRecv, recvBuffer, ref pcbRecvLength);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                throw new ApplicationException("NFCカードへの送信に失敗しました。code = " + ret);
            }

            // 受信データからIDmを抽出する
            // recvBuffer = IDm + SW1 + SW2 (SW = StatusWord)
            // SW1 = 0x90 (144) SW1 = 0x00 (0) で正常だが、ここでは見ていない
            string cardId = BitConverter.ToString(recvBuffer, 0, pcbRecvLength - 2);
            Console.WriteLine(" カードからデータを取得しました。");
            Console.WriteLine(" 【IDm】:" + cardId);


            // ##################################################
            // 5. SCardDisconnect
            // ##################################################
            Console.WriteLine("***** 5. SCardDisconnect *****");
            ret = Api.SCardDisconnect(hCard, Constant.SCARD_LEAVE_CARD);
            if (ret != Constant.SCARD_S_SUCCESS)
            {
                throw new ApplicationException("NFCカードとの切断に失敗しました。code = " + ret);
            }
            Console.WriteLine(" カードを切断しました。");
        }
    }
}

API定義

nfcapi.cs
using System;
using System.Runtime.InteropServices;

namespace PCSC
{
    class Api
    {
        [DllImport("winscard.dll")]
        public static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);

        [DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
        public static extern uint SCardListReaders(
          IntPtr hContext, byte[] mszGroups, byte[] mszReaders, ref UInt32 pcchReaders);

        [DllImport("winscard.dll")]
        public static extern uint SCardReleaseContext(IntPtr phContext);

        [DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
        public static extern uint SCardConnect(IntPtr hContext, string szReader,
             uint dwShareMode, uint dwPreferredProtocols, ref IntPtr phCard,
             ref IntPtr pdwActiveProtocol);

        [DllImport("winscard.dll")]
        public static extern uint SCardDisconnect(IntPtr hCard, int Disposition);

        [StructLayout(LayoutKind.Sequential)]
        internal class SCARD_IO_REQUEST
        {
            internal uint dwProtocol;
            internal int cbPciLength;
            public SCARD_IO_REQUEST()
            {
                dwProtocol = 0;
            }
        }

        [DllImport("winscard.dll")]
        public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
                byte[] RecvBuff, ref int RecvBuffLen);

        [DllImport("winscard.dll")]
        public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct SCARD_READERSTATE
        {
            internal string szReader;
            internal IntPtr pvUserData;
            internal UInt32 dwCurrentState;
            internal UInt32 dwEventState;
            internal UInt32 cbAtr;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
            internal byte[] rgbAtr;
        }

        [DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
        public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);

        [DllImport("winscard.dll")]
        public static extern int SCardStatus(IntPtr hCard, string szReader, ref int cch, ref int state, ref int protocol, ref byte[] bAttr, ref int cByte);


        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadLibrary(string lpFileName);

        [DllImport("kernel32.dll")]
        public static extern void FreeLibrary(IntPtr handle);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr handle, string procName);

    }
}

APIの定数定義

pcsc_const.cs
using System;

namespace PCSC
{
    class Constant
    {
        public const uint SCARD_S_SUCCESS = 0;
        public const uint SCARD_E_NO_SERVICE = 0x8010001D;
        public const uint SCARD_E_TIMEOUT = 0x8010000A;

        public const uint SCARD_SCOPE_USER = 0;
        public const uint SCARD_SCOPE_TERMINAL = 1;
        public const uint SCARD_SCOPE_SYSTEM = 2;

        public const int SCARD_STATE_UNAWARE = 0x0000;
        public const int SCARD_STATE_CHANGED = 0x00000002;
        public const int SCARD_STATE_PRESENT = 0x00000020;
        public const UInt32 SCARD_STATE_EMPTY = 0x00000010;
        public const int SCARD_SHARE_SHARED = 0x00000002;
        public const int SCARD_SHARE_EXCLUSIVE = 0x00000001;
        public const int SCARD_SHARE_DIRECT = 0x00000003;

        public const int SCARD_PROTOCOL_T0 = 1;
        public const int SCARD_PROTOCOL_T1 = 2;
        public const int SCARD_PROTOCOL_RAW = 4;

        public const int SCARD_LEAVE_CARD = 0;
        public const int SCARD_RESET_CARD = 1;
        public const int SCARD_UNPOWER_CARD = 2;
        public const int SCARD_EJECT_CARD = 3;

        // SCardStatus status values
        public const int SCARD_UNKNOWN = 0x00000000;
        public const int SCARD_ABSENT = 0x00000001;
        public const int SCARD_PRESENT = 0x00000002;
        public const int SCARD_SWALLOWED = 0x00000003;
        public const int SCARD_POWERED = 0x00000004;
        public const int SCARD_NEGOTIABLE = 0x00000005;
        public const int SCARD_SPECIFICMODE = 0x00000006;
    }
}

実行結果

本プログラムでは非常に基本的なことしか実装していません。
パソコンにNFCリーダを接続し、事前にNFCリーダ上にNFCタグ(カード、タグ、スマホなど)を置いた上で実行すると、コンソール上に以下のように表示されます。

***** 1. SCardEstablishContext *****
 サービスに接続しました。
***** 2. SCardListReaders *****
 NFCリーダを検出しました。 Sony FeliCa Port/PaSoRi 3.0 0
***** 3. SCardConnect *****
 カードに接続しました。
***** 4. SCardTransmit *****
 カードからデータを取得しました。
 【IDm】:04-D6-0E-42-**-**-**
***** 5. SCardDisconnect *****
 カードを切断しました。

4. SCardTransmit で表示している 【IDm】: 以降がNFCタグから取得した固有IDの値になります。
NFCリーダが認識できなかったり、NFCタグが存在しない/繋がらない場合など、異常時には例外を吐きます。

なお、NFCタグがリーダにかざされるまで待つには 3. SCardConnect でループさせるか、 SCardGetStatusChange メソッドを使ってNFCリーダの状態変化を検出するまで待つ必要があります。

また、ソースコード中の var sendBuffer = new byte[] { 0xff, 0xca, 0x00, 0x00, 0x00 }; の部分がNFCタグに送信するコマンドになります。
サンプルではIDmを取得するコマンドになっていますが、それ以外のやり取りを行いたい場合、各サービスの仕様書を確認する必要があります。

C# csc.rspファイルを編集してcsc.exeのデフォルトのオプションを変更しようと思ったらファイルアクセス権が面倒だった話(Visual Studio使わない人向け)

$
0
0

(結論)

csc.rspは編集せずに、バッチファイルつくるのがおすすめ()

はじめに

Windows10環境です。

csc.exeのあるフォルダ1csc.rspというファイルがあり、
これを編集するとオプションを変えられます。
が、そのままだと上書き保存ができません

csc.rspの中身

デフォルトは下記になっているはず。

csc.rsp
# This file contains command-line options that the C#
# command line compiler (CSC) will process as part
# of every compilation, unless the "/noconfig" option
# is specified. 

# Reference the common Framework libraries
/r:Accessibility.dll
/r:Microsoft.CSharp.dll
/r:System.Configuration.dll
/r:System.Configuration.Install.dll
/r:System.Core.dll
/r:System.Data.dll
/r:System.Data.DataSetExtensions.dll
/r:System.Data.Linq.dll
/r:System.Data.OracleClient.dll
/r:System.Deployment.dll
/r:System.Design.dll
/r:System.DirectoryServices.dll
/r:System.dll
/r:System.Drawing.Design.dll
/r:System.Drawing.dll
/r:System.EnterpriseServices.dll
/r:System.Management.dll
/r:System.Messaging.dll
/r:System.Runtime.Remoting.dll
/r:System.Runtime.Serialization.dll
/r:System.Runtime.Serialization.Formatters.Soap.dll
/r:System.Security.dll
/r:System.ServiceModel.dll
/r:System.ServiceModel.Web.dll
/r:System.ServiceProcess.dll
/r:System.Transactions.dll
/r:System.Web.dll
/r:System.Web.Extensions.Design.dll
/r:System.Web.Extensions.dll
/r:System.Web.Mobile.dll
/r:System.Web.RegularExpressions.dll
/r:System.Web.Services.dll
/r:System.Windows.Forms.Dll
/r:System.Workflow.Activities.dll
/r:System.Workflow.ComponentModel.dll
/r:System.Workflow.Runtime.dll
/r:System.Xml.dll
/r:System.Xml.Linq.dll

csc.rspのアクセス権の変更~編集~保存

'1. 下記でファイルの所有権を変更。

cd \Windows\Microsoft.NET\Framework64\v4.0.30319\
takeown /f csc.rsp

'2. エクスプローラでファイルcsc.rspを右クリックし、プロパティを開く。

'3. 「セキュリティ」タブを開く。

'4. 「編集」を押す。(下図参照)
image.png

'5. Administatorsの「許可」に「変更」権限を追加する(「変更」をチェックすると「書き込み」もチェックされる)。(下図参照)
image.png
(※AdministratorsではなくUsersにしておけば以降管理者権限不要のはずだが、不安なのでAdministratorsにしておく。)

'6. メモ帳(notepad.exe)を右クリックで管理者として実行。(メモ帳じゃなくても可)

'7. デバイスへの変更許可が求められるので、承諾する。

'8. csc.rspをstep 6のエディタで編集して上書き保存する。

めんどくさっ

追加したくなりそうなオプション

/nologo ・・・ cscのバージョン表示とかを抑制
/target:winexe ・・・ 生成したexe実行時にコンソールを表示しない(※デバッグ作業中は不便なので微妙)
/platform:x64 もしくは /platform:x86 ・・・ 64bit/32bit指定
/r:DLLのパス ・・・ バッチファイルにしたほうが良い気がする。

csc.rspを編集するデメリット

  • 環境変わったときに戸惑う。 (PC変えたり、ほかの人との環境が変わってしまう。リリース時に事故るリスクがある。)
  • 戻すとき面倒。
  • 変更元をバックアップしてないと戻せなくなるかも。

記事を書いてみたものの、個別にバッチファイル(.bat)作ったほうがよいのではないか・・。

バッチファイル例

compile.bat
csc /r:ほげほげ.dll ^
   /nologo ^
   /target:winexe ^
   %*
  • 行末の ^ は 改行を無視してつなげるためです。(C言語の行末のエスケープ\みたいなもの。)
  • %*は、コマンドラインオプションがそのまま渡ります。(compile.bat /xxx yyy.csとすると、/xxx yyy.csの部分が入ります。)

参考サイト

csc.rsp
https://www.atmarkit.co.jp/fdotnet/dotnettips/179cscrsp/cscrsp.html

ファイル所有者の変更(takeown /f ファイル名)
https://freesoft.tvbok.com/windows7/general/trustedinstaller.html


  1. where csc で調べられます。この辺(C:\Windows\Microsoft.NET\Framework64\v4.0.30319\)にいるはず(環境による)。 

C# イベントハンドラの追加の書きかた変遷

$
0
0

いまさらだけども。

書き方#1

以前はこんな感じで書いていた(気がする)。

イベントハンドラの追加処理
class クラス
{
    メソッド
    {
        
        button1.Click += new EventHandler(button1_Click);
        
    }
}
イベントハンドラのメソッド記載
void button1_Click(object sender, EventArgs e)
{
    
}

書き方#2

new EventHandlerは省略できて、下記のように書ける。

イベントハンドラの追加処理
class クラス
{
    メソッド
    {
        
        button1.Click += button1_Click;
        
    }
}
イベントハンドラのメソッド記載
void button1_Click(object sender, EventArgs e)
{
    
}

書き方#3

ラムダ式で匿名メソッドとして書ける。かなり短くなった。
「処理内容」は、1行で書けないならメソッド化したほうが見やすい気がする。

イベントハンドラの追加処理
class クラス
{
    メソッド
    {
        
        button1.Click += (sender,e)=>{ 処理内容 };
        
    }
}

使いわけ

EventArgs(もしくはその派生クラス)の引数を使いたいときは書き方#2を使いたくなるところ。
同じ.csファイル内では、書き方#2か書き方#3で統一したい。

参考サイト

https://www.ipentec.com/document/csharp-add-event-handler-by-code
https://tnakamura.hatenablog.com/entry/20090309/1236549534

バッチファイルにC#コードを埋め込み実行する

$
0
0

バッチファイル内でpowershell経由でC#コードを実行する記事は既にいくつか存在しますが、1ファイル内でバッチファイル部とC#コード部が綺麗に分かれているものが見つからなかったので、より可読性とメンテナンス性向上を目指しつつ作成してみました。

コード

RunCSCode.bat
@echo off
setlocal
set BAT_PATH=%~f0
powershell -NoProfile -Command "& {$cscode=[regex]::Split([IO.File]::ReadAllText($env:BAT_PATH,[Text.Encoding]::UTF8),':EndBatch')[2]; Add-Type -TypeDefinition $cscode -Language CSharpVersion3; [CSBatch.Program]::Main($Args);}" %*
endlocal
exit /b

:EndBatch

//CSCode
using System;
using System.Runtime.InteropServices;

namespace CSBatch
{
    public class Program
    {
        [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
        private static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);

        public static int Main(string[] args)
        {
            string msg = "";
            for( int i=0; i<args.Length; i++)
            {
                msg += String.Format("args[{0}]={1}\r\n", i, args[i]);
            }
            if( msg=="" )
            {
                msg = "no args.";
            }

            int MB_OK = 0;
            MessageBox(IntPtr.Zero, msg, "CSTestCode", MB_OK);
            return args.Length;
        }
    }
}

実行結果

>RunCSCode.bat あああ いいい ううう
3

実行結果

動作原理

1.通常のバッチファイルとして起動
2.環境変数にバッチファイル自身のパスを格納(直接powershellコマンドに埋め込んでもいいかも)
3.(powershellコマンド) バッチファイル自身をFile.ReadAllTextでutf-8 stringデータとして読み込む
4.(powershellコマンド) Regex.Splitを使用して上記stringデータを:EndBatchラベルで分割し、C#コード部を分離
5.(powershellコマンド) 分離したC#コード部をAdd-Typeで型追加し、C#のメソッドを実行
6. powershellでのC#メソッド実行終了後、exit /b でバッチファイル終了。
(これにより、以降のC#コード部の影響を受けなくなる)

注意)バッチファイル読み込み時にutf-8エンコーディングを指定していますが、BOM無しutf-8でバッチファイルを保存しないと、実行時にゴミが付きます。

MessageBox.Show じゃなくて WinAPI の MessageBox 使っているのは、WinAPIも呼び出せますよっていうのを見せたかっただけです。まあC#なので出来て当たり前ですが…
なんか色々面白い事(危ない事ともいう)出来そうですね。

参考記事

バッチファイルにC#コードを書く
バッチファイルからps1ファイルに記述したC#コードを呼び出して満足した後にこの記事を見て、目から鱗が落ちました。

ちょっと複雑な並べ替えをするときはLINQが楽でよい

$
0
0

C#のソート方法って色々ありますよね。
検索かけたら、結局どれ使えばいいの?って私はなりました。

一項目だけ昇順にソートできればそれでいいって場合や、複数項目を指定したい…って場合あります。
私としては、複数項目をソートしたい場合はLINQが良いと思いました。
超簡単なコンソールアプリ作って理由を説明していきます。

Student.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace Sort
{
    class Student
    {
        // 生徒番号
        public string StudentNumber { get; set; }

        // 名前
        public string Name { get; set; }

        // 血液型
        public string BloodType { get; set; }

        // 組
        public string Department { get; set; }

        // 国語
        public int National { get; set; }

        // 算数
        public int Mathematics { get; set; }

        // コンストラクタ
        public Student(string studentNumber, string name, string bloodType, string department, int national, int mathematics)
        {
            StudentNumber = studentNumber;
            Name = name;
            BloodType = bloodType;
            Department = department;
            National = national;
            Mathematics = mathematics;
        }
    }
}

上のようなクラスを用意しました。
このクラスのデータを以下のようなルールでソートしたいとします。

・最初に、血液型をO型→A型→AB型→B型の順でソート
・同じ血液型の場合は、組をB組→C組→A組の順でソート
・組も同じ場合は、国語の点数を降順にソート

このような要求があったらどうでしょう?Sortメソッドを使う方法だと結構面倒なんじゃないでしょうか。
少なくとも私は、Sortメソッドを使って引数にComparisonデリゲートを使うやり方を試したら非常につらかったです。

ここでLINQが登場するわけです。
上で挙げたルールを満たすLINQを使用したソースコードがこちら。

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sort
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Student> lists = new List<Student>();
            IEnumerable<Student> query;

            lists.Add(new Student("00010", "和田", "B", "A", 55, 30));
            lists.Add(new Student("00003", "ダル", "B", "B", 50, 30));
            lists.Add(new Student("00002", "田中", "O", "A", 40, 70));
            lists.Add(new Student("00007", "野茂", "A", "B", 15, 50));
            lists.Add(new Student("00006", "黒田", "AB", "A", 55, 10));
            lists.Add(new Student("00009", "伊良部", "AB", "C", 85, 90));
            lists.Add(new Student("00008", "長谷川", "B", "C", 60, 45));
            lists.Add(new Student("00005", "前田", "O", "A", 60, 30));
            lists.Add(new Student("00004", "菊池", "O", "B", 50, 20));
            lists.Add(new Student("00001", "平野", "A", "C", 30, 40));
            lists.Add(new Student("00011", "大家", "AB", "B", 25, 45));
            lists.Add(new Student("00012", "佐々木", "O", "A", 90, 80));

            query = lists
                 .OrderBy(value =>
                 {
                     int result = 0;

                     switch (value.BloodType)
                     {
                         case "A":
                             result = 1;
                             break;
                         case "B":
                             result = 3;
                             break;
                         case "O":
                             result = 0;
                             break;
                         case "AB":
                             result = 2;
                             break;
                     }

                     return result;
                 })
                 .ThenBy(value =>
                 {
                     int result = 0;
                     switch (value.Department)
                     {
                         case "A":
                             result = 2;
                             break;
                         case "B":
                             result = 0;
                             break;
                         case "C":
                             result = 1;
                             break;
                     }
                     return result;
                 })
                 .ThenByDescending(value => value.National);

            Console.WriteLine("生徒番号\t名前\t血液型\tクラス\t国語\t算数");
            foreach (Student a in query)
            {
                Console.WriteLine("{0}\t\t{1}\t{2}\t{3}\t{4}\t{5}"
                    , a.StudentNumber
                    , a.Name
                    , a.BloodType
                    , a.Department
                    , a.National
                    , a.Mathematics
                    );
            }
        }
    }
}

適当にデータをリストに追加したのち、そのデータをソート、表示しております。

リストデータの値を調べ、まず血液型がA型の場合は1、Bは3、Oは0、ABは2をresultに代入、リターンします。
リターンしたデータをOrderByは昇順で並べ替えてくれます。つまり、O型→A型→AB型→B型の順ですね。
次に、ThenBy(OrderBy,OrderByDescendingを使うのは最初だけ)でA組所属であれば2、Bは0、Cは1、というように組のソートもOK。
最後にThenByDescendingで国語の成績を降順に評価し並び替えます。この場合は省略していますが、国語の値を返しています。

以上のコードでコンソールアプリを構成し、実行した結果がこちら。

5.png

きちんとルール通り並べ替えられていますね。
ちょっと自分語り入りますが、現在私はこの並び替えルール以上に複雑怪奇な画面表示用データ並び替えを要求されています。
その蹴飛ばしたくなる要求を答えることができたのはLINQの並び替えが強力だったためです。
Sortメソッドも試しましたが、2つ目以降の項目(今回でいうと"組")の並び替えがどうも辛いというか苦手というか。
ThenByの存在がLINQソートの強みですね。

LINQは様々な言語で使えるようです。
駆け出しエンジニアの皆さん、ソートに困ったらLINQ使ってみてください。

追記

albireoさんからコメントで保守性の高い書き方を教えて頂きました。

// 血液型のソート
.OrderBy(value => Array.IndexOf(new[] { "O", "A", "AB", "B" }, value.BloodType))
// 組のソート
.ThenBy(value => Array.IndexOf(new[] { "B", "C", "A" }, value.Department))

以上のような書き方にすることで、
今回出したソートのルールを守ったうえで美しくメンテしやすいコードになりました。

追記2

htsignさんからタプルを利用する方法を教えて頂きました。

// タプルの定義
int orderBloodType(Student student) =>
    student.BloodType switch
    {
        "A" => 1,
        "B" => 3,
        "O" => 0,
        "AB" => 2,
        _ => throw new ArgumentOutOfRangeException(nameof(Student.BloodType)),
    };
int orderDepartment(Student student) =>
    student.Department switch
    {
        "A" => 2,
        "B" => 0,
        "C" => 1,
        _ => throw new ArgumentOutOfRangeException(nameof(Student.Department)),
    };

// ソート処理
var query = lists.OrderBy(x => (orderBloodType(x), orderDepartment(x), -x.National));
/*
一括でやらない場合は
var query = lists.OrderBy(x => orderBloodType(x))
                 .ThenBy(x => orderDepartment(x))
                 .ThenByDescending(x => x.National);
*/

例外処理まで含めて簡潔に書けますね。
C#のバージョンが8.0である場合は検討ください。

C#でgifアニメを作る - ルパン三世のタイトルコール風

$
0
0

こんな感じのgifファイルができる

out.gif

※画像をクリックして別ウィンドウで見てもらえれば、少しはましに見えるはず。

やりたかったこと

Qiitaにアニメーションを投稿できるようにしようと思い、gifアニメファイルを作る方法を調べた。

今回はGifBitmapEncoderクラスを使ったが、
この方法は参考サイトにもあるように、

繰り返し回数や遅延時間は指定できません

結果として、速すぎる&繰り返せないものになった。
繰り返せないのは、わりと致命的・・・。
同参考サイトに、ほかの方法も記載されているので、別途試してみたい。

エンコーダ側のソースコード

GifEncoderTest.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Media.Imaging;
using System.Xaml;


class GifEncoderTest
{
    public static void CreateAnimatedGif(string savePath, List<string> imageFiles)
    {
        var encoder = new GifBitmapEncoder();

        foreach (string f in imageFiles) {
            //画像ファイルからBitmapFrameを作成する
            BitmapFrame bmpFrame = BitmapFrame.Create(new Uri(f, UriKind.RelativeOrAbsolute));
            encoder.Frames.Add(bmpFrame);
        }

        var outputFileStrm = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None);
        try {
            encoder.Save(outputFileStrm);
        }
        finally {
            outputFileStrm.Close();
        }
    }

    [STAThread]
    static void Main(string[] args)
    {
        const int MaxFrames = 1000;
        const int FileNameDigits = 3;

        if (args.Length != 1) {
            Console.WriteLine("Argument error.");
            return;
        }

        string prefix = args[0];
        var fileNames = new List<string>();

        for (int i=0;i<MaxFrames;i++) {
            string s = prefix + i.ToString("D"+FileNameDigits.ToString()) + ".png";
            FileInfo fi = new FileInfo(s);

            if ( fi.Exists ) {
                fileNames.Add(fi.FullName);
            }
            else {
                if ( i==0 ) {
                    Console.WriteLine("File \"" + fi.FullName + "\" is not found.");
                    return;
                }
                break;
            }
        }

        CreateAnimatedGif("out.gif", fileNames);
    }
}

コンパイル方法 - バッチファイル

32bit向けと64bit向けが別のdllのようなので、環境に合わせて64を指定した。

csc /nologo ^
 /platform:x64 ^
 /r:C:\Windows\assembly\NativeImages_v4.0.30319_64\System.Xaml\1e45023cc3b2fa3145024be9518b38d3\System.Xaml.ni.dll ^
 /r:C:\Windows\assembly\NativeImages_v4.0.30319_64\WindowsBase\954bf80526cb14a926c4e2335a4e5803\WindowsBase.ni.dll ^
 /r:C:\Windows\assembly\NativeImages_v4.0.30319_64\PresentationCore\99085e4311ca84f7357f2d1a2794ca28\PresentationCore.ni.dll ^
 %*

文字の画像ファイル1枚1枚を生成するソースコード

注意事項:フォントの著作権に注意(よくわからないのでとりあえず喚起だけ・・)

using System;
using System.Drawing;
using System.Drawing.Imaging;


class SampleBitmapGenerator
{
    const string FileNamePrefix = "sample";
    const int Width = 200;
    const int Height = 200;
    const int YAdjust = 10; // アセント(?)による縦方向のずれを無理やり調整
    const float FontSize = 150.0f;
    const int MaxTitleLength = 100;

    [STAThread]
    static void Main(string[] args)
    {
        string title;
        Font font = new Font("MS P明朝", FontSize);

        if (args.Length == 0) {
            title = "gifアニメをC#でやってみた";
        }
        else {
            title = String.Join(" ", args);
        }

        Bitmap bmp = new Bitmap(Width, Height);
        Graphics g = Graphics.FromImage(bmp);

        for (int i=0;i<title.Length&&i<MaxTitleLength;i++) {
            var sf = new StringFormat();
            sf.Alignment = StringAlignment.Center; // 横中央
            sf.LineAlignment = StringAlignment.Center; // 縦中央

            g.Clear(Color.Black);
            g.DrawString(title[i].ToString(), font, Brushes.White, new Point(Width/2,Height/2+YAdjust), sf);

            bmp.Save(FileNamePrefix + i.ToString("D3") + ".png", ImageFormat.Png);
        }

        g.Dispose();
    }
}

参考サイト

GIFアニメーションを作成する - dobon.net


【C#】Genericな基底クラスから派生したクラスをListで管理したい

$
0
0

はじめに

この記事は非常にニッチなケースだということを予めお伝えしておきます。

どういう状況か

例えばこのようなクラスがあります。

abstract class HogeBase<T> where T : HogeBase<T> {}

このクラスを継承したクラス一覧を配列などで管理しようとしたことはありませんか?
そこで特に意識せずタイピングするとこうしたくなります。

List<HogeBase<T>> list;

そして思います。
あれ?ちょっと待ってTは何?どこで決まるの?
変数でwhere使えない!

そうです、Generic型の制約を持つGenericクラスを配列などで管理しようとするとできません。
これはそんな状況をどうやって解決するかについての記事になります。

答えを最初に言ってしまうと、interfaceを使って解決します。
それでは実装を見てみましょう。

実装

Framework

何らかのFramework上のクラスです。
外部の何かかもしれませんし、自社製の何かかもしれません。
とりあえず自分では触れることができない前提です。

Framework.cs
namespace Framework
{
    public abstract class Framework<T> where T : Framework<T>
    {
        public void Function()
        {
            UnityEngine.Debug.Log(typeof(T).Name);
        }
    }
}

Interface

IHoge.cs
public interface IHoge
{
    void WrapFunction();
}

Base class

HogeBase.cs
using Framework;

public abstract class HogeBase<T> : Framework<T>, IHoge where T : HogeBase<T>
{
    void IHoge.WrapFunction()
    {
        Function();
    }
}

派生1

HogeEntity1.cs
public sealed class HogeEntity1 : HogeBase<HogeEntity1>
{
}

派生2

HogeEntity2.cs
public sealed class HogeEntity2 : HogeBase<HogeEntity2>
{
}

HogeBaseを継承しないクラス

HogeOther.cs
using Framework;

public class HogeOther : Framework<HogeOther>, IHoge
{
    void IHoge.WrapFunction()
    {
        Function();
    }
}

Manager

こちらが実際の管理クラスになります。
Frameworkではなく、IHogeをベースとして管理しています。
使用しているのはDictionaryですが、Listなどでも一緒です。

HogeManager.cs
using Framework;
using System;
using System.Collections.Generic;

public class HogeManager
{
    private Dictionary<Type, IHoge> _hogeEntities;

    public HogeManager()
    {
        _hogeEntities = new Dictionary<Type, IHoge>
        {
            {typeof(HogeEntity1), new HogeEntity1()},
            {typeof(HogeEntity2), new HogeEntity2()},
            {typeof(HogeOther),   new HogeOther()},
        };
    }

    public void ExecuteFrameworkFunction<THoge>() where THoge : Framework<THoge>, IHoge
    {
        Type type = typeof(THoge);
        var entity = _hogeEntities[type];
        entity.WrapFunction();
    }
}

実際に使う時の例

呼び出し側では、管理に扱っている何らかのパラメータを渡して制御する対象を変えます。
今回の例ではクラスの型を管理パラメータとして扱っています。

var hogeManager = new HogeManager();
hogeManager.ExecuteFrameworkFunction<HogeEntity1>();
hogeManager.ExecuteFrameworkFunction<HogeEntity2>();
hogeManager.ExecuteFrameworkFunction<HogeOther>();

補足

①全ての管理対象となるクラスは、特定の一つのクラスから派生していた方が良いのでは?

本当はFramework<THoge>, IHogeの両方を継承してる基底クラスを一つ作って起き、管理対象となるクラスはそれを継承する方が良さそうですが、そうじゃない(HogeOtherのような)ケースも実際にはあったのでこのような作りになっています。

②基底となるクラスをGenericじゃなくしちゃダメなの?

基底クラスを

abstract class HogeBase : Framework<HogeBase> {}

にすれば良くね?と思われるかもしれませんが、その場合さらに基底にあるFrameworkが参照するTが、すべてHogeBaseになってしまいます。
最上層の型を対象とした何らかの処理がある場合にはそれだと都合が悪い場合があります。

③ExecuteFrameworkFunctionメソッドの制約はinterfaceだけじゃダメなの?

ExecuteFrameworkFunctionの制約は where THoge : IHoge だけだとダメです。
利用側で hogeManager.ExecuteFrameworkFunction<IHoge>(); とすることができてしまいます。

実際にどういう時?

例えば複数のウィンドウを管理するクラスがこのような作りだったとして、各ウィンドウから管理中のいずれかのウィンドウを呼び出す、といった場合に使える可能性があります。
例えばウィンドウを生成したり、前面に出したり、値を受け渡したり、そんな感じです。

最後に

冒頭でもお伝えした通り、非常に稀なケースですが、やりたくなる場合があるかもしれません。
その時にこの記事を思い出して、それが少しでもお役に立てれば幸いです。

C# - フォントメトリクスを調査してみたらカオスだった件 - 未解決

$
0
0

混乱した結果、記事も空中分解気味になってしまったが、とりあえず投稿してみる。

経緯

下記みたいな感じでStringFormatでセンタリング指定してDrawStringしても縦位置がずれるので、調べてみた。

var sf = new StringFormat();
sf.Alignment = StringAlignment.Center; // 横中央
sf.LineAlignment = StringAlignment.Center; // 縦中央 ・・・ にならない(?)

g.DrawString(text, font, Brushes.Black, new Point(Width/2,Height/2), sf);

gとかqとかの下にはみ出る文字の影響と推測。
参考サイト フォント メトリックを取得する - Microsoft Docs から抜粋:

image.png

Descentの分だけ上に寄っているとすれば、Descentの半分だけY座標を足して描画すれば大体中央に来そう。

謎#1

上記の参考サイトはSizeを使っているが、Heightを使わないと何故か位置が合わない。(?)

descentPixel = font.Size * descent / fontFamily.GetEmHeight(FontStyle.Regular);

いろんなフォントを描画してみる

AscentとHeightの位置に横線を描画してみる。
コード抜粋

    FontFamily ff = font.FontFamily;
    int ascent = ff.GetCellAscent(font.Style);
    int emHeight = ff.GetEmHeight(font.Style);

    // 下記の各Y座標(pixel単位)で横線を引いてみた
    float fontHeight = font.GetHeight(g); // g: 描画に使用するGraphicsクラス
    float ascentHeight  = fontHeight * ascent  / emHeight;

システムのデフォルトフォント

それっぽい(※ドキュメントがよく分からないため根拠がない)

image.png

ビルマ文字(なぜか、はみ出る)

Font.GetHeightがやたら大きい値になったので、縦長の文字を探して色々やってたらビルマ文字1にたどり着いた。なぜかはみ出る。

image.png

ソースコード

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;

class FontMetricsTest : Form
{
    TrackBar   trackbar;
    PictureBox pct;
    ComboBox   cmbFonts;
    TextBox    txtContent;

    FontMetricsTest()
    {
        Text = "Font metrics test";
        ClientSize = new Size(600, 400);

        cmbFonts = new ComboBox();
        foreach (FontFamily ff in FontFamily.Families) {
            cmbFonts.Items.Add(ff.Name);
        }
        cmbFonts.Text = SystemFonts.DefaultFont.Name;
        cmbFonts.Location = new Point(0,0);
        cmbFonts.Width = 300;
        cmbFonts.DropDownHeight = 500;
        cmbFonts.DropDownStyle = ComboBoxStyle.DropDownList;
        cmbFonts.SelectedIndexChanged += (sender,e)=>{MyRedraw();};
        Controls.Add(cmbFonts);

        trackbar = new TrackBar();
        trackbar.Location = new Point(300,0);
        trackbar.Maximum = 100;
        trackbar.Value   = 50;
        trackbar.Minimum = 1;
        trackbar.TickFrequency = 33;
        trackbar.ValueChanged += (sender,e)=>{MyRedraw();};
        Controls.Add(trackbar);

        txtContent = new TextBox();
        txtContent.Location = new Point(0, 50);
        txtContent.Width = 300;
        txtContent.Text = "g あいう Qiita";
        txtContent.TextChanged += (sender,e)=>{MyRedraw();};
        Controls.Add(txtContent);

        pct = new PictureBox();
        pct.Location = new Point(0, 80);
        pct.Size = new Size(600, 300);
        pct.Image = new Bitmap(600,300);
        Controls.Add(pct);

        MyRedraw();
    }

    void MyRedraw()
    {
        int w = pct.Image.Width;
        int h = pct.Image.Height;

        string fontName = cmbFonts.Text;
        Font font = new Font(fontName, (float)trackbar.Value);

        string text = txtContent.Text;
        if (font.Name != fontName) {
            text = "Font unmatch: \"" + font.Name + "\" is loaded.";
        }

        using ( Graphics g = Graphics.FromImage(pct.Image) ) {
            g.Clear(Color.Black);

            FontFamily ff = font.FontFamily;

            int ascent = ff.GetCellAscent(font.Style);
            int descent = ff.GetCellDescent(font.Style);
            int emHeight = ff.GetEmHeight(font.Style);

            float fontHeight = font.GetHeight(g);
            float ascentHeight  = fontHeight * ascent  / emHeight;
//          float ascentSize    = font.Size  * ascent  / emHeight;

            Console.Write("==== "); Console.Write(font.Name); Console.WriteLine(" ====");
            Console.Write("font.Size: ");  Console.WriteLine(font.Size);
            Console.Write("fontHeight: "); Console.WriteLine(fontHeight);
            Console.Write("ascent: ");     Console.WriteLine(ascent);
            Console.Write("descent: ");    Console.WriteLine(descent);
            Console.Write("sum: ");        Console.WriteLine(ascent+descent);
            Console.Write("emHeight: ");   Console.WriteLine(emHeight);

            Pen pen = new Pen(Color.LightGray, 1.0f);
//          Pen penDash = new Pen(Color.LightGray, 1.0f);
//          penDash.DashStyle = DashStyle.Dash;
            g.DrawLine(pen,     0, fontHeight, w, fontHeight);
            g.DrawLine(pen,     0, ascentHeight, w, ascentHeight);
//          g.DrawLine(penDash, 0, font.Size, w, font.Size);
//          g.DrawLine(penDash, 0, ascentSize, w, ascentSize);

            g.DrawString(text, font, Brushes.White, new PointF(0,0));
        }

        pct.Refresh();
    }

    static void DumpSystemFontNames()
    {
        var dict = new Dictionary<string,Font>() {
            {"CaptionFont      " , SystemFonts.CaptionFont     },
            {"DefaultFont      " , SystemFonts.DefaultFont     },
            {"DialogFont       " , SystemFonts.DialogFont      },
            {"IconTitleFont    " , SystemFonts.IconTitleFont   },
            {"MenuFont         " , SystemFonts.MenuFont        },
            {"MessageBoxFont   " , SystemFonts.MessageBoxFont  },
            {"SmallCaptionFont " , SystemFonts.SmallCaptionFont},
            {"StatusFont       " , SystemFonts.StatusFont      }
        };

        foreach (var pair in dict) {
            Console.Write(pair.Key);
            Console.Write(",");
            Console.Write(pair.Value.Size);
            Console.Write(",");
            Console.WriteLine(pair.Value.Name);
        }
    }

    [STAThread]
    static void Main(string[] args)
    {
        // DumpSystemFontNames();
        Application.Run(new FontMetricsTest());
    }
}

結論

色々やってみたが、結局わからん・・・
実験してみるとFont.Size≠描画の高さだし、
Font.Height=行間、とドキュメントからは読み取ったけど、はみ出るケースもあるし、、

特定の文字列を中央表示したいなら、Graphics.MeasureStringとかを使って、描画する文字列に応じて調整するほうがよいかもしれない。

参考サイト

そのた

調べてる最中にSystem.Windows.MediaのほうのFontFamilyに迷い込んだりしたが別モノと思われる。


  1. 文字はwikipediaからのコピペ。 

Unityで使える便利関数(拡張メソッド)達

$
0
0

はじめに

約一年半「Unity」を使ってきて、よく使う機能やよく繰り返す機能を
たくさん作ってきたので、それらの一部分をいくつかのクラスにまとめて公表したいと思います!

そのまま使って頂いても構いませんし、
これらのアイデアを元に、更にいいものを作り上げて欲しいとも思っています
(それが出来上がったら是非コピp、、、もとい、参考にさせていただきたいです)

公表場所

以下のリポジトリにあります。
※C#7.0以上が必要です。
https://github.com/0Nome0/NerScript

機能紹介

ここでは内容されている機能の一部分を説明したいと思います。

機能は全て、namespace NerScriptにまとめてありますので、
using NerScript;

を忘れずにお書きください

ComponentExtend

アタッチされているRigidBodyやColliderの取得

gameObject.GetRigidBody();
gameObject.GetRigidBody2D();
gameObject.GetCollider();
gameObject.GetCollider2D();

GameObjectExtend

ありそうで無い便利なやつ

gameObject.Destroy();
gameObject.DontDestroyOnLoad();
gameObject.AddChild([オブジェクト]);

一番近いObjectを返してくれる

gameObject.GetClosestObjectInList([Objectのリスト]);

RigidBodyExtend

RigidBodyのFreeze操作

一時期とてもお世話になった

rigidbody.UnFreezeAll(); //Freeze全解除
rigidbody.FreezePosY(); //PositionYのみ凍結
rigidbody.UnFreezeRotation(); //Rotationの凍結解除
rigidbody //もちろんRigidbodyConstraintsで設定可能
.Freeze(RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionY);

内部実装を久しぶりに見たらString で1と0を管理しててビビった。
今ならビット使おうぜってなる代物。修正はしない。

StringExtend

文字列を数値に変換

int num = "12345".Int(); //Int(12345)

文字列を反転

"あいうえお".Reverse(); //おえういあ

UnityExtend

デバックがすこし捗る

int num = 5000.DebugLog(); //num:5000
string str = "ほげ".DebugLog(); //str;"ほげ"

代入するついでにLog表示してくれます。
こんなこともできちゃいます。

int num = 5000.DebugLog() + 300;
float length = new Vector3(12, 34, 56).DebugLog().magnitude;

この場合もしっかりと値が入ります。

Colors

大量の色が定義されています。

これに関しては自分で作ったものではなく、他人のものなのですが、
どこにあったか見つけ次第リンクを張っておきます。

追記:見つけました。こちらを参考にさせていただきました。
http://baba-s.hatenablog.com/entry/2017/12/28/145900

EnumLib

int->Enum変換

MyEnum enum = 3.ToEnumName<MyEnum>();

面倒くさい処理(「()」が多すぎる)を一発で出来ます。

GizmosLib

これに関しても他者のものを自分なりに拡張したものです。

参考にしたもの
https://github.com/code-beans/GizmoExtensions

ListLib

よく使うものたちです。
速度?気にしたら負けです。
人間様が使いやすいので問題ないです。

要素数を変更

list.Cut(5); //max = 5
list.Fill(10); // min = 10
list.SetCount(7); //count = 7

最後の要素を削除

list.RemoveFirst();

配列/リストに変換

int num = 10;
int[] nums = num.ShiftArray();
//nums : {10}
int[] nums2 = num.ShiftArray(30,40,700);
//nums2 : {10,30,40,700}

MathLib

intのClamp

this ref により、そのモノの変更が可能

int num = 10;
num.Clamp(0, 5);
//num : 5

num = num.Clamp(0,5);にする必要がない!!

RandomLib

ここには様々なRandomがあります

Colorで色,PointInCircleで円の中の一点など

ReflectionLib

使う人は使うSystem.Reflection

これが必要になるほどの人はきっと説明がなくても使いこなせるでしょう。

RectLib

AddやSet,Addedなど

Addや、Addedがありますが、
Add,SetはRectそのものを操作します。
対して、Added,Setedは操作された、別物を返します。

rect
.AddedPosX(10) //x += 10
.SetedPosY(-20.5f) // y = -20.5
.SetedSize(100, 120);size = 100,120

よくこんな使い方をしています。
メソッドチェーン使いやすい。

ちなみに前述のListLibもチェーンできるように設計されています。

TransformExtend

Positionなどを変更

transform.AddPosX(10); //x += 10
transform.SetLclPos(0, -1, 20); /LocalPosition = 0,-1,20
transform.SetSclY(4); //LocalScale = 4,4,4
transform.AddPosX(10).SetSclY(4).SetLclPos(0, -1, 20); 

これも割と使いやすいです。
速度?メモリ?知らん。

VectorLib

変換

Vector2 v2 = new Vector2(2,3);
Vector3 v3 = v2.ToVec3().OXY; 
//v3 : 0,2,3

並び替え

v3 = v3.SortVec3().YZX;
//v3 : 2,3,0

v3 = v3.SortVec3().OYY;
//v3 : 0,3,3

応用

//v3 : 1,2,3
v3 = v3
.AddedX(3) //4,2,3
.SetedZ(-2) //4,2,-2
.AddedAxis(Axis.Y, 5) //4,7,-2
.Clamp(Vector3.zero, new Vector3(5, 5, 5)) // 4,5,0
.MergeVec3(new Vector3(22, 33, 44))["xXy"]; //4,22,5

終わりに

これからも追加されていくとおもうので
参考がてらに覗いて見ていってください

急カーブにも強いNavMeshAgentを作る

$
0
0

NavMeshAgentは簡単にオブジェクトを自動で移動させることができる便利な機能ですが、使っていてある問題が発生したので、その解決策を載せます。

githubに全ソースコードを載せています。
https://github.com/AtshshiMori/NavMeshMovement

問題

以下のようにスピードがある程度遅ければ良いが、早くすると急に曲がるときに通り過ぎてしまう。

Speed=3のときは問題なく曲がれる。
speed3.gif

Speed=5 のときは行き過ぎてしまい上手く曲がれない。
speed5.gif

ソースコード

サンプルシーンのソースコードです。
Unityちゃんに以下のスクリプトを追加します。
targetにはインスペクターからUnityちゃんが向かう先であるカプセル型のオブジェクトを指定しておきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] GameObject target; // ゴールとなるターゲットオブジェクト
    UnityEngine.AI.NavMeshAgent agent;
    Animator animator;

    void Start()
    {
        agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
        animator = GetComponentInChildren<Animator>();
    }

    void Update()
    {
        agent.destination = target.transform.position; // ターゲットの設定
        animator.SetFloat("Speed", agent.velocity.sqrMagnitude); // Unityちゃんをアニメーションさせるためのパラメータ
    }
}

解決策1 等速で動かす

とりあえず等速で動くようにすることで解決ができます。
Update関数内に以下を追加します。

agent.velocity = (agent.steeringTarget - transform.position).normalized * agent.speed;
transform.forward = agent.steeringTarget - transform.position;

agent.velocityでNavMeshAgentのスピードを指定できます。

NavMeshAgentは常にゴールまでのパスを保持しており、
agent.steeringTargetはパスの中継地点のうち、現在向かっている地点となります。

steeringTarget.png

したがって、
(agent.steeringTarget - transform.position)
により方向を取得し、agent.speedをかけることで常にインスペクターでNavMeshAgentに設定したスピードで動くことになります。

結果がこちら
fixedSpeed.gif

上手く曲がれるようになりましたが、動きに滑らかさはなくなってしまいます。表現したいものによっては使えると思います。

解決策2 曲がり角でスピードを落とす

曲がり角が近くなったらスピードを落とすようにします。
以下のようにスクリプトに追加します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] GameObject target; // ゴールとなるターゲットオブジェクト

    UnityEngine.AI.NavMeshAgent agent;
    Animator animator;

    private float speed = 0.0f; // 追加:NavMeshAgentのspeedを保持するための変数

    void Start()
    {
        agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
        animator = GetComponentInChildren<Animator>();
        speed = agent.speed;            // 追加:初期値の保持
    }

    void Update()
    {
        agent.destination = target.transform.position; // ターゲットの設定
        animator.SetFloat("Speed", agent.velocity.sqrMagnitude); // Unityちゃんをアニメーションさせるためのパラメータ

        // 以下を追加
        if (Vector3.Distance(agent.steeringTarget, transform.position) < 1.0f)
        {
            agent.speed = 1.0f;
        }
        else
        {
            agent.speed = speed;
        }
    }
}

先ほどの steeringTarget で曲がり角の位置を取得し、距離が近くなったときに speed を 1.0f に変更しています。

その結果がこちら
slowSpeed.gif

スムーズに曲がれるようになりました。

VSCodeでOmniSharpのdebuggerが使えない

$
0
0

VSCodeでC#の拡張機能を入れたのにエラーが出る

困ったことにエラーが消えなかったのです。
全く新しいMacOS(MacBook Pro Catalina 10.15.1)にVisual Studio Code(1.39.2)をいれて、C#の拡張機能(Omnisharp)をいれたところ、次のエラーがでました。
「The .NET CLI tools cannot be located. .NET Core debugging will not be enabled. Make sure .NET CLI tools are installed and are on the path.」
dotnet sdkが入っていないよってことなので、リンク先からダウンロードしてインストールします。
- https://dotnet.microsoft.com/download

だけど、エラーは消えません。
コンソール(OUTPUTを選択して、C#を選ぶ)と「Failed to spawn 'dotnet --info'」と出ています。
ターミナルでdotnet --infoを打ってみたら、ちゃんとでます。

bash-3.2$ dotnet --info
.NET Core SDK (global.json を反映):
 Version:   3.0.100
 Commit:    04339c3a26

ランタイム環境:
 OS Name:     Mac OS X
 OS Version:  10.15
 OS Platform: Darwin
 RID:         osx.10.15-x64
 Base Path:   /usr/local/share/dotnet/sdk/3.0.100/

Host (useful for support):
  Version: 3.0.0
  Commit:  7d57652f33

.NET Core SDKs installed:
  3.0.100 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

なぜや。

ためしたこと

VSCode、拡張機能、dotnet sdkの再インストール

それでうまく行けるなら記事にしません。
なお、VSCodeと拡張機能の削除は下記を参考に完全削除しました。
また、再インストール時にはbrew cask install dotnet-sdkを使ってみました。

pathの追加

パスが通っていないかと思ったので、パスを通したけどうまくいかず。
ということで、下記を参考にシンボリックリンクをはってみたのですがこれもうまくいかず。。。

うまくいったのは

ダウンロード先を変えたらうまく行きました。
なにこれ。
エラーが出ていたとき(brew使ったとき)は「dotnet-sdk-3.0.100-osx-x64.pkg」というファイルだったのです。
これを下記リンクからダウンロードした「dotnet-sdk-3.0.100-osx-gs-x64.pkg」でインストールしたらうまく行きました。
なにこれ。

比較してみると、うまくいったバージョンは.Net Core SDKのcommitが[02df19ceac]になっていました。
(どっちが新しいのかはわかりませんが)
brewのfomulaもgsなしの.pkgになっていました。
(dotnet-sdkとdotnet両方とも)

結局、なぜdotnetコマンドが通らなかったかは(あと、-gsのありなしの.pkgの違いも)わかりませんでしたが、うごいたのでよしとします。
うまくいっている人と同じやり方をしたほうがいいですよね。。。南無三。

Viewing all 9366 articles
Browse latest View live