20190822のC#に関する記事は6件です。

【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.");
    }

当たり前と言えば当たり前なんですが、私はこれで躓いてしばらく悩みました…。

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

【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のチェックをオフにすると単発再生ができて、途中でオフにすると止まります。
SpriteAnimation.PNG

ReorderableListを使っているのでなかなかの使い心地です。

chain_2.gif

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

ドットインストールのUnity入門の補足説明

この記事は、筆者が大学でUnityを教えるにあたって、事前準備として夏休みの宿題として学生にドットインストールのUnity入門を学習させるための補足説明です。

学生用に書いたものですが他にも役に立つ人がいるかもしれないので公開します。

ドットインストール - Unity入門

この記事の目的

ドットインストールのレッスンを否定している訳ではなく、夏休みの宿題用の素晴らしい教材としてありがたく使わせていただいています。その上で、本当に何も知らない学生にとっていきなり出てくる様々な概念や用語について、さらに詳しく説明することでより理解度を深めることを目的としています。

この記事の対象者

この記事はUnity入門のためにドットインストールで学ぶ、プログラミング初心者を対象にしています。

目次

この記事には以下のような構成でUnity入門について解説しています。カッコの中の#11などはドットインストールのレッスンの中で対応する動画の番号を示しています。

  1. まずは一度レッスンを全部通してやってみる
  2. スクリプトおよび GameObject の関連付けについて (#11)
  3. スクリプトを作成すると自動的に挿入されているコードについて (#11)
  4. 型についての補足説明 (#12)
  5. クラスについての補足説明 (#12)
  6. #12のスクリプトの補足説明 (#12)
  7. #14のスクリプトの補足説明 (#14)
  8. プレハブ(Prefab)って何? (#16)
  9. なぜ空のGameObjectを作成するのか? (#16)
  10. RigidBodyって何? (#19)
  11. OnCollisionEnterって何? (#19)

1. まずは一度レッスンを全部通してやってみる

まずは頭の中に?がいっぱいになったとしてもとりあえず、言われるがままにレッスンを進めて最後までやってみてください。数時間で終わると思います。その後この記事を見ながら2周目をやるとかなり理解が深まると思います。

ドットインストール Unity入門

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.html

3. スクリプトを作成すると自動的に挿入されているコードについて (#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.CollectionsSystem.Collections.GenericUnityEngine という名前空間を利用するということを宣言しています。

「名前空間」という変な名前ですが、スクリプトを書いていると「Ball」とか「BallFactory」とか「speed」とかクラスや変数にいろんな名前を付けます。自分で適当に付けた名前と、Unityに標準で用意されている名前と被ってしまっても大丈夫なように、さまざまな機能が名前空間で分けられているのでちゃんと区別できるようになっています。なのでこの名前が被らないようにするという意味で「名前空間」(name space)という名前が付いています。

UnityEngine

Unityで標準装備されている基本的なC#の部品がこの中にまとまっています。
以下のUnity公式スクリプトリファレンスにアクセスしてください。

https://docs.unity3d.com/ja/current/ScriptReference/index.html

左側の「UnityEngine」の「+」をクリックして開いて、
スクリーンショット 2019-08-23 22.05.01.png

その中にある「Classes」の「+」をクリックすると UnityEngine の中にあるたくさんのC#の部品を確認することができます。
スクリーンショット 2019-08-23 22.05.18.png

このようにUnityには大量の便利な機能(クラス)が用意されています。ただその全てを覚えることはできないので、分からないことがあれば、ますスクリプトリファレンスを見るようにしてください。

セミコロン

;(セミコロン)はC#の命令の区切りです。それぞれの命令の最後には必ず ; が必要です。これを書き忘れるとエラーになるので、スクリプトを実行したときにエラーが発生したときはまず ; を忘れてないか確認してみましょう。

コメント

// Start is called before the first frame update

// Update is called once per frame

スクリプトを書くときにちょっとしたメモを書いたり、書いたスクリプトを部分的に無効にしたいといった場合には、「コメント」を使います。Unityが生成するスクリプトにも上記のような解説がコメントとして記述されています。
コメントを書くには // と記述するとその行の // 以降の文字はコメントとなり、スクリプトを実行するときに無視されるようになります。 // は1行ずつしかコメントにできないので、複数行まとめてコメントにしたい場合は /* と書くと、そこから */ と記述するまでがコメントになります。

// 1行コメント

/*
複数行の
コメント
です
*/

クラス

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    // クラスの中身
}

この行では PaddleScript というクラスを宣言しています。クラスはスクリプトで使うさまざまな部品のことで、あらかじめさまざまなC#標準のクラスやUnity標準のクラスが用意されており、ここで宣言しているように、自分で作ることもできます。

後ろの : MonoBehaviour の部分は、このクラスはUnityで標準装備されている MonoBehaviour というクラスの機能を継承するということを指定しています。機能を継承することで、もともとの MonoBehaviour が持っている機能にさらに自分で機能を追加した新しいクラスを作ることができます。

MonoBehaviourUnityEngine という名前空間で定義されているので、これを利用するには1行目の using UnityEngine; が必要になります。宣言したクラスの中身のスクリプトはすべてここに書かれた中カッコ { から最後の中カッコ } の内側に記述します。

メソッド

PaddleScript.cs
void Start()
{
    // メソッドの中身
}

ここではクラス宣言の内側に Start というメソッド(関数、ファンクションとも言う)を宣言しています。メソッドはクラスの持つ機能のことで、自由に名前をつけていくつでも作ることができます。ここで宣言している Start メソッドはもともと継承元の MonoBehaviour クラスで宣言されていて、紐づけられた GameObject が作成された時に一度だけ実行されます。

ここではその Start メソッドを上書きすることで、GameObject が作成されたときに実行したい独自の処理を記述することができます。メソッドの中身のスクリプトは、ここにかかれた中カッコ { からメソッドの終わりの中カッコ } の内側に記述します。

メソッドは何らかの処理を実行した後に、結果を返すことができますが、ここにある void は結果を何も返さないということを示しています。

Updateメソッド

PaddleScript.cs
void Update()
{
    // メソッドの中身
}

ここは Start メソッドと同様に継承元の MonoBehaviour クラスで宣言されている Update というメソッドを上書きしています。Update メソッドは 紐付けられた GameObject がシーンに配置されている間、画面が更新される度に実行されます。通常だと1秒間に60回ほど実行されます。

インデントについて

PaddleScript.cs
public 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/3c2e23bb77cc9085bfda

4. 変数と型についての補足説明(#12)

変数について

プログラミングをする上でとても基本的な概念に「変数」があります。数学で出てくるx, yといった変数とは少し違います。プログラミングでは、数値や文字列など様々な値を扱いますが、そういう値を保持しておく箱のようなものがプログラミングにおける変数です。変数について簡単にまとめると以下のような特徴があります。

  • 変数は値を保存したり読み出したりできる箱のようなもの
  • 変数と言っても数値だけだはなく文字列やオブジェクトなどなんでも保存できる
  • 変数には自由な名前をつけることができる
  • 変数を作るには、宣言をする必要がある
  • 変数には型という概念があり、宣言するときに型の指定が必要
  • 変数に値を保存することを「代入」という

変数を宣言するには、型の後に変数名を記述します。

int x;

この例では x という名前で int という型の変数を宣言しています。( int は整数値を扱う型)
変数に値を保存することを「代入」と言いますが、値を代入するには = を使います。

int x;
x = 2;

この例では1行目で int 型の変数 x を宣言して、2行目で x2 という値を代入しています。
値の代入は = の右側に記述された値が、左側に記述された変数に保存されます。必ず右→左です。左→右はありませんので注意してください。

また、変数の宣言と同時に値を代入して初期化することもできます。その場合は以下のように記述します。

int x = 2;

また、変数に値を代入する場合の = の左側に変数を使うこともできます。
以下の例では変数 x に 2 を代入し、変数 y に x + 1 の計算結果を代入しているので、 y の中身は3になります。

int x = 2;
int y;
y = x + 1;

0ff って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

C#のスクリプトでは整数値や小数値、文字列などいろいろな値を扱います。それらはの値の種類を「型」と言います。
例えば整数を扱うには int という型を利用し、小数を扱うには精度の高い double とその半分の精度の float といった種類の型があります。
C#では 01 のような整数の値は自動的に int として扱われ、 0.00.5 のように記述した値は自動的に double として扱われます。 0f0.5f のように数値の後に f を付けるとその数値は double ではなく float として扱われるようになります。

ここで使われている Vector3double ではなく float を扱うクラスなので、 0 ではなく 0f とする必要があります。(ドットインストールでは浮動小数点数を渡す決まりになっているので f を付けると解説されていますが、実際には浮動小数点数を渡すから f を付けるのではなく、 浮動小数点数の中でも double ではなくて float を渡す決まりになっているので f を付ける必要があるということになります)

5. クラスについての補足説明 (#12)

Unityで書く1つのスクリプトは基本的に1つのクラスになります。クラスはスクリプトで扱うオブジェクトの型の種類の1つで、クラスを作ることで型を自分でつくることができます。クラスを作るとその型のオブジェクトをいくつでも作ることができ、そのオブジェクトのことを「インスタンス」と言います。

クラス(型)はオブジェクトの設計図のようなもので、それを元に実際に生成されたオブジェクトがインスタンスです。

スクリーンショット 2019-08-27 20.32.54.png

いきなり出てくる transform って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

ここで書いている PaddleScriptMonoBehaviour というクラスの持つメソッド(機能)や変数を全て継承して、それに独自の機能を新たに追加しているクラスです。 transformMonoBehaviour クラスが持つ変数の1つで位置情報や変形などに関する情報がまとめて格納されています。

Unity公式スクリプトリファレンスに書いてありますが MonoBehaviour クラスは transform という変数を持つと書いてあります。なので、 MonoBehaviour を継承している PaddleScript も変数 transform を持つことになります。

MonoBehaviour クラスのリファレンス(継承メンバーの変数のところに transform の記述がある)
https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.html

そしてそこをクリックすると transformTransform というクラスの変数だということが分かります。

スクリーンショット 2019-08-26 22.30.31.png

transform.position って何?

PaddleScript.cs
transform.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 を付け加えると以下のような相関関係になります。


スクリーンショット 2019-08-26 22.22.59.png

Vector3 って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

Vector3x, y, z の3つの float の値をまとめて扱えるので3D空間での位置情報を扱うのに便利なオブジェクトです。transform.position の型は Vector3 なので、ここでは Vector3 を使う必要があります。
Vector3 はクラスではなく構造体というものですが、今さらに新たな概念を理解すると大変だし、ほぼクラスと同じように使えるので今はほぼクラスみたいなものだと思ってもらって大丈夫です。)

Vector3 の公式リファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Vector3.html

new って何?

PaddleScript.cs
transform.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.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

transofrm.position の後に +=new Vector3が続いていますが、これは、PaddleScript クラスのインスタンスが持っている変数 transformposition= の右側で新しく作られる Vector3 クラスのインスタンスを足しているということになります。

transform.positionnew Vector3 で作られるオブジェクトもどちらも Vector3 のインスタンスで、それぞれのインスタンスの持つ x, y, z の変数が足し合わされた結果が transform.position に保存されます。

6. その他スクリプトの補足説明(#12)

Input って何?

PaddleScript.cs
void Update()
{
    transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime * speed, 0f, 0f);
}

Input はUnityで、キーボードやマウス、タッチディスプレイ、ゲームパッド、ジョイスティックなどのユーザーのさまざまな入力を取得することができるクラスです。

UnityのInputで入力を扱う - Qiita
https://qiita.com/yando/items/c406690c9ad87ecfc8e5

Unity公式マニュアル - 一般的なゲーム入力
https://docs.unity3d.com/ja/current/Manual/ConventionalGameInput.html

Input のスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Input.html

Input.GetAxis("Horizontal") って何?

PaddleScript.cs
void 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.cs
void Update()
{
    float horizontalAxis = Input.GetAxis("Horizontal");
    float x = horizontalAxis * Time.deltaTime * speed;
    transform.position += new Vector3(x, 0f, 0f);
    Debug.Log(horizontalAxis);
}

まず、 float 型の変数 horizontalAxisInput.GetAxis("Horizontal") で取得したゲームコントローラーの状態を取得しています。キーボードのキーを何も押していないときには、変数 horizontalAxis には 0f が入り、左キーを押しているときは -1f が、右キーを押しているときは 1f が入ります。

次の行では、それに Time.deltaTimespeed をかけた値を float 型の変数 x に代入しています。

その次の行では new Vector3 でコンストラクタのパラメータに x を渡しています。これらを1行で書いていましたが、こうやって分けて書くとよりわかりやすいと思います。

次の行の Debug.Log(horizontalAxis); をすることで、実行した時にキーボードの状態に応じて horizontalAxis の値が変化することがコンソールで確認できると思います。(この行は確認した後は不要なので消しましょう)

スクリーンショット 2019-08-25 22.07.40.png

キーボードはゲームコントローラーがない場合にコントローラーの代わりに使うことができるので、Input.GetAxis("Horizontal") で矢印キーの状態が取得できます。

Input.GetAxis("Horizontal") を使っておけば、USBのゲームコントローラーなどUnityが対応するさまざまなコントローラーを接続した時にプログラムを書き変えることなく、そのままプレイできます。

Time.deltaTime って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

Time.deltaTime は、前回のフレーム(前回 Update メソッドが実行されたタイミング)からの経過時間(秒)を取得することができます。 Update メソッドは必ずしも一定間隔で実行されるわけではありません。コンピュータの負荷によって遅くなったり早くなったりします。

例えば1フレームごとに1ピクセルずつ動くプログラムを書いた場合、フレームの間隔が安定して0.1秒だとすると1秒間に10回実行され、10ピクセル動くことになりますが、もしコンピュータの負荷によりフレームの実行間隔が遅くなり、1秒間に5回しか実行されなければ、5ピクセルしか動かないことになります。

スクリーンショット 2019-08-27 10.33.01.png

このようにフレームごとに一定の値だけ動かすといったスクリプトを書くとフレームの実行間隔により見た目のスピードは一定ではなくなります。なので、フレームの実行回数によりオブジェクトを動かすのではなく、経過時間によりオブジェクトを動かす方が正しくスムーズにオブジェクトを動かすことができます。そのために Time.deltaTime を利用します。

スクリーンショット 2019-08-27 10.33.36.png

Unity公式マニュアル - タイムとフレームレートの管理
https://docs.unity3d.com/ja/current/Manual/TimeFrameManagement.html

7. #14のスクリプトの補足説明 (#14)

変数 speed はなぜその場所に記述するのか?

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    private float speed;

    // Start is called before the first frame update
    void Start()
    {
        speed = 5f;
    }

クラスの記述の決まりとして、class の宣言の後に中カッコがあり、その中にメソッドを記述しますが、class 宣言の中にメソッドと同列で変数を宣言すると、その変数は「インスタンス変数」としてそのクラスのインスタンスが持つ変数になります。ドットインストールの例で言うと、Ball の数だけそれぞれがこの speed という変数を持っていて、Start() でランダムな値が設定されるので、インスタンスごとに個別の値を保持しています。

スクリーンショット 2019-08-27 20.41.23.png

変数のスコープ(有効範囲)について

変数にはスコープ(有効範囲)というものがあり、宣言する場所によってスコープが変わります。ここでは、クラス宣言の内側で変数 speed を宣言しています。こうすることで、このクラスのインスタンスが存在する限り、変数 speed も存在します。

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    private float speed;
    void Start()
    {
        speed = 5f;
    }
}

メソッドの中で変数を宣言することもできます。ただし、メソッドの中で宣言した変数はそのメソッドの実行が終了した時点で破棄されてしまうので、メソッドの実行時に一時的に使いたいといった場合にメソッド内で変数を宣言します。

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    void Start()
    {
        float speed;
        speed = 5f;
        // Start()メソッドはここまでなので、ここで変数 speed は破棄される
    }

    void Update()
    {
        // ここでは speed 変数は存在しない
    }
}

private って何?

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    private float speed;

クラスの持つインスタンス変数は、そのクラスの外側からアクセス可能にするか、そのクラス内でのみアクセスできるようにするかを設定できます。変数宣言の前に private と付けるとその変数にはクラス内からしかアクセスできないようになります。private の代わりに public とすると、その変数には外部からアクセスできるようになり、クラスの外部(他のクラスやUnityのInspectorなど)から値を参照したり変更したりすることができるようになります。

8. プレハブ(Prefab)って何? (#16)

ドットインストールではさらっと登場して使われているプレハブについて、とても重要な概念なのでもう少し説明しておきます。

プレハブは、同じような GameObject をたくさん配置して、それぞれを個別に動かしたいような場合のテンプレートのような機能です。スクリプトで使うクラスと似た概念です。

Ballのプレハブをドラッグ&ドロップでシーンにたくさん配置するとヒエラルキービューでは青く表示されます。これはこのオブジェクトがプレハブから作られたことを意味します。

スクリーンショット 2019-08-26 17.45.25.png

これらの球体にまとめて変更を加えたければ、元のプレハブのScaleを変えればシーンに配置した全てのオブジェクトに反映されます。また、色を変えたければ、元のプレハブにマテリアルを適用することで全てのオブジェクトに反映されます。

スクリーンショット 2019-08-26 17.52.14.png

ただ、これらのオブジェクトは個別にそれぞれの設定をすることも出来て、個別のオブジェクトのプロパティをプレハブのデフォルトの値から変更すると、それらは元のプレハブの設定を上書きした個別の値を持つことができます。

スクリーンショット 2019-08-26 17.54.50.png

また、ドットインストールでやっているようにスクリプトからプレハブを元にオブジェクトをシーンに追加することもできます。

Unity公式マニュアル - プレハブ
https://docs.unity3d.com/ja/2018.1/Manual/Prefabs.html

9. なぜ空の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 にさまざまな挙動をさせることができます。

スクリーンショット 2019-08-26 18.07.55.png

Unity公式マニュアル - RigidBody
https://docs.unity3d.com/ja/current/Manual/class-Rigidbody.html

11. OnCollisionEnter って何?(#19)

BallScript.cs
private 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にある以下の素晴らしい入門記事を読むと良いと思います。

UnityをC#で超入門してみる #0 超目次

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

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)

  1. Python3(client) で画像を open して byte 列で読み込み.
  2. base64 形式の文字列に encode.
  3. この base64 文字列を送信する byte 数で分割.
  4. 分割した base64 文字列を TCP を使って逐次的に Python から HoloLens(server) に送信.

server 側 (HoloLens)

  1. 分割された base64 文字列を逐次的に処理(受信->decode->ファイルへの書き込みを繰り返す).
  2. 受信した byte 数が指定した値より小さければ EOF とみなし最後の書き込みを行う.
  3. 受信した画像に応じた処理.

動作確認

  1. ImageReceiver.cs を Unity 内の texture を持つオブジェクトに Add します.(僕は Canvas 内に RawImage を作ってそこへ Add しました.)
  2. HoloLens に書き込み動作させます.
  3. client.py と同じディレクトリに sample.jpg (.png でも大丈夫です)用意します.
  4. client.py を走らせます.
  5. sample.jpg と同じ画像が HoloLens の view に現れたら成功です.

直接 byte 列を送受信するとなぜかファイルが壊れてしまいます.
そこで一旦 base64 という形式の文字列に変換し,これを通して送受信します.

client 側のコード (Python)

host には各自の HoloLens の IP address を入力してください.

client.py
import 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.cs
using 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# コードを書いております.
問題点等ございましたら,ぜひご指摘お願いします!

参考にした記事

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

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)

  1. Python3(client) で画像を open して byte 列で読み込み.
  2. base64 形式の文字列に encode.
  3. この base64 文字列を送信する byte 数で分割.
  4. 分割した base64 文字列を TCP を使って逐次的に Python から HoloLens(server) に送信.

server 側 (HoloLens)

  1. 分割された base64 文字列を逐次的に処理(受信->decode->ファイルへの書き込みを繰り返す).
  2. 受信した byte 数が指定した値より小さければ EOF とみなし最後の書き込みを行う.
  3. 受信した画像に応じた処理.

動作確認

  1. ImageReceiver.cs を Unity 内の texture を持つオブジェクトに Add します.(僕は Canvas 内に RawImage を作ってそこへ Add しました.)
  2. HoloLens に書き込み動作させます.
  3. client.py と同じディレクトリに sample.jpg (.png でも大丈夫です)用意します.
  4. client.py を走らせます.
  5. sample.jpg と同じ画像が HoloLens の view に現れたら成功です.

直接 byte 列を送受信するとなぜかファイルが壊れてしまいます.
そこで一旦 base64 という形式の文字列に変換し,これを通して送受信します.

client 側のコード (Python)

host には各自の HoloLens の IP address を入力してください.

client.py
import 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.cs
using 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# コードを書いております.
問題点等ございましたら,ぜひご指摘お願いします!

参考にした記事

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

マネージコードについて、自分なりに解釈する

はじめに

ネイティブプラグインを作る必要が出てきて色々と調べているうちに
マネージコードとかアンマネージコードとか出てきて、
何それとなったので、関連用語含めてメモ。

「マネージコード(マネージドコード)」とは

マネージコードとは、.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++はアンマネージコード

最後に

なんか遠回りして理解した気がするけど、関連用語への理解が深まったしいいや。
(間違っている部分ございましたら、ご指摘いただけますと幸いです。)

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