Help us understand the problem. What is going on with this article?

Unityを触り始めたのでメモ(C#編)

More than 3 years have passed since last update.

最近Unityを触り始め、さらにC#の勉強も同時にやっているので、色々知ったことを細々とまとめていきます。なので、随時更新していきます。


Table of Contents

  • transformのコントロール
  • タイムステップによる操作
  • 生成
  • 取得・検索
  • エディタ
  • Leapmotionを使う
  • デバッグ
  • コリジョン(Collision)
  • マウス操作
  • スマホ関連
  • 通信関連
  • テクスチャ操作
  • Tips

transformのコントロール

回転をQuaternionでがんばる

通常は transform.Rotate などを使って回転させるのが早いですが、物理演算にまかせているオブジェクトを transform から直接操作するのはあまりよくありません。
そこで利用するのが Rigidbody.MoveRotateion(quaternion) です。
これは引数に渡した角度に「変更する」ものなので、徐々に回転、みたいな操作ができません。
なので、自分でQuaternionを計算してやる必要があります。

Quaternionのx, y, z, wのそれぞれの要素は角度がそのまま入っているわけではありません。

Quaternionの各要素は以下の構成になっています。

(x, y, z, w) = (x \sin(\frac{\theta}{2}}), y \sin(\frac{\theta}{2}}), z \sin(\frac{\theta}{2}}), \cos(\frac{\theta}{2}))

x, y, zはそれぞれ軸となるベクトルです。
それに、半分の角度の $sin$ を掛けたものがx, y, z、そして半分の角度の $cos$ がwになります。

ということで、前置きはこれくらいにして実際に計算すると以下のようになります。

float angleSpeed = 0.5f;
Quaternion rot = transform.rotation;
float angle = angleSpeed * Mathf.Deg2Rad;
float y = Mathf.Sin(angle * 0.5f);
float w = Mathf.Cos(angle * 0.5f);
rot *= new Quaternion (0f, y, 0f, w);
GetComponent<Rigidbody> ().MoveRotation (rot);

上の例では、Y軸だけ徐々に回転する、という操作です。
説明の通り、Y軸に対して半分の角度の値を与えているのが分かるかと思います。
あとは生成したクォータニオンを、もともとの rotation に掛けてやることで目的の回転を表したクォータニオンを手に入れることができます。
それをあとは指定してやるだけです。


タイムステップによる操作

タイムステップの時間を得る

タイマーなどを自作する場合、今、開始からどれくらい時間が経ったのかを知りたいはずです。
その場合は以下のようにTime.deltaTimeを使うことで実現できます。

float timer;
float waitTime = 5000.0f;

void Update() {
    timer += Time.deltaTime;
    if (timer > waitTime) {
        //do something.
    }
}

一定時間後に処理をする

void Start()
{
    Invoke("MethodName", 5); //5秒後にMethodNameメソッドを実行する
}

一定時間後にオブジェクトを削除する

弾丸とか、生成したあと消えてほしいオブジェクトがあると思います。
消えるタイミングが時間駆動の場合はDestroyメソッドを使うことで実現できます。

void Start() {
    Destroy(gameObject, 3); //3秒後にGameObjectを削除
}

コルーチンを使った遅延処理

遅延処理など、時間の処理はコルーチンを使っても実現できます。

void Start() {
    StartCoroutine(WaitSomething());
}

IEnumerator WaitSomething() {
    yield return new WaitForSeconds(2.0f);
    //do something.
}

生成

空のGameObjectを作る

単純にGameObjectクラスにnameを与えて生成するだけ。

GameObject emptyObj = new GameObject("object name");

プリミティブなGameObjectを作る

専用のメソッドから作成する。

//Cubeオブジェクトを生成
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);

PrimitiveTypeはenum型で、以下が定義されている。

public enum PrimitiveType
{
    Sphere,
    Capsule,
    Cylinder,
    Cube,
    Plane,
    Quad
}

prefabから複製を作る

[SerializeField]
private GameObject prefab;

