- 投稿日:2019-08-22T23:59:14+09:00
【Unity】Animator.GetCurrentAnimatorStateInfoでステート遷移を待機するときの注意
バージョン
Unity 2019.1.9f1
はじめに
コルーチンを使ってステート遷移待ちをするときやらかしがちなミス。
ステート「Idle」から抜けたら何か処理をしたいとします。NG例
NG// NG private IEnumerator WaitStateChange() { var currentAnimatorState = _animator.GetCurrentAnimatorStateInfo(0); yield return new WaitWhile(() => currentAnimatorState.IsName("Idle")); Debug.Log("Do Something."); }上のコードは一見問題なさそうですが、実際に実行すると上手く動作しません。
「Idle」から抜けているはずなのに、いつまでたってもWaitWhileから抜けてくれないのです。…それもそのはず。
Animator.GetCurrentAnimatorStateInfoの戻り値をキャッシュしたものでチェックしていますが、戻り値のAnimatorStateInfoは構造体なので値型。
それをいくら監視していても書き換わらないですね。OK例
正しくは下記のように、毎回取る形にします。
OK// OK private IEnumerator WaitStateChange() { yield return new WaitWhile(() => _animator.GetCurrentAnimatorStateInfo(0).IsName("Idle")); Debug.Log("Do Something."); }当たり前と言えば当たり前なんですが、私はこれで躓いてしばらく悩みました…。
- 投稿日:2019-08-22T23:59:13+09:00
【Unity(C#)】Animatorを使わずにパラパラ漫画作成
SpriteAnimation
Unityの機能を利用してきれいに作れるみたいです。
参考リンク:Unity でスプライトアニメーションを作るしかし、ここまでガッツリと2Dのアニメーションを作りたいわけではありません。
何より、Animatorの設定を毎回やるのはタフなので
パラパラ漫画のような簡易スプライトアニメーションを作れないかと思いやってみました。指定した秒数間隔で画像を切り替える
処理の内容としては
ImageコンポーネントのSource Imageを指定秒数後に別のTextureに入れ替えるだけです。以下コードです。
using System.Collections; using UnityEngine.UI; using UnityEngine; #if UNITY_EDITOR using UnityEditor; using UnityEditorInternal; #endif public class SpriteAnimation : MonoBehaviour { [SerializeField] Image spriteImage; [SerializeField,HideInInspector] Sprite[] spriteTextures; [SerializeField] float animationFrameSeconds; [SerializeField] bool debug; public bool loop; Coroutine runCoroutine; void Update() { //デバッグキー if (Input.GetKeyDown(KeyCode.T) && debug) { SpriteAnimeStart(); } } public void SpriteAnimeStart() { if (runCoroutine == null) { runCoroutine = StartCoroutine(SpriteAnimeCoroutine()); } } IEnumerator SpriteAnimeCoroutine() { if(loop) { while(loop) { for (int i = 0; i < spriteTextures.Length; i++) { spriteImage.sprite = spriteTextures[i]; yield return new WaitForSeconds(animationFrameSeconds); } } runCoroutine = null; yield break; } else { for (int i = 0; i < spriteTextures.Length; i++) { spriteImage.sprite= spriteTextures[i]; yield return new WaitForSeconds(animationFrameSeconds); } runCoroutine = null; } } #if UNITY_EDITOR [CustomEditor(typeof(SpriteAnimation))] public class SpriteAnimationEditor : Editor { ReorderableList reorderableList; void OnEnable() { SerializedProperty prop = serializedObject.FindProperty("spriteTextures"); reorderableList = new ReorderableList(serializedObject, prop); reorderableList.drawHeaderCallback = (rect) => EditorGUI.LabelField(rect, "アニメーションに使用する画像"); reorderableList.drawElementCallback = (rect, index, isActive, isFocused) => { SerializedProperty element = prop.GetArrayElementAtIndex(index); rect.height -= 4; rect.y += 2; EditorGUI.PropertyField(rect, element, new GUIContent("フレーム" + index)); }; } public override void OnInspectorGUI() { base.OnInspectorGUI(); serializedObject.Update(); reorderableList.DoLayoutList(); serializedObject.ApplyModifiedProperties(); } } #endif }Inspectorはこんな感じです。
Loopのチェックをオフにすると単発再生ができて、途中でオフにすると止まります。
ReorderableListを使っているのでなかなかの使い心地です。
- 投稿日:2019-08-22T18:57:29+09:00
ドットインストールのUnity入門の補足説明
この記事は、筆者が大学でUnityを教えるにあたって、事前準備として夏休みの宿題として学生にドットインストールのUnity入門を学習させるための補足説明です。
学生用に書いたものですが他にも役に立つ人がいるかもしれないので公開します。
この記事の目的
ドットインストールのレッスンを否定している訳ではなく、夏休みの宿題用の素晴らしい教材としてありがたく使わせていただいています。その上で、本当に何も知らない学生にとっていきなり出てくる様々な概念や用語について、さらに詳しく説明することでより理解度を深めることを目的としています。
この記事の対象者
この記事はUnity入門のためにドットインストールで学ぶ、プログラミング初心者を対象にしています。
目次
この記事には以下のような構成でUnity入門について解説しています。カッコの中の#11などはドットインストールのレッスンの中で対応する動画の番号を示しています。
- まずは一度レッスンを全部通してやってみる
- スクリプトおよび GameObject の関連付けについて (#11)
- スクリプトを作成すると自動的に挿入されているコードについて (#11)
- 型についての補足説明 (#12)
- クラスについての補足説明 (#12)
- #12のスクリプトの補足説明 (#12)
- #14のスクリプトの補足説明 (#14)
- プレハブ(Prefab)って何? (#16)
- なぜ空のGameObjectを作成するのか? (#16)
- RigidBodyって何? (#19)
- OnCollisionEnterって何? (#19)
1. まずは一度レッスンを全部通してやってみる
まずは頭の中に?がいっぱいになったとしてもとりあえず、言われるがままにレッスンを進めて最後までやってみてください。数時間で終わると思います。その後この記事を見ながら2周目をやるとかなり理解が深まると思います。
2. スクリプトおよび GameObject の関連付けについて (#11)
いきなり Add Component してスクリプトを作って、さらっと紐づけられていますが、はじめてUnityに触れる人にとっては以下のような疑問が出てくると思いますので補足として解説します。
スクリプトって何?
「スクリプト」とはプログラミング言語(UnityではC#)で書かれたプログラムのことです。Unityでは(最初のうちは)1つの GameObject に対して1つのスクリプトを書いて、その GameObject の挙動を制御します。
コンポーネントって何?
コンポーネントは、GameObject に対してさまざまな見た目や、機能、挙動を加えることができます。このレッスンではスクリプトをコンポーネントとしてGameObjectに追加していますが、GameObject の位置や大きさ、回転を指定する Transform、オブジェクトの色や質感を指定する Material などもコンポーネントの1種です。
紐づけるってどういうこと?
GameObject にコンポーネントとしてスクリプトを追加すると、GameObject とスクリプトが関連づけられた状態になります。その状態になってはじめて、スクリプトに紐づけられた GameObject がシーン上にあるときに、そのスクリプトが実行されます。スクリプトを書いただけでは実行されませんので注意してください。
翻訳が中途半端ですが、以下のUnity公式マニュアルでも解説されています。
https://docs.unity3d.com/ja/current/Manual/CreatingAndUsingScripts.html3. スクリプトを作成すると自動的に挿入されているコードについて (#11)
Unityでスクリプトを作成すると、自動的に以下のようなコードが書かれたスクリプトが作成されます。
この内容についてそれぞれ説明していきます。using System.Collections; using System.Collections.Generic; using UnityEngine; public class BallScript : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }using
using System.Collections; using System.Collections.Generic; using UnityEngine;Unityのスクリプトではさまざまな機能が使えますが、それらはいきなり使うことはできず、使いたい機能を呼び出さなければいけません。例えば、スマホでは本当にいろんなことができますが、アプリをまったくインストールしていないスマホでは何もできないことと似ています。
C#では、さまざまな機能が「名前空間」というものの中にまとめられています。コンピュータのフォルダにのような概念です。これらの行では
usingの後に続いて名前空間を記述することで、System.Collections、System.Collections.Generic、UnityEngineという名前空間を利用するということを宣言しています。「名前空間」という変な名前ですが、スクリプトを書いていると「Ball」とか「BallFactory」とか「speed」とかクラスや変数にいろんな名前を付けます。自分で適当に付けた名前と、Unityに標準で用意されている名前と被ってしまっても大丈夫なように、さまざまな機能が名前空間で分けられているのでちゃんと区別できるようになっています。なのでこの名前が被らないようにするという意味で「名前空間」(name space)という名前が付いています。
UnityEngine
Unityで標準装備されている基本的なC#の部品がこの中にまとまっています。
以下のUnity公式スクリプトリファレンスにアクセスしてください。https://docs.unity3d.com/ja/current/ScriptReference/index.html
左側の「UnityEngine」の「+」をクリックして開いて、
その中にある「Classes」の「+」をクリックすると UnityEngine の中にあるたくさんのC#の部品を確認することができます。
このようにUnityには大量の便利な機能(クラス)が用意されています。ただその全てを覚えることはできないので、分からないことがあれば、ますスクリプトリファレンスを見るようにしてください。
セミコロン
;(セミコロン)はC#の命令の区切りです。それぞれの命令の最後には必ず;が必要です。これを書き忘れるとエラーになるので、スクリプトを実行したときにエラーが発生したときはまず;を忘れてないか確認してみましょう。コメント
// Start is called before the first frame update // Update is called once per frameスクリプトを書くときにちょっとしたメモを書いたり、書いたスクリプトを部分的に無効にしたいといった場合には、「コメント」を使います。Unityが生成するスクリプトにも上記のような解説がコメントとして記述されています。
コメントを書くには//と記述するとその行の//以降の文字はコメントとなり、スクリプトを実行するときに無視されるようになります。//は1行ずつしかコメントにできないので、複数行まとめてコメントにしたい場合は/*と書くと、そこから*/と記述するまでがコメントになります。// 1行コメント /* 複数行の コメント です */クラス
PaddleScript.cspublic class PaddleScript : MonoBehaviour { // クラスの中身 }この行では
PaddleScriptというクラスを宣言しています。クラスはスクリプトで使うさまざまな部品のことで、あらかじめさまざまなC#標準のクラスやUnity標準のクラスが用意されており、ここで宣言しているように、自分で作ることもできます。後ろの
: MonoBehaviourの部分は、このクラスはUnityで標準装備されているMonoBehaviourというクラスの機能を継承するということを指定しています。機能を継承することで、もともとのMonoBehaviourが持っている機能にさらに自分で機能を追加した新しいクラスを作ることができます。
MonoBehaviourはUnityEngineという名前空間で定義されているので、これを利用するには1行目のusing UnityEngine;が必要になります。宣言したクラスの中身のスクリプトはすべてここに書かれた中カッコ{から最後の中カッコ}の内側に記述します。メソッド
PaddleScript.csvoid Start() { // メソッドの中身 }ここではクラス宣言の内側に
Startというメソッド(関数、ファンクションとも言う)を宣言しています。メソッドはクラスの持つ機能のことで、自由に名前をつけていくつでも作ることができます。ここで宣言しているStartメソッドはもともと継承元のMonoBehaviourクラスで宣言されていて、紐づけられた GameObject が作成された時に一度だけ実行されます。ここではその
Startメソッドを上書きすることで、GameObject が作成されたときに実行したい独自の処理を記述することができます。メソッドの中身のスクリプトは、ここにかかれた中カッコ{からメソッドの終わりの中カッコ}の内側に記述します。メソッドは何らかの処理を実行した後に、結果を返すことができますが、ここにある
voidは結果を何も返さないということを示しています。Updateメソッド
PaddleScript.csvoid Update() { // メソッドの中身 }ここは
Startメソッドと同様に継承元のMonoBehaviourクラスで宣言されているUpdateというメソッドを上書きしています。Updateメソッドは 紐付けられた GameObject がシーンに配置されている間、画面が更新される度に実行されます。通常だと1秒間に60回ほど実行されます。インデントについて
PaddleScript.cspublic class BallScript : MonoBehaviour { private float speed; // Start is called before the first frame update void Start() { speed = Random.Range(5f, 15f); } // Update is called once per frame void Update() { transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f); } }このスクリプトではクラスの宣言の後の中カッコ
{以降、クラス宣言の終わりの中カッコ}まで、それぞれの行の始まりが右に一段ずれています。また、クラス内のメソッド宣言の後の中カッコ{以降さらに右にずれます。
こうやって、始まりの中カッコ{が入力される度に行の始まりを1段ずつ右にずらしていくことを「インデント」と言います。
インデントすることで、今クラス宣言の中身を書いているのか、メソッド宣言の中身を書いているのかが分かりやすくなります。逆に、インデントされていないスクリプトは長くなってくると解読が困難になるため、必ずインデントをする癖をつけましょう。実際にはタブを入力することでインデントできるのですが、最近のエディタでは自動的にインデントしてくれるものが多いです。
少し古い記事ですが、Unityが最初に生成するスクリプトの中身について、この記事でもう少し詳しく解説されています。
https://qiita.com/JunShimura/items/3c2e23bb77cc9085bfda4. 変数と型についての補足説明(#12)
変数について
プログラミングをする上でとても基本的な概念に「変数」があります。数学で出てくるx, yといった変数とは少し違います。プログラミングでは、数値や文字列など様々な値を扱いますが、そういう値を保持しておく箱のようなものがプログラミングにおける変数です。変数について簡単にまとめると以下のような特徴があります。
- 変数は値を保存したり読み出したりできる箱のようなもの
- 変数と言っても数値だけだはなく文字列やオブジェクトなどなんでも保存できる
- 変数には自由な名前をつけることができる
- 変数を作るには、宣言をする必要がある
- 変数には型という概念があり、宣言するときに型の指定が必要
- 変数に値を保存することを「代入」という
変数を宣言するには、型の後に変数名を記述します。
int x;この例では
xという名前でintという型の変数を宣言しています。(intは整数値を扱う型)
変数に値を保存することを「代入」と言いますが、値を代入するには=を使います。int x; x = 2;この例では1行目で
int型の変数xを宣言して、2行目でxに2という値を代入しています。
値の代入は=の右側に記述された値が、左側に記述された変数に保存されます。必ず右→左です。左→右はありませんので注意してください。また、変数の宣言と同時に値を代入して初期化することもできます。その場合は以下のように記述します。
int x = 2;また、変数に値を代入する場合の
=の左側に変数を使うこともできます。
以下の例では変数xに2を代入し、変数yにx + 1の計算結果を代入しているので、yの中身は3になります。int x = 2; int y; y = x + 1;
0fのfって何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);C#のスクリプトでは整数値や小数値、文字列などいろいろな値を扱います。それらはの値の種類を「型」と言います。
例えば整数を扱うにはintという型を利用し、小数を扱うには精度の高いdoubleとその半分の精度のfloatといった種類の型があります。
C#では0や1のような整数の値は自動的に int として扱われ、0.0や0.5のように記述した値は自動的にdoubleとして扱われます。0fや0.5fのように数値の後にfを付けるとその数値はdoubleではなくfloatとして扱われるようになります。ここで使われている
Vector3はdoubleではなくfloatを扱うクラスなので、0ではなく0fとする必要があります。(ドットインストールでは浮動小数点数を渡す決まりになっているのでfを付けると解説されていますが、実際には浮動小数点数を渡すからfを付けるのではなく、 浮動小数点数の中でもdoubleではなくてfloatを渡す決まりになっているのでfを付ける必要があるということになります)5. クラスについての補足説明 (#12)
Unityで書く1つのスクリプトは基本的に1つのクラスになります。クラスはスクリプトで扱うオブジェクトの型の種類の1つで、クラスを作ることで型を自分でつくることができます。クラスを作るとその型のオブジェクトをいくつでも作ることができ、そのオブジェクトのことを「インスタンス」と言います。
クラス(型)はオブジェクトの設計図のようなもので、それを元に実際に生成されたオブジェクトがインスタンスです。
いきなり出てくる
transformって何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);ここで書いている
PaddleScriptはMonoBehaviourというクラスの持つメソッド(機能)や変数を全て継承して、それに独自の機能を新たに追加しているクラスです。transformはMonoBehaviourクラスが持つ変数の1つで位置情報や変形などに関する情報がまとめて格納されています。Unity公式スクリプトリファレンスに書いてありますが
MonoBehaviourクラスはtransformという変数を持つと書いてあります。なので、MonoBehaviourを継承しているPaddleScriptも変数transformを持つことになります。
MonoBehaviourクラスのリファレンス(継承メンバーの変数のところにtransformの記述がある)
https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.htmlそしてそこをクリックすると
transformはTransformというクラスの変数だということが分かります。
transform.positionって何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);スクリプトリファレンスを見ると、
Transformクラスはpositionという変数を持っていて、この変数positionは Vector3 という型だということが分かります。
Transformクラスのリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Transform.html
Transformクラスの変数positionのリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Transform-position.htmlなので先ほどの図に
positionを付け加えると以下のような相関関係になります。
Vector3って何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);
Vector3はx,y,zの3つのfloatの値をまとめて扱えるので3D空間での位置情報を扱うのに便利なオブジェクトです。transform.positionの型は Vector3 なので、ここでは Vector3 を使う必要があります。
(Vector3はクラスではなく構造体というものですが、今さらに新たな概念を理解すると大変だし、ほぼクラスと同じように使えるので今はほぼクラスみたいなものだと思ってもらって大丈夫です。)
Vector3の公式リファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Vector3.html
newって何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);
newはクラス(構造体も含む)のインスタンスを新しく作成する時に使うキーワードです。
newを使ってクラスのインスタンスを新しく作るときには「コンストラクタ」と言う特殊なメソッドが実行されます。コンストラクタはインスタンスが新しく作成されるときに最初にやっておくべき初期化処理をしたり、パラメータ(引数)を受け取ってインスタンスの持つ変数に値をセットするといった処理をします。
newキーワードを使ってクラスのインスタンスを作成するには、newの後にクラス名(構造体名)を記述して、その後にカッコ()を書いて、コンストラクタに渡すパラメータを書きます。コンストラクタに渡すパラメータについて調べるには、
Vector3の公式リファレンスを確認してください。
https://docs.unity3d.com/ja/current/ScriptReference/Vector3.html「コンストラクタ」の部分の
Vector3をクリックするとコンストラクタのページに移動します。
https://docs.unity3d.com/ja/current/ScriptReference/Vector3-ctor.html
Vector3のリファレンスを見てみるとコンストラクタで3つのfloatの値をパラメータとして受け取ることが分かります。なので、
new Vector3(0f, 0f, 0f)と書くと、
Vector3のインスタンスを新たに1つ作成して、コンストラクタに0f, 0f, 0fを渡しているので、x, y, z が全部ゼロのVector3 のインスタンスが作成されます。
+=って何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);
transofrm.positionの後に+=、new Vector3が続いていますが、これは、PaddleScriptクラスのインスタンスが持っている変数transformのpositionに=の右側で新しく作られるVector3クラスのインスタンスを足しているということになります。
transform.positionもnew Vector3で作られるオブジェクトもどちらもVector3のインスタンスで、それぞれのインスタンスの持つ x, y, z の変数が足し合わされた結果がtransform.positionに保存されます。6. その他スクリプトの補足説明(#12)
Inputって何?PaddleScript.csvoid Update() { transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime * speed, 0f, 0f); }
InputはUnityで、キーボードやマウス、タッチディスプレイ、ゲームパッド、ジョイスティックなどのユーザーのさまざまな入力を取得することができるクラスです。UnityのInputで入力を扱う - Qiita
https://qiita.com/yando/items/c406690c9ad87ecfc8e5Unity公式マニュアル - 一般的なゲーム入力
https://docs.unity3d.com/ja/current/Manual/ConventionalGameInput.html
Inputのスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Input.html
Input.GetAxis("Horizontal")って何?PaddleScript.csvoid Update() { transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime * speed, 0f, 0f); }
Input.GetAxis("Horizontal")はゲームコントローラーの十字キーやジョイスティックの水平方向の状態をfloat型の数値として取得します。垂直方区の状態はGetAxisに渡すパラメータを"Horizontal"ではなく"Vertical"に変えることで取得できます。
Input.GetAxisのスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Input.GetAxis.htmlこの部分の1行はとても長いので、スクリプトを以下のように書きかえてみましょう。
PaddleScript.csvoid Update() { float horizontalAxis = Input.GetAxis("Horizontal"); float x = horizontalAxis * Time.deltaTime * speed; transform.position += new Vector3(x, 0f, 0f); Debug.Log(horizontalAxis); }まず、
float型の変数horizontalAxisにInput.GetAxis("Horizontal")で取得したゲームコントローラーの状態を取得しています。キーボードのキーを何も押していないときには、変数horizontalAxisには0fが入り、左キーを押しているときは-1fが、右キーを押しているときは1fが入ります。次の行では、それに
Time.deltaTimeとspeedをかけた値をfloat型の変数xに代入しています。その次の行では
new Vector3でコンストラクタのパラメータにxを渡しています。これらを1行で書いていましたが、こうやって分けて書くとよりわかりやすいと思います。次の行の
Debug.Log(horizontalAxis);をすることで、実行した時にキーボードの状態に応じてhorizontalAxisの値が変化することがコンソールで確認できると思います。(この行は確認した後は不要なので消しましょう)キーボードはゲームコントローラーがない場合にコントローラーの代わりに使うことができるので、
Input.GetAxis("Horizontal")で矢印キーの状態が取得できます。
Input.GetAxis("Horizontal")を使っておけば、USBのゲームコントローラーなどUnityが対応するさまざまなコントローラーを接続した時にプログラムを書き変えることなく、そのままプレイできます。
Time.deltaTimeって何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);
Time.deltaTimeは、前回のフレーム(前回Updateメソッドが実行されたタイミング)からの経過時間(秒)を取得することができます。Updateメソッドは必ずしも一定間隔で実行されるわけではありません。コンピュータの負荷によって遅くなったり早くなったりします。例えば1フレームごとに1ピクセルずつ動くプログラムを書いた場合、フレームの間隔が安定して0.1秒だとすると1秒間に10回実行され、10ピクセル動くことになりますが、もしコンピュータの負荷によりフレームの実行間隔が遅くなり、1秒間に5回しか実行されなければ、5ピクセルしか動かないことになります。
このようにフレームごとに一定の値だけ動かすといったスクリプトを書くとフレームの実行間隔により見た目のスピードは一定ではなくなります。なので、フレームの実行回数によりオブジェクトを動かすのではなく、経過時間によりオブジェクトを動かす方が正しくスムーズにオブジェクトを動かすことができます。そのために
Time.deltaTimeを利用します。Unity公式マニュアル - タイムとフレームレートの管理
https://docs.unity3d.com/ja/current/Manual/TimeFrameManagement.html7. #14のスクリプトの補足説明 (#14)
変数
speedはなぜその場所に記述するのか?PaddleScript.cspublic class PaddleScript : MonoBehaviour { private float speed; // Start is called before the first frame update void Start() { speed = 5f; }クラスの記述の決まりとして、
classの宣言の後に中カッコがあり、その中にメソッドを記述しますが、class宣言の中にメソッドと同列で変数を宣言すると、その変数は「インスタンス変数」としてそのクラスのインスタンスが持つ変数になります。ドットインストールの例で言うと、Ball の数だけそれぞれがこのspeedという変数を持っていて、Start()でランダムな値が設定されるので、インスタンスごとに個別の値を保持しています。変数のスコープ(有効範囲)について
変数にはスコープ(有効範囲)というものがあり、宣言する場所によってスコープが変わります。ここでは、クラス宣言の内側で変数
speedを宣言しています。こうすることで、このクラスのインスタンスが存在する限り、変数speedも存在します。PaddleScript.cspublic class PaddleScript : MonoBehaviour { private float speed; void Start() { speed = 5f; } }メソッドの中で変数を宣言することもできます。ただし、メソッドの中で宣言した変数はそのメソッドの実行が終了した時点で破棄されてしまうので、メソッドの実行時に一時的に使いたいといった場合にメソッド内で変数を宣言します。
PaddleScript.cspublic class PaddleScript : MonoBehaviour { void Start() { float speed; speed = 5f; // Start()メソッドはここまでなので、ここで変数 speed は破棄される } void Update() { // ここでは speed 変数は存在しない } }
privateって何?PaddleScript.cspublic class PaddleScript : MonoBehaviour { private float speed;クラスの持つインスタンス変数は、そのクラスの外側からアクセス可能にするか、そのクラス内でのみアクセスできるようにするかを設定できます。変数宣言の前に private と付けるとその変数にはクラス内からしかアクセスできないようになります。private の代わりに public とすると、その変数には外部からアクセスできるようになり、クラスの外部(他のクラスやUnityのInspectorなど)から値を参照したり変更したりすることができるようになります。
8. プレハブ(Prefab)って何? (#16)
ドットインストールではさらっと登場して使われているプレハブについて、とても重要な概念なのでもう少し説明しておきます。
プレハブは、同じような GameObject をたくさん配置して、それぞれを個別に動かしたいような場合のテンプレートのような機能です。スクリプトで使うクラスと似た概念です。
Ballのプレハブをドラッグ&ドロップでシーンにたくさん配置するとヒエラルキービューでは青く表示されます。これはこのオブジェクトがプレハブから作られたことを意味します。
これらの球体にまとめて変更を加えたければ、元のプレハブのScaleを変えればシーンに配置した全てのオブジェクトに反映されます。また、色を変えたければ、元のプレハブにマテリアルを適用することで全てのオブジェクトに反映されます。
ただ、これらのオブジェクトは個別にそれぞれの設定をすることも出来て、個別のオブジェクトのプロパティをプレハブのデフォルトの値から変更すると、それらは元のプレハブの設定を上書きした個別の値を持つことができます。
また、ドットインストールでやっているようにスクリプトからプレハブを元にオブジェクトをシーンに追加することもできます。
Unity公式マニュアル - プレハブ
https://docs.unity3d.com/ja/2018.1/Manual/Prefabs.html9. なぜ空のGameObjectを作成するのか? (#16)
ドットインストールでは BallFactory という空の GameObject を作成し、そこの BallFactoryScript という名前でスクリプトを関連づけています。
このスクリプトは Ball のプレハブから 実際のシーンに Ball を生成して配置するためのスクリプトですが、スクリプトを作っただけでは実行することが出来ないので、スクリプトを実行するためだけの空の GameObject を生成して、それに関連付けすることで、スクリプトを実行できるようにしています。
また、Ballに関連付けられた BallScript は、BallFactoryScript から Ball のオブジェクトが生成される度に Start() が実行されて、画面上の Ball の数だけそれぞれの Update() が実行されます。10. RigidBodyって何?(#19)
RigidBody はコンポーネントの1つで、 このコンポーネントを追加した GameObject は物理演算により動くようになります。物理演算によって動くということは、現実世界の物理の法則と同じように、Unityの3D空間で、重力により落下したり、他のオブジェクトと衝突して跳ね返ったり、空気抵抗を受けたりといったことをシミュレーションすることができます。
Inspector で RigidBody の設定項目を見てみると、重量や空気抵抗の大きさ、重力の影響を受けるかどうか、他のオブジェクトとの衝突を検知するかといった項目があります。これらを設定することで GameObject にさまざまな挙動をさせることができます。
Unity公式マニュアル - RigidBody
https://docs.unity3d.com/ja/current/Manual/class-Rigidbody.html11.
OnCollisionEnterって何?(#19)BallScript.csprivate void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Paddle")) { Destroy(gameObject); } }
OnCollisionEnterはこのスクリプトに紐づけられた GameObject が他のオブジェクトと衝突した場合に実行されるメソッドです。( RigidBody コンポーネントが追加されている必要があります)
MonoBehaviourのスクリプトリファレンスを見てみると「メッセージ」の欄にOnCollisionEnterが記載されています。
MonoBehaviourクラスのスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.htmlそこをクリックして
OnCollisionEnterのリファレンスを見てみると、パラメータ(引数)としてCollisionという型のデータを受け取ることが分かります。さらに
CollisionをクリックしてCollisionクラスのリファレンスに移動すると、Collisionクラスは衝突した相手のオブジェクトや、衝突時のスピードなどの情報をまとめたクラスだということが分かります。このようにして、はじめて使うクラスやメソッドが出てきた時は、スクリプトリファレンスをたどって読んでいくと理解が深まると思います。
最後に
ドットインストールのUnity入門をやって、ある程度Unityの雰囲気が分かった後は、Qiitaにある以下の素晴らしい入門記事を読むと良いと思います。
- 投稿日:2019-08-22T17:05:47+09:00
HoloLens を TCP server とした画像の分割受信
動機
Python から HoloLens へ画像を送信する必要があったため,このコードを書きました.
TCP サーバの受信一回で受信可能な小さな画像でなら記事がいくつか散見されるのですが, HoloLens 環境で複数回に分けて受信する方法が見つからずハマってしまい,試行錯誤したので記録します.環境
- Unity 2017.4.24f1
- HoloLens (server) ※2じゃないよ
- HoloToolkit-Unity-2017.4.1.0
- Python3.6 (client)
大まかな動作
client 側 (Python)
- Python3(client) で画像を open して byte 列で読み込み.
- base64 形式の文字列に encode.
- この base64 文字列を送信する byte 数で分割.
- 分割した base64 文字列を TCP を使って逐次的に Python から HoloLens(server) に送信.
server 側 (HoloLens)
- 分割された base64 文字列を逐次的に処理(受信->decode->ファイルへの書き込みを繰り返す).
- 受信した byte 数が指定した値より小さければ EOF とみなし最後の書き込みを行う.
- 受信した画像に応じた処理.
動作確認
- ImageReceiver.cs を Unity 内の texture を持つオブジェクトに Add します.(僕は Canvas 内に RawImage を作ってそこへ Add しました.)
- HoloLens に書き込み動作させます.
- client.py と同じディレクトリに sample.jpg (.png でも大丈夫です)用意します.
- client.py を走らせます.
- sample.jpg と同じ画像が HoloLens の view に現れたら成功です.
直接 byte 列を送受信するとなぜかファイルが壊れてしまいます.
そこで一旦 base64 という形式の文字列に変換し,これを通して送受信します.client 側のコード (Python)
host には各自の HoloLens の IP address を入力してください.
client.pyimport socket # Import socket module import base64 N_byte = 1024*8*8 if __name__ == '__main__': host = '163.221.000.000' # here is your hololens ip address. port = 8080 # Reserve a port for your service. imgfile = './sample.jpg' with socket.socket() as s: s.connect((host, port)) s.settimeout(3) with open(imgfile, 'rb') as f: imgstring = base64.b64encode(f.read()) sub_strings = [imgstring[i: i+N_byte] for i in range(0, len(imgstring), N_byte)] print('Sending...') for sub_string in sub_strings: s.send(sub_string) print(imgstring[-20:]) print("Done Sending") print(s.recv(N_byte)) s.shutdown(socket.SHUT_WR)server 側のコード (HoloLens)
Unity 内で texture を持っているオブジェクトに Add して使います.
僕の環境ではなぜか画像を書き込む直前に少し delay を入れないと動作が不安定になりました.ImageReceiver.csusing UnityEngine; using System; using System.IO; using System.Security.Cryptography; using UnityEngine.UI; //<JEM>Ignore unity editor and run this code in the hololens instead</JEM> #if !UNITY_EDITOR using Windows.Networking.Sockets; using Windows.Storage.Streams; using WinRTLegacy; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; #endif // Able to act as a reciever public class ImageReceiver : MonoBehaviour { RawImage rend; bool socketClosed = false; bool writeStringToFile = false; bool loadTexture = false; bool logSize = false; public uint BUFFER_SIZE = 8192; public uint PORT = 8080; private readonly int DELAYMILLISEC = 10; public string textAll = ""; string error_message; string error_source; string FILENAME = "received.png" #if !UNITY_EDITOR StreamSocketListener listener; #endif // Use this for initialization void Start() { #if !UNITY_EDITOR rend = this.GetComponent<RawImage>(); listener = new StreamSocketListener(); listener.ConnectionReceived += _receiver_socket_ConnectionReceived; listener.Control.KeepAlive = true; Listener_Start(); #endif } #if !UNITY_EDITOR private async void Listener_Start() { try { await listener.BindServiceNameAsync(PORT.ToString()); Debug.Log("Listener started"); Debug.Log(NetworkUtils.GetMyIPAddress() + " : " + PORT.ToString()); } catch (Exception e) { Debug.Log("Error: " + e.Message); } } private async void _receiver_socket_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { try { if (loadTexture != true) { string folderPath = System.IO.Directory.GetCurrentDirectory(); // Create sample file; replace if exists. // Must be set as TemporaryFolder to read files from HoloLens. Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.TemporaryFolder; using (var dr = new DataReader(args.Socket.InputStream)) { using (IInputStream input = args.Socket.InputStream) { using (var imageFile = new FileStream(storageFolder.Path + @"\" + FILENAME, FileMode.Create)) { using (FromBase64Transform myTransform = new FromBase64Transform( FromBase64TransformMode.IgnoreWhiteSpaces)) { byte[] data = new byte[BUFFER_SIZE]; IBuffer buffer = data.AsBuffer(); uint dataRead = BUFFER_SIZE; byte[] dataTransformed = new byte[BUFFER_SIZE]; while (dataRead == BUFFER_SIZE) { await input.ReadAsync(buffer, BUFFER_SIZE, InputStreamOptions.Partial); int bytesWritten = myTransform.TransformBlock(data, 0, (int)BUFFER_SIZE, dataTransformed, 0); await Task.Delay(DELAYMILLISEC); imageFile.Write(dataTransformed, 0, bytesWritten); dataRead = buffer.Length; } dataTransformed = myTransform.TransformFinalBlock(data, 0, data.Length - (int)dataRead); imageFile.Write(dataTransformed, 0, dataTransformed.Length); myTransform.Clear(); } imageFile.Flush(); } } } loadTexture = true; } } catch (Exception e) { error_source = e.Source; error_message = e.Message; socketClosed = true; } finally { if (loadTexture == true) { using (var dw = new DataWriter(args.Socket.OutputStream)) { dw.WriteString("OK"); await dw.StoreAsync(); dw.DetachStream(); } } else { using (var dw = new DataWriter(args.Socket.OutputStream)) { dw.WriteString("NG"); await dw.StoreAsync(); dw.DetachStream(); } } } } void Update() { if (logSize) { Debug.Log("SIZE IS : " + BUFFER_SIZE.ToString()); logSize = false; } if (socketClosed) { Debug.Log(error_source); Debug.Log(error_message); Debug.Log("OOPS SOCKET CLOSED "); socketClosed = false; Debug.Log(textAll); } if (writeStringToFile) { Debug.Log("WRITTEN TO FILE"); writeStringToFile = false; } if (loadTexture) { Debug.Log("LOADING IMAGE CURRENTLY"); // Must be set as TemporaryFolder to read files from HoloLens. Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.TemporaryFolder; string imgpath = storageFolder.Path + @"\" + FILENAME; Destroy(this.rend.texture); this.rend.texture = ReadPngAsTexture(imgpath); this.rend.SetNativeSize(); Debug.Log("LOADED IMAGE"); Debug.Log(textAll); loadTexture = false; } } #endif private static byte[] ReadPngFile(string path) { byte[] values; using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) { using (BinaryReader bin = new BinaryReader(fileStream)) { values = bin.ReadBytes((int)bin.BaseStream.Length); } } return values; } private static Texture2D ReadPngAsTexture(string path) { byte[] readBinary = ReadPngFile(path); Texture2D texture = new Texture2D(1, 1); texture.LoadImage(readBinary); return texture; } }既知のバグ
BUFFER_SIZE を下回るサイズの画像を送信すると受信できない不具合を確認しております.
最後に
右も左もわからない状況で HoloLens 用 C# コードを書いております.
問題点等ございましたら,ぜひご指摘お願いします!参考にした記事
- https://stackoverflow.com/questions/51411832/hololens-tcp-sockets-python-client-to-hololens-server
- https://codeday.me/jp/qa/20190124/164425.html
- https://symfoware.blog.fc2.com/blog-entry-786.html
- https://www.moonmile.net/blog/archives/7122
- https://qiita.com/shino_312/items/3c81ed8d8dfd0d53f25a
- https://docs.microsoft.com/ja-jp/dotnet/api/system.security.cryptography.frombase64transform?view=netframework-4.8
- https://qiita.com/tempura/items/b87eb07568d974664671
- 投稿日:2019-08-22T17:05:47+09:00
HoloLens を TCP server とした画像の受信
動機
Python から HoloLens へ画像を送信する必要があったため,このコードを書きました.
TCP サーバの受信一回で受信可能な小さな画像でなら記事がいくつか散見されるのですが, HoloLens 環境で複数回に分けて受信する方法が見つからずハマってしまい,試行錯誤したので記録します.環境
- Unity 2017.4.24f1
- HoloLens (server) ※2じゃないよ
- HoloToolkit-Unity-2017.4.1.0
- Python3.6 (client)
大まかな動作
client 側 (Python)
- Python3(client) で画像を open して byte 列で読み込み.
- base64 形式の文字列に encode.
- この base64 文字列を送信する byte 数で分割.
- 分割した base64 文字列を TCP を使って逐次的に Python から HoloLens(server) に送信.
server 側 (HoloLens)
- 分割された base64 文字列を逐次的に処理(受信->decode->ファイルへの書き込みを繰り返す).
- 受信した byte 数が指定した値より小さければ EOF とみなし最後の書き込みを行う.
- 受信した画像に応じた処理.
動作確認
- ImageReceiver.cs を Unity 内の texture を持つオブジェクトに Add します.(僕は Canvas 内に RawImage を作ってそこへ Add しました.)
- HoloLens に書き込み動作させます.
- client.py と同じディレクトリに sample.jpg (.png でも大丈夫です)用意します.
- client.py を走らせます.
- sample.jpg と同じ画像が HoloLens の view に現れたら成功です.
直接 byte 列を送受信するとなぜかファイルが壊れてしまいます.
そこで一旦 base64 という形式の文字列に変換し,これを通して送受信します.client 側のコード (Python)
host には各自の HoloLens の IP address を入力してください.
client.pyimport socket # Import socket module import base64 N_byte = 1024*8*8 if __name__ == '__main__': host = '163.221.000.000' # here is your hololens ip address. port = 8080 # Reserve a port for your service. imgfile = './sample.jpg' with socket.socket() as s: s.connect((host, port)) s.settimeout(3) with open(imgfile, 'rb') as f: imgstring = base64.b64encode(f.read()) sub_strings = [imgstring[i: i+N_byte] for i in range(0, len(imgstring), N_byte)] print('Sending...') for sub_string in sub_strings: s.send(sub_string) print(imgstring[-20:]) print("Done Sending") print(s.recv(N_byte)) s.shutdown(socket.SHUT_WR)server 側のコード (HoloLens)
Unity 内で texture を持っているオブジェクトに Add して使います.
僕の環境ではなぜか画像を書き込む直前に少し delay を入れないと動作が不安定になりました.ImageReceiver.csusing UnityEngine; using System; using System.IO; using System.Security.Cryptography; using UnityEngine.UI; //<JEM>Ignore unity editor and run this code in the hololens instead</JEM> #if !UNITY_EDITOR using Windows.Networking.Sockets; using Windows.Storage.Streams; using WinRTLegacy; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; #endif // Able to act as a reciever public class ImageReceiver : MonoBehaviour { RawImage rend; bool socketClosed = false; bool writeStringToFile = false; bool loadTexture = false; bool logSize = false; public uint BUFFER_SIZE = 8192; public uint PORT = 8080; private readonly int DELAYMILLISEC = 10; public string textAll = ""; string error_message; string error_source; string FILENAME = "received.png" #if !UNITY_EDITOR StreamSocketListener listener; #endif // Use this for initialization void Start() { #if !UNITY_EDITOR rend = this.GetComponent<RawImage>(); listener = new StreamSocketListener(); listener.ConnectionReceived += _receiver_socket_ConnectionReceived; listener.Control.KeepAlive = true; Listener_Start(); #endif } #if !UNITY_EDITOR private async void Listener_Start() { try { await listener.BindServiceNameAsync(PORT.ToString()); Debug.Log("Listener started"); Debug.Log(NetworkUtils.GetMyIPAddress() + " : " + PORT.ToString()); } catch (Exception e) { Debug.Log("Error: " + e.Message); } } private async void _receiver_socket_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { try { if (loadTexture != true) { string folderPath = System.IO.Directory.GetCurrentDirectory(); // Create sample file; replace if exists. // Must be set as TemporaryFolder to read files from HoloLens. Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.TemporaryFolder; using (var dr = new DataReader(args.Socket.InputStream)) { using (IInputStream input = args.Socket.InputStream) { using (var imageFile = new FileStream(storageFolder.Path + @"\" + FILENAME, FileMode.Create)) { using (FromBase64Transform myTransform = new FromBase64Transform( FromBase64TransformMode.IgnoreWhiteSpaces)) { byte[] data = new byte[BUFFER_SIZE]; IBuffer buffer = data.AsBuffer(); uint dataRead = BUFFER_SIZE; byte[] dataTransformed = new byte[BUFFER_SIZE]; while (dataRead == BUFFER_SIZE) { await input.ReadAsync(buffer, BUFFER_SIZE, InputStreamOptions.Partial); int bytesWritten = myTransform.TransformBlock(data, 0, (int)BUFFER_SIZE, dataTransformed, 0); await Task.Delay(DELAYMILLISEC); imageFile.Write(dataTransformed, 0, bytesWritten); dataRead = buffer.Length; } dataTransformed = myTransform.TransformFinalBlock(data, 0, data.Length - (int)dataRead); imageFile.Write(dataTransformed, 0, dataTransformed.Length); myTransform.Clear(); } imageFile.Flush(); } } } loadTexture = true; } } catch (Exception e) { error_source = e.Source; error_message = e.Message; socketClosed = true; } finally { if (loadTexture == true) { using (var dw = new DataWriter(args.Socket.OutputStream)) { dw.WriteString("OK"); await dw.StoreAsync(); dw.DetachStream(); } } else { using (var dw = new DataWriter(args.Socket.OutputStream)) { dw.WriteString("NG"); await dw.StoreAsync(); dw.DetachStream(); } } } } void Update() { if (logSize) { Debug.Log("SIZE IS : " + BUFFER_SIZE.ToString()); logSize = false; } if (socketClosed) { Debug.Log(error_source); Debug.Log(error_message); Debug.Log("OOPS SOCKET CLOSED "); socketClosed = false; Debug.Log(textAll); } if (writeStringToFile) { Debug.Log("WRITTEN TO FILE"); writeStringToFile = false; } if (loadTexture) { Debug.Log("LOADING IMAGE CURRENTLY"); // Must be set as TemporaryFolder to read files from HoloLens. Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.TemporaryFolder; string imgpath = storageFolder.Path + @"\" + FILENAME; Destroy(this.rend.texture); this.rend.texture = ReadPngAsTexture(imgpath); this.rend.SetNativeSize(); Debug.Log("LOADED IMAGE"); Debug.Log(textAll); loadTexture = false; } } #endif private static byte[] ReadPngFile(string path) { byte[] values; using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) { using (BinaryReader bin = new BinaryReader(fileStream)) { values = bin.ReadBytes((int)bin.BaseStream.Length); } } return values; } private static Texture2D ReadPngAsTexture(string path) { byte[] readBinary = ReadPngFile(path); Texture2D texture = new Texture2D(1, 1); texture.LoadImage(readBinary); return texture; } }既知のバグ
BUFFER_SIZE を下回るサイズの画像を送信すると受信できない不具合を確認しております.
最後に
右も左もわからない状況で HoloLens 用 C# コードを書いております.
問題点等ございましたら,ぜひご指摘お願いします!参考にした記事
- https://stackoverflow.com/questions/51411832/hololens-tcp-sockets-python-client-to-hololens-server
- https://codeday.me/jp/qa/20190124/164425.html
- https://symfoware.blog.fc2.com/blog-entry-786.html
- https://www.moonmile.net/blog/archives/7122
- https://qiita.com/shino_312/items/3c81ed8d8dfd0d53f25a
- https://docs.microsoft.com/ja-jp/dotnet/api/system.security.cryptography.frombase64transform?view=netframework-4.8
- https://qiita.com/tempura/items/b87eb07568d974664671
- 投稿日:2019-08-22T13:07:01+09:00
マネージコードについて、自分なりに解釈する
はじめに
ネイティブプラグインを作る必要が出てきて色々と調べているうちに
マネージコードとかアンマネージコードとか出てきて、
何それとなったので、関連用語含めてメモ。「マネージコード(マネージドコード)」とは
マネージコードとは、.NET Frameworkの共通言語ランタイム環境で実行されるコードのことである。対義語として「アンマネージドコード」がある。
(IT用語辞典バイナリ)
https://www.sophia-it.com/content/%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%82%B3%E3%83%BC%E3%83%89なるほどわからん。
偏差値が3なので何もわかりませんでした。
というわけで、出てきたワードを読み解いていきます。「.NET Framework」とは
ひとことで言うと、ランタイム(実行環境)。
Windowsに入っているランタイム。以下のサイトで.NET Frameworkについて簡単に説明しています。
https://wa3.i-3-i.info/word13559.html以下は、ちょっと難しく書いてあります(IT用語辞典バイナリ)。
https://www.sophia-it.com/content/.NET+Framework.NET Frameworkで利用できるもっとも特徴的な機能として、.NET Framework対応アプリケーションのための実行エンジンであるCLR(共通言語ランタイム)がある。
なるほど?
いや、共通言語ランタイム環境ってなんやねん。「共通言語ランタイム環境(CLR)」とは
CLRは、Java仮想マシンと同じように、実行に際して特定のアーキテクチャに依存しない。
(IT用語辞典バイナリ .NET Frameworkの説明より抜粋)うーん、わかるようなわからないような・・・。
では、「Java仮想マシン」とは
ひとことで言うと、Javaで作ったプログラムを動かすためのソフトウェア。
Javaで作ったプログラムをどのPCで動かそうとも、Java仮想マシン上で動かすだけだから動く、という仕組みらしいです。また丸投げになってしまいますが、以下のサイトがわかりやすいです。
https://wa3.i-3-i.info/word12704.html
Java仮想マシン、なるほど。つまり、共通言語ランタイム環境(CLR)は同じように、仮想マシン上で動くっぽい。
ということはマネージコードとは、.NET Frameworkの共通言語ランタイム環境で実行されるコードのことである。
=.NET Frameworkの仮想マシン上で実行されるコードである。
わかった。
いや、嘘です。や、なんとなくはわかりましたけれども。つまり・・・だから何・・・?ということで、一旦「アンマネージドコード」について調べました
.NET Frameworkの登場以前から使用されているコードはすべてアンマネージドコードとなり、例えばActiveXやWin32 APIなどを用いるコードはアンマネージドコードに該当する。アンマネージドコードはCLRによって管理されないため、CLRのセキュリティ機能やガベージコレクションなどの機能を利用することができない。
(IT用語辞典バイナリ)
https://www.sophia-it.com/content/%E3%82%A2%E3%83%B3%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%89%E3%82%B3%E3%83%BC%E3%83%89.NET Framework登場以前から使用されているコードはすべてアンマネージドコード、それはそうだがよくわからん・・・。
と思っていたら、例えばってのも書いてあった・・・!以下、なんとなくの解釈
アンマネージドコード:めんどくさい後始末系の処理とかをプログラマが書かなきゃいけないコード
冷静に考えたら「.NET Frameworkさんに管理(manage)されてないコード」だからそうだわ。つまり「マネージコード」とは
.NET Frameworkさんがめんどいことやってくれてるからプログラマが楽できるコード。
Microsoftさんのとこにわかりやすいページがありました。
https://docs.microsoft.com/ja-jp/dotnet/standard/managed-code
具体的に言語を挙げると(一番知りたかったのはコレ)
・C#はマネージコード
・CとかC++はアンマネージコード最後に
なんか遠回りして理解した気がするけど、関連用語への理解が深まったしいいや。
(間違っている部分ございましたら、ご指摘いただけますと幸いです。)














