20210213のC#に関する記事は4件です。

Unityの野球ゲームで簡単に変化球を実装する

Unityの野球ゲームで、投手に変化球を投げさせたい場合の簡易的な方法を書きました。

あくまで簡易的なので若干リアルさには欠けるかもしれません。

手順

PlaneとSphereを用意します。
Sphereとカメラの距離はある程度空けておきます。
653885a600eab7c7c73992f394624da7.png

ボールをど真ん中に投げてみる

まずはボールを真ん中に投げてみます。
SphereにRigidBodyコンポーネントを追加し、スクリプトは以下の通りで試してみます。
powerは1000ぐらい。

Ball.cs
public class BallTest : MonoBehaviour
{
    public Rigidbody _rb;
    public int power;
    Vector3 direction = Vector3.back;//カメラの方向に投げてみる

    void Start()
    {
        _rb.AddForce(direction * power);
    }
}

Image from Gyazo
powerは丁度いいですが、球が転がってしまったので力を与える方向(direction)を修正します。

    Vector3 direction = new Vector3(0,0.2f,-1.0f);

Image from Gyazo
これをど真ん中のストレートとします。

球に変化を与える

飛ばした球がColliderに当たった際に変化方向に力を加えるという方法でいきます。
以下のように空のオブジェクトを作成しBoxColliderを追加。ボールの軌道の被るように配置します。
isTrrigerはONにします。
Image from Gyazo

最初はスライダーをかけてみます。
Sphereオブジェクトのtagは"Ball"に変更しておきます。

ChangeCollider.cs
    int changePower = 100; //変化させる力
    Vector3 sliderDirection = new Vector3(1.0f, -1.0f, 0); //変化の方向

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.tag == "Ball")
        {
            Rigidbody ball_rb = other.gameObject.GetComponent<Rigidbody>();
            ball_rb.AddForce(sliderDirection * changePower);//ボールに力を加える
        }
    }


Image from Gyazo
なかなかいい感じですね。

Colliderの位置を前後させることで曲がり始めのタイミングをずらせます。
Z軸に力を加えるとブレーキをかけることもできます。

様々な変化をさせてみた

せっかくなので投手を準備して色々な変化球を試してみました。
directionをいじれば様々な変化球を実現できます。
ezgif.com-gif-maker.gif

投手のアセットはこちらを使用しました。

1d589c9b7008af5afc597af6df96509e.png

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

ASP.NET Core MVC WWWROOT以外の静的ファイル

ASP.NET Core 2.1~5.0までは同じみたい。
実際にプログラムで確認したのはASP.NET Core 3.1です。

課題

システムの構成はこうなります
  • 動的なシステム部分(ASP.NET Core MVCで構築)
  • 静的な既定ドキュメント「展示、説明」など(ログインかもしくは申込などリンクで動的な部分と繋がる)

「wwwroot」以外の場所から静的な部分を提供する、理由は展開して確認

  上記二つの部分、それぞれ独自のcss,javascript,imageが存在します。
  基本的にウエブシステムを構築する際は「wwwroot」に静的内容とcss,javascript,imageなどを配置することになります。でも今回の場合は全部一ヶ所に配置すると、色々と混乱し易くなります。
  そもそも上の二つの部分はそれぞれ単独に動けるから、二つのシステムと考えても全然問題ありませんし、一ヶ所に配置するのもどうかと思う。
  開発の観点から見ると、静的の部分は基本的にデザイナーかデザイン会社にお願いして、動的な部分と並行して開発することになりますので。
  以上の原因で「wwwroot」以外の場所から静的な部分を提供するほうが良い。

実装

Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //前略...
    //wwwrootに対して静的コンテンツサービスを登録
    app.UseStaticFiles();
    //wwwroot以外に静的コンテンツサービスを登録
    app.UseFileServer(new FileServerOptions
    {
        //登録するフォルダーを指定、絶対パスを設定する必要がある
        FileProvider = new PhysicalFileProvider("C:\\StaticContent"),
    });
    //後略...
}

