- 投稿日:2020-10-17T18:37:08+09:00
UnityのStandardAssetsで、ThirdPersonControllerが機能しなかった原因はBuildSettingsのPlatform選択にあった
起きていたこと
Unityでキャラクターを動かしたくてStandardAssetsをインポートしました。
ThirdPersonControllerをヒエラルキーにドラッグアンドドロップした後、早速キャラクターを動かそうとしてみたのですが、
- 矢印コントローラーを押下しても移動ができない
- スペースキーを押下してもジャンプしない
- しゃがむ(cキー)事だけできる
原因
Build SettingsのPlatformがAndroidになっていた。
PCに変更したところ、矢印キー押下でキャラクターが動くようになりました。
StandardAssetsを、スマホ時のキャラクター操作に使う場合はジョイステックを表示することになる
元々は、スマホ時にフリック,スワイプでキャラクターを操作したくてStandardAssetsを利用しました。
ですが調べて見たところ、StandardAssetsでスマホの操作を行う場合はジョイスティックによる操作になるみたいでした。
なのでそもそもStandardAssetsを利用しない方向となりました。
参考記事: Unity5版Standard Assetsを使って仮想ジョイスティックを実装
- 投稿日:2020-10-17T17:10:50+09:00
[.NET][C#]NUnitのFileAssert・DirectoryAssertアサーションメソッド一覧
はじめに
前回、NUnitのCollectionAssertクラスのアサーションメソッドを一通り試したので
今回はFileAssertクラス、DirectoryAssertクラスのアサーションメソッドを一通り試してみる。実施環境
.NET:3.1.401
C#:8.0
NUnit:3.12.0FileAssertクラス
NUnitのClassicModelのアサーションクラスの1つ。
クラス名の通り、ファイルを検査する。1. AreEqual、AreNotEqualメソッド
AreEqualメソッドは、2つのファイル内容が同じ内容であることを検査する。
AreNotEqualメソッドは、その逆の判定を行う。[TestCase] public void FileAreEqualTest1() { // Test OK. using (FileStream expected = new FileStream(@"M:\work\TestA1.txt", FileMode.Open), actual = new FileStream(@"M:\work\TestA2.txt", FileMode.Open)) { FileAssert.AreEqual(expected, actual); } // Test OK. FileAssert.AreEqual( new FileInfo(@"M:\work\TestA1.txt"), new FileInfo(@"M:\work\TestA2.txt")); // Test OK. FileAssert.AreEqual( @"M:\work\TestA1.txt", @"M:\work\TestA2.txt"); }上記のように引数にFileStream型、FileInfo型、もしくはstring型のファイルパスを指定することが可能。
上記例だとファイルのTestA1.txtとTestA2.txtの内容が一致していれば、テストOKとなる。[TestCase] public void BinaryFileAreEqualTest() { // Test OK. FileAssert.AreEqual( @"M:\work\TestA1.jpg", @"M:\work\TestA2.jpg"); }テキストファイルだけではなく、バイナリファイルも検査できる。
2. Exists、DoesNotExistメソッド
Existsメソッドは、指定したファイルが存在することを検査する。
DoesNotExistメソッドは、その逆の判定を行う。[TestCase] public void FileExistsTest() { // Test OK. FileAssert.Exists(new FileInfo(@"M:\work\TestA1.txt")); // Test OK. FileAssert.Exists(@"M:\work\TestA1.jpg"); }引数にFileInfo型、もしくはstring型のファイルパスを指定することが可能。
FileStream型が無いのは、ファイルが存在しなかった場合、FileStream型のオブジェクトを生成する段階でFileNotFoundExceptionが発生するからだろう。DirectoryAssertクラス
NUnitのClassicModelのアサーションクラスの1つ。
クラス名の通り、ディレクトリを検査する。1. AreEqual、AreNotEqualメソッド
AreEqualメソッドは、2つのディレクトリが同じであることを検査する。
AreNotEqualメソッドは、その逆の判定を行う。[TestCase] public void DirectoryAreEqualTest() { // Test OK. DirectoryAssert.AreEqual( new DirectoryInfo(@"M:\work\TestA1"), new DirectoryInfo(@"M:\work\TestA1")); // Test NG. (ディレクトリ配下のファイル内容は同じ) DirectoryAssert.AreEqual( new DirectoryInfo(@"M:\work\TestA1"), new DirectoryInfo(@"M:\work\TestA2")); }引数はDirectoryInfo型のみ指定可能。
ディレクトリそのものを検査するので、ディレクトリ配下のファイル内容が全く同じ内容であっても、異なるディレクトリを指定するとAreEqualメソッドはテストNG、AreNotEqualメソッドはテストOKとなる。2. Exists、DoesNotExistメソッド
Existsメソッドは、指定したディレクトリが存在することを検査する。
DoesNotExistメソッドは、その逆の判定を行う。[TestCase] public void DirectoryExistsTest() { // Test OK. DirectoryAssert.Exists(new DirectoryInfo(@"M:\work\TestA1")); // Test OK. DirectoryAssert.Exists(@"M:\work\TestA1"); }AreEqualメソッドとは異なり、こちらはstring型での指定も可能。
まとめ
ClasicModelのアサーションメソッドは一通り試してみた。
[.NET][C#]NUnitのClassic Modelアサーションメソッド一覧 ※Assertクラス
[.NET][C#]NUnitのStringAssertアサーションメソッド一覧
[.NET][C#]NUnitのCollectionAssertアサーションメソッド一覧
[.NET][C#]NUnitのFileAssert・DirectoryAssertアサーションメソッド一覧なお、ConstraintModelでは
Assert.Thatメソッド
一つで
上記アサーションクラスの制約(テスト判定条件)を全て表現できる。
(記事にしたりするかは保留)参考
- 投稿日:2020-10-17T17:09:58+09:00
Unity講座 #5コンパスを作ってみる
はじめに
某インターンで制作物のプレゼンをする必要があったため、一年生の頃に作ったゲームをつい最近アップデートしました。
そこで新たに実装した機能の実装方法を解説しようと思い、この記事を書いています。せっかくなのでUnity講座の一環としてしまおうということで書いていますが、前回までの内容に比べ、内積・外積といった数学的な内容のものも含んでいるので、前提知識がないとちょっと厳しいかもしれません。
数学のお話はなるべく補足説明を入れて解説しようと思いますが「分からない」とか「間違いがあるよ」などなどあればコメントください。
それでは解説に入っていきましょう。機能概要
自分のゲームは簡単に言うと敵が追ってきて、捕まるとゲームオーバーというゲームです。
したがって、どのくらい近いかを確認したりしたいですが、ミニマップのようなものを作ってしまうと簡単になりすぎてしまうかなと考えました。
これまでの機能として、後方確認はいつでもできましたが、壁の向こう側にいたりすると全く気づけませんでした。
そこで、方向だけは常に分かるようにしようと考えました。
方角だけでもわかれば逃げやすくなるだろうということですね。一応距離をついでに表示させたりも考えましたが、そこまで来るとミニマップと変わらないかなぁと思って、今回は実装を見送りました。
自分のゲームでは追ってくる敵に対してコンパスを適用していますが、ゴール地点だったり、アイテムを目標にすることも可能ですので、使えるゲームジャンルは割と広いかなと思っています。それでは早速実装に入っていきましょう。
前準備(Canvas&デバッグ用)
まず最初に、コンパスのリングとデバッグ用のテキストを準備しました。
console.log()はたしかに便利なのですが、プレイ中に確認しにくいのと、Update内で実行している処理で呼び出すと、ログが埋まってしまうので、実装中は画面に数値を表示することをおすすめします。
空オブジェクトのEnemyCompassの直下にUI/Textを作ります。
今回は名前を変えていませんが、プロジェクト内に既に複数のTextが存在しているならば、EnemyCompassDebugTextとかに変更しておいたほうがいいかもしれません。あとはコンパスのフレームに使えそうな素材を見繕ってきました。
「円形フレーム」などで検索すれば、他にもいろいろな素材サイトが見つかると思うので、自分好みのフレームを探してきてください。
今回のコンパスでは下手に色がついているよりは黒単色のほうがいいかな、と思いImageのColorを黒にしました。
コンパスの後ろの画面を隠さないように、アルファ値をを100くらいにして半透明にしておきましたが、別に気にならないという方は変更しなくても大丈夫です。人によって違うので割愛しますが、ここで目標物のアイコンなどの素材を準備して、置いておくといいと思います。
ここまでできればコンパス機能の見た目は大丈夫かなと思います。目標物との角度計算
内積・外積
さて、ここからは数学のお時間です。
流石にベクトルの話からするのは大変なので、高校数学の美しい物語などを確認してもらうとします。
今回は実装方法の話をしたいので、なぜこうなるかについては解説しません。
気になる方はベクトルの勉強をしてみてください。コンパス機能を実装するためには、自分の正面方向のベクトルと目標物への方向ベクトルのなす角の大きさを計算する必要があります。
正面方向のベクトルは
Player.transform.forward
で、目標物への方向ベクトルは
Target.transform.position-Player.transform.position
で求めることができますね(ベクトルABの求め方は原点をOとしてベクトルOB - ベクトルOAで求めるという公式)この2つのベクトルのなす角の大きさを調べるのに、内積や外積が役に立つのです。
※一応図を自作しましたが正直出来はいまいちなので、参考リンクの画像などを参照してください
内積は2つのベクトルの各成分の掛け算を取ります。
↑a・↑b=a.x*b.x+a.y*b.y
上の式と、この図で示した式を使うことで、2つのベクトルのなす角の大きさをθとしたとき、cosθを求めることができるのです。
今回は3Dゲームなのでy方向の情報もありますが、今回はy方向の情報を切り捨てて二次元の内積を求めてくれる関数、dot_2dを作成しました。
公式ではaベクトルの大きさとbベクトルの大きさを掛けた上で内積の大きさを割っていますが、どちらのベクトルも正規化を行い、大きさを1にしてから計算することで手間を省けます。float dot_2d(Vector3 aVec, Vector3 bVec) { return (aVec.x * bVec.x + aVec.z * bVec.z); }さて、これだけでコンパスができると思いきや、cos関数はcosθ=cos(-θ)という厄介な(ときには便利な)性質があります。
つまり、なす角の大きさはわかっても、正面から見て右に向かう角の大きさなのか、左に向かう角の大きさなのか区別がつかないのです。そこで使うのが外積です。
こっちは大学数学の内容なので知らない人のほうが多いと思いますが、これを知らずにゲームを作るのはすごく難しいと言うくらい、使えるものです。
こっちは外部サイトの図を見てきてくださいと言う形になりますが、求めることで2つのベクトルのなす角の大きさをθとしたときに、sinθを求めることが出来ます。内積と違い、2次元と3次元で意味が違います。
2次元のときにはaベクトルとbベクトルで作られる平行四辺形の面積を、3次元ではaベクトルとbベクトルのなす平面の法線ベクトルを求めます。
面積(数値)と法線ベクトル(ベクトル)というように、結果の単位が変わるのは面白いですね。外積は、内積と違ってマイナスの値も取ります。
また、aベクトルとbベクトルの外積と、bベクトルとaベクトルの外積の値は違うというように、演算の順序が関係してきます。
aベクトルとbベクトルの外積は、aベクトルをx軸正の方向と重なるように置いたとき、
偏角が0からπの間なら正の値に、
偏角がπから2πの間なら負の値になります。
偏角が0やπのときは、2つのベクトルが平行になるので、0になります。
こちらも、y方向の情報を切り捨てて外積を計算してくれる関数、cross_2dを作成しました。float cross_2d(Vector3 aVec,Vector3 bVec) { return (aVec.x * bVec.z) - (aVec.z * bVec.x); }実装に使ってみる
内積と外積を使うことで、2つのベクトルのなす角の大きさθについて、cosθとsinθの値を求めることが出来ました。
この2つの情報を使うことで、偏角が第何象限にあるかを調べることが出来ます。
そうすればあとはAsinやAcosを用いて、偏角θを求めることが出来ます。var PlayerForward = Player.transform.forward; var ToEnemyVec = Enemy.transform.position - Player.transform.position; ToEnemyVec = ToEnemyVec.normalized; float SinPlayerToEnemy = cross_2d(PlayerForward, ToEnemyVec); float CosPlayerToEnemy = dot_2d(PlayerForward, ToEnemyVec); //第何象限に敵が位置しているか int State = 0; //第一象限のとき if (SinPlayerToEnemy > 0 && CosPlayerToEnemy > 0) { State = 0; } //第二象限のとき else if (SinPlayerToEnemy > 0 && CosPlayerToEnemy < 0) { State = 1; } //第三象限のとき else if (SinPlayerToEnemy < 0 && CosPlayerToEnemy < 0) { State = 2; } //第四象限のとき else if (SinPlayerToEnemy < 0 && CosPlayerToEnemy > 0) { State = 3; } float Rad = Mathf.Asin(SinPlayerToEnemy); //Asinでは(-π/2,π/2)の範囲しか表せないので、象限によって範囲を(-π,π)に拡張する switch (State) { case 0: break; case 1: Rad = Mathf.PI - Rad; break; case 2: Rad = -Mathf.PI - Rad; break; case 3: break; } RadText.text = "Rad:" + Rad;RadTextが宣言なしに出てきていますが、ここは最初で設定したデバッグ用テキストのTextを変更するように自分で変えてみてください(一応最後にプログラム全体を載せるので参考にしてください)。
別の使いみちをしている方はEnemyを適当な変数名に変更して使ってください。
UI更新
さて、これで目標物への角度を求めることが可能になりました。
しかし、この情報を使ってUIを更新してあげないと使えませんよね。
ここまでついてこれた人なら簡単だと思いますが、UIの更新には求めた偏角を使って、
目標物の画像の位置を(x,y)=(cosθ,sinθ)にしてあげれば大丈夫です。
ただ、このままだと半径が1の単位円上を動くので、UIとしては動きが小さいです。
適当な長さを掛けてあげて、コンパスのリング部分の画像とうまく合うように調整してください。Vector3 ImageTransform = new Vector3(uiImageRadius * Mathf.Cos(Rad), uiImageRadius * Mathf.Sin(Rad), 0); EnemyImage.rectTransform.localPosition = ImageTransform;さて、これで実行してみるとうまくいかないはずです。
90度分右にずれていませんか?ピンときた方もいるかもしれませんが、二次元平面を半径と偏角を用いて表すときを思い出してみましょう。
偏角は、x軸正方向から反時計回りに動いていきましたね。
つまり、UIではプレイヤーの正面が始まりとして考えているのに、偏角はプレイヤーの右方向を始まりとして計算されてしまっているのです。対策方法は簡単で、最終的に計算された偏角にπ/2を足してあげればいいだけですね。
つまり正しい形でのUIの更新処理はこの様になります。//プレイヤーの正面始まりなので、表示のための計算にはラジアンをπ/2だけ移動させる float RadOffset = Mathf.PI / 2; Rad += RadOffset; Vector3 ImageTransform = new Vector3(uiImageRadius * Mathf.Cos(Rad), uiImageRadius * Mathf.Sin(Rad), 0); EnemyImage.rectTransform.localPosition = ImageTransform;完成形
自分のゲームでは難易度によって敵の数が変わり、複数体の敵に対応する必要があったため、UIの画像の位置更新処理を関数に分割しました。
そのため、参考にならない部分もあるとは思いますが、なるべくわかりやすくコメントをしたので、取捨選択をして、自分のゲームに組み込んでみてください。EnemyCompassControl.csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class EnemyCompassControl : MonoBehaviour { private GameObject Player; private GameObject Burger; private GameObject Fries; private GameObject Drink; //private Text RadText; [SerializeField] private Image BurgerImage; [SerializeField] private Image FriesImage; [SerializeField] private Image DrinkImage; [SerializeField] [Tooltip("敵のイメージを表示させる円の半径")] private float uiImageRadius=180; //private Text RadText; // Start is called before the first frame update void Start() { Player = GameObject.FindWithTag("Player"); Burger = GameObject.FindGameObjectWithTag("Burger"); Fries = GameObject.FindGameObjectWithTag("Fries"); Drink = GameObject.FindGameObjectWithTag("Drink"); //RadText = GetComponentInChildren<Text>(); } // Update is called once per frame void Update() { UpdateEnemyImage(Burger, BurgerImage); if (Fries != null && Fries.activeInHierarchy) { UpdateEnemyImage(Fries, FriesImage); } else { FriesImage.enabled = false; } if (Drink != null && Drink.activeInHierarchy) { UpdateEnemyImage(Drink, DrinkImage); } else { DrinkImage.enabled = false; } } float cross_2d(Vector3 aVec,Vector3 bVec) { return (aVec.x * bVec.z) - (aVec.z * bVec.x); } float dot_2d(Vector3 aVec, Vector3 bVec) { return (aVec.x * bVec.x + aVec.z * bVec.z); } void UpdateEnemyImage(GameObject Enemy,Image EnemyImage) { var PlayerForward = Player.transform.forward; var ToEnemyVec = Enemy.transform.position - Player.transform.position; //正規化することで角度を求めるときに大きさで割るのを省略する ToEnemyVec = ToEnemyVec.normalized; float SinPlayerToEnemy = cross_2d(PlayerForward, ToEnemyVec); float CosPlayerToEnemy = dot_2d(PlayerForward, ToEnemyVec); //第何象限に敵が位置しているか int State = 0; //第一象限のとき if (SinPlayerToEnemy > 0 && CosPlayerToEnemy > 0) { State = 0; } //第二象限のとき else if (SinPlayerToEnemy > 0 && CosPlayerToEnemy < 0) { State = 1; } //第三象限のとき else if (SinPlayerToEnemy < 0 && CosPlayerToEnemy < 0) { State = 2; } //第四象限のとき else if (SinPlayerToEnemy < 0 && CosPlayerToEnemy > 0) { State = 3; } float Rad = Mathf.Asin(SinPlayerToEnemy); //RadText.text = "Rad:" + Rad; //Asinでは(-π/2,π/2)の範囲しか表せないので、象限によって範囲を(-π,π)に拡張する switch (State) { case 0: break; case 1: Rad = Mathf.PI - Rad; break; case 2: Rad = -Mathf.PI - Rad; break; case 3: break; } //プレイヤーの正面始まりなので、表示のための計算にはラジアンをπ/2だけ移動させる float RadOffset = Mathf.PI / 2; Rad += RadOffset; Vector3 ImageTransform = new Vector3(uiImageRadius * Mathf.Cos(Rad), uiImageRadius * Mathf.Sin(Rad), 0); EnemyImage.rectTransform.localPosition = ImageTransform; } }さいごに
実装してる自分ではわかることでも、人に伝えるために説明し直すのはやはり大変ですね。
正直、同じ内積・外積を使うやり方でも、より簡単な処理方法があるのではないかとも考えています。
もしこういう処理はどうかな?みたいに思ったことがありましたら、コメントなど頂けるとありがたいです。
ここが分からなかった、という意見でもいいので、どうぞ気軽にコメントを下さい。
- 投稿日:2020-10-17T15:59:45+09:00
Java/JavaScript/C#で暗号化する
各言語で暗号化。
やってみると、暗号化後の文字列が各言語で違ってしまったりして、意外とハマったりします。
ここでは、Java/JavaScript/C#を取り上げてみます。暗号化
Java
まずは、Java。
Crypto.javaimport java.nio.charset.StandardCharsets; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public final class Crypto { private static final String KEY = "1234567890abcdef"; private static final String IV = "abcdef1234567890"; private Crypto() { } private static Cipher createCipher(int mode) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec key = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), "AES"); IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)); cipher.init(mode, key, iv); return cipher; } public static String encrypt(String text) { try { Cipher cipher = createCipher(Cipher.ENCRYPT_MODE); Base64.Encoder encoder = Base64.getEncoder(); return encoder.encodeToString(cipher.doFinal(text.getBytes(StandardCharsets.UTF_8))); } catch (Exception e) { throw new RuntimeException(e); } } public static String decrypt(String text) { try { Cipher cipher = createCipher(Cipher.DECRYPT_MODE); Base64.Decoder decoder = Base64.getDecoder(); return new String(cipher.doFinal(decoder.decode(text))); } catch (Exception e) { throw new RuntimeException(e); } } }JavaScript
次にJavaScript。実行環境はNode.jsです。
Crypto.jsconst crypto = require('crypto') const KEY = '1234567890abcdef' const IV = 'abcdef1234567890' function createCipher(mode) { return crypto[mode]('aes-128-cbc', KEY, IV) } function encrypt(text) { const cipher = createCipher('createCipheriv') const encrypted = cipher.update(text); return Buffer.concat([encrypted, cipher.final()]).toString('base64') } function decrypt(text) { const buf = Buffer.from(text, 'base64') const cipher = createCipher('createDecipheriv') const decrypted = cipher.update(buf); return Buffer.concat([decrypted, cipher.final()]).toString('utf-8') }C#
最後にC#。
Crypto.csusing System; using System.Security.Cryptography; using System.Text; public sealed class Crypto { private const string KEY = "1234567890abcdef"; private const string IV = "abcdef1234567890"; private Crypto() { } private static AesManaged CreateAesManaged() { AesManaged aes = new AesManaged(); aes.KeySize = 256; aes.BlockSize = 128; aes.Mode = CipherMode.CBC; aes.IV = Encoding.UTF8.GetBytes(IV); aes.Key = Encoding.UTF8.GetBytes(KEY); aes.Padding = PaddingMode.PKCS7; return aes; } public static string Encrypt(string text) { AesManaged aes = CreateAesManaged(); byte[] byteText = Encoding.UTF8.GetBytes(text); byte[] encryptText = aes.CreateEncryptor().TransformFinalBlock(byteText, 0, byteText.Length); return Convert.ToBase64String(encryptText); } public static string Decrypt(string text) { AesManaged aes = CreateAesManaged(); byte[] src = System.Convert.FromBase64String(text); byte[] dest = aes.CreateDecryptor().TransformFinalBlock(src, 0, src.Length); return Encoding.UTF8.GetString(dest); } }結果
暗号化(
encrypt
)すると、以下の結果になります。
- ""(空文字)=> 0e8Yyuobp55/giEyC01lbg==
- alz1590-'$ => cMgaJ1X/cVENp+rnMo/8Kw==
- あむんアムンアムン亜無漚錡?? => jXw9bzISBZsndhcrJx/gJU7ltBzSp34xn6CL3xBbNE2yC0FhQBisP27WOXan/W2o
復号(
decrypt
)すると、元の文字列が得られます。補足
IV
は初期化ベクトルです。同じデータであっても違う暗号文にするための文字列です。KEY
/IV
は長さに決まりがあるので注意。
- 投稿日:2020-10-17T15:52:38+09:00
64bit OS上の32bitアプリで32bit/64bit両方のProgram Filesフォルダを取得
Program.csusing System; namespace ShowProgramFilesFolder { class Program { static void Main(string[] args) { // for 32bit Console.WriteLine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)); // for 64bit Console.WriteLine(Environment.ExpandEnvironmentVariables("%ProgramW6432%")); } } }実行結果C:\Program Files (x86) C:\Program Files
- 投稿日:2020-10-17T14:30:10+09:00
C# - TabControl, TabPageのサンプル コードべた書き(Visual Studio不使用)
意外とTabControl&TabPageを使うサンプルソースがサクッと見つからなかったので、メモ代わりに置いておきます。
キャプチャ
ソースコード
TabControl
にタブTabPage
を登録して使います。using System; using System.Drawing; using System.Windows.Forms; class SampleForm : Form { TabControl tabc; TabPage tabp1; TabPage tabp2; SampleForm() { ClientSize = new Size(500, 300); Controls.Add(tabc = new TabControl(){ Dock = DockStyle.Fill, }); tabp1 = new TabPage(){ Text = "Sample Page #1", Dock = DockStyle.Fill, }; tabc.TabPages.Add(tabp1); tabp2 = new TabPage(){ Text = "Sample Page #2", Dock = DockStyle.Fill, }; tabc.TabPages.Add(tabp2); tabp1.Controls.Add(new Button(){ Text="Button on tabp1", Dock = DockStyle.Fill, }); tabp2.Controls.Add(new Button(){ Text="Button on tabp2", Dock = DockStyle.Fill, }); } [STAThread] static void Main(string[] args) { Application.Run(new SampleForm()); } }参考サイト
- 投稿日:2020-10-17T14:09:42+09:00
C# - SplitContainerのサンプル コードべた書き(Visual Studio不使用)
意外とSplitContainerを使うサンプルソースがサクッと見つからなかったので、メモ代わりに置いておきます。
キャプチャ
ソースコード
SplitterDistance
は、親であるForm
のコンストラクタ内で設定してもうまく反映されないようなのでLoad
イベントにつっこんでみた。(そのうち調べる・・・かも・・・)using System; using System.Drawing; using System.Windows.Forms; class SampleForm : Form { SplitContainer spl1; SplitContainer spl2; SampleForm() { ClientSize = new Size(500, 300); Controls.Add(spl1 = new SplitContainer(){ Dock = DockStyle.Fill, Orientation = Orientation.Horizontal, // 横線で上下に分割 // SplitterDistance = 250, // 分割線の位置を指定[pixel] Horizontalの場合は上端からの位置 BorderStyle = BorderStyle.FixedSingle, SplitterWidth = 2, FixedPanel = FixedPanel.Panel1, // spl1が属すコントロールがリサイズされたときに、Panel1側のサイズ(分割線)を保持(固定)する }); spl1.Panel2.Controls.Add(spl2 = new SplitContainer(){ Dock = DockStyle.Fill, Orientation = Orientation.Vertical, // 縦線で左右に分割 // SplitterDistance = 100,// 分割線の位置を指定[pixel] Verticalの場合は左端からの位置 BorderStyle = BorderStyle.Fixed3D, SplitterWidth = 2, IsSplitterFixed = true, }); Load += (s,e)=>{ spl1.SplitterDistance = 150; // 分割線の位置を指定[pixel] Horizontalの場合は上端からの位置 spl2.SplitterDistance = 200; // 分割線の位置を指定[pixel] Verticalの場合は左端からの位置 }; spl1.Panel1.Controls.Add(new Button(){ Text="Button on spl1.Panel1", Dock = DockStyle.Fill, }); spl2.Panel1.Controls.Add(new Button(){ Text="Button on spl2.Panel1", Dock = DockStyle.Fill, }); spl2.Panel2.Controls.Add(new Button(){ Text="Button on spl2.Panel2", Dock = DockStyle.Fill, }); } [STAThread] static void Main(string[] args) { Application.Run(new SampleForm()); } }参考サイト
- 投稿日:2020-10-17T14:07:15+09:00
防備録 Ubuntu Linuxでcommand lineでC#を実行する。
以下のサイトを参考にさせていただきました。
https://stackoverflow.com/questions/20392243/run-c-sharp-code-on-linux-terminal/20392531#20392531
$sudo apt update $sudo apt install mono-completeちょっとインストール時間かかります。
$vi hello.cshello.csusing System; class HelloWorld { static void Main() { Console.WriteLine("Hello World!"); } }$mcs -out:hello.exe hello.cs $mono hello.exe出力
Hello World!
- 投稿日:2020-10-17T14:07:15+09:00
備忘録 Ubuntu Linuxでcommand lineでC#を実行する。
以下のサイトを参考にさせていただきました。
https://stackoverflow.com/questions/20392243/run-c-sharp-code-on-linux-terminal/20392531#20392531
$sudo apt update $sudo apt install mono-completeちょっとインストール時間かかります。
$vi hello.cshello.csusing System; class HelloWorld { static void Main() { Console.WriteLine("Hello World!"); } }$mcs -out:hello.exe hello.cs $mono hello.exe出力
Hello World!
- 投稿日:2020-10-17T13:41:48+09:00
ASP.NET MVC でアプリケーションを複数のプロジェクトに分けてみたかった
環境
- Windows10 Pro (2004) 64bit
- Visual Studio 2019
- .NET Frame Work 4.5
//会社の環境に合わせてフレームワークバージョン低くしがち要約すると
複数のWebシステムと、それを統括する1つのWebシステム…みたいな構成にしたいとき、
ただAreasで分けるだけだと、結局1プロジェクトに複数のWebシステムが全部入ってくることになってしまう。
別システムは別プロジェクトで開発したいと思ったのでちょっと調べた。
結果[AreaRegistration]なるクラスを使えばある程度出来るらしいのでまとめておく。アプローチ
メインとなるサイトのプロジェクトを立てる
メインのサイトはテンプレート[MVC]で作成する。
出来上がりはこんな感じ。
実行するとこう。親の顔より見たデフォルトページ。別システムのプロジェクトを立てる
こちらのテンプレートは空、コア参照のMVCにチェックを入れる。
//こちらのテンプレートをMVCで作成したら起動に失敗したのでとりあえず空。詳しい人何でか教えてください。
出来上がりはこう。
ControllerもViewもないからまだ動かない。別システムにとりあえず表示機能を付ける
Controllerを作成する。
Controllersフォルダを右クリックして[追加]→[コントローラー]→[MVC5コントローラー -空]SubSystemHomeController.csusing System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace SubSystemWeb.Controllers { public class SubSystemHomeController : Controller { // GET: SubSystemHome public ActionResult Index() { return View(); } } }とりあえず自動作成されたコードをそのまま使う。
Viewを作成する。
Controllerを作成するとViewsフォルダ内にSubSystemHomeフォルダ(Controller名から自動作成)が作成される。
SubSystemHomeフォルダを右クリックして[追加]→[ビュー]→ビュー名を「Index」に指定。Index.cshtml@{ ViewBag.Title = "Index"; } <h2>SubSystemIndex</h2> <!--デフォルトだと[Index]なので、[SubSystemIndex]に変更した-->//Qiitaでcshtml(Razor?)のコード入れるときの設定も誰か教えて…
デフォルトで実行するコントローラーが[SubSystemHome]なので、ルーティングを変更。
App_Startフォルダ内、RouteConfig.csを変更するRouteConfig.csusing System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace SubSystemWeb { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //defaultsに指定しているクラスのcontrollerプロパティを[Home]→[SubSystemHome]に変更 routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "SubSystemHome", action = "Index", id = UrlParameter.Optional } ); } } }別システム側にAreaRegistrationを継承したクラスを追加
今回の肝。
別システム側にAreaRegistrationクラスを継承したクラスを作成する。
SubSystemAreaRegistration.csusing System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace SubSystemWeb { public class SubSystemAreaRegistration : AreaRegistration { public override string AreaName => "SubSystem"; public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( name: "SubSystemDefault", url: "SubSystem/{controller}/{action}/{id}", defaults: new { controller = "SubSystemHome", action = "Index", id = UrlParameter.Optional } ); } } }メイン側から参照設定する
メイン側のプロジェクトから別システムをプロジェクト参照する。
いざ実行 からのエラー…
メイン側をデバッグ実行して、URLを[localhost:(PortNumber)/SubSystem]にアクセスする。
無事エラーになりました。
エラーになる理由は、ビューが見つからないから。
実行しているのはMainSiteであり、SubSystem側のビューを見に行くことができないからです。
ASP.NET MVCはビュー(cshtml)がdllに含まれないので、ファイルとして存在する必要があります。仕方ないのでビュー追加
表示されたエラーを見ると[Areas]フォルダを参照していることがわかります。
Areasフォルダは普通にArea分けするときに作るフォルダです。
エラーに表示された通り、\Areas\SubSystemを作成し、
その中にSubSystemWebのViewsフォルダをコピーします。
これがあるから冒頭で「ある程度」と書きました。
「参照設定を変えるだけで機能を追加したり削除したりできる」というお手軽さには微妙に届かない残念な結果です……再度実行したらちゃんと表示される
まとめ
- サーバーサイドだけ見ればうまくいっている。デバッグを別システム単体で実行する形であれば今回のやり方で問題なさそう。
- サイト発行した場合もAreasフォルダにViewsを放り込んでいけば動く。
- メイン側からのデバッグでサブ側のビューをデバッグする場合はひと手間必要。
以上です。
もっといいやり方がある気がするので、詳しい方教えてください。
- 投稿日:2020-10-17T09:49:09+09:00
【C#】Debug.WriteLineとConsole.WriteLineの速度を比べてみた
注意
多少雑なところがあるかもしれませんがご了承ください
Debug.WriteLineとConsole.WriteLineとは
Debug.WriteLine
デバッグについての情報を Listeners コレクションのトレース リスナーに書き込むメソッドです
VisualStudioでDebugに設定したときに出力の欄にLogを出したりできますConsole.WriteLine
説明するまでもないと思いますが
コンソールにデータを出力するメソッドです遅いと思ったら
Debug.WriteLineは一般に遅いといわれています
あまり使いすぎるとかなり遅くなってしまいます検証
どのくらい遅いかをConsole.WriteLineと比べようと思いました
検証方法
計測はそれぞれ10000回を10回繰り返した時間を計測
10000回の平均値を計算し、さらにそこから一回の速度を計算します
プログラムusing System; using System.Diagnostics; using System.Linq; namespace log_speed { class Program { static void Main(string[] args) { long[] TempTime=new long[10]; double AvDebug,AvConsole; Stopwatch sw = new Stopwatch(); for (int i = 0; i < 10; i++) { sw.Start(); for (int j = 1; j <= 10000; j++) { Debug.WriteLine(j + "回目"); } sw.Stop(); TempTime[i] = sw.ElapsedMilliseconds; sw.Reset(); } AvDebug =TempTime.Average(); for (int i = 0; i < 10; i++) { sw.Start(); for (int j = 1; j <= 10000; j++) { Console.WriteLine(j + "回目"); } sw.Stop(); TempTime[i] = sw.ElapsedMilliseconds; sw.Reset(); } AvConsole = TempTime.Average(); Console.WriteLine("Debug10000回の平均速度:" + AvDebug+ " Debug1回の平均速度:" + AvDebug/10000 + "\nConsole10000回の平均速度:" + AvConsole+ " Console1回の平均速度:" + AvConsole/10000); Console.ReadKey(); } } }変なところがあればご連絡ください
実行
実行環境
種類 メーカー パーツ名 個数 CPU AMD Ryzen 5 1600 1 マザーボード ASUS PRIME B450M-A 1 GPU nVIDIA GTX960 1 RAM G.Skill F4-2400C15-4GNT 4 SSD Samsung 860 EVO M.2 500GB 1 今回の検証ではボトルネックとなる部分は少ないと思います
IDEはMicrosoft Visual Studio Community 2019 Version 16.7.6です
対象フレームワークは.NET Framework 4.7.2です実行結果
10000回の平均速度は10000回を10回繰り返したうちの10000回にかかる時間です
約3.4倍遅いようですまとめと感想
今回比較してみて思ったことはDebug.WriteLineもそこまで遅くないということです、ただ3.4倍も速度が違うので
極力Console.WriteLineを使うほうがいいと思います
- 投稿日:2020-10-17T07:27:04+09:00
C#_条件分岐
C#における条件分岐についてまとめていきます。
どの言語でも使えるように "if" と "switch" があります。本文は以下の記事の内容を前提に記述しています。
if-else文
ある処理Aと処理Bのどちらを実行するか、その時の条件によって分岐させたい場合、if文を使用していきます。
また、同時にその条件を満たさなかった場合の処理をelseによって記述することもできます。Program.csif (a == 5) { Console.WriteLine("a is equal 5"); } else { Console.WriteLine("a is NOT equal 5"); }a=5> a is 5a=8> a is NOT 5さらに、else以降に重ねてif文を繋げることで連続して条件分岐を行うことが可能です。
Program.csif (a == 5) { Console.WriteLine("a is equal 5"); } else if (a > 5) { Console.WriteLine("a is larger than 5"); } else if (a == 0) { Console.WriteLine("a is equal zero"); } else { Console.WriteLine("a is smaller than 5 and NOT equal zero"); }a=5> a is equal 5a=8> a is larger than 5a=0> a is equal zeroa=-1> a is smaller than 5 and NOT equal zeroif文の( )の中に入る式を "条件式(評価式)" と呼び、条件式はbool値(論理値)を返す必要があります。
条件式はOR演算やAND演算によって複数の式を結合することが可能です。
Program.csif (a == 0 || 10 < a) { Console.WriteLine("a is equal zero OR larger than 10"); } else if (-5 < a && a <= 10) { Console.WriteLine("a is larger than -5 AND equal or smaller than 10"); } else { Console.WriteLine("a is smaller than -5"); }a=0> a is equal zero OR larger than 10a=20> a is equal zero OR larger than 10a=3> a is larger than -5 AND equal or smaller than 10a=-15> a is smaller than -5短絡評価
C#に限らずですが、実はOR演算やAND演算では "短絡評価" という評価法が採用されています。
どういう方法かというと、ORやANDによって複数の条件式が結合された時、
1つずつ式を評価していき、途中で結果が確定した段階で以降の評価をスキップする動きになります。次の例を見てみましょう
Program.csint a = 0, b = 0; if (a == 0 || b++ == 10) { a++; } if (a < 0 && b++ == 1) { a++; } Console.WriteLine("a = " + a); Console.WriteLine("b = " + b);出力> a = 1 > b = 0インクリメントを多用していますが、まず最初のif文から見ていきます。
if (a == 0 || b++ == 10)
このif文ではまず最初に左側の"a == 0"を評価し、これがtrueであることが分かります。
その次にOR演算が来ますが、OR演算は被演算子(||の両端にある式)の内
「少なくとも一方が真であれば全体の結果も真」だと言えるので
以降の評価"b++ == 10"を実行せず、if文の中に入って"a++"を実行します。さて次のif文を見ていきましょう。
if (a < 0 && b++ == 1)
これも先ほどと同様にまず左側から見ていき、"a < 0"を評価したところa = 1なので"a < 0 → false"であることが分かります。
AND演算では「被演算子の少なくとも一方が偽であれば全体が偽」だと言えるので、
この場合にも、以降の評価"b++ == 1"は実行されません。if文の結果は偽でしたのでその中に入らず処理を終了します。
最終的にaは一度だけインクリメントされて1に。bは一度も値を変更されることなく0のままであることが確認できます。
今回は例としてインクリメントを使用していますが、可読性の考慮すると評価式中に変数の値を更新するのは非推奨です。
その式が実行されたかどうかがパッと見で分かりづらいので、できるだけ分かりやすい書き方を心掛けましょうswitch-case文
さて、if文以外にも条件によって実行内容を分岐させる方法が "switch-case文" になります。
実際に動きを見てみましょう。Program.csswitch (a) { case 0: Console.WriteLine("a is equal 0"); break; case 1: Console.WriteLine("a is equal 1"); break; default: Console.WriteLine("a is unknown"); break; }a=0> a is equal 0a=1> a is equal 1a=3> a is unknown動きを見ればわかる通り、aの値によって処理が変わる点についてはif-else文と同じです。
switch-case文は選択肢が系統的に列挙できる場合、if-else文よりも可読性を上げることができます。
例えばaの値が1~10まで全てについて異なる処理をしたい場合、switch-case文の方が有用です。反対に、a < 5の様な範囲指定をする評価をする場合にはif-elseが必要になるので、
その時々によってうまく使い分けられるようにしましょう今回はここまで。
- 投稿日:2020-10-17T04:51:03+09:00
【Unity】UPMのVSCodePackageを使っていると、CallOnGeneratedCSProjectが呼ばれない
TL;DR;
UPMでVisualStudioCodeEditorの代わりにこちらを使ってください。
git@github.com:wallstudio/com.unity.ide.vscode.git
原因っぽいもの
CsProjファイルの
ReferenceOutputAssembly
の項目がfalseになっているからの様で、これを手動でtrueにすると治る。
ただ、これは自動生成ファイルなので、手直しでOKとはいきません。
もう治ってるみたいなことがかいてあったのですが…とりあえず、当時の対処療法を試みるも、UPMのCode拡張を入れていると、
AssetPostprocessor.OnGeneratedCSProject
が呼ばれなくなってしまうらしいです。public class FIX : AssetPostprocessor { private static string OnGeneratedCSProject(string path, string content) => content.Replace( "<ReferenceOutputAssembly>false</ReferenceOutputAssembly>", "<ReferenceOutputAssembly>true</ReferenceOutputAssembly>"); } }対処
com.unity.ide.vscode
を直接書き換えちゃうのが手っ取り早いということで、こちらですhttps://github.com/needle-mirror/com.unity.ide.vscode/compare/master...wallstudio:master
- 投稿日:2020-10-17T04:51:03+09:00
【Unity】Asmdefに参照を追加してもVSCode側で反映されない
TL;DR;
UPMでVisualStudioCodeEditorの代わりにこちらを使ってください。
git@github.com:wallstudio/com.unity.ide.vscode.git
原因っぽいもの
CsProjファイルの
ReferenceOutputAssembly
の項目がfalseになっているからの様で、これを手動でtrueにすると治る。
ただ、これは自動生成ファイルなので、手直しでOKとはいきません。
もう治ってるみたいなことがかいてあったのですが…とりあえず、当時の対処療法を試みるも、UPMのCode拡張を入れていると、
AssetPostprocessor.OnGeneratedCSProject
が呼ばれなくなってしまうらしいです。public class FIX : AssetPostprocessor { private static string OnGeneratedCSProject(string path, string content) => content.Replace( "<ReferenceOutputAssembly>false</ReferenceOutputAssembly>", "<ReferenceOutputAssembly>true</ReferenceOutputAssembly>"); } }対処
com.unity.ide.vscode
を直接書き換えちゃうのが手っ取り早いということで、こちらですhttps://github.com/needle-mirror/com.unity.ide.vscode/compare/master...wallstudio:master
追記
OmniSharpの方にIssueが上がっていました
https://github.com/OmniSharp/omnisharp-vscode/issues/4113
ReferenceOutputAssemblyという名前的に本来trueであるべきのような気もするのですが(com.unity.ide.vscide側が悪い)どうなのでしょうか。
今まではfalseで動いていましたが。Unityがプロジェクトメタファイルの出力クラスを切り替えている部分が分かりました。
AssetPostprocessor.cs#L159
どうやら、これからはOnGeneratedCSProject
ではなく、IExternalCodeEditor
を使って出力機構丸ごと差し替える形でフックするのがよさそうです。
また、力業ではありますが、ReadDirectoryChangesW
(Win32API)などで、ファイルの書き換えをフックするという手段もあるにはあります。
- 投稿日:2020-10-17T03:18:32+09:00
Google Drive API V3 で共有ドライブにアップロードしよう
なぜか404 親フォルダが見つかりませんエラー
Google Drive API V3で共有ドライブを使うときの話です、超ハマったわりに情報が少なかったのでメモ!
共有ドライブ内のフォルダにファイルをアップロードしようとして、meta.Parents
に親フォルダのIDを指定して動かすと、なぜかFile not found [folderid] 404
というエラーがかえってきてしまいます・・・
ためしに共有ドライブでないMyDrive内のフォルダIDを指定すると成功するので、認証やプログラムの流れは問題なさそうです。共有ドライブのアップロードはひと手間
いきなり結論ですが、以下の2プロパティを設定する必要がありました。
- metaオブジェクトのDriveId
プロパティ
- CreateRequestオブジェクのSupportsAllDrives
プロパティDriveIdもURLからコピー
DriveIdは親フォルダのIDと同じく、ブラウザで該当の共有ドライブのルートディレクトリを開いたときのURLの末尾文字列です。
なおTeamDriveId
というプロパティもありますがDeprecated 変わりにDriveIdを使え
とのことでした。完成
認証を含んだ全体的なコードは↓のようになりました。
ほぼQuickStartのサンプルコピペですが。
(コードはC#のSDKですけど他のSDKも同じでしょう)GoogleDriveUploader.csstring[] Scopes = { DriveService.Scope.DriveFile }; // アップロードするのでDriveFile string ApplicationName = "適当にアプリケーション名"; UserCredential credential; using (var stream = new FileStream("client_secret_~~.apps.googleusercontent.com.json", FileMode.Open, FileAccess.Read)) { string credPath = "token.json"; credential = GoogleWebAuthorizationBroker.AuthorizeAsync( GoogleClientSecrets.Load(stream).Secrets, Scopes, "user", CancellationToken.None, new FileDataStore(credPath, true)).Result; Console.WriteLine("Credential file saved to: " + credPath); } // Create Drive API service. var service = new DriveService(new BaseClientService.Initializer() { HttpClientInitializer = credential, ApplicationName = ApplicationName, }); var meta = new Google.Apis.Drive.v3.Data.File() { Name = "作成するファイル", MimeType = "application/octet-stream", // 適宜設定しましょう DriveId = "共有ドライブのID (0ABcDeJiHiJKLmn23 みたいな)", Parents = new List<string> { "親フォルダのID (1auYhd9J9j~みたいな)" } }; using (var stream = new System.IO.FileStream("アップロードするファイルのフルパス", System.IO.FileMode.Open)) { // 新規追加 var request = service.Files.Create(meta, stream, meta.MimeType); request.Fields = "id, name"; request.SupportsAllDrives = true; var ret = request.UploadAsync().Result; if (ret.Status == Google.Apis.Upload.UploadStatus.Failed) { throw ret.Exception; } }