void Start()
{
    //InstantiateはObject型が返るので、GameObject型にキャストする
    GameObject clone = Instantiate(prefab, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
}

動的にMaterialを生成する

動的に生成する場合は、UnityEngine.Materialクラスを使う。

using UnityEngine;
// 中略
Material material = new Material(Shader.Find("Diffuse"));
material.color = Color.red;

Materialクラスの引数は、シェーダをstringで与える必要がある。
ただ、基本的なシェーダはいくつか準備されているようなので、Shaderクラスから探して設定してやることで生成できる。
Findメソッドに渡すテキストは、UnityのMaterialのInspectorで指定できるもの(と思われる)。


取得・検索

GameObjectのサイズを取得する

float width = gameObject.renderer.bounds.size.x;

renderer.bounds.sizeから取得する

マテリアルなど、Unityのライブラリに入っているデータを取得する

例えば、動的にマテリアルを変更する場合。

GameObject obj = GameObject.Find("obj name");
Material material = Resources.Load("path/to/item") as Material;
obj.renderer.material = material;

path/to/itemの部分は、Assetsフォルダ内にResourcesフォルダを作成し、その中に入っている要素への相対パスとなる。

unity-resources-sample.png

仮に、HandPrimaryにアクセスする場合は

Material material = Resources.Load("Materials/HandPrimary") as Material;

となる。

GameObjectにアタッチされたスクリプトへの参照を得る

参照を得たいケースとしては、別のGameObjectにアタッチされたスクリプト内で定義された変数を参照したい、という例。
static変数(グローバル変数)経由はNG。

取得方法は該当GameObjectを見つけ、さらにアタッチされたスクリプトのコンポーネントを取得することで参照を得る。C#で書くと以下。

GameObject targetObj = GameObject.Find("someObjectName");
TargetScriptName script = targetObj.GetComponent<TargetScriptName>();
Debug.Log(script.targetVariable); //=>なにがしかの値

ここで、TargetScriptName型はスクリプトの名前。
具体的にはファイル名。(多分)
もし「HogeScript.cs」がファイル名だとしたらHogeScript script = …という書き方になる。

詳細は以下を参照
必見!Unity初心者が学ぶ「別スクリプトの変数やメソッドへの参照」

GameObjectの階層構造を検索して該当オブジェクトを得る

unity-object-find.png
上記のような構造になっている場合、rootにスクリプトがアタッチされているとすると、

Transform mixTransform = transform.Find("body/arm");

とすることで、armオブジェクトを取得することができる。

FindChildメソッドを使って子のGameObjectを得る

//子供のGameObjectのparticle systemを取得する例
ParticleSystem particle = transform.FindChild("ChildGameObjectName").GetComponent<ParticleSystem>();


エディタ

private変数をエディタから操作する

通常、publicにするとエディタから編集することができる。
しかし、プログラム上はやはりprivateにしたいときもある。
そんなときは属性を使って、以下のようにすることでエディタから編集させつつ、privateなメンバを宣言できる。

[SerializeField]
private float hoge = 1;

Leapmotionを使う

UnityのFree版でLeapmotionの開発をしていると、dllが読み込めない?ため、通常のPlayモードではライブラリが読み込めず動作しない。
以下の記事を参考に色々やってみることでPlayモードでも指の検出ができるようになった。
http://pierresemaan.com/getting-the-leap-to-work-with-unity-free-version-not-pro/

Leapmotionのサンプルに入っているUnitySandboxの中身を解説した記事。色々参考になります。
http://d.hatena.ne.jp/hecomi/20130723/1374598279


デバッグ

ブレークポイントなどで停止させるには、MonoDevelop側からUnityを起動しないとならないらしい。
MonoDevelopを起動し、Unityを閉じたら「実行>デバッグ」を実行するとUnityが立ち上がりブレークポイントでブレークするようになる。


コリジョン(Collision)

OnCollisionEnterを利用する

Collisionの検出には、ColliderとRigidbodyのコンポーネントをそれぞれ検出させたいもの同士に追加しないと検出されない。参考:
http://gamesonytablet.blogspot.jp/2012/11/unityontriggeroncollision.html


マウス操作

マウスの当たり判定

マウスと該当オブジェクトとの衝突検出。
考え方は2次元に存在するマウスを3D空間に逆投影変換する。
まず、マウスの位置からカメラ視点方向に向かって真っ直ぐな光線を飛ばし(概念的な意味で)、その光線と接触しているかを判定することで行う。

計算方法など詳しくはこちらを参考に→ その48 スクリーン座標でワールド空間の地面を指す

Unityではそのへんのめんどくさいことをやってくれるメソッドがあるので、以下のようにして判定できる。

void Update () {
    Ray ray;
    RaycastHit hit;
    float distance = 1000.0f; //光線を伸ばす距離

    //メインカメラのスクリーン上のポイントを光線に変換
    ray = Camera.main.ScreenPointToRay(Input.mousePosition);

    //光線がhitしているオブジェクトがあるかチェック
    //もし該当のオブジェクトがあればhitに格納される
    if (Physics.Raycast(ray, out hit, distance))
    {
        //該当オブジェクトの判別にtagを利用。ここはやりたいことに応じて適宜変更する。
        if (hit.collider.gameObject.tag == "Touchable")
        {
            selectedObject = hit.collider.gameObject;
        }
        else
        {
            selectedObject = null;
        }

    }
    else
    {
        selectedObject = null;
    }
}

クリックされた位置を取る

本を参考にさせてもらった、マウスの位置に応じてオブジェクトを移動する処理のサンプル。

public bool UnprojectMousePosition(out Vector3 world_position, Vector3 mouse_position)
{
    bool ret;
    float depth;

    //チェック用のPlaneオブジェクトを生成する
    Plane plane = new Plane(Vector3.up, new Vector3(0.0f, transform.position.y, 0.0f));

    //当たり判定用のRay(光線)を生成
    Ray ray = Camera.main.ScreenPointToRay(mouse_position);

    //planeとの当たり判定をチェック
    if (plane.Raycast(ray, out depth))
    {
        //マウスがあたっていたら位置を補正してその値を返す
        world_position = ray.origin + ray.direction * depth;
        ret = true;
    }
    else
    {
        world_position = Vector3.zero;
        ret = false;
    }

    return ret;
}

マウスの位置にオブジェクトを移動する

カメラのScreenToWorldPointを使えば比較的簡単に実装できるよう。

public void MoveToMouseposition()
{
    //マウスの位置を取得
    Vector3 screen_point = Input.mousePosition;

    //マウス位置のZ座標を2.0fに設定
    screen_point.z = 2.0f;

    //上記マウス位置を元に、ワールド空間の位置を取得    
    Vector3 world_point = Camera.main.ScreenToWorldPoint(screen_point);

    //取得した位置をGameObjectのpositionに設定する
    transform.position = world_point;
}

ポイントは、マウス位置に明示的にZ座標の値を設定しているところ。
元々スクリーン上のマウス座標はZ軸の値を持っていないので(2次元なので)、表示したい位置を明示的に指定してやる必要がある。

カメラの位置から見てなので、基本的にマイナス方向に値を設定すると画面内に映らなくなるので注意。

スマホ関連

スマホのジャイロセンサにアクセス

Quaternion gyro = Input.gyro.attitude;

で取れるらしい。まだ使ったことないけどいずれ使いそうなのでメモw


通信関連

HTTP通信を利用する

HTTP通信は、UnityEngine.WWWクラスを使う。

private string res;

// Use this for initialization
void Start () {
    StartCoroutine (Download());
}

IEnumerator Download()
{
    WWW www = new WWW("http://css-eblog.com/");

    yield return www;

    res = "";

    Debug.Log(www.text);
}

読み込みは非同期になるため、コルーチンを利用する必要がある。


テクスチャ操作

テクスチャでスプライトアニメーション(の準備)

テクスチャのオフセットをいじるには、renderer.material.mainTextureOffstをいじる。
あるいはrenderer.material.SetTextureOffst("_MainTex", offset)のようにしてもいい。

ここで_MainTexはUnityで作成したマテリアルが自動で生成するシェーダ内に定義されているテクスチャの変数ぽい。
これに値を設定することで、アニメーションを実現している。

using UnityEngine;
using System.Collections;

public class SpriteAnimScript : MonoBehaviour {

    private Material material;
    private float offsetX = 0.0f;
    private float offsetSpeed = 0.1f;

    // Use this for initialization
    void Start () {
        this.material = renderer.material;
    }

    // Update is called once per frame
    void Update () {
        offsetX += offsetSpeed * Time.deltaTime;
        Vector2 offset = new Vector2(offsetX, 0.0f);
        this.material.SetTextureOffset("_MainTex", offset);
        //こっちでも動く
        //this.material.mainTextureOffset = offset;
    }
}

※offsetの値は整数を与えると100%の移動になるので画面上はまったく動いていないように見えるので注意。UV座標かな。

こちらの公式Wikiを参考にしました → Animating Tiled texture

Tips

通常のPlaneをBillboardにする

パーティクルシステムはビルボードになっているので常にカメラ方向に面を向けてくれます。
しかしどうやらUnityにはデフォルトでビルボードを実行する機能がないらしいので、自前で書く必要がある。
コード自体は非常にシンプルで、以下のものをビルボード化したいオブジェクトにアタッチするだけ。

billboard.cs
Camera m_Camera = Camera.main;

//以下でrotationを常にカメラ方向に向ける
this.transform.LookAt (this.transform.position + m_Camera.transform.rotation * Vector3.back, m_Camera.transform.rotation * Vector3.up * 270);

//Planeの場合、デフォルトの回転は面がup方向を向いているので追加でX軸に90度回転させてやる
this.transform.Rotate(90, 0, 0);

役立ちリンク

edo_m18
現在はUnity ARエンジニア。 主にARのコンテンツ制作をしています。 最近は機械学習にも興味が出て勉強中です。 Unityに関するブログは別で書いています↓ https://edom18.hateblo.jp/
http://edom18.hateblo.jp/
unity-game-dev-guild
趣味・仕事問わずUnityでゲームを作っている開発者のみで構成されるオンラインコミュニティです。Unityでゲームを開発・運用するにあたって必要なあらゆる知見を共有することを目的とします。
https://unity-game-dev-guild.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away