そうすることで「https://<ホスト名>」をアクセスした場合、「C:\StaticContent\」の中で以下のファイルを検索し

  • default.htm
  • default.html
  • index.htm
  • index.html

最初に見つけたファイルをクライアントへ返す。
「wwwroot」のなかに「index.html」を配置しても、検索されることはない。
静的な既定ドキュメントを提供するには「UseDefaultFiles」でサービスを登録する必要があります。
静的なファイルに関するサービスは以下四つがある

  • UseStaticFiles
  • UseDefaultFiles
  • UseDirectoryBrowser
  • UseFileServer

この四つのサービスに関して、詳しく見ていきましょう。

UseStaticFiles

静的ファイルを提供するサービスです。

Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //前略...
    //パラメータなしの場合は「wwwroot」フォルダー及びサブフォルダの全ファイルを静的コンテンツとして提供する
    //デフォルトでは認証と認可ミドルウェアの前に登録されているので、「wwwroot」の静的コンテンツをアクセスする際は認証と認可は不要
    app.UseStaticFiles();
    //後略...
    app.UseAuthentication();
    app.UseAuthorization();
    //後略...
}

UseStaticFilesがパラメータ無しの場合は「wwwroot」を指しているので、最初から入っている静的ファイルをアクセスできる理由はここにあります。

Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //前略...
    //パラメータなしの場合は「wwwroot」フォルダー及びサブフォルダの全ファイルを静的コンテンツとして提供する
    app.UseStaticFiles();
    //「wwwroot」以外に「StaticContent」フォルダーも静的ファイルを提供する
    //app.UseStaticFiles();の呼び出しがないと「wwwroot」へのアクセスができなくなる
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider("C:\\StaticContent")
    });
    //後略...
    app.UseAuthentication();
    app.UseAuthorization();
    //後略...
}

UseDefaultFiles

既定のドキュメントの提供

Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //前略...
    app.UseDefaultFiles();
    app.UseStaticFiles();
    //「wwwroot」以外に「StaticContent」フォルダーも静的ファイルを提供する
    //app.UseStaticFiles();の呼び出しがないと「wwwroot」へのアクセスができなくなる
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider("C:\\StaticContent")
    });
    //後略...
    app.UseAuthentication();
    app.UseAuthorization();
    //後略...
}

UseDefaultFilesは「wwwroot」にある以下のファイルを既定ドキュメントとして提供

  • default.htm
  • default.html
  • index.htm
  • index.html

上記四つ以外のファイルを既定ドキュメントにするには以下のようにパラメータを設定する必要がある

Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //前略...
    //既定ドキュメントの設定
    DefaultFilesOptions defaultFilesOptions = new DefaultFilesOptions();
    //デフォルトのファイル名設定をクリア
    defaultFilesOptions.DefaultFileNames.Clear();
    //既定ドキュメント名を指定
    defaultFilesOptions.DefaultFileNames.Add("myIndex.html");
    app.UseDefaultFiles(defaultFilesOptions);

    app.UseStaticFiles();
    //「wwwroot」以外に「StaticContent」フォルダーも静的ファイルを提供する
    //app.UseStaticFiles();の呼び出しがないと「wwwroot」へのアクセスができなくなる
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider("C:\\StaticContent")
    });
    //後略...
    app.UseAuthentication();
    app.UseAuthorization();
    //後略...
}

UseDefaultFilesが影響できるのは「wwwroot」フォルダーのみ、独自に新規で追加した静的コンテンツフォルダーには影響しない

UseDirectoryBrowser

サーバー上に指定したディレクトリ内のディレクトリを一覧表示にする、だがセキュリティー上の問題があるため、利用を避けるべき。デフォルトは無効になっている。

Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    //前略...
    //EnableDirectoryBrowsing = trueの時、下記を呼び出す必要がある
    services.AddDirectoryBrowser();
    //後略...
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //前略...
    app.UseStaticFiles();
    //「wwwroot」以外に「StaticContent」フォルダーも静的ファイルを提供する
    //app.UseStaticFiles();の呼び出しがないと「wwwroot」へのアクセスができなくなる
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider("C:\\StaticContent")
    });
    //ディレクトリ参照を起用する
    app.UseDirectoryBrowser(new DirectoryBrowserOptions
    {
        //ディレクトリ参照の対象フォルダー
        FileProvider = new PhysicalFileProvider("C:\\StaticContent"),
        //ディレクトリ参照するためのURL相対パス
        //https://<hostname>/DirectoryBrowser
        RequestPath = "/DirectoryBrowser"
    });
    //略...
    app.UseAuthentication();
    app.UseAuthorization();
    //後略...
}

「https://<ホスト名>/DirectoryBrowser」でアクセスすると、「C:\StaticContent」内のディレクトリを一覧で表示される

UseFileServer

UseStaticFiles、UseDefaultFiles、UseDirectoryBrowser の機能を兼ね備えているのがUseFileServerです

Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    //前略...
    //EnableDirectoryBrowsing = trueの時、下記を呼び出す必要がある
    services.AddDirectoryBrowser();
    //後略...
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //前略...
    app.UseStaticFiles();
    //「wwwroot」以外に「StaticContent」フォルダーも静的ファイルを提供する
    app.UseFileServer(new FileServerOptions
    {
        //静的ファイルを提供する対象フォルダー
        FileProvider = new PhysicalFileProvider("C:\\StaticContent"),
        //デフォルトがtrue:デフォルトファイルを起用
        EnableDefaultFiles = true,
        //デフォルトがfalse:ディレクトリブラウザ禁止
        EnableDirectoryBrowsing = true
        //静的コンテンツをアクセスするための相対パス
        RequestPath = "/StaticFiles",

    });
    //略...
    app.UseAuthentication();
    app.UseAuthorization();
    //後略...
}

まとめ

UseStaticFilesとUseDefaultFilesの既定では下記のFileProviderを提供している

FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "wwwroot"));

だからパラメータ無しの場合の操作対象が「wwwroot」となっている。
でも既定の「wwwroot」を変更することは可能
変更方法は

Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            //ここで既定の「wwwroot」を指定したフォルダーに変更できる
            webBuilder.UseStartup<Startup>().UseWebRoot("C:\\NewWebRoot");
        });

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

ビット演算を理解する

なぜビット演算が必要なのか?

大きく2つのメリットがあります。

・多くのbool変数を1つの変数にまとめる事が出来る
・1つにまとまってるので操作が色々便利

例えば戦闘中のゲームのキャラクターの状態を示す以下のフラグ(bool型変数)があったとします。

bool isSleep;           //眠らされているか?
bool isPoison;          //毒に侵されているか?
bool isNoMagic;         //魔法を封じ込められているか?
bool isConfusion;       //混乱して敵味方の区別がつかないか?
bool isAttackDown;      //攻撃力がダウンしているか?
bool isDefDown;         //防御力がダウンしているか?
bool isAttackUp;        //攻撃力がアップしているか?
bool isDefUp;           //防御力がアップしているか?

ビット演算を使えば、全て1つの変数だけで制御できます。
ドラクエだとキャラクターの状態変化は数十もあるので1つ1つbool変数にしていたら面倒です。

そして回復魔法でマイナスの状態だけ(攻撃力・防御力アップ以外)を回復させるなど一括操作も簡単にできます。

このページの目的は、最後に載せているゲームのキャラクターの状態を管理するビット演算のコードを理解できるようにする事です。

だいたいこんな感じ

0001の眠りと、0010の毒を足すと、0011の毒に侵されて眠ってる状態が表現できます。
ただビット演算では、+-ではなく、|&~などを使います。

2進数の書き方

ビット演算するには2進数の書き方を覚えた方が便利です。ついでに16進数の書き方も示します。

var dec = 123;      //10進数
var bin = 0B111;    //2進数(0Bか、0bを頭につける)4 + 2 + 1 = 7
var hex = 0X1F;     //16進数(0Xか、0xを頭につける)16 + 15 = 31

//C#7以降では見やすいように"_"で、区切る事が出来ます
var bin2 = 0B_0011_1111_1001;   //2進数
var hex2 = 0X_FF_2F_FF;         //16進数

10進数を2進数で表示

10進数で返されたビット演算の結果は、2進数で確認した方がフラグの状態を理解しやすいです。
Convert.ToStringメソッドの第2引数に2を指定すると、第1引数を2進数に変換されます。
ちなみに16だと16進数に変換されます。
PadLeftメソッドは第1引数の桁になるまで、第2引数で文字列の左側を埋めます。

using System;

public class Prog {
    public static void Main() {
        int[] array = { 1, 2, 3, 4, 5, 0B111, 0XFF };

        foreach(    int i in array) {
            Console.WriteLine(  Convert.ToString(i, 2).PadLeft(8, '0')     + "  " + i);
        }
    }
}
//00000001  1
//00000010  2
//00000011  3
//00000100  4
//00000101  5
//00000111  7
//11111111  255

ビット演算子

2つの数値をビット演算子を使って計算します。1+1=2のみたいものです。
特に良く使う | と & はどっちがどっちか分からなくなるので、自分は次のイメージで覚えています。

| は、片方だけでも1があれば、回転して残像で両方1になる。
& は、両方の穴に1が収まって、はじめて1になる。

Bit.gif

using System;

class Prog {
    static void Main(string[] args) {
        int a = 0B_0101;
        int b = 0B_0110;
        Print(      a&b     );  //両方1なら1      論理積
        Print(      a|b     );  //どちらか1なら1     論理和
        Print(      a^b     );  //片方だけ1なら1    排他的論理和
        Print(      ~a      );  //0なら1に、1なら0に   反転
                                            Console.WriteLine("");
        Print(      a<<1    );  //左にシフト
        Print(      a>>1    );  //右にシフト
    }
    static void Print(int i) {
        Console.WriteLine(      Convert.ToString(i, 2).PadLeft(4, '0'));
    }
}
//0100
//0111
//0011
//11111111111111111111111111111010

//1010
//0010

良く使う演算子のパターン

ビット演算はかなり特殊なので、最初は分からなくても実際に使う段階で理解出来ます。最初はこういうものかと深く考えずに暗記するのが早いです。

左からn番目にフラグを立てる i | (1 << n)

int b = 1 << 2;     //0100      10進数だと4を表します

int i = 0B_0001;
i |= (1 << 2);      //0101      10進数だと5を表します

同じ値をXOR 演算子^を使ってゼロクリアする。

int i = 0B_0101;
i ^= i;     //0000

左からn番目のフラグを消す i &= ~(1 << n)

int i = 0B_1111;
i &= ~(1 << 2);     //1011

左からn番目のフラグが立っているか確認する i & (1 << n)

int i = 0B_0101;
Convert.ToBoolean(  i & (1 << 2));  //True
int i = 0B_0101;
bool b = (i & (1 << 2)) != 1;       //True

複数のフラグをまとめて、マスクビットを作る

int a = (1 << 1);       //0010
int b = (1 << 2);       //0100
var mask = a |= b;      //0110

int c = 0B_1111;        //1111
c &= ~mask;             //1001

enumを使ってビット演算を見やすく

ビット演算は便利ですが、フラグを数値だけで管理すると分からなくなりそうです。(0B_0100は「毒」で、0B_1000は「混乱」など自分で決めても間違えそうです‥)
enumの前に、FlagsAttribute属性を指定することで、列挙型の名前でビット演算のフラグを管理出来ます。(Flagsを外すと数値に対してビット演算しません[Flags]をコメントにすると違いが分かります)

using System;

class Prog{
    [Flags]         //enumをビットフラグにするFlagsAttribute属性
    enum Col:short {
        Red =   1 << 0,     //0001
        Green = 1 << 1,     //0010
        Blue =  1 << 2,     //0100
    };
    static void Main() {

        for(int i = 0; i <= 8; i++)
            Console.WriteLine("{0}  {1}  {2}",
                i,
                Convert.ToString(i, 2).PadLeft(4, '0'),
                (Col)i);
    }
}
//0  0000  0
//1  0001  Red
//2  0010  Green
//3  0011  Red, Green
//4  0100  Blue
//5  0101  Red, Blue
//6  0110  Green, Blue
//7  0111  Red, Green, Blue
//8  1000  8

ちなみに赤と緑をかけ合わせると黄色になり、赤と青をかけ合わせると紫色になり、赤、緑、青、全てをかけ合わせると無色になります。三原色をビット演算で組み合わせる事で虹色を表現する事が出来るわけです。

1つ具体例として、タスクのオブションで使うフラグの例を見てみましょう。

TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairnessは、列挙型のフラグです。

var task = new Task(    () => Method_A(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
        task3.Start();

TaskCreationOptions 列挙型

列挙型の説明を見ると、フィールド毎に番号がふってありますが、全て2の倍数になっています。
この整数値をビット演算で組み合わせて、1つの引数で複数のフラグを立てる事を可能にしています。番号だけだと分かりにくいので列挙型にして名前を付けている訳です。

ゲームのキャラクターの戦闘状態を制御

ビット演算の締めくくりに、ゲームの戦闘中のキャラクターの状態を次々と変化させていきましょう。

using System;

[Flags]
public enum Status {        //10進数で書く場合は、2の倍数にする
    Normal = 0,
    //マイナスの状態異常
    Sleep = 1,      //眠っている               0001
    Poison = 2,     //毒に侵されている      0010
    //プラスの状態変化
    AttackUp = 4,   //魔法で攻撃力アップ       0100
    DefUp = 8,      //魔法で防御力アップ       1000
}

public class Prog {
    public static void Main() {

        var chara = Status.Normal;  //キャラクター

        chara |= Status.AttackUp;   //味方の魔法で、攻撃力アップ!
        Print(  chara);             //AttackUp

        chara |= Status.Poison;     //敵の毒に侵される!
        Print(  chara);             //Poison, AttackUp

        chara |= Status.Sleep;      //敵に眠らされる!
        Print(  chara);             //Sleep, Poison, AttackUp

        //回復魔法(眠りと毒だけを解除、攻撃力・防御力アップはそのまま)
        Status heal = Status.Sleep | Status.Poison;

        chara &= ~heal;             //味方の回復魔法で復活!
        Print(  chara);             //AttackUp

        //攻撃力・防御力どちらか、もしくは両方がアップしているか確認
        Status up = Status.AttackUp | Status.DefUp;
        bool b = (chara & up) != Status.Normal;     //True  

        chara ^= chara;             //全ての効果を無効にする魔法を唱える!
        Print(  chara);             //Normal
    }
    //状態を確認するメソッド
    static void Print(  Status s){
        Console.WriteLine(  "{0}  {1}", Convert.ToString(   (int)s, 2).PadLeft(4,'0'), s);
    }
}

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

値型シーケンスをCast<T>, OfType<T>でキャスト出来ない

結論

LINQ自体の実装に問題がある。
Selectクエリを使う。
自分用にメモ。

いきさつ

int[]double[]にキャストしたい時にCast<double>()を使ったらエラーになってしまった。
要素のアップキャストなのになぜか失敗する。

int[] hoge = { 1, 2, 3 };
// InvalidCastExceptionが発生する
var fuga = hoge.Cast<double>();
// 結果は空(すなわちキャスト出来ていない)
var piyo = hoge.OfType<double>();

なんで

内部実装ではシーケンス要素を一旦object型にキャストしてからT型にキャストしている模様。

参考:
C# IEnumerable.Castメソッドでint→longはできない
OfTypeメソッドとCastメソッド
Enumerable.Castメソッドの罠

// 要素に対する等価式(当然例外になる)
var element = (double)((object)1);

どうする

上記リンク通りこれでOK。

var fuga = hoge.Select(a => (double)a);

終わりに

アカウントがあるのに何も書かないのはアレなので。